From dfbc2e201fbae1e3418ba94a01a5fe44826a87d7 Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Mon, 14 Aug 2023 17:03:39 +0700 Subject: [PATCH 01/70] Only ask for camera permission when necessary --- .../Example-iOS/CustomizedMediaPicker.swift | 1 - .../Permissions/PermissionsService.swift | 12 ++- .../PhotoKit/PhotoLibraryChangeNotifier.swift | 7 ++ .../Views/Pages/Camera/LiveCameraView.swift | 6 +- .../Views/Pages/MediaPicker/MediaPicker.swift | 91 ++++++++++--------- 5 files changed, 68 insertions(+), 49 deletions(-) diff --git a/Examples/Example-iOS/CustomizedMediaPicker.swift b/Examples/Example-iOS/CustomizedMediaPicker.swift index 4eca860..19e1004 100644 --- a/Examples/Example-iOS/CustomizedMediaPicker.swift +++ b/Examples/Example-iOS/CustomizedMediaPicker.swift @@ -59,7 +59,6 @@ struct CustomizedMediaPicker: View { .background(Color.black) } ) - .showLiveCameraCell() .albums($albums) .pickerMode($mediaPickerMode) .orientationHandler { diff --git a/Sources/ExyteMediaPicker/Drivers/Permissions/PermissionsService.swift b/Sources/ExyteMediaPicker/Drivers/Permissions/PermissionsService.swift index cb98df9..0f4e866 100644 --- a/Sources/ExyteMediaPicker/Drivers/Permissions/PermissionsService.swift +++ b/Sources/ExyteMediaPicker/Drivers/Permissions/PermissionsService.swift @@ -16,14 +16,18 @@ final class PermissionsService: ObservableObject { init() { photoLibraryChangePermissionPublisher .sink { [weak self] in - self?.reload() + self?.checkPhotoLibraryAuthorizationStatus() + } + .store(in: &subscriptions) + + cameraChangePermissionPublisher + .sink { [weak self] in + self?.checkCameraAuthorizationStatus() } .store(in: &subscriptions) - reload() } - func reload() { - checkCameraAuthorizationStatus() + func askLibraryPermissionIfNeeded() { checkPhotoLibraryAuthorizationStatus() } diff --git a/Sources/ExyteMediaPicker/Drivers/PhotoKit/PhotoLibraryChangeNotifier.swift b/Sources/ExyteMediaPicker/Drivers/PhotoKit/PhotoLibraryChangeNotifier.swift index c28bced..4e662c7 100644 --- a/Sources/ExyteMediaPicker/Drivers/PhotoKit/PhotoLibraryChangeNotifier.swift +++ b/Sources/ExyteMediaPicker/Drivers/PhotoKit/PhotoLibraryChangeNotifier.swift @@ -10,6 +10,8 @@ let photoLibraryChangePermissionNotification = Notification.Name(rawValue: "Phot let photoLibraryChangeLimitedPhotosNotification = Notification.Name(rawValue: "PhotoLibraryChangeLimitedPhotosNotification") +let cameraChangePermissionNotification = Notification.Name(rawValue: "cameraChangePermissionNotification") + let photoLibraryChangePermissionPublisher = NotificationCenter.default .publisher(for: photoLibraryChangePermissionNotification) .map { _ in } @@ -20,6 +22,11 @@ let photoLibraryChangeLimitedPhotosPublisher = NotificationCenter.default .map { _ in } .share() +let cameraChangePermissionPublisher = NotificationCenter.default + .publisher(for: cameraChangePermissionNotification) + .map { _ in } + .share() + final class PhotoLibraryChangePermissionWatcher: NSObject, PHPhotoLibraryChangeObserver { override init() { super.init() diff --git a/Sources/ExyteMediaPicker/Views/Pages/Camera/LiveCameraView.swift b/Sources/ExyteMediaPicker/Views/Pages/Camera/LiveCameraView.swift index c006444..52b452c 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Camera/LiveCameraView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Camera/LiveCameraView.swift @@ -15,7 +15,11 @@ struct LiveCameraView: UIViewRepresentable { var orientation: UIDeviceOrientation = UIDevice.current.orientation func makeUIView(context: Context) -> LiveVideoCaptureView { - LiveVideoCaptureView( + NotificationCenter.default.post( + name: cameraChangePermissionNotification, + object: nil) + + return LiveVideoCaptureView( session: session, videoGravity: videoGravity, orientation: orientation diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift index c4d9c02..9adadc2 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift @@ -72,47 +72,14 @@ public struct MediaPicker MediaPicker { + func showLiveCameraCell(_ show: Bool = true) -> MediaPicker { var mediaPicker = self - mediaPicker.showingLiveCameraCell = true + mediaPicker.showingLiveCameraCell = show return mediaPicker } From 2189ab9363ca08af358ab8d36ec8ada0e39c7487 Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Mon, 14 Aug 2023 18:22:48 +0700 Subject: [PATCH 02/70] Use current fullscreen media if nothing else is selected --- .../Selection/SelectionParamsHolder.swift | 2 +- .../Fullscreen/FullscreenContainer.swift | 2 +- .../Views/Pages/MediaPicker/MediaPicker.swift | 21 ++++++++++++------- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/Sources/ExyteMediaPicker/Drivers/Selection/SelectionParamsHolder.swift b/Sources/ExyteMediaPicker/Drivers/Selection/SelectionParamsHolder.swift index 6b98d2f..34bd06d 100644 --- a/Sources/ExyteMediaPicker/Drivers/Selection/SelectionParamsHolder.swift +++ b/Sources/ExyteMediaPicker/Drivers/Selection/SelectionParamsHolder.swift @@ -11,7 +11,7 @@ final class SelectionParamsHolder: ObservableObject { @Published var mediaType: MediaSelectionType = .photoAndVideo @Published var selectionStyle: MediaSelectionStyle = .checkmark - @Published var selectionLimit: Int? // if nill - unlimited + @Published var selectionLimit: Int? // if nil - unlimited } public enum MediaSelectionStyle { diff --git a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift index 83654a1..657c3eb 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift @@ -35,7 +35,7 @@ struct FullscreenContainer: View { } } .overlay { - if let selectedMediaModel = selectedMediaModel { + if let selectedMediaModel = selectedMediaModel, selectionParamsHolder.selectionLimit != 1 { SelectIndicatorView(index: selectionServiceIndex, isFullscreen: true, canSelect: selectionService.canSelect(assetMediaModel: selectedMediaModel), selectionParamsHolder: selectionParamsHolder) .padding([.horizontal, .bottom], 20) .contentShape(Rectangle()) diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift index 9adadc2..47690d0 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift @@ -34,7 +34,7 @@ public struct MediaPicker? private var showingLiveCameraCell: Bool = false @@ -53,7 +53,8 @@ public struct MediaPicker) -> MediaPicker { var mediaPicker = self - mediaPicker._currentFullscreenMedia = currentFullscreenMedia + mediaPicker._currentFullscreenMediaBinding = currentFullscreenMedia return mediaPicker } @@ -335,7 +342,7 @@ public extension MediaPicker where AlbumSelectionContent == EmptyView, CameraSel self._isPresented = isPresented self._albums = .constant([]) - self._currentFullscreenMedia = .constant(nil) + self._currentFullscreenMediaBinding = .constant(nil) self.onChange = onChange } @@ -349,7 +356,7 @@ public extension MediaPicker where AlbumSelectionContent == EmptyView { self._isPresented = isPresented self._albums = .constant([]) - self._currentFullscreenMedia = .constant(nil) + self._currentFullscreenMediaBinding = .constant(nil) self.onChange = onChange self.cameraSelectionBuilder = cameraSelectionBuilder @@ -364,7 +371,7 @@ public extension MediaPicker where CameraSelectionContent == EmptyView { self._isPresented = isPresented self._albums = .constant([]) - self._currentFullscreenMedia = .constant(nil) + self._currentFullscreenMediaBinding = .constant(nil) self.onChange = onChange self.albumSelectionBuilder = albumSelectionBuilder From 2a182bc5d8e0e6ae4f7f69a61c15b8277eee2768 Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Thu, 17 Aug 2023 13:00:43 +0700 Subject: [PATCH 03/70] Add sim check --- Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift index d47564d..89fcc7d 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift @@ -55,11 +55,13 @@ private extension AlbumView { .font(.title3) } else { MediasGrid(viewModel.assetMediaModels) { +#if !targetEnvironment(simulator) if shouldShowCamera && permissionsService.cameraAction == nil { LiveCameraCell { showingCamera = true } } +#endif } content: { assetMediaModel in let index = selectionService.index(of: assetMediaModel) SelectableView(selected: index, isFullscreen: false, canSelect: selectionService.canSelect(assetMediaModel: assetMediaModel), selectionParamsHolder: selectionParamsHolder) { From 6d404a50bac81ed9c691780b06cb6f8fbde986a6 Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Thu, 17 Aug 2023 14:26:44 +0700 Subject: [PATCH 04/70] Add header hiding in fullscreen --- Examples/Example-iOS/CustomizedMediaPicker.swift | 2 +- README.md | 7 +++++-- .../Views/Pages/AlbumView/AlbumView.swift | 4 ++++ .../Pages/MediaPicker/AlbumSelectionView.swift | 4 ++++ .../Views/Pages/MediaPicker/MediaPicker.swift | 14 ++++++++++---- 5 files changed, 24 insertions(+), 7 deletions(-) diff --git a/Examples/Example-iOS/CustomizedMediaPicker.swift b/Examples/Example-iOS/CustomizedMediaPicker.swift index 19e1004..b9ed05b 100644 --- a/Examples/Example-iOS/CustomizedMediaPicker.swift +++ b/Examples/Example-iOS/CustomizedMediaPicker.swift @@ -25,7 +25,7 @@ struct CustomizedMediaPicker: View { MediaPicker( isPresented: $isPresented, onChange: { medias = $0 }, - albumSelectionBuilder: { _, albumSelectionView in + albumSelectionBuilder: { _, albumSelectionView, _ in VStack { headerView albumSelectionView diff --git a/README.md b/README.md index ec4faf0..1fd3862 100644 --- a/README.md +++ b/README.md @@ -73,9 +73,11 @@ You can pass two view builders in order to add your own buttons and other elemen MediaPicker( isPresented: $isPresented, onChange: { selectedMedia = $0 }, - albumSelectionBuilder: { defaultHeaderView, albumSelectionView in + albumSelectionBuilder: { defaultHeaderView, albumSelectionView, isInFullscreen in VStack { - defaultHeaderView + if !isInFullscreen { + defaultHeaderView + } albumSelectionView Spacer() footerView @@ -88,6 +90,7 @@ MediaPicker( `albumSelectionBuilder` gives you two views to work with: - `defaultHeaderView` - a default looking `header` with photos/albums mode switcher - `albumSelectionView` - the photos grid itself +- `isInFullscreen` - is fullscreen photo details screen displayed. Use for example to hide the header while in fullscreen mode. The second customizable screen is the one you see after taking a photo. Pass `cameraSelectionBuilder` like this: ```swift diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift index 89fcc7d..1be96db 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift @@ -12,7 +12,9 @@ struct AlbumView: View { @StateObject var viewModel: AlbumViewModel @Binding var showingCamera: Bool + @Binding var isInFullscreen: Bool @Binding var currentFullscreenMedia: Media? + var shouldShowCamera: Bool var shouldShowLoadingCell: Bool var selectionParamsHolder: SelectionParamsHolder @@ -20,8 +22,10 @@ struct AlbumView: View { @State private var fullscreenItem: AssetMediaModel? { didSet { if let item = fullscreenItem { + isInFullscreen = true currentFullscreenMedia = Media(source: item) } else { + isInFullscreen = false currentFullscreenMedia = nil } } diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/AlbumSelectionView.swift b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/AlbumSelectionView.swift index a11f217..8f14b5b 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/AlbumSelectionView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/AlbumSelectionView.swift @@ -12,7 +12,9 @@ public struct AlbumSelectionView: View { @ObservedObject var viewModel: MediaPickerViewModel @Binding var showingCamera: Bool + @Binding var isInFullscreen: Bool @Binding var currentFullscreenMedia: Media? + let showingLiveCameraCell: Bool let selectionParamsHolder: SelectionParamsHolder let filterClosure: MediaPicker.FilterClosure? @@ -28,6 +30,7 @@ public struct AlbumSelectionView: View { mediasProvider: AllPhotosProvider(selectionParamsHolder: selectionParamsHolder, filterClosure: filterClosure, massFilterClosure: massFilterClosure, showingLoadingCell: $showingLoadingCell) ), showingCamera: $showingCamera, + isInFullscreen: $isInFullscreen, currentFullscreenMedia: $currentFullscreenMedia, shouldShowCamera: showingLiveCameraCell, shouldShowLoadingCell: showingLoadingCell, @@ -55,6 +58,7 @@ public struct AlbumSelectionView: View { mediasProvider: AlbumMediasProvider(album: albumModel, selectionParamsHolder: selectionParamsHolder, filterClosure: filterClosure, massFilterClosure: massFilterClosure, showingLoadingCell: $showingLoadingCell) ), showingCamera: $showingCamera, + isInFullscreen: $isInFullscreen, currentFullscreenMedia: $currentFullscreenMedia, shouldShowCamera: false, shouldShowLoadingCell: showingLoadingCell, diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift index 47690d0..1619049 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift @@ -10,7 +10,8 @@ public struct MediaPicker AlbumSelectionContent) + /// - is in fullscreen photo details mode + public typealias AlbumSelectionClosure = ((ModeSwitcher, AlbumSelectionView, Bool) -> AlbumSelectionContent) /// To provide custom buttons layout for camera selection view use actions and views by this closure: /// - add more photos closure @@ -54,6 +55,7 @@ public struct MediaPicker Date: Thu, 17 Aug 2023 14:53:34 +0700 Subject: [PATCH 05/70] Fix single selection mode --- .../Views/Pages/AlbumView/AlbumView.swift | 4 +++- .../Fullscreen/FullscreenContainer.swift | 22 +++++++++++++------ .../MediaPicker/AlbumSelectionView.swift | 7 ++++-- .../Views/Pages/MediaPicker/MediaPicker.swift | 5 ++++- 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift index 1be96db..c71f764 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift @@ -18,6 +18,7 @@ struct AlbumView: View { var shouldShowCamera: Bool var shouldShowLoadingCell: Bool var selectionParamsHolder: SelectionParamsHolder + var shouldDismiss: ()->() @State private var fullscreenItem: AssetMediaModel? { didSet { @@ -106,7 +107,8 @@ private extension AlbumView { isPresented: fullscreenPresentedBinding(), assetMediaModels: viewModel.assetMediaModels, selection: item.id, - selectionParamsHolder: selectionParamsHolder + selectionParamsHolder: selectionParamsHolder, + shouldDismiss: shouldDismiss ) } } diff --git a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift index 657c3eb..2a9a619 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift @@ -14,6 +14,7 @@ struct FullscreenContainer: View { let assetMediaModels: [AssetMediaModel] @State var selection: AssetMediaModel.ID var selectionParamsHolder: SelectionParamsHolder + var shouldDismiss: ()->() private var selectedMediaModel: AssetMediaModel? { assetMediaModels.first { $0.id == selection } @@ -34,15 +35,22 @@ struct FullscreenContainer: View { .tag(assetMediaModel.id) } } - .overlay { - if let selectedMediaModel = selectedMediaModel, selectionParamsHolder.selectionLimit != 1 { - SelectIndicatorView(index: selectionServiceIndex, isFullscreen: true, canSelect: selectionService.canSelect(assetMediaModel: selectedMediaModel), selectionParamsHolder: selectionParamsHolder) - .padding([.horizontal, .bottom], 20) - .contentShape(Rectangle()) - .onTapGesture { + .overlay(alignment: .topTrailing) { + if let selectedMediaModel = selectedMediaModel { + if selectionParamsHolder.selectionLimit == 1 { + Button("Select") { selectionService.onSelect(assetMediaModel: selectedMediaModel) + shouldDismiss() } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing) + .padding([.horizontal, .bottom], 20) + } else { + SelectIndicatorView(index: selectionServiceIndex, isFullscreen: true, canSelect: selectionService.canSelect(assetMediaModel: selectedMediaModel), selectionParamsHolder: selectionParamsHolder) + .padding([.horizontal, .bottom], 20) + .contentShape(Rectangle()) + .onTapGesture { + selectionService.onSelect(assetMediaModel: selectedMediaModel) + } + } } } .onTapGesture { diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/AlbumSelectionView.swift b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/AlbumSelectionView.swift index 8f14b5b..158dd1f 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/AlbumSelectionView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/AlbumSelectionView.swift @@ -19,6 +19,7 @@ public struct AlbumSelectionView: View { let selectionParamsHolder: SelectionParamsHolder let filterClosure: MediaPicker.FilterClosure? let massFilterClosure: MediaPicker.MassFilterClosure? + var shouldDismiss: ()->() @State private var showingLoadingCell = false @@ -34,7 +35,8 @@ public struct AlbumSelectionView: View { currentFullscreenMedia: $currentFullscreenMedia, shouldShowCamera: showingLiveCameraCell, shouldShowLoadingCell: showingLoadingCell, - selectionParamsHolder: selectionParamsHolder + selectionParamsHolder: selectionParamsHolder, + shouldDismiss: shouldDismiss ) case .albums: AlbumsView( @@ -62,7 +64,8 @@ public struct AlbumSelectionView: View { currentFullscreenMedia: $currentFullscreenMedia, shouldShowCamera: false, shouldShowLoadingCell: showingLoadingCell, - selectionParamsHolder: selectionParamsHolder + selectionParamsHolder: selectionParamsHolder, + shouldDismiss: shouldDismiss ) .id(album.id) } diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift index 1619049..e376efb 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift @@ -126,7 +126,10 @@ public struct MediaPicker Date: Tue, 22 Aug 2023 15:21:01 +0700 Subject: [PATCH 06/70] Forbid screen rotation for the whole picker --- .../Views/Pages/MediaPicker/MediaPicker.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift index e376efb..f5a16f6 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift @@ -103,9 +103,6 @@ public struct MediaPicker Date: Tue, 22 Aug 2023 15:31:34 +0700 Subject: [PATCH 07/70] Fix orientation locking --- Examples/Example-iOS/AppDelegate.swift | 2 +- .../Views/Pages/MediaPicker/MediaPicker.swift | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Examples/Example-iOS/AppDelegate.swift b/Examples/Example-iOS/AppDelegate.swift index 4b66d7a..37d1b51 100644 --- a/Examples/Example-iOS/AppDelegate.swift +++ b/Examples/Example-iOS/AppDelegate.swift @@ -13,6 +13,7 @@ final class AppDelegate: NSObject, UIApplicationDelegate, ObservableObject { private var orientationLock = UIInterfaceOrientationMask.all func lockOrientationToPortrait() { + orientationLock = .portrait if #available(iOS 16, *) { if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene { scene.requestGeometryUpdate(.iOS(interfaceOrientations: .portrait)) @@ -21,7 +22,6 @@ final class AppDelegate: NSObject, UIApplicationDelegate, ObservableObject { } else { UIDevice.current.setValue(UIDeviceOrientation.portrait.rawValue, forKey: "orientation") } - orientationLock = .portrait } func unlockOrientation() { diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift index f5a16f6..ab13283 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift @@ -118,10 +118,6 @@ public struct MediaPicker Date: Tue, 22 Aug 2023 17:54:59 +0700 Subject: [PATCH 08/70] Fix slow camera dismissal --- .../Views/Pages/MediaPicker/MediaPicker.swift | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift index ab13283..2e0f8fb 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift @@ -58,6 +58,8 @@ public struct MediaPicker, @@ -76,14 +78,14 @@ public struct MediaPicker Date: Tue, 22 Aug 2023 18:19:03 +0700 Subject: [PATCH 09/70] Don't show circles in the library for single selection --- Examples/Example-iOS/ContentView.swift | 1 + .../Views/Pages/AlbumView/AlbumView.swift | 41 ++++++++++++------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/Examples/Example-iOS/ContentView.swift b/Examples/Example-iOS/ContentView.swift index 9785f6a..88b22b6 100644 --- a/Examples/Example-iOS/ContentView.swift +++ b/Examples/Example-iOS/ContentView.swift @@ -60,6 +60,7 @@ struct ContentView: View { onChange: { medias = $0 } ) .showLiveCameraCell() + .mediaSelectionLimit(1) .orientationHandler { switch $0 { case .lock: appDelegate.lockOrientationToPortrait() diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift index c71f764..eec5c24 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift @@ -68,20 +68,7 @@ private extension AlbumView { } #endif } content: { assetMediaModel in - let index = selectionService.index(of: assetMediaModel) - SelectableView(selected: index, isFullscreen: false, canSelect: selectionService.canSelect(assetMediaModel: assetMediaModel), selectionParamsHolder: selectionParamsHolder) { - selectionService.onSelect(assetMediaModel: assetMediaModel) - } content: { - Button { - if fullscreenItem == nil { - fullscreenItem = assetMediaModel - } - } label: { - MediaCell(viewModel: MediaViewModel(assetMediaModel: assetMediaModel)) - } - .buttonStyle(MediaButtonStyle()) - .contentShape(Rectangle()) - } + cellView(assetMediaModel) } loadingCell: { if shouldShowLoadingCell { ZStack { @@ -124,4 +111,30 @@ private extension AlbumView { } ) } + + @ViewBuilder + func cellView(_ assetMediaModel: AssetMediaModel) -> some View { + if selectionService.mediaSelectionLimit == 1 { + MediaCell(viewModel: MediaViewModel(assetMediaModel: assetMediaModel)) + .onTapGesture { + selectionService.onSelect(assetMediaModel: assetMediaModel) + shouldDismiss() + } + } else { + SelectableView(selected: selectionService.index(of: assetMediaModel), isFullscreen: false, canSelect: selectionService.canSelect(assetMediaModel: assetMediaModel), selectionParamsHolder: selectionParamsHolder) { + selectionService.onSelect(assetMediaModel: assetMediaModel) + } content: { + Button { + if fullscreenItem == nil { + fullscreenItem = assetMediaModel + } + } label: { + MediaCell(viewModel: MediaViewModel(assetMediaModel: assetMediaModel)) + } + .buttonStyle(MediaButtonStyle()) + .contentShape(Rectangle()) + } + } + } + } From 9c36467b8f20923631f9377dd013b0cd058f47c1 Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Thu, 24 Aug 2023 14:43:02 +0700 Subject: [PATCH 10/70] Don't make media smaller while keyboard is displayed --- .../Modifiers/FrameGetter.swift | 35 ++++++++ .../Modifiers/KeyboardHeightHelper.swift | 39 +++++++++ .../Pages/Fullscreen/FullscreenCell.swift | 83 +++++++++++++------ 3 files changed, 132 insertions(+), 25 deletions(-) create mode 100644 Sources/ExyteMediaPicker/Modifiers/FrameGetter.swift create mode 100644 Sources/ExyteMediaPicker/Modifiers/KeyboardHeightHelper.swift diff --git a/Sources/ExyteMediaPicker/Modifiers/FrameGetter.swift b/Sources/ExyteMediaPicker/Modifiers/FrameGetter.swift new file mode 100644 index 0000000..2b27e19 --- /dev/null +++ b/Sources/ExyteMediaPicker/Modifiers/FrameGetter.swift @@ -0,0 +1,35 @@ +// +// FrameGetter.swift +// Example-iOS +// +// Created by Alisa Mylnikova on 23.08.2023. +// + +import SwiftUI + +struct FrameGetter: ViewModifier { + + @Binding var frame: CGRect + + func body(content: Content) -> some View { + content + .background( + GeometryReader { proxy -> AnyView in + DispatchQueue.main.async { + let rect = proxy.frame(in: .global) + // This avoids an infinite layout loop + if rect.integral != self.frame.integral { + self.frame = rect + } + } + return AnyView(EmptyView()) + } + ) + } +} + +internal extension View { + func frameGetter(_ frame: Binding) -> some View { + modifier(FrameGetter(frame: frame)) + } +} diff --git a/Sources/ExyteMediaPicker/Modifiers/KeyboardHeightHelper.swift b/Sources/ExyteMediaPicker/Modifiers/KeyboardHeightHelper.swift new file mode 100644 index 0000000..bd41de9 --- /dev/null +++ b/Sources/ExyteMediaPicker/Modifiers/KeyboardHeightHelper.swift @@ -0,0 +1,39 @@ +// +// KeyboardHeightHelper.swift +// Example-iOS +// +// Created by Alisa Mylnikova on 23.08.2023. +// + +import SwiftUI + +class KeyboardHeightHelper: ObservableObject { + + static var shared = KeyboardHeightHelper() + + @Published var keyboardHeight: CGFloat = 0 + @Published var keyboardDisplayed: Bool = false + + init() { + self.listenForKeyboardNotifications() + } + + private func listenForKeyboardNotifications() { + NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, + object: nil, + queue: .main) { (notification) in + guard let userInfo = notification.userInfo, + let keyboardRect = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return } + + self.keyboardHeight = keyboardRect.height + self.keyboardDisplayed = true + } + + NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, + object: nil, + queue: .main) { (notification) in + self.keyboardHeight = 0 + self.keyboardDisplayed = false + } + } +} diff --git a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift index 644982e..751b0a7 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift @@ -9,42 +9,41 @@ import AVKit struct FullscreenCell: View { @StateObject var viewModel: FullscreenCellViewModel - + @ObservedObject var keyboardHeightHelper = KeyboardHeightHelper.shared + + @State var availableFrame: CGRect = .zero + @State var videoFrame: CGRect = .zero + var body: some View { VStack { + Spacer() if let image = viewModel.image { - ZoomableScrollView { - Image(uiImage: image) - .resizable() - .scaledToFit() + if !keyboardHeightHelper.keyboardDisplayed { + ZoomableScrollView { + imageView(image: image) + } + } else { + imageView(image: image) + .padding(.vertical, calculatePadding(imageSize: image.size, availableSize: availableFrame.size)) } } else if let player = viewModel.player { - ZoomableScrollView { - VideoPlayer(player: player) - .disabled(true) - .overlay { - ZStack { - Color.clear - if !viewModel.isPlaying { - Image(systemName: "play.fill") - .resizable() - .frame(width: 50, height: 50) - .foregroundColor(.white.opacity(0.8)) - } - } - .contentShape(Rectangle()) - .onTapGesture { - viewModel.togglePlay() - } - } + if !keyboardHeightHelper.keyboardDisplayed { + ZoomableScrollView { + videoView(player: player) + } + } else { + videoView(player: player) + .frameGetter($videoFrame) + .padding(.vertical, calculatePadding(imageSize: videoFrame.size, availableSize: availableFrame.size)) } } else { - Spacer() ProgressView() .tint(.white) - Spacer() } + Spacer() } + .frame(width: UIScreen.main.bounds.size.width) + .frameGetter($availableFrame) .task { await viewModel.onStart() } @@ -52,4 +51,38 @@ struct FullscreenCell: View { viewModel.onStop() } } + + func imageView(image: UIImage) -> some View { + Image(uiImage: image) + .resizable() + .scaledToFit() + } + + func videoView(player: AVPlayer) -> some View { + VideoPlayer(player: player) + .disabled(true) + .overlay { + ZStack { + Color.clear + if !viewModel.isPlaying { + Image(systemName: "play.fill") + .resizable() + .frame(width: 50, height: 50) + .foregroundColor(.white.opacity(0.8)) + } + } + .contentShape(Rectangle()) + .onTapGesture { + viewModel.togglePlay() + } + } + } + + func calculatePadding(imageSize: CGSize, availableSize: CGSize) -> CGFloat { + let ratio = imageSize.width / imageSize.height + let height = UIScreen.main.bounds.size.width / ratio + let extra = availableSize.height - height + print(availableSize, extra) + return extra > 0 ? 0 : extra / 2 - 6 + } } From 1192d7e900f6fc4e8942f55d16acb2e277117b34 Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Thu, 24 Aug 2023 14:57:08 +0700 Subject: [PATCH 11/70] Current fullscreen item fix --- .../Views/Pages/Fullscreen/FullscreenContainer.swift | 3 +++ .../ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift index 2a9a619..7289bc6 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift @@ -10,6 +10,8 @@ struct FullscreenContainer: View { @EnvironmentObject private var selectionService: SelectionService @Environment(\.mediaPickerTheme) private var theme + @ObservedObject var keyboardHeightHelper = KeyboardHeightHelper.shared + @Binding var isPresented: Bool let assetMediaModels: [AssetMediaModel] @State var selection: AssetMediaModel.ID @@ -58,6 +60,7 @@ struct FullscreenContainer: View { selectionService.onSelect(assetMediaModel: selectedMediaModel) } } + .disabled(keyboardHeightHelper.keyboardDisplayed) .overlay(closeButton) .tabViewStyle(.page(indexDisplayMode: .never)) .background( diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift index 2e0f8fb..44e8085 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift @@ -117,7 +117,7 @@ public struct MediaPicker Date: Fri, 25 Aug 2023 15:14:24 +0700 Subject: [PATCH 12/70] Fix video size calculations --- ExyteMediaPicker.podspec | 2 +- .../Views/Pages/Fullscreen/FullscreenCell.swift | 4 +--- .../Pages/Fullscreen/FullscreenCellViewModel.swift | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/ExyteMediaPicker.podspec b/ExyteMediaPicker.podspec index b5cc127..d7ab182 100644 --- a/ExyteMediaPicker.podspec +++ b/ExyteMediaPicker.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "ExyteMediaPicker" - s.version = "1.2.4" + s.version = "1.2.6" s.summary = "MediaPicker is a customizable photo/video picker for iOS written in pure SwiftUI" s.homepage = 'https://github.com/exyte/MediaPicker.git' diff --git a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift index 751b0a7..a6b2d67 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift @@ -12,7 +12,6 @@ struct FullscreenCell: View { @ObservedObject var keyboardHeightHelper = KeyboardHeightHelper.shared @State var availableFrame: CGRect = .zero - @State var videoFrame: CGRect = .zero var body: some View { VStack { @@ -33,8 +32,7 @@ struct FullscreenCell: View { } } else { videoView(player: player) - .frameGetter($videoFrame) - .padding(.vertical, calculatePadding(imageSize: videoFrame.size, availableSize: availableFrame.size)) + .padding(.vertical, calculatePadding(imageSize: viewModel.videoSize, availableSize: availableFrame.size)) } } else { ProgressView() diff --git a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCellViewModel.swift b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCellViewModel.swift index 758b795..ef0680b 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCellViewModel.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCellViewModel.swift @@ -15,6 +15,7 @@ final class FullscreenCellViewModel: ObservableObject { @Published var image: UIImage? = nil @Published var player: AVPlayer? = nil @Published var isPlaying = false + @Published var videoSize: CGSize = .zero private var currentTask: Task? @@ -38,6 +39,7 @@ final class FullscreenCellViewModel: ObservableObject { let url = await mediaModel.getURL() guard let url = url else { return } setupPlayer(url) + videoSize = getVideoSize(url) case .none: break } @@ -71,4 +73,16 @@ final class FullscreenCellViewModel: ObservableObject { } isPlaying = !isPlaying } + + func getVideoSize(_ url: URL) -> CGSize { + let videoAsset = AVURLAsset(url : url) + let videoAssetTrack = videoAsset.tracks(withMediaType: .video).first + let naturalSize = videoAssetTrack?.naturalSize ?? .zero + let transform = videoAssetTrack?.preferredTransform + if (transform?.tx == naturalSize.width && transform?.ty == naturalSize.height) || (transform?.tx == 0 && transform?.ty == 0) { + return naturalSize + } else { + return CGSize(width: naturalSize.height, height: naturalSize.width) + } + } } From b58ddea9ea10f98e169fa9c42045a67f23337db1 Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Fri, 1 Sep 2023 16:08:15 +0700 Subject: [PATCH 13/70] Fix single media selection mode --- .../Drivers/PhotoKit/Medias/MediasProviderProtocol.swift | 1 - .../ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/ExyteMediaPicker/Drivers/PhotoKit/Medias/MediasProviderProtocol.swift b/Sources/ExyteMediaPicker/Drivers/PhotoKit/Medias/MediasProviderProtocol.swift index ce73c1c..bcab269 100644 --- a/Sources/ExyteMediaPicker/Drivers/PhotoKit/Medias/MediasProviderProtocol.swift +++ b/Sources/ExyteMediaPicker/Drivers/PhotoKit/Medias/MediasProviderProtocol.swift @@ -53,7 +53,6 @@ class BaseMediasProvider: MediasProviderProtocol { if let media = await filterClosure(Media(source: $0)), let model = media.source as? AssetMediaModel { serialQueue.sync { result.append(model) - print(result.count) assetMediaModelsPublisher.send(result) } } diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift index eec5c24..385ae8a 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift @@ -117,8 +117,9 @@ private extension AlbumView { if selectionService.mediaSelectionLimit == 1 { MediaCell(viewModel: MediaViewModel(assetMediaModel: assetMediaModel)) .onTapGesture { - selectionService.onSelect(assetMediaModel: assetMediaModel) - shouldDismiss() + if fullscreenItem == nil { + fullscreenItem = assetMediaModel + } } } else { SelectableView(selected: selectionService.index(of: assetMediaModel), isFullscreen: false, canSelect: selectionService.canSelect(assetMediaModel: assetMediaModel), selectionParamsHolder: selectionParamsHolder) { From b60bd2507eaaf92673b53504622d2520b634358e Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Fri, 1 Sep 2023 17:18:19 +0700 Subject: [PATCH 14/70] Add audio capture for videos --- .../Views/Pages/Camera/CameraViewModel.swift | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraViewModel.swift b/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraViewModel.swift index f71923d..dcc3c37 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraViewModel.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraViewModel.swift @@ -154,6 +154,11 @@ final class CameraViewModel: NSObject, ObservableObject { guard session.canAddInput(captureDeviceInput) else { return } session.addInput(captureDeviceInput) + guard let captureAudioDevice = selectAudioCaptureDevice() else { return } + guard let captureAudioDeviceInput = try? AVCaptureDeviceInput(device: captureAudioDevice) else { return } + guard session.canAddInput(captureAudioDeviceInput) else { return } + session.addInput(captureAudioDeviceInput) + let defaultZoom = CGFloat(truncating: captureDevice.virtualDeviceSwitchOverVideoZoomFactors.first ?? minScale as NSNumber) let maxZoom: CGFloat @@ -217,6 +222,15 @@ final class CameraViewModel: NSObject, ObservableObject { } } + private func selectAudioCaptureDevice() -> AVCaptureDevice? { + let session = AVCaptureDevice.DiscoverySession( + deviceTypes: [.builtInMicrophone], + mediaType: .audio, + position: .unspecified) + + return session.devices.first + } + } extension CameraViewModel: AVCapturePhotoCaptureDelegate { From 154ec104ac96b799c2b5af33d8d43cf013c719b7 Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Mon, 4 Sep 2023 13:43:22 +0700 Subject: [PATCH 15/70] Add custom bg for video player --- .../Pages/Fullscreen/FullscreenCell.swift | 4 +- .../Fullscreen/FullscreenContainer.swift | 2 +- .../Views/Widgets/PlayerUIView.swift | 66 +++++++++++++++++++ .../Views/Widgets/SelectIndicatorView.swift | 1 + 4 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 Sources/ExyteMediaPicker/Views/Widgets/PlayerUIView.swift diff --git a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift index a6b2d67..ef75247 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift @@ -7,6 +7,8 @@ import SwiftUI import AVKit struct FullscreenCell: View { + + @Environment(\.mediaPickerTheme) private var theme @StateObject var viewModel: FullscreenCellViewModel @ObservedObject var keyboardHeightHelper = KeyboardHeightHelper.shared @@ -57,7 +59,7 @@ struct FullscreenCell: View { } func videoView(player: AVPlayer) -> some View { - VideoPlayer(player: player) + PlayerView(player: player, bgColor: theme.main.fullscreenPhotoBackground) .disabled(true) .overlay { ZStack { diff --git a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift index 7289bc6..267df2d 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift @@ -75,8 +75,8 @@ struct FullscreenContainer: View { } label: { Image(systemName: "xmark") .resizable() - .tint(theme.selection.fullscreenTint) .frame(width: 20, height: 20) + .tint(theme.selection.fullscreenTint) } .padding([.horizontal, .bottom], 20) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) diff --git a/Sources/ExyteMediaPicker/Views/Widgets/PlayerUIView.swift b/Sources/ExyteMediaPicker/Views/Widgets/PlayerUIView.swift new file mode 100644 index 0000000..982354c --- /dev/null +++ b/Sources/ExyteMediaPicker/Views/Widgets/PlayerUIView.swift @@ -0,0 +1,66 @@ +// +// File.swift +// +// +// Created by Alisa Mylnikova on 04.09.2023. +// + +import SwiftUI +import AVFoundation + +struct PlayerView: UIViewRepresentable { + + var player: AVPlayer + var bgColor: Color + + func makeUIView(context: Context) -> PlayerUIView { + PlayerUIView(player: player, bgColor: bgColor) + } + + func updateUIView(_ uiView: PlayerUIView, context: UIViewRepresentableContext) { + uiView.playerLayer.player = player + } +} + +class PlayerUIView: UIView { + + // MARK: Class Property + + let playerLayer = AVPlayerLayer() + + // MARK: Init + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + init(player: AVPlayer, bgColor: Color) { + super.init(frame: .zero) + self.playerSetup(player: player, bgColor: bgColor) + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + // MARK: Life-Cycle + + override func layoutSubviews() { + super.layoutSubviews() + playerLayer.frame = bounds + } + + // MARK: Class Methods + + private func playerSetup(player: AVPlayer, bgColor: Color) { + playerLayer.player = player + player.actionAtItemEnd = .none + layer.addSublayer(playerLayer) + print(bgColor.description.debugDescription) + playerLayer.backgroundColor = bgColor.cgColor + } +} diff --git a/Sources/ExyteMediaPicker/Views/Widgets/SelectIndicatorView.swift b/Sources/ExyteMediaPicker/Views/Widgets/SelectIndicatorView.swift index 93cf4fe..72c3548 100644 --- a/Sources/ExyteMediaPicker/Views/Widgets/SelectIndicatorView.swift +++ b/Sources/ExyteMediaPicker/Views/Widgets/SelectIndicatorView.swift @@ -25,6 +25,7 @@ struct SelectIndicatorView: View { } } .frame(width: 24, height: 24) + .padding(.top, -2) } var checkView: some View { From e5cae02a7a16d5544acd6f7dc78995672fc83ed1 Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Mon, 4 Sep 2023 14:09:26 +0700 Subject: [PATCH 16/70] Add keyboard dismiss on tap --- ExyteMediaPicker.podspec | 2 +- .../Extensions/View+Keyboard.swift | 14 ++++++++++++++ .../Pages/Fullscreen/FullscreenContainer.swift | 12 ++++++++---- 3 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 Sources/ExyteMediaPicker/Extensions/View+Keyboard.swift diff --git a/ExyteMediaPicker.podspec b/ExyteMediaPicker.podspec index d7ab182..4cd1a3c 100644 --- a/ExyteMediaPicker.podspec +++ b/ExyteMediaPicker.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "ExyteMediaPicker" - s.version = "1.2.6" + s.version = "1.3.0" s.summary = "MediaPicker is a customizable photo/video picker for iOS written in pure SwiftUI" s.homepage = 'https://github.com/exyte/MediaPicker.git' diff --git a/Sources/ExyteMediaPicker/Extensions/View+Keyboard.swift b/Sources/ExyteMediaPicker/Extensions/View+Keyboard.swift new file mode 100644 index 0000000..229a605 --- /dev/null +++ b/Sources/ExyteMediaPicker/Extensions/View+Keyboard.swift @@ -0,0 +1,14 @@ +// +// File.swift +// +// +// Created by Alisa Mylnikova on 04.09.2023. +// + +import SwiftUI + +extension View { + func dismissKeyboard() { + UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) + } +} diff --git a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift index 267df2d..a952728 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift @@ -56,11 +56,14 @@ struct FullscreenContainer: View { } } .onTapGesture { - if let selectedMediaModel = selectedMediaModel, selectedMediaModel.mediaType == .image { - selectionService.onSelect(assetMediaModel: selectedMediaModel) + if keyboardHeightHelper.keyboardDisplayed { + dismissKeyboard() + } else { + if let selectedMediaModel = selectedMediaModel, selectedMediaModel.mediaType == .image { + selectionService.onSelect(assetMediaModel: selectedMediaModel) + } } } - .disabled(keyboardHeightHelper.keyboardDisplayed) .overlay(closeButton) .tabViewStyle(.page(indexDisplayMode: .never)) .background( @@ -77,8 +80,9 @@ struct FullscreenContainer: View { .resizable() .frame(width: 20, height: 20) .tint(theme.selection.fullscreenTint) + .padding(12) } - .padding([.horizontal, .bottom], 20) + .padding([.horizontal, .bottom], 8) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) } } From 922bde116b1ef6a1bee1e46a603f8773a4c7711d Mon Sep 17 00:00:00 2001 From: Basel Baragabah Date: Tue, 3 Oct 2023 23:29:50 +0300 Subject: [PATCH 17/70] add cell style add abality to customize the and columns spacing, row spacing, and cornerRadius of the Media Grid --- .../Drivers/Theme/MediaPickerTheme.swift | 17 +++++++++++++++++ .../Modifiers/MediaPickerThemeModifier.swift | 3 ++- .../Views/Pages/AlbumView/MediaCell.swift | 5 ++++- .../Views/Widgets/MediasGrid.swift | 6 ++++-- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/Sources/ExyteMediaPicker/Drivers/Theme/MediaPickerTheme.swift b/Sources/ExyteMediaPicker/Drivers/Theme/MediaPickerTheme.swift index efb885f..a82369c 100644 --- a/Sources/ExyteMediaPicker/Drivers/Theme/MediaPickerTheme.swift +++ b/Sources/ExyteMediaPicker/Drivers/Theme/MediaPickerTheme.swift @@ -8,13 +8,16 @@ import SwiftUI public struct MediaPickerTheme { public let main: Main public let selection: Selection + public let cellStyle: CellStyle public let error: Error public init(main: MediaPickerTheme.Main = .init(), selection: MediaPickerTheme.Selection = .init(), + cellStyle: MediaPickerTheme.CellStyle = .init(), error: MediaPickerTheme.Error = .init()) { self.main = main self.selection = selection + self.cellStyle = cellStyle self.error = error } } @@ -39,6 +42,20 @@ extension MediaPickerTheme { self.cameraSelectionBackground = cameraSelectionBackground } } + + public struct CellStyle { + public let columnsSpacing: CGFloat + public let rowSpacing: CGFloat + public let cornerRadius: CGFloat + + public init(columnsSpacing: CGFloat = 1, + rowSpacing: CGFloat = 1, + cornerRadius: CGFloat = 0) { + self.columnsSpacing = columnsSpacing + self.rowSpacing = rowSpacing + self.cornerRadius = cornerRadius + } + } public struct Selection { public let emptyTint: Color diff --git a/Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift b/Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift index 9935246..20e9447 100644 --- a/Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift +++ b/Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift @@ -23,7 +23,8 @@ public extension View { func mediaPickerTheme(main: MediaPickerTheme.Main = .init(), selection: MediaPickerTheme.Selection = .init(), + cellStyle: MediaPickerTheme.CellStyle = .init(), error: MediaPickerTheme.Error = .init()) -> some View { - self.environment(\.mediaPickerTheme, MediaPickerTheme(main: main, selection: selection, error: error)) + self.environment(\.mediaPickerTheme, MediaPickerTheme(main: main, selection: selection, cellStyle: cellStyle, error: error)) } } diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/MediaCell.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/MediaCell.swift index 3f4e169..aa12c19 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/MediaCell.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/MediaCell.swift @@ -8,15 +8,18 @@ struct MediaCell: View { @StateObject var viewModel: MediaViewModel + @Environment(\.mediaPickerTheme) private var theme + var body: some View { ZStack { GeometryReader { geometry in ThumbnailView(preview: viewModel.preview) + .cornerRadius(theme.cellStyle.cornerRadius) .onAppear { viewModel.onStart(size: geometry.size) } } - .aspectRatio(1, contentMode: .fill) + .aspectRatio(1, contentMode: .fit) .clipped() if let duration = viewModel.assetMediaModel.asset.formattedDuration { diff --git a/Sources/ExyteMediaPicker/Views/Widgets/MediasGrid.swift b/Sources/ExyteMediaPicker/Views/Widgets/MediasGrid.swift index e21304d..228235d 100644 --- a/Sources/ExyteMediaPicker/Views/Widgets/MediasGrid.swift +++ b/Sources/ExyteMediaPicker/Views/Widgets/MediasGrid.swift @@ -12,8 +12,10 @@ public struct MediasGrid: View where Data: R public let content: (Data.Element) -> Content public let loadingCell: () -> LoadingCell + @Environment(\.mediaPickerTheme) private var theme + private var columns: [GridItem] { - [GridItem(.adaptive(minimum: 100), spacing: 1, alignment: .top)] + [GridItem(.adaptive(minimum: 100), spacing: theme.cellStyle.columnsSpacing, alignment: .top)] } public init(_ data: Data, @ViewBuilder camera: @escaping () -> Camera, @ViewBuilder content: @escaping (Data.Element) -> Content, @ViewBuilder loadingCell: @escaping () -> LoadingCell) { @@ -24,7 +26,7 @@ public struct MediasGrid: View where Data: R } public var body: some View { - LazyVGrid(columns: columns, spacing: 1) { + LazyVGrid(columns: columns, spacing: theme.cellStyle.rowSpacing) { camera() ForEach(data) { item in content(item) From 6c0b9d0867bcef9be0b0eb221e470ca941014d64 Mon Sep 17 00:00:00 2001 From: "b.baragabah" <> Date: Tue, 10 Oct 2023 14:45:57 +0300 Subject: [PATCH 18/70] fix issue when tap on MediaCell open wrong image fix issue when tap on MediaCell open wrong image sometime --- Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift index 385ae8a..86d41ec 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift @@ -116,6 +116,7 @@ private extension AlbumView { func cellView(_ assetMediaModel: AssetMediaModel) -> some View { if selectionService.mediaSelectionLimit == 1 { MediaCell(viewModel: MediaViewModel(assetMediaModel: assetMediaModel)) + .contentShape(Rectangle()) .onTapGesture { if fullscreenItem == nil { fullscreenItem = assetMediaModel From 75f4393172f50ee3368433ce23f70bdfe2ad8ae1 Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Wed, 11 Oct 2023 15:10:48 +0700 Subject: [PATCH 19/70] Renew example project --- Examples/Example-iOS/Info.plist | 12 - .../contents.xcworkspacedata | 7 - .../project.pbxproj | 303 +++++++++--------- .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../MediaPickerExample}/AppDelegate.swift | 0 .../MediaPickerExample}/Application.swift | 0 .../AccentColor.colorset/Contents.json | 0 .../AppIcon.appiconset/Contents.json | 0 .../Chevron.imageset}/Contents.json | 0 .../Chevron.imageset}/chevron.pdf | Bin .../Assets.xcassets/Contents.json | 0 .../CustomGreen.colorset/Contents.json | 0 .../CustomPurple.colorset/Contents.json | 0 .../MediaPickerExample}/ContentView.swift | 0 .../CustomizedMediaPicker.swift | 28 +- .../FilterMediaPicker.swift | 0 .../MediaPickerExample}/MediaCell.swift | 0 .../Preview Assets.xcassets/Contents.json | 0 .../Modifiers/KeyboardHeightHelper.swift | 25 +- .../Views/Pages/AlbumView/AlbumView.swift | 13 + .../Pages/Fullscreen/FullscreenCell.swift | 2 - 21 files changed, 191 insertions(+), 199 deletions(-) delete mode 100644 Examples/Example-iOS/Info.plist delete mode 100644 Examples/Examples.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename {Examples/Examples.xcodeproj => MediaPickerExample/MediaPickerExample.xcodeproj}/project.pbxproj (54%) rename {Examples/Examples.xcodeproj => MediaPickerExample/MediaPickerExample.xcodeproj}/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename {Examples/Example-iOS => MediaPickerExample/MediaPickerExample}/AppDelegate.swift (100%) rename {Examples/Example-iOS => MediaPickerExample/MediaPickerExample}/Application.swift (100%) rename {Examples/Example-iOS => MediaPickerExample/MediaPickerExample}/Assets.xcassets/AccentColor.colorset/Contents.json (100%) rename {Examples/Example-iOS => MediaPickerExample/MediaPickerExample}/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename {Examples/Example-iOS/Assets.xcassets/chevron.imageset => MediaPickerExample/MediaPickerExample/Assets.xcassets/Chevron.imageset}/Contents.json (100%) rename {Examples/Example-iOS/Assets.xcassets/chevron.imageset => MediaPickerExample/MediaPickerExample/Assets.xcassets/Chevron.imageset}/chevron.pdf (100%) rename {Examples/Example-iOS => MediaPickerExample/MediaPickerExample}/Assets.xcassets/Contents.json (100%) rename {Examples/Example-iOS => MediaPickerExample/MediaPickerExample}/Assets.xcassets/CustomGreen.colorset/Contents.json (100%) rename {Examples/Example-iOS => MediaPickerExample/MediaPickerExample}/Assets.xcassets/CustomPurple.colorset/Contents.json (100%) rename {Examples/Example-iOS => MediaPickerExample/MediaPickerExample}/ContentView.swift (100%) rename {Examples/Example-iOS => MediaPickerExample/MediaPickerExample}/CustomizedMediaPicker.swift (87%) rename {Examples/Example-iOS => MediaPickerExample/MediaPickerExample}/FilterMediaPicker.swift (100%) rename {Examples/Example-iOS => MediaPickerExample/MediaPickerExample}/MediaCell.swift (100%) rename {Examples/Example-iOS => MediaPickerExample/MediaPickerExample}/Preview Content/Preview Assets.xcassets/Contents.json (100%) diff --git a/Examples/Example-iOS/Info.plist b/Examples/Example-iOS/Info.plist deleted file mode 100644 index f9ff86c..0000000 --- a/Examples/Example-iOS/Info.plist +++ /dev/null @@ -1,12 +0,0 @@ - - - - - NSPhotoLibraryUsageDescription - Grant access to photo library to be able to select photos - NSMicrophoneUsageDescription - Grant access to microphone to be able to take videos - NSCameraUsageDescription - Grant access to camera to be able to take photos and videos - - diff --git a/Examples/Examples.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Examples/Examples.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a..0000000 --- a/Examples/Examples.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/Examples/Examples.xcodeproj/project.pbxproj b/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj similarity index 54% rename from Examples/Examples.xcodeproj/project.pbxproj rename to MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj index 5dd69f1..7919879 100644 --- a/Examples/Examples.xcodeproj/project.pbxproj +++ b/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj @@ -3,106 +3,88 @@ archiveVersion = 1; classes = { }; - objectVersion = 55; + objectVersion = 56; objects = { /* Begin PBXBuildFile section */ - 130FBF5528740D3E0086478A /* CustomizedMediaPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 130FBF5428740D3E0086478A /* CustomizedMediaPicker.swift */; }; - 1384FF0B283F912E00C9AAC1 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1384FF0A283F912E00C9AAC1 /* Application.swift */; }; - 1384FF0D283F912E00C9AAC1 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1384FF0C283F912E00C9AAC1 /* ContentView.swift */; }; - 1384FF0F283F912F00C9AAC1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1384FF0E283F912F00C9AAC1 /* Assets.xcassets */; }; - 1384FF12283F912F00C9AAC1 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1384FF11283F912F00C9AAC1 /* Preview Assets.xcassets */; }; - 5B1CA4E029F137E100245B8B /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B1CA4DF29F137E100245B8B /* ExyteMediaPicker */; }; - 5B5AA87329F2923A001306F9 /* MediaCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5AA87229F2923A001306F9 /* MediaCell.swift */; }; - 5B92AABC2A162DB3002F9EB2 /* FilterMediaPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B92AABB2A162DB3002F9EB2 /* FilterMediaPicker.swift */; }; - A82559832901A437008D9CC5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A82559822901A437008D9CC5 /* AppDelegate.swift */; }; + 5BFF684D2AD68B990099D333 /* MediaCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFF68432AD68B990099D333 /* MediaCell.swift */; }; + 5BFF684E2AD68B990099D333 /* FilterMediaPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFF68442AD68B990099D333 /* FilterMediaPicker.swift */; }; + 5BFF684F2AD68B990099D333 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5BFF68452AD68B990099D333 /* Assets.xcassets */; }; + 5BFF68502AD68B990099D333 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5BFF68472AD68B990099D333 /* Preview Assets.xcassets */; }; + 5BFF68512AD68B990099D333 /* CustomizedMediaPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFF68482AD68B990099D333 /* CustomizedMediaPicker.swift */; }; + 5BFF68522AD68B990099D333 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFF68492AD68B990099D333 /* AppDelegate.swift */; }; + 5BFF68532AD68B990099D333 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFF684A2AD68B990099D333 /* Application.swift */; }; + 5BFF68542AD68B990099D333 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFF684B2AD68B990099D333 /* ContentView.swift */; }; + 5BFF68592AD68C510099D333 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5BFF68582AD68C510099D333 /* ExyteMediaPicker */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 130FBF5428740D3E0086478A /* CustomizedMediaPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizedMediaPicker.swift; sourceTree = ""; }; - 1384FF07283F912E00C9AAC1 /* Example-iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Example-iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - 1384FF0A283F912E00C9AAC1 /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = ""; }; - 1384FF0C283F912E00C9AAC1 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; - 1384FF0E283F912F00C9AAC1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 1384FF11283F912F00C9AAC1 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - 1384FF19283F920A00C9AAC1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 5B1CA4DE29F137A600245B8B /* MediaPicker */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = MediaPicker; path = ..; sourceTree = ""; }; - 5B5AA87229F2923A001306F9 /* MediaCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaCell.swift; sourceTree = ""; }; - 5B92AABB2A162DB3002F9EB2 /* FilterMediaPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterMediaPicker.swift; sourceTree = ""; }; - A82559822901A437008D9CC5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 5BFF68312AD689C80099D333 /* MediaPickerExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MediaPickerExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 5BFF68432AD68B990099D333 /* MediaCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaCell.swift; sourceTree = ""; }; + 5BFF68442AD68B990099D333 /* FilterMediaPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterMediaPicker.swift; sourceTree = ""; }; + 5BFF68452AD68B990099D333 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 5BFF68472AD68B990099D333 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 5BFF68482AD68B990099D333 /* CustomizedMediaPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizedMediaPicker.swift; sourceTree = ""; }; + 5BFF68492AD68B990099D333 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 5BFF684A2AD68B990099D333 /* Application.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = ""; }; + 5BFF684B2AD68B990099D333 /* ContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 5BFF68562AD68C050099D333 /* MediaPicker */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = MediaPicker; path = ..; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 1384FF04283F912E00C9AAC1 /* Frameworks */ = { + 5BFF682E2AD689C80099D333 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5B1CA4E029F137E100245B8B /* ExyteMediaPicker in Frameworks */, + 5BFF68592AD68C510099D333 /* ExyteMediaPicker in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 130FBF5628740FB30086478A /* Support Files */ = { + 5BFF68282AD689C80099D333 = { isa = PBXGroup; children = ( - 1384FF0E283F912F00C9AAC1 /* Assets.xcassets */, - 1384FF19283F920A00C9AAC1 /* Info.plist */, - 1384FF10283F912F00C9AAC1 /* Preview Content */, + 5BFF68562AD68C050099D333 /* MediaPicker */, + 5BFF68422AD68B990099D333 /* MediaPickerExample */, + 5BFF68322AD689C80099D333 /* Products */, + 5BFF68572AD68C510099D333 /* Frameworks */, ); - name = "Support Files"; sourceTree = ""; }; - 1384FEFA283F90C800C9AAC1 = { + 5BFF68322AD689C80099D333 /* Products */ = { isa = PBXGroup; children = ( - 1384FF01283F90FA00C9AAC1 /* Packages */, - 1384FF09283F912E00C9AAC1 /* Example-iOS */, - 1384FF08283F912E00C9AAC1 /* Products */, - 1384FF16283F919C00C9AAC1 /* Frameworks */, - ); - sourceTree = ""; - }; - 1384FF01283F90FA00C9AAC1 /* Packages */ = { - isa = PBXGroup; - children = ( - 5B1CA4DE29F137A600245B8B /* MediaPicker */, - ); - name = Packages; - sourceTree = ""; - }; - 1384FF08283F912E00C9AAC1 /* Products */ = { - isa = PBXGroup; - children = ( - 1384FF07283F912E00C9AAC1 /* Example-iOS.app */, + 5BFF68312AD689C80099D333 /* MediaPickerExample.app */, ); name = Products; sourceTree = ""; }; - 1384FF09283F912E00C9AAC1 /* Example-iOS */ = { + 5BFF68422AD68B990099D333 /* MediaPickerExample */ = { isa = PBXGroup; children = ( - 130FBF5628740FB30086478A /* Support Files */, - 1384FF0A283F912E00C9AAC1 /* Application.swift */, - 130FBF5428740D3E0086478A /* CustomizedMediaPicker.swift */, - 1384FF0C283F912E00C9AAC1 /* ContentView.swift */, - A82559822901A437008D9CC5 /* AppDelegate.swift */, - 5B5AA87229F2923A001306F9 /* MediaCell.swift */, - 5B92AABB2A162DB3002F9EB2 /* FilterMediaPicker.swift */, + 5BFF684A2AD68B990099D333 /* Application.swift */, + 5BFF68492AD68B990099D333 /* AppDelegate.swift */, + 5BFF684B2AD68B990099D333 /* ContentView.swift */, + 5BFF68432AD68B990099D333 /* MediaCell.swift */, + 5BFF68482AD68B990099D333 /* CustomizedMediaPicker.swift */, + 5BFF68442AD68B990099D333 /* FilterMediaPicker.swift */, + 5BFF68452AD68B990099D333 /* Assets.xcassets */, + 5BFF68462AD68B990099D333 /* Preview Content */, ); - path = "Example-iOS"; + path = MediaPickerExample; sourceTree = ""; }; - 1384FF10283F912F00C9AAC1 /* Preview Content */ = { + 5BFF68462AD68B990099D333 /* Preview Content */ = { isa = PBXGroup; children = ( - 1384FF11283F912F00C9AAC1 /* Preview Assets.xcassets */, + 5BFF68472AD68B990099D333 /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; - 1384FF16283F919C00C9AAC1 /* Frameworks */ = { + 5BFF68572AD68C510099D333 /* Frameworks */ = { isa = PBXGroup; children = ( ); @@ -112,109 +94,96 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 1384FF06283F912E00C9AAC1 /* Example-iOS */ = { + 5BFF68302AD689C80099D333 /* MediaPickerExample */ = { isa = PBXNativeTarget; - buildConfigurationList = 1384FF13283F912F00C9AAC1 /* Build configuration list for PBXNativeTarget "Example-iOS" */; + buildConfigurationList = 5BFF683F2AD689C90099D333 /* Build configuration list for PBXNativeTarget "MediaPickerExample" */; buildPhases = ( - 1384FF03283F912E00C9AAC1 /* Sources */, - 1384FF04283F912E00C9AAC1 /* Frameworks */, - 1384FF05283F912E00C9AAC1 /* Resources */, + 5BFF682D2AD689C80099D333 /* Sources */, + 5BFF682E2AD689C80099D333 /* Frameworks */, + 5BFF682F2AD689C80099D333 /* Resources */, ); buildRules = ( ); dependencies = ( ); - name = "Example-iOS"; + name = MediaPickerExample; packageProductDependencies = ( - 5B1CA4DF29F137E100245B8B /* ExyteMediaPicker */, + 5BFF68582AD68C510099D333 /* ExyteMediaPicker */, ); - productName = "Example-iOS"; - productReference = 1384FF07283F912E00C9AAC1 /* Example-iOS.app */; + productName = MediaPickerExample; + productReference = 5BFF68312AD689C80099D333 /* MediaPickerExample.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ - 1384FEFB283F90C800C9AAC1 /* Project object */ = { + 5BFF68292AD689C80099D333 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1340; - LastUpgradeCheck = 1340; + LastSwiftUpdateCheck = 1500; + LastUpgradeCheck = 1500; TargetAttributes = { - 1384FF06283F912E00C9AAC1 = { - CreatedOnToolsVersion = 13.4; + 5BFF68302AD689C80099D333 = { + CreatedOnToolsVersion = 15.0; }; }; }; - buildConfigurationList = 1384FEFE283F90C800C9AAC1 /* Build configuration list for PBXProject "Examples" */; - compatibilityVersion = "Xcode 13.0"; + buildConfigurationList = 5BFF682C2AD689C80099D333 /* Build configuration list for PBXProject "MediaPickerExample" */; + compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); - mainGroup = 1384FEFA283F90C800C9AAC1; - productRefGroup = 1384FF08283F912E00C9AAC1 /* Products */; + mainGroup = 5BFF68282AD689C80099D333; + productRefGroup = 5BFF68322AD689C80099D333 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( - 1384FF06283F912E00C9AAC1 /* Example-iOS */, + 5BFF68302AD689C80099D333 /* MediaPickerExample */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - 1384FF05283F912E00C9AAC1 /* Resources */ = { + 5BFF682F2AD689C80099D333 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 1384FF12283F912F00C9AAC1 /* Preview Assets.xcassets in Resources */, - 1384FF0F283F912F00C9AAC1 /* Assets.xcassets in Resources */, + 5BFF68502AD68B990099D333 /* Preview Assets.xcassets in Resources */, + 5BFF684F2AD68B990099D333 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - 1384FF03283F912E00C9AAC1 /* Sources */ = { + 5BFF682D2AD689C80099D333 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - A82559832901A437008D9CC5 /* AppDelegate.swift in Sources */, - 1384FF0D283F912E00C9AAC1 /* ContentView.swift in Sources */, - 5B5AA87329F2923A001306F9 /* MediaCell.swift in Sources */, - 130FBF5528740D3E0086478A /* CustomizedMediaPicker.swift in Sources */, - 5B92AABC2A162DB3002F9EB2 /* FilterMediaPicker.swift in Sources */, - 1384FF0B283F912E00C9AAC1 /* Application.swift in Sources */, + 5BFF68522AD68B990099D333 /* AppDelegate.swift in Sources */, + 5BFF68532AD68B990099D333 /* Application.swift in Sources */, + 5BFF68542AD68B990099D333 /* ContentView.swift in Sources */, + 5BFF68512AD68B990099D333 /* CustomizedMediaPicker.swift in Sources */, + 5BFF684D2AD68B990099D333 /* MediaCell.swift in Sources */, + 5BFF684E2AD68B990099D333 /* FilterMediaPicker.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ - 1384FEFF283F90C800C9AAC1 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - }; - name = Debug; - }; - 1384FF00283F90C800C9AAC1 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - }; - name = Release; - }; - 1384FF14283F912F00C9AAC1 /* Debug */ = { + 5BFF683D2AD689C90099D333 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; @@ -240,16 +209,12 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_ASSET_PATHS = "\"Example-iOS/Preview Content\""; - DEVELOPMENT_TEAM = FZXCM5CJ7P; - ENABLE_PREVIEWS = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; @@ -263,42 +228,25 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = "Example-iOS/Info.plist"; - INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchScreen_Generation = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 1.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "com.example.media-picker"; - PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; - 1384FF15283F912F00C9AAC1 /* Release */ = { + 5BFF683E2AD689C90099D333 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; @@ -324,16 +272,12 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_ASSET_PATHS = "\"Example-iOS/Preview Content\""; - DEVELOPMENT_TEAM = FZXCM5CJ7P; ENABLE_NS_ASSERTIONS = NO; - ENABLE_PREVIEWS = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; @@ -341,50 +285,95 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 5BFF68402AD689C90099D333 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"MediaPickerExample/Preview Content\""; + ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = "Example-iOS/Info.plist"; + INFOPLIST_KEY_NSCameraUsageDescription = "Grant access to camera to be able to take photos and videos"; + INFOPLIST_KEY_NSMicrophoneUsageDescription = "Grant access to microphone to be able to take videos"; + INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "Grant access to photo library to be able to select photos"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "com.example.media-picker"; + PRODUCT_BUNDLE_IDENTIFIER = exyte.MediaPickerExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 5BFF68412AD689C90099D333 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"MediaPickerExample/Preview Content\""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSCameraUsageDescription = "Grant access to camera to be able to take photos and videos"; + INFOPLIST_KEY_NSMicrophoneUsageDescription = "Grant access to microphone to be able to take videos"; + INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "Grant access to photo library to be able to select photos"; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = exyte.MediaPickerExample; PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 1384FEFE283F90C800C9AAC1 /* Build configuration list for PBXProject "Examples" */ = { + 5BFF682C2AD689C80099D333 /* Build configuration list for PBXProject "MediaPickerExample" */ = { isa = XCConfigurationList; buildConfigurations = ( - 1384FEFF283F90C800C9AAC1 /* Debug */, - 1384FF00283F90C800C9AAC1 /* Release */, + 5BFF683D2AD689C90099D333 /* Debug */, + 5BFF683E2AD689C90099D333 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 1384FF13283F912F00C9AAC1 /* Build configuration list for PBXNativeTarget "Example-iOS" */ = { + 5BFF683F2AD689C90099D333 /* Build configuration list for PBXNativeTarget "MediaPickerExample" */ = { isa = XCConfigurationList; buildConfigurations = ( - 1384FF14283F912F00C9AAC1 /* Debug */, - 1384FF15283F912F00C9AAC1 /* Release */, + 5BFF68402AD689C90099D333 /* Debug */, + 5BFF68412AD689C90099D333 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -392,11 +381,11 @@ /* End XCConfigurationList section */ /* Begin XCSwiftPackageProductDependency section */ - 5B1CA4DF29F137E100245B8B /* ExyteMediaPicker */ = { + 5BFF68582AD68C510099D333 /* ExyteMediaPicker */ = { isa = XCSwiftPackageProductDependency; productName = ExyteMediaPicker; }; /* End XCSwiftPackageProductDependency section */ }; - rootObject = 1384FEFB283F90C800C9AAC1 /* Project object */; + rootObject = 5BFF68292AD689C80099D333 /* Project object */; } diff --git a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/MediaPickerExample/MediaPickerExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to MediaPickerExample/MediaPickerExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/Examples/Example-iOS/AppDelegate.swift b/MediaPickerExample/MediaPickerExample/AppDelegate.swift similarity index 100% rename from Examples/Example-iOS/AppDelegate.swift rename to MediaPickerExample/MediaPickerExample/AppDelegate.swift diff --git a/Examples/Example-iOS/Application.swift b/MediaPickerExample/MediaPickerExample/Application.swift similarity index 100% rename from Examples/Example-iOS/Application.swift rename to MediaPickerExample/MediaPickerExample/Application.swift diff --git a/Examples/Example-iOS/Assets.xcassets/AccentColor.colorset/Contents.json b/MediaPickerExample/MediaPickerExample/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from Examples/Example-iOS/Assets.xcassets/AccentColor.colorset/Contents.json rename to MediaPickerExample/MediaPickerExample/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/Examples/Example-iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/MediaPickerExample/MediaPickerExample/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from Examples/Example-iOS/Assets.xcassets/AppIcon.appiconset/Contents.json rename to MediaPickerExample/MediaPickerExample/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/Examples/Example-iOS/Assets.xcassets/chevron.imageset/Contents.json b/MediaPickerExample/MediaPickerExample/Assets.xcassets/Chevron.imageset/Contents.json similarity index 100% rename from Examples/Example-iOS/Assets.xcassets/chevron.imageset/Contents.json rename to MediaPickerExample/MediaPickerExample/Assets.xcassets/Chevron.imageset/Contents.json diff --git a/Examples/Example-iOS/Assets.xcassets/chevron.imageset/chevron.pdf b/MediaPickerExample/MediaPickerExample/Assets.xcassets/Chevron.imageset/chevron.pdf similarity index 100% rename from Examples/Example-iOS/Assets.xcassets/chevron.imageset/chevron.pdf rename to MediaPickerExample/MediaPickerExample/Assets.xcassets/Chevron.imageset/chevron.pdf diff --git a/Examples/Example-iOS/Assets.xcassets/Contents.json b/MediaPickerExample/MediaPickerExample/Assets.xcassets/Contents.json similarity index 100% rename from Examples/Example-iOS/Assets.xcassets/Contents.json rename to MediaPickerExample/MediaPickerExample/Assets.xcassets/Contents.json diff --git a/Examples/Example-iOS/Assets.xcassets/CustomGreen.colorset/Contents.json b/MediaPickerExample/MediaPickerExample/Assets.xcassets/CustomGreen.colorset/Contents.json similarity index 100% rename from Examples/Example-iOS/Assets.xcassets/CustomGreen.colorset/Contents.json rename to MediaPickerExample/MediaPickerExample/Assets.xcassets/CustomGreen.colorset/Contents.json diff --git a/Examples/Example-iOS/Assets.xcassets/CustomPurple.colorset/Contents.json b/MediaPickerExample/MediaPickerExample/Assets.xcassets/CustomPurple.colorset/Contents.json similarity index 100% rename from Examples/Example-iOS/Assets.xcassets/CustomPurple.colorset/Contents.json rename to MediaPickerExample/MediaPickerExample/Assets.xcassets/CustomPurple.colorset/Contents.json diff --git a/Examples/Example-iOS/ContentView.swift b/MediaPickerExample/MediaPickerExample/ContentView.swift similarity index 100% rename from Examples/Example-iOS/ContentView.swift rename to MediaPickerExample/MediaPickerExample/ContentView.swift diff --git a/Examples/Example-iOS/CustomizedMediaPicker.swift b/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift similarity index 87% rename from Examples/Example-iOS/CustomizedMediaPicker.swift rename to MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift index b9ed05b..c3719c6 100644 --- a/Examples/Example-iOS/CustomizedMediaPicker.swift +++ b/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift @@ -26,11 +26,16 @@ struct CustomizedMediaPicker: View { isPresented: $isPresented, onChange: { medias = $0 }, albumSelectionBuilder: { _, albumSelectionView, _ in - VStack { - headerView - albumSelectionView - Spacer() + ZStack(alignment: .bottom) { + VStack { + headerView + albumSelectionView + Spacer() + } + .ignoresSafeArea(.keyboard) + footerView + .background(Color.black) } .background(Color.black) }, @@ -59,6 +64,7 @@ struct CustomizedMediaPicker: View { .background(Color.black) } ) + .showLiveCameraCell() .albums($albums) .pickerMode($mediaPickerMode) .orientationHandler { @@ -115,13 +121,12 @@ struct CustomizedMediaPicker: View { var footerView: some View { HStack { - Button { - medias = [] - isPresented = false - } label: { - Text("Cancel") - .foregroundColor(.white.opacity(0.7)) - } + TextField("", text: .constant(""), prompt: Text("Add a caption").foregroundColor(.gray)) + .padding() + .background { + Color.white.opacity(0.2) + .cornerRadius(6) + } Spacer(minLength: 70) @@ -148,6 +153,7 @@ struct CustomizedMediaPicker: View { } } .padding(.horizontal) + .padding(.vertical, 8) } var albumsDropdown: some View { diff --git a/Examples/Example-iOS/FilterMediaPicker.swift b/MediaPickerExample/MediaPickerExample/FilterMediaPicker.swift similarity index 100% rename from Examples/Example-iOS/FilterMediaPicker.swift rename to MediaPickerExample/MediaPickerExample/FilterMediaPicker.swift diff --git a/Examples/Example-iOS/MediaCell.swift b/MediaPickerExample/MediaPickerExample/MediaCell.swift similarity index 100% rename from Examples/Example-iOS/MediaCell.swift rename to MediaPickerExample/MediaPickerExample/MediaCell.swift diff --git a/Examples/Example-iOS/Preview Content/Preview Assets.xcassets/Contents.json b/MediaPickerExample/MediaPickerExample/Preview Content/Preview Assets.xcassets/Contents.json similarity index 100% rename from Examples/Example-iOS/Preview Content/Preview Assets.xcassets/Contents.json rename to MediaPickerExample/MediaPickerExample/Preview Content/Preview Assets.xcassets/Contents.json diff --git a/Sources/ExyteMediaPicker/Modifiers/KeyboardHeightHelper.swift b/Sources/ExyteMediaPicker/Modifiers/KeyboardHeightHelper.swift index bd41de9..08bcf38 100644 --- a/Sources/ExyteMediaPicker/Modifiers/KeyboardHeightHelper.swift +++ b/Sources/ExyteMediaPicker/Modifiers/KeyboardHeightHelper.swift @@ -19,21 +19,26 @@ class KeyboardHeightHelper: ObservableObject { } private func listenForKeyboardNotifications() { - NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, - object: nil, - queue: .main) { (notification) in + NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: .main) { (notification) in guard let userInfo = notification.userInfo, let keyboardRect = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return } - self.keyboardHeight = keyboardRect.height - self.keyboardDisplayed = true + DispatchQueue.main.async { + self.keyboardHeight = keyboardRect.height + self.keyboardDisplayed = true + } } - NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, - object: nil, - queue: .main) { (notification) in - self.keyboardHeight = 0 - self.keyboardDisplayed = false + NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: .main) { (notification) in + DispatchQueue.main.async { + self.keyboardHeight = 0 + } + } + + NotificationCenter.default.addObserver(forName: UIResponder.keyboardDidHideNotification, object: nil, queue: .main) { (notification) in + DispatchQueue.main.async { + self.keyboardDisplayed = false + } } } } diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift index 385ae8a..d3ef4a9 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift @@ -10,6 +10,8 @@ struct AlbumView: View { @EnvironmentObject private var permissionsService: PermissionsService @Environment(\.mediaPickerTheme) private var theme + @ObservedObject var keyboardHeightHelper = KeyboardHeightHelper.shared + @StateObject var viewModel: AlbumViewModel @Binding var showingCamera: Bool @Binding var isInFullscreen: Bool @@ -88,6 +90,11 @@ private extension AlbumView { .frame(maxWidth: .infinity) } .background(theme.main.albumSelectionBackground) + .onTapGesture { + if keyboardHeightHelper.keyboardDisplayed { + dismissKeyboard() + } + } .overlay { if let item = fullscreenItem { FullscreenContainer( @@ -117,6 +124,9 @@ private extension AlbumView { if selectionService.mediaSelectionLimit == 1 { MediaCell(viewModel: MediaViewModel(assetMediaModel: assetMediaModel)) .onTapGesture { + if keyboardHeightHelper.keyboardDisplayed { + dismissKeyboard() + } if fullscreenItem == nil { fullscreenItem = assetMediaModel } @@ -126,6 +136,9 @@ private extension AlbumView { selectionService.onSelect(assetMediaModel: assetMediaModel) } content: { Button { + if keyboardHeightHelper.keyboardDisplayed { + dismissKeyboard() + } if fullscreenItem == nil { fullscreenItem = assetMediaModel } diff --git a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift index ef75247..7440bff 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift @@ -25,7 +25,6 @@ struct FullscreenCell: View { } } else { imageView(image: image) - .padding(.vertical, calculatePadding(imageSize: image.size, availableSize: availableFrame.size)) } } else if let player = viewModel.player { if !keyboardHeightHelper.keyboardDisplayed { @@ -34,7 +33,6 @@ struct FullscreenCell: View { } } else { videoView(player: player) - .padding(.vertical, calculatePadding(imageSize: viewModel.videoSize, availableSize: availableFrame.size)) } } else { ProgressView() From 777d395d988c35be6cb4a7fcb6bdb8e4977e831a Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Wed, 11 Oct 2023 15:19:57 +0700 Subject: [PATCH 20/70] Fix padding --- .../ExyteMediaPicker/Views/Widgets/SelectIndicatorView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ExyteMediaPicker/Views/Widgets/SelectIndicatorView.swift b/Sources/ExyteMediaPicker/Views/Widgets/SelectIndicatorView.swift index 72c3548..d672976 100644 --- a/Sources/ExyteMediaPicker/Views/Widgets/SelectIndicatorView.swift +++ b/Sources/ExyteMediaPicker/Views/Widgets/SelectIndicatorView.swift @@ -25,7 +25,7 @@ struct SelectIndicatorView: View { } } .frame(width: 24, height: 24) - .padding(.top, -2) + .padding(.top, isFullscreen ? 11 : -2) } var checkView: some View { From 9947ee56c440ca412720e2244e1fb82f8f5d8531 Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Mon, 16 Oct 2023 14:02:33 +0700 Subject: [PATCH 21/70] Simplify keyboard avoidence --- .../CustomizedMediaPicker.swift | 12 ++--- .../Pages/Fullscreen/FullscreenCell.swift | 49 +++++++------------ .../Views/Widgets/PlayerUIView.swift | 11 +++-- 3 files changed, 27 insertions(+), 45 deletions(-) diff --git a/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift b/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift index c3719c6..8aa236f 100644 --- a/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift +++ b/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift @@ -26,14 +26,10 @@ struct CustomizedMediaPicker: View { isPresented: $isPresented, onChange: { medias = $0 }, albumSelectionBuilder: { _, albumSelectionView, _ in - ZStack(alignment: .bottom) { - VStack { - headerView - albumSelectionView - Spacer() - } - .ignoresSafeArea(.keyboard) - + VStack { + headerView + albumSelectionView + Spacer() footerView .background(Color.black) } diff --git a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift index 7440bff..80e83f4 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift @@ -13,35 +13,27 @@ struct FullscreenCell: View { @StateObject var viewModel: FullscreenCellViewModel @ObservedObject var keyboardHeightHelper = KeyboardHeightHelper.shared - @State var availableFrame: CGRect = .zero - var body: some View { - VStack { - Spacer() - if let image = viewModel.image { - if !keyboardHeightHelper.keyboardDisplayed { + GeometryReader { g in + Group { + if let image = viewModel.image { + let useFill = g.size.width / g.size.height > image.size.width / image.size.height ZoomableScrollView { - imageView(image: image) + imageView(image: image, useFill: useFill) } - } else { - imageView(image: image) - } - } else if let player = viewModel.player { - if !keyboardHeightHelper.keyboardDisplayed { + } else if let player = viewModel.player { + let useFill = g.size.width / g.size.height > viewModel.videoSize.width / viewModel.videoSize.height ZoomableScrollView { - videoView(player: player) + videoView(player: player, useFill: useFill) } } else { - videoView(player: player) + ProgressView() + .tint(.white) } - } else { - ProgressView() - .tint(.white) } - Spacer() + .allowsHitTesting(!keyboardHeightHelper.keyboardDisplayed) + .position(x: g.frame(in: .local).midX, y: g.frame(in: .local).midY) } - .frame(width: UIScreen.main.bounds.size.width) - .frameGetter($availableFrame) .task { await viewModel.onStart() } @@ -50,14 +42,15 @@ struct FullscreenCell: View { } } - func imageView(image: UIImage) -> some View { + @ViewBuilder + func imageView(image: UIImage, useFill: Bool) -> some View { Image(uiImage: image) .resizable() - .scaledToFit() + .aspectRatio(contentMode: useFill ? .fill : .fit) } - func videoView(player: AVPlayer) -> some View { - PlayerView(player: player, bgColor: theme.main.fullscreenPhotoBackground) + func videoView(player: AVPlayer, useFill: Bool) -> some View { + PlayerView(player: player, bgColor: theme.main.fullscreenPhotoBackground, useFill: useFill) .disabled(true) .overlay { ZStack { @@ -75,12 +68,4 @@ struct FullscreenCell: View { } } } - - func calculatePadding(imageSize: CGSize, availableSize: CGSize) -> CGFloat { - let ratio = imageSize.width / imageSize.height - let height = UIScreen.main.bounds.size.width / ratio - let extra = availableSize.height - height - print(availableSize, extra) - return extra > 0 ? 0 : extra / 2 - 6 - } } diff --git a/Sources/ExyteMediaPicker/Views/Widgets/PlayerUIView.swift b/Sources/ExyteMediaPicker/Views/Widgets/PlayerUIView.swift index 982354c..750c206 100644 --- a/Sources/ExyteMediaPicker/Views/Widgets/PlayerUIView.swift +++ b/Sources/ExyteMediaPicker/Views/Widgets/PlayerUIView.swift @@ -12,9 +12,10 @@ struct PlayerView: UIViewRepresentable { var player: AVPlayer var bgColor: Color + var useFill: Bool func makeUIView(context: Context) -> PlayerUIView { - PlayerUIView(player: player, bgColor: bgColor) + PlayerUIView(player: player, bgColor: bgColor, useFill: useFill) } func updateUIView(_ uiView: PlayerUIView, context: UIViewRepresentableContext) { @@ -38,9 +39,9 @@ class PlayerUIView: UIView { fatalError("init(coder:) has not been implemented") } - init(player: AVPlayer, bgColor: Color) { + init(player: AVPlayer, bgColor: Color, useFill: Bool) { super.init(frame: .zero) - self.playerSetup(player: player, bgColor: bgColor) + self.playerSetup(player: player, bgColor: bgColor, useFill: useFill) } deinit { @@ -56,11 +57,11 @@ class PlayerUIView: UIView { // MARK: Class Methods - private func playerSetup(player: AVPlayer, bgColor: Color) { + private func playerSetup(player: AVPlayer, bgColor: Color, useFill: Bool) { playerLayer.player = player + playerLayer.videoGravity = useFill ? .resizeAspectFill : .resizeAspect player.actionAtItemEnd = .none layer.addSublayer(playerLayer) - print(bgColor.description.debugDescription) playerLayer.backgroundColor = bgColor.cgColor } } From d25d75019b3a990bdf34457e21bb9da297c2acea Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Mon, 16 Oct 2023 16:20:14 +0700 Subject: [PATCH 22/70] Fix current fullscreen item --- ExyteMediaPicker.podspec | 2 +- .../CustomizedMediaPicker.swift | 2 + .../Views/Pages/AlbumView/AlbumView.swift | 49 +++++++------------ .../Views/Pages/AlbumView/MediaCell.swift | 2 +- .../Fullscreen/FullscreenContainer.swift | 6 +++ 5 files changed, 27 insertions(+), 34 deletions(-) diff --git a/ExyteMediaPicker.podspec b/ExyteMediaPicker.podspec index 4cd1a3c..30f6072 100644 --- a/ExyteMediaPicker.podspec +++ b/ExyteMediaPicker.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "ExyteMediaPicker" - s.version = "1.3.0" + s.version = "1.4.0" s.summary = "MediaPicker is a customizable photo/video picker for iOS written in pure SwiftUI" s.homepage = 'https://github.com/exyte/MediaPicker.git' diff --git a/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift b/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift index 8aa236f..1d957e0 100644 --- a/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift +++ b/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift @@ -17,6 +17,7 @@ struct CustomizedMediaPicker: View { @State private var mediaPickerMode = MediaPickerMode.photos @State private var selectedAlbum: Album? + @State private var currentFullscreenMedia: Media? @State private var showAlbumsDropDown: Bool = false let maxCount: Int = 5 @@ -63,6 +64,7 @@ struct CustomizedMediaPicker: View { .showLiveCameraCell() .albums($albums) .pickerMode($mediaPickerMode) + .currentFullscreenMedia($currentFullscreenMedia) .orientationHandler { switch $0 { case .lock: appDelegate.lockOrientationToPortrait() diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift index d3ef4a9..e77d38d 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift @@ -22,17 +22,7 @@ struct AlbumView: View { var selectionParamsHolder: SelectionParamsHolder var shouldDismiss: ()->() - @State private var fullscreenItem: AssetMediaModel? { - didSet { - if let item = fullscreenItem { - isInFullscreen = true - currentFullscreenMedia = Media(source: item) - } else { - isInFullscreen = false - currentFullscreenMedia = nil - } - } - } + @State private var fullscreenItem: AssetMediaModel? var body: some View { if let title = viewModel.title { @@ -99,6 +89,7 @@ private extension AlbumView { if let item = fullscreenItem { FullscreenContainer( isPresented: fullscreenPresentedBinding(), + currentFullscreenMedia: $currentFullscreenMedia, assetMediaModels: viewModel.assetMediaModels, selection: item.id, selectionParamsHolder: selectionParamsHolder, @@ -121,32 +112,26 @@ private extension AlbumView { @ViewBuilder func cellView(_ assetMediaModel: AssetMediaModel) -> some View { - if selectionService.mediaSelectionLimit == 1 { + let imageButton = Button { + if keyboardHeightHelper.keyboardDisplayed { + dismissKeyboard() + } + if fullscreenItem == nil { + fullscreenItem = assetMediaModel + } + } label: { MediaCell(viewModel: MediaViewModel(assetMediaModel: assetMediaModel)) - .onTapGesture { - if keyboardHeightHelper.keyboardDisplayed { - dismissKeyboard() - } - if fullscreenItem == nil { - fullscreenItem = assetMediaModel - } - } + } + .buttonStyle(MediaButtonStyle()) + .contentShape(Rectangle()) + + if selectionService.mediaSelectionLimit == 1 { + imageButton } else { SelectableView(selected: selectionService.index(of: assetMediaModel), isFullscreen: false, canSelect: selectionService.canSelect(assetMediaModel: assetMediaModel), selectionParamsHolder: selectionParamsHolder) { selectionService.onSelect(assetMediaModel: assetMediaModel) } content: { - Button { - if keyboardHeightHelper.keyboardDisplayed { - dismissKeyboard() - } - if fullscreenItem == nil { - fullscreenItem = assetMediaModel - } - } label: { - MediaCell(viewModel: MediaViewModel(assetMediaModel: assetMediaModel)) - } - .buttonStyle(MediaButtonStyle()) - .contentShape(Rectangle()) + imageButton } } } diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/MediaCell.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/MediaCell.swift index 3f4e169..c44a210 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/MediaCell.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/MediaCell.swift @@ -18,7 +18,7 @@ struct MediaCell: View { } .aspectRatio(1, contentMode: .fill) .clipped() - + if let duration = viewModel.assetMediaModel.asset.formattedDuration { VStack { Spacer() diff --git a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift index a952728..1eeb1ae 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift @@ -13,6 +13,7 @@ struct FullscreenContainer: View { @ObservedObject var keyboardHeightHelper = KeyboardHeightHelper.shared @Binding var isPresented: Bool + @Binding var currentFullscreenMedia: Media? let assetMediaModels: [AssetMediaModel] @State var selection: AssetMediaModel.ID var selectionParamsHolder: SelectionParamsHolder @@ -37,6 +38,11 @@ struct FullscreenContainer: View { .tag(assetMediaModel.id) } } + .onChange(of: selection) { newValue in + if let selectedMediaModel { + currentFullscreenMedia = Media(source: selectedMediaModel) + } + } .overlay(alignment: .topTrailing) { if let selectedMediaModel = selectedMediaModel { if selectionParamsHolder.selectionLimit == 1 { From 2b2644fd27744993336cec0a59ee167411f4f95c Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Mon, 16 Oct 2023 17:33:48 +0700 Subject: [PATCH 23/70] Raise ios target to 16 --- ExyteMediaPicker.podspec | 4 ++-- Package.swift | 5 ++--- .../Pages/Fullscreen/FullscreenCellViewModel.swift | 11 ++++++----- .../ExyteMediaPicker/Views/Widgets/PlayerUIView.swift | 1 + 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/ExyteMediaPicker.podspec b/ExyteMediaPicker.podspec index 30f6072..6163c91 100644 --- a/ExyteMediaPicker.podspec +++ b/ExyteMediaPicker.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "ExyteMediaPicker" - s.version = "1.4.0" + s.version = "2.0.0" s.summary = "MediaPicker is a customizable photo/video picker for iOS written in pure SwiftUI" s.homepage = 'https://github.com/exyte/MediaPicker.git' @@ -9,7 +9,7 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/exyte/MediaPicker.git', :tag => s.version.to_s } s.social_media_url = 'http://exyte.com' - s.ios.deployment_target = '15.0' + s.ios.deployment_target = '16.0' s.requires_arc = true s.swift_version = "5.2" diff --git a/Package.swift b/Package.swift index 4b56e26..b49262b 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.6 +// swift-tools-version: 5.7 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -6,8 +6,7 @@ import PackageDescription let package = Package( name: "ExyteMediaPicker", platforms: [ - .iOS(.v15), - .macOS(.v11) + .iOS(.v16) ], products: [ .library( diff --git a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCellViewModel.swift b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCellViewModel.swift index ef0680b..6b21910 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCellViewModel.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCellViewModel.swift @@ -39,7 +39,7 @@ final class FullscreenCellViewModel: ObservableObject { let url = await mediaModel.getURL() guard let url = url else { return } setupPlayer(url) - videoSize = getVideoSize(url) + videoSize = await getVideoSize(url) case .none: break } @@ -74,11 +74,12 @@ final class FullscreenCellViewModel: ObservableObject { isPlaying = !isPlaying } - func getVideoSize(_ url: URL) -> CGSize { + func getVideoSize(_ url: URL) async -> CGSize { let videoAsset = AVURLAsset(url : url) - let videoAssetTrack = videoAsset.tracks(withMediaType: .video).first - let naturalSize = videoAssetTrack?.naturalSize ?? .zero - let transform = videoAssetTrack?.preferredTransform + + let videoAssetTrack = try? await videoAsset.loadTracks(withMediaType: .video).first + let naturalSize = (try? await videoAssetTrack?.load(.naturalSize)) ?? .zero + let transform = try? await videoAssetTrack?.load(.preferredTransform) if (transform?.tx == naturalSize.width && transform?.ty == naturalSize.height) || (transform?.tx == 0 && transform?.ty == 0) { return naturalSize } else { diff --git a/Sources/ExyteMediaPicker/Views/Widgets/PlayerUIView.swift b/Sources/ExyteMediaPicker/Views/Widgets/PlayerUIView.swift index 750c206..a2d1556 100644 --- a/Sources/ExyteMediaPicker/Views/Widgets/PlayerUIView.swift +++ b/Sources/ExyteMediaPicker/Views/Widgets/PlayerUIView.swift @@ -20,6 +20,7 @@ struct PlayerView: UIViewRepresentable { func updateUIView(_ uiView: PlayerUIView, context: UIViewRepresentableContext) { uiView.playerLayer.player = player + uiView.playerLayer.videoGravity = useFill ? .resizeAspectFill : .resizeAspect } } From 8e5e4d8819046998f4e4e8d9c88c2e9d2042b2b0 Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Mon, 16 Oct 2023 19:17:57 +0700 Subject: [PATCH 24/70] Fix "empty data" flashing --- .../Drivers/PhotoKit/Medias/MediasProviderProtocol.swift | 4 ++-- .../ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift | 1 + .../ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsView.swift | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/ExyteMediaPicker/Drivers/PhotoKit/Medias/MediasProviderProtocol.swift b/Sources/ExyteMediaPicker/Drivers/PhotoKit/Medias/MediasProviderProtocol.swift index bcab269..c8b7075 100644 --- a/Sources/ExyteMediaPicker/Drivers/PhotoKit/Medias/MediasProviderProtocol.swift +++ b/Sources/ExyteMediaPicker/Drivers/PhotoKit/Medias/MediasProviderProtocol.swift @@ -11,7 +11,7 @@ protocol MediasProviderProtocol { func reload() func cancel() - var assetMediaModelsPublisher: CurrentValueSubject<[AssetMediaModel], Never> { get } + var assetMediaModelsPublisher: PassthroughSubject<[AssetMediaModel], Never> { get } } class BaseMediasProvider: MediasProviderProtocol { @@ -21,7 +21,7 @@ class BaseMediasProvider: MediasProviderProtocol { @Binding var showingLoadingCell: Bool - var assetMediaModelsPublisher = CurrentValueSubject<[AssetMediaModel], Never>([]) + var assetMediaModelsPublisher = PassthroughSubject<[AssetMediaModel], Never>() @Published var cancellableTask: Task? diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift index e77d38d..6e615ce 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift @@ -47,6 +47,7 @@ private extension AlbumView { } if viewModel.isLoading { ProgressView() + .padding() } else if viewModel.assetMediaModels.isEmpty, !shouldShowLoadingCell { Text("Empty data") .font(.title3) diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsView.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsView.swift index 1c6e090..c294975 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsView.swift @@ -38,6 +38,7 @@ struct AlbumsView: View { } if viewModel.isLoading { ProgressView() + .padding() } else if viewModel.albums.isEmpty { Text("Empty data") .font(.title3) From 97d1f33c004cf00dbd45a1be04ae4245d114a29e Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Wed, 18 Oct 2023 13:47:08 +0700 Subject: [PATCH 25/70] Add option to remove fullscreen preview --- MediaPickerExample/MediaPickerExample/ContentView.swift | 1 + README.md | 1 + .../Drivers/Selection/SelectionParamsHolder.swift | 1 + .../ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift | 6 +++++- .../Views/Pages/MediaPicker/MediaPicker.swift | 5 +++++ 5 files changed, 13 insertions(+), 1 deletion(-) diff --git a/MediaPickerExample/MediaPickerExample/ContentView.swift b/MediaPickerExample/MediaPickerExample/ContentView.swift index 88b22b6..e60e282 100644 --- a/MediaPickerExample/MediaPickerExample/ContentView.swift +++ b/MediaPickerExample/MediaPickerExample/ContentView.swift @@ -61,6 +61,7 @@ struct ContentView: View { ) .showLiveCameraCell() .mediaSelectionLimit(1) + .showFullscreenPreview(false) .orientationHandler { switch $0 { case .lock: appDelegate.lockOrientationToPortrait() diff --git a/README.md b/README.md index 1fd3862..766d2bc 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,7 @@ You can pass one, both or none of these when creating your `MediaPicker`. (see t `mediaSelectionType` - limit displayed media type: .photo, .video or both `mediaSelectionStyle` - a way to display selected/unselected media state: a counter or a simple checkmark `mediaSelectionLimit` - the maximum selection quantity allowed, 'nil' for unlimited selection +`showFullscreenPreview` - if true - tap on media opens fullscreen preview, if false - tap on image immediately selects this image and closes the picker ### Available modifiers - filtering `applyFilter((Media) async -> Media?)` - pass a closure to apply to each of medias individually. Closures's return type is `Media?`: return `Media` the closure gives to you if you want it to be displayed on photo grid, or `nil` if you want to exclude it. The code you apply to each media can be asyncronous (using async/await syntactics, check out `FilterMediaPicker` in example project) diff --git a/Sources/ExyteMediaPicker/Drivers/Selection/SelectionParamsHolder.swift b/Sources/ExyteMediaPicker/Drivers/Selection/SelectionParamsHolder.swift index 34bd06d..dccc146 100644 --- a/Sources/ExyteMediaPicker/Drivers/Selection/SelectionParamsHolder.swift +++ b/Sources/ExyteMediaPicker/Drivers/Selection/SelectionParamsHolder.swift @@ -12,6 +12,7 @@ final class SelectionParamsHolder: ObservableObject { @Published var mediaType: MediaSelectionType = .photoAndVideo @Published var selectionStyle: MediaSelectionStyle = .checkmark @Published var selectionLimit: Int? // if nil - unlimited + @Published var showFullscreenPreview: Bool = true // if false, tap on image immediately selects this image and closes the picker } public enum MediaSelectionStyle { diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift index 6e615ce..257050f 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift @@ -117,7 +117,11 @@ private extension AlbumView { if keyboardHeightHelper.keyboardDisplayed { dismissKeyboard() } - if fullscreenItem == nil { + if !selectionParamsHolder.showFullscreenPreview { // select immediately + selectionService.onSelect(assetMediaModel: assetMediaModel) + shouldDismiss() + } + else if fullscreenItem == nil { fullscreenItem = assetMediaModel } } label: { diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift index 44e8085..29c4d49 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift @@ -307,6 +307,11 @@ public extension MediaPicker { return self } + func showFullscreenPreview(_ show: Bool) -> MediaPicker { + selectionParamsHolder.showFullscreenPreview = show + return self + } + func applyFilter(_ filterClosure: @escaping FilterClosure) -> MediaPicker { var mediaPicker = self mediaPicker.filterClosure = filterClosure From ed484235574a28180c1e76eea996bc6667eeb470 Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Wed, 18 Oct 2023 17:59:08 +0700 Subject: [PATCH 26/70] Add live camera view customization closure --- ExyteMediaPicker.podspec | 2 +- .../project.pbxproj | 2 + .../MediaPickerExample/ContentView.swift | 1 - .../CustomizedMediaPicker.swift | 52 ++++++--- README.md | 42 ++++++- .../Views/Pages/AlbumView/MediaCell.swift | 2 +- .../Views/Pages/Camera/CameraView.swift | 49 +++++++- .../Views/Pages/Camera/LiveCameraView.swift | 10 +- .../Pages/MediaPicker/GenenricsTrick.swift | 110 ++++++++++++++++++ .../Views/Pages/MediaPicker/MediaPicker.swift | 76 ++++-------- 10 files changed, 265 insertions(+), 81 deletions(-) create mode 100644 Sources/ExyteMediaPicker/Views/Pages/MediaPicker/GenenricsTrick.swift diff --git a/ExyteMediaPicker.podspec b/ExyteMediaPicker.podspec index 6163c91..25f1a22 100644 --- a/ExyteMediaPicker.podspec +++ b/ExyteMediaPicker.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "ExyteMediaPicker" - s.version = "2.0.0" + s.version = "2.1.0" s.summary = "MediaPicker is a customizable photo/video picker for iOS written in pure SwiftUI" s.homepage = 'https://github.com/exyte/MediaPicker.git' diff --git a/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj b/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj index 7919879..f08beee 100644 --- a/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj +++ b/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj @@ -303,6 +303,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"MediaPickerExample/Preview Content\""; + DEVELOPMENT_TEAM = FZXCM5CJ7P; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSCameraUsageDescription = "Grant access to camera to be able to take photos and videos"; @@ -334,6 +335,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"MediaPickerExample/Preview Content\""; + DEVELOPMENT_TEAM = FZXCM5CJ7P; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSCameraUsageDescription = "Grant access to camera to be able to take photos and videos"; diff --git a/MediaPickerExample/MediaPickerExample/ContentView.swift b/MediaPickerExample/MediaPickerExample/ContentView.swift index e60e282..88b22b6 100644 --- a/MediaPickerExample/MediaPickerExample/ContentView.swift +++ b/MediaPickerExample/MediaPickerExample/ContentView.swift @@ -61,7 +61,6 @@ struct ContentView: View { ) .showLiveCameraCell() .mediaSelectionLimit(1) - .showFullscreenPreview(false) .orientationHandler { switch $0 { case .lock: appDelegate.lockOrientationToPortrait() diff --git a/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift b/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift index 1d957e0..f85859b 100644 --- a/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift +++ b/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift @@ -19,6 +19,7 @@ struct CustomizedMediaPicker: View { @State private var selectedAlbum: Album? @State private var currentFullscreenMedia: Media? @State private var showAlbumsDropDown: Bool = false + @State private var videoIsBeingRecorded: Bool = false let maxCount: Int = 5 @@ -42,23 +43,39 @@ struct CustomizedMediaPicker: View { Spacer() Button("Done", action: { isPresented = false }) } + .padding() cameraSelectionView HStack { Button("Cancel", action: cancelClosure) Spacer() Button(action: addMoreClosure) { Text("Take more photos") - .font(.headline) - .foregroundColor(.black) - .padding() - } - .background { - Color("CustomGreen") - .cornerRadius(16) + .greenButtonStyle() } } + .padding() } .background(Color.black) + }, + cameraViewBuilder: { cameraSheetView, cancelClosure, takePhotoClosure, startVideoCaptureClosure, stopVideoCaptureClosure, _, _ in + cameraSheetView + .overlay(alignment: .topLeading) { + Button("Cancel") { cancelClosure() } + .foregroundColor(Color("CustomPurple")) + .padding() + } + .overlay(alignment: .bottom) { + HStack { + Button("Take photo") { takePhotoClosure() } + .greenButtonStyle() + Button(videoIsBeingRecorded ? "Stop video capture" : "Capture video") { + videoIsBeingRecorded ? stopVideoCaptureClosure() : startVideoCaptureClosure() + videoIsBeingRecorded.toggle() + } + .greenButtonStyle() + } + .padding() + } } ) .showLiveCameraCell() @@ -139,16 +156,9 @@ struct CustomizedMediaPicker: View { .background(Color.white) .clipShape(Circle()) } - .font(.headline) - .foregroundColor(.black) - .padding(.horizontal) - .padding(.vertical, 15) .frame(maxWidth: .infinity) } - .background { - Color("CustomGreen") - .cornerRadius(16) - } + .greenButtonStyle() } .padding(.horizontal) .padding(.vertical, 8) @@ -170,3 +180,15 @@ struct CustomizedMediaPicker: View { .frame(maxHeight: 300) } } + +extension View { + func greenButtonStyle() -> some View { + self.font(.headline) + .foregroundColor(.black) + .padding() + .background { + Color("CustomGreen") + .cornerRadius(16) + } + } +} diff --git a/README.md b/README.md index 766d2bc..4c648b1 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ After making one photo, you see a preview of it and a little plus icon, by tappi `onChange` - a closure that returns the selected media every time the selection changes ### Init - optional view builders -You can pass two view builders in order to add your own buttons and other elements to media picker screens. First screen you can customize is default photos grid view. Pass `albumSelectionBuilder` closure like this to replace the standard one with your own view: +You can pass 1-3 view builders in order to add your own buttons and other elements to media picker screens. You can pass all, some or none of these when creating your `MediaPicker` (see the custom picker in the example project for usage example). First screen you can customize is default photos grid view. Pass `albumSelectionBuilder` closure like this to replace the standard one with your own view: ```swift MediaPicker( isPresented: $isPresented, @@ -120,7 +120,43 @@ MediaPicker( - `cancelClosure` - show confirmation and return to photos grid screen if confirmed - `cameraSelectionView` - swipable camera photos preview collection itself -You can pass one, both or none of these when creating your `MediaPicker`. (see the custom picker in the example project for usage example) +The last one is live camera screen + +```swift +MediaPicker( + isPresented: $isPresented, + onChange: { selectedMedia = $0 }, + cameraViewBuilder: { cameraSheetView, cancelClosure, takePhotoClosure, startVideoCaptureClosure, stopVideoCaptureClosure, toggleFlash, flipCamera in + cameraSheetView + .overlay(alignment: .topLeading) { + Button("Cancel") { cancelClosure() } + .foregroundColor(Color("CustomPurple")) + .padding() + } + .overlay(alignment: .bottom) { + HStack { + Button("Take photo") { takePhotoClosure() } + .greenButtonStyle() + Button(videoIsBeingRecorded ? "Stop video capture" : "Capture video") { + videoIsBeingRecorded ? stopVideoCaptureClosure() : startVideoCaptureClosure() + videoIsBeingRecorded.toggle() + } + .greenButtonStyle() + } + .padding() + } + } +) +``` + +`cameraViewBuilder` live camera capture view and a lot of closures to do with as you please: +- `cameraSheetView` - live camera capture view +- `cancelClosure` - if you want to display "are you sure" before closing +- `takePhotoClosure` - takes a photo +- `startVideoCaptureClosure` - starts video capture, you'll need a bollean varialbe to track recording state +- `stopVideoCaptureClosure` - stops video capture +- `toggleFlash` - flash off/on +- `flipCamera` - camera back/front ## Available modifiers `showLiveCameraCell` - show live camera feed cell in the top left corner of the gallery grid @@ -200,7 +236,7 @@ github "Exyte/MediaPicker" ## Requirements -* iOS 15+ +* iOS 16+ * Xcode 13+ ## Our other open source SwiftUI libraries diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/MediaCell.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/MediaCell.swift index 5cc40bf..893eab9 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/MediaCell.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/MediaCell.swift @@ -19,7 +19,7 @@ struct MediaCell: View { viewModel.onStart(size: geometry.size) } } - .aspectRatio(1, contentMode: .fit) + .aspectRatio(1, contentMode: .fill) .clipped() if let duration = viewModel.assetMediaModel.asset.formattedDuration { diff --git a/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraView.swift b/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraView.swift index f9bd1b9..8f8af70 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraView.swift @@ -5,22 +5,63 @@ import SwiftUI import Photos -struct CameraView: View { +struct CustomCameraView: View { + @EnvironmentObject private var cameraSelectionService: CameraSelectionService + + public typealias CameraViewClosure = ((LiveCameraView, @escaping SimpleClosure, @escaping SimpleClosure, @escaping SimpleClosure, @escaping SimpleClosure, @escaping SimpleClosure, @escaping SimpleClosure) -> CameraViewContent) + + // params @ObservedObject var viewModel: MediaPickerViewModel let didTakePicture: () -> Void let didPressCancel: () -> Void + var cameraViewBuilder: CameraViewClosure @StateObject private var cameraViewModel = CameraViewModel() + + var body: some View { + cameraViewBuilder( + LiveCameraView( + session: cameraViewModel.captureSession, + videoGravity: .resizeAspectFill, + orientation: .portrait + ), + { // cancel + if cameraSelectionService.hasSelected { + viewModel.showingExitCameraConfirmation = true + } else { + didPressCancel() + } + }, + { cameraViewModel.takePhoto() }, // takePhoto + { cameraViewModel.startVideoCapture() }, // start record video + { cameraViewModel.stopVideoCapture() }, // stop record video + { cameraViewModel.toggleFlash() }, // flash off/on + { cameraViewModel.flipCamera() } // camera back/front + ) + .onReceive(cameraViewModel.capturedPhotoPublisher) { + viewModel.pickedMediaUrl = $0 + didTakePicture() + } + } +} + +struct StandardConrolsCameraView: View { + @EnvironmentObject private var cameraSelectionService: CameraSelectionService @Environment(\.safeAreaInsets) private var safeAreaInsets @Environment(\.mediaPickerTheme) private var theme - @State var capturingPhotos = true - @State var videoCaptureInProgress = false - + @ObservedObject var viewModel: MediaPickerViewModel + let didTakePicture: () -> Void + let didPressCancel: () -> Void let selectionParamsHolder: SelectionParamsHolder + @StateObject private var cameraViewModel = CameraViewModel() + + @State private var capturingPhotos = true + @State private var videoCaptureInProgress = false + var body: some View { VStack(spacing: 0) { HStack { diff --git a/Sources/ExyteMediaPicker/Views/Pages/Camera/LiveCameraView.swift b/Sources/ExyteMediaPicker/Views/Pages/Camera/LiveCameraView.swift index 52b452c..6713d54 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Camera/LiveCameraView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Camera/LiveCameraView.swift @@ -8,13 +8,13 @@ import SwiftUI import AVFoundation -struct LiveCameraView: UIViewRepresentable { +public struct LiveCameraView: UIViewRepresentable { let session: AVCaptureSession var videoGravity: AVLayerVideoGravity = .resizeAspect var orientation: UIDeviceOrientation = UIDevice.current.orientation - func makeUIView(context: Context) -> LiveVideoCaptureView { + public func makeUIView(context: Context) -> LiveVideoCaptureView { NotificationCenter.default.post( name: cameraChangePermissionNotification, object: nil) @@ -26,13 +26,13 @@ struct LiveCameraView: UIViewRepresentable { ) } - func updateUIView(_ uiView: LiveVideoCaptureView, context: Context) { + public func updateUIView(_ uiView: LiveVideoCaptureView, context: Context) { uiView.updateOrientation(orientation) } } -final class LiveVideoCaptureView: UIView { +public final class LiveVideoCaptureView: UIView { var session: AVCaptureSession? { get { @@ -43,7 +43,7 @@ final class LiveVideoCaptureView: UIView { } } - override class var layerClass: AnyClass { AVCaptureVideoPreviewLayer.self } + public override class var layerClass: AnyClass { AVCaptureVideoPreviewLayer.self } private var videoLayer: AVCaptureVideoPreviewLayer { layer as! AVCaptureVideoPreviewLayer } diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/GenenricsTrick.swift b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/GenenricsTrick.swift new file mode 100644 index 0000000..9b93c18 --- /dev/null +++ b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/GenenricsTrick.swift @@ -0,0 +1,110 @@ +// +// SwiftUIView.swift +// +// +// Created by Alisa Mylnikova on 18.10.2023. +// + +import SwiftUI + +// MARK: - Partial genereic specification imitation + +public extension MediaPicker where AlbumSelectionContent == EmptyView, CameraSelectionContent == EmptyView, CameraViewContent == EmptyView { + + init(isPresented: Binding, + onChange: @escaping MediaPickerCompletionClosure) { + + self.init(isPresented: isPresented, + onChange: onChange, + albumSelectionBuilder: nil, + cameraSelectionBuilder: nil, + cameraViewBuilder: nil) + } +} + +public extension MediaPicker where CameraSelectionContent == EmptyView, CameraViewContent == EmptyView { + + init(isPresented: Binding, + onChange: @escaping MediaPickerCompletionClosure, + albumSelectionBuilder: @escaping AlbumSelectionClosure) { + + self.init(isPresented: isPresented, + onChange: onChange, + albumSelectionBuilder: albumSelectionBuilder, + cameraSelectionBuilder: nil, + cameraViewBuilder: nil) + } +} + +public extension MediaPicker where AlbumSelectionContent == EmptyView, CameraViewContent == EmptyView { + + init(isPresented: Binding, + onChange: @escaping MediaPickerCompletionClosure, + cameraSelectionBuilder: @escaping CameraSelectionClosure) { + + self.init(isPresented: isPresented, + onChange: onChange, + albumSelectionBuilder: nil, + cameraSelectionBuilder: cameraSelectionBuilder, + cameraViewBuilder: nil) + } +} + +public extension MediaPicker where AlbumSelectionContent == EmptyView, CameraSelectionContent == EmptyView { + + init(isPresented: Binding, + onChange: @escaping MediaPickerCompletionClosure, + cameraViewBuilder: @escaping CameraViewClosure) { + + self.init(isPresented: isPresented, + onChange: onChange, + albumSelectionBuilder: nil, + cameraSelectionBuilder: nil, + cameraViewBuilder: cameraViewBuilder) + } +} + +public extension MediaPicker where CameraViewContent == EmptyView { + + init(isPresented: Binding, + onChange: @escaping MediaPickerCompletionClosure, + albumSelectionBuilder: @escaping AlbumSelectionClosure, + cameraSelectionBuilder: @escaping CameraSelectionClosure) { + + self.init(isPresented: isPresented, + onChange: onChange, + albumSelectionBuilder: albumSelectionBuilder, + cameraSelectionBuilder: cameraSelectionBuilder, + cameraViewBuilder: nil) + } +} + +public extension MediaPicker where CameraViewContent == EmptyView { + + init(isPresented: Binding, + onChange: @escaping MediaPickerCompletionClosure, + albumSelectionBuilder: @escaping AlbumSelectionClosure, + cameraViewBuilder: @escaping CameraViewClosure) { + + self.init(isPresented: isPresented, + onChange: onChange, + albumSelectionBuilder: albumSelectionBuilder, + cameraSelectionBuilder: nil, + cameraViewBuilder: cameraViewBuilder) + } +} + +public extension MediaPicker where AlbumSelectionContent == EmptyView { + + init(isPresented: Binding, + onChange: @escaping MediaPickerCompletionClosure, + cameraSelectionBuilder: @escaping CameraSelectionClosure, + cameraViewBuilder: @escaping CameraViewClosure) { + + self.init(isPresented: isPresented, + onChange: onChange, + albumSelectionBuilder: nil, + cameraSelectionBuilder: cameraSelectionBuilder, + cameraViewBuilder: cameraViewBuilder) + } +} diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift index 29c4d49..0511a9c 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift @@ -5,7 +5,7 @@ import SwiftUI import Combine -public struct MediaPicker: View { +public struct MediaPicker: View { /// To provide custom buttons layout for photos grid view use actions and views provided by this closure: /// - standard header with photos/albums switcher @@ -13,12 +13,22 @@ public struct MediaPicker AlbumSelectionContent) - /// To provide custom buttons layout for camera selection view use actions and views by this closure: + /// To provide custom buttons layout for camera selection view use actions and views provided by this closure: /// - add more photos closure /// - cancel closure /// - selection view you can embed in your view public typealias CameraSelectionClosure = ((@escaping SimpleClosure, @escaping SimpleClosure, CameraSelectionView) -> CameraSelectionContent) + /// To provide custom buttons layout for camera view use actions and views provided by this closure: + /// - live camera capture view + /// - cancel closure + /// - take photo closure + /// - start record video closure + /// - stop record video closure + /// - flash off/on closure + /// - camera back/front closure + public typealias CameraViewClosure = ((LiveCameraView, @escaping SimpleClosure, @escaping SimpleClosure, @escaping SimpleClosure, @escaping SimpleClosure, @escaping SimpleClosure, @escaping SimpleClosure) -> CameraViewContent) + public typealias FilterClosure = (Media) async -> Media? public typealias MassFilterClosure = ([Media]) async -> [Media] @@ -31,6 +41,7 @@ public struct MediaPicker, onChange: @escaping MediaPickerCompletionClosure, - albumSelectionBuilder: @escaping AlbumSelectionClosure, - cameraSelectionBuilder: @escaping CameraSelectionClosure) { + albumSelectionBuilder: AlbumSelectionClosure? = nil, + cameraSelectionBuilder: CameraSelectionClosure? = nil, + cameraViewBuilder: CameraViewClosure? = nil) { self._isPresented = isPresented self._albums = .constant([]) @@ -74,6 +86,7 @@ public struct MediaPicker(), didPressCancel: @escaping ()->()) -> some View { #if targetEnvironment(simulator) CameraStubView(isPresented: cameraBinding()) #elseif os(iOS) - CameraView(viewModel: viewModel, didTakePicture: didTakePicture, didPressCancel: didPressCancel, selectionParamsHolder: selectionParamsHolder) - .ignoresSafeArea() + if let cameraViewBuilder = cameraViewBuilder { + CustomCameraView(viewModel: viewModel, didTakePicture: didTakePicture, didPressCancel: didPressCancel, cameraViewBuilder: cameraViewBuilder) + .ignoresSafeArea() + } else { + StandardConrolsCameraView(viewModel: viewModel, didTakePicture: didTakePicture, didPressCancel: didPressCancel, selectionParamsHolder: selectionParamsHolder) + .ignoresSafeArea() + } #endif } } @@ -354,48 +373,3 @@ public extension MediaPicker { return mediaPicker } } - -// MARK: - Partial genereic specification imitation - -public extension MediaPicker where AlbumSelectionContent == EmptyView, CameraSelectionContent == EmptyView { - - init(isPresented: Binding, - onChange: @escaping MediaPickerCompletionClosure) { - - self._isPresented = isPresented - self._albums = .constant([]) - self._currentFullscreenMediaBinding = .constant(nil) - - self.onChange = onChange - } -} - -public extension MediaPicker where AlbumSelectionContent == EmptyView { - - init(isPresented: Binding, - onChange: @escaping MediaPickerCompletionClosure, - cameraSelectionBuilder: @escaping CameraSelectionClosure) { - - self._isPresented = isPresented - self._albums = .constant([]) - self._currentFullscreenMediaBinding = .constant(nil) - - self.onChange = onChange - self.cameraSelectionBuilder = cameraSelectionBuilder - } -} - -public extension MediaPicker where CameraSelectionContent == EmptyView { - - init(isPresented: Binding, - onChange: @escaping MediaPickerCompletionClosure, - albumSelectionBuilder: @escaping AlbumSelectionClosure) { - - self._isPresented = isPresented - self._albums = .constant([]) - self._currentFullscreenMediaBinding = .constant(nil) - - self.onChange = onChange - self.albumSelectionBuilder = albumSelectionBuilder - } -} From f493425514490b0629725387122d15d2876aeff5 Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Thu, 19 Oct 2023 16:44:06 +0700 Subject: [PATCH 27/70] Allow to pass selection parameters struct --- .../Selection/SelectionParamsHolder.swift | 17 ++++++++++++----- .../Views/Pages/MediaPicker/MediaPicker.swift | 9 +++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/Sources/ExyteMediaPicker/Drivers/Selection/SelectionParamsHolder.swift b/Sources/ExyteMediaPicker/Drivers/Selection/SelectionParamsHolder.swift index dccc146..552cfe5 100644 --- a/Sources/ExyteMediaPicker/Drivers/Selection/SelectionParamsHolder.swift +++ b/Sources/ExyteMediaPicker/Drivers/Selection/SelectionParamsHolder.swift @@ -7,12 +7,19 @@ import SwiftUI -final class SelectionParamsHolder: ObservableObject { +final public class SelectionParamsHolder: ObservableObject { - @Published var mediaType: MediaSelectionType = .photoAndVideo - @Published var selectionStyle: MediaSelectionStyle = .checkmark - @Published var selectionLimit: Int? // if nil - unlimited - @Published var showFullscreenPreview: Bool = true // if false, tap on image immediately selects this image and closes the picker + @Published public var mediaType: MediaSelectionType = .photoAndVideo + @Published public var selectionStyle: MediaSelectionStyle = .checkmark + @Published public var selectionLimit: Int? // if nil - unlimited + @Published public var showFullscreenPreview: Bool = true // if false, tap on image immediately selects this image and closes the picker + + public init(mediaType: MediaSelectionType = .photoAndVideo, selectionStyle: MediaSelectionStyle = .checkmark, selectionLimit: Int? = nil, showFullscreenPreview: Bool = true) { + self.mediaType = mediaType + self.selectionStyle = selectionStyle + self.selectionLimit = selectionLimit + self.showFullscreenPreview = showFullscreenPreview + } } public enum MediaSelectionStyle { diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift index 0511a9c..2511a2c 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift @@ -331,6 +331,15 @@ public extension MediaPicker { return self } + func setSelectionParameters(_ params: SelectionParamsHolder?) -> MediaPicker { + guard let params = params else { + return self + } + var mediaPicker = self + mediaPicker.selectionParamsHolder = params + return mediaPicker + } + func applyFilter(_ filterClosure: @escaping FilterClosure) -> MediaPicker { var mediaPicker = self mediaPicker.filterClosure = filterClosure From 467ba6dd686a88584dea5cc82b3b87cbc9f0aa19 Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Thu, 19 Oct 2023 17:17:08 +0700 Subject: [PATCH 28/70] Bump version --- ExyteMediaPicker.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ExyteMediaPicker.podspec b/ExyteMediaPicker.podspec index 25f1a22..d237242 100644 --- a/ExyteMediaPicker.podspec +++ b/ExyteMediaPicker.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "ExyteMediaPicker" - s.version = "2.1.0" + s.version = "2.1.1" s.summary = "MediaPicker is a customizable photo/video picker for iOS written in pure SwiftUI" s.homepage = 'https://github.com/exyte/MediaPicker.git' From 708e9d28272e56b190ff9409a684a213ececb680 Mon Sep 17 00:00:00 2001 From: "Alex.M" Date: Mon, 23 Oct 2023 15:26:15 +0600 Subject: [PATCH 29/70] Add iOs 15 support; --- ExyteMediaPicker.podspec | 2 +- .../project.pbxproj | 26 ++++++++++++------- .../contents.xcworkspacedata | 7 +++++ Package.swift | 2 +- .../Drivers/PhotoKit/PHAsset+Utils.swift | 1 + 5 files changed, 27 insertions(+), 11 deletions(-) create mode 100644 MediaPickerExample/MediaPickerExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/ExyteMediaPicker.podspec b/ExyteMediaPicker.podspec index d237242..2cef607 100644 --- a/ExyteMediaPicker.podspec +++ b/ExyteMediaPicker.podspec @@ -9,7 +9,7 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/exyte/MediaPicker.git', :tag => s.version.to_s } s.social_media_url = 'http://exyte.com' - s.ios.deployment_target = '16.0' + s.ios.deployment_target = '15.0' s.requires_arc = true s.swift_version = "5.2" diff --git a/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj b/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj index f08beee..f9f6c90 100644 --- a/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj +++ b/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj @@ -3,10 +3,11 @@ archiveVersion = 1; classes = { }; - objectVersion = 56; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ + 13738D1C2AE272FA005BBAD5 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 13738D1B2AE272FA005BBAD5 /* ExyteMediaPicker */; }; 5BFF684D2AD68B990099D333 /* MediaCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFF68432AD68B990099D333 /* MediaCell.swift */; }; 5BFF684E2AD68B990099D333 /* FilterMediaPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFF68442AD68B990099D333 /* FilterMediaPicker.swift */; }; 5BFF684F2AD68B990099D333 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5BFF68452AD68B990099D333 /* Assets.xcassets */; }; @@ -15,7 +16,6 @@ 5BFF68522AD68B990099D333 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFF68492AD68B990099D333 /* AppDelegate.swift */; }; 5BFF68532AD68B990099D333 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFF684A2AD68B990099D333 /* Application.swift */; }; 5BFF68542AD68B990099D333 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFF684B2AD68B990099D333 /* ContentView.swift */; }; - 5BFF68592AD68C510099D333 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5BFF68582AD68C510099D333 /* ExyteMediaPicker */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -28,7 +28,6 @@ 5BFF68492AD68B990099D333 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 5BFF684A2AD68B990099D333 /* Application.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = ""; }; 5BFF684B2AD68B990099D333 /* ContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; - 5BFF68562AD68C050099D333 /* MediaPicker */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = MediaPicker; path = ..; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -36,7 +35,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5BFF68592AD68C510099D333 /* ExyteMediaPicker in Frameworks */, + 13738D1C2AE272FA005BBAD5 /* ExyteMediaPicker in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -46,7 +45,6 @@ 5BFF68282AD689C80099D333 = { isa = PBXGroup; children = ( - 5BFF68562AD68C050099D333 /* MediaPicker */, 5BFF68422AD68B990099D333 /* MediaPickerExample */, 5BFF68322AD689C80099D333 /* Products */, 5BFF68572AD68C510099D333 /* Frameworks */, @@ -108,7 +106,7 @@ ); name = MediaPickerExample; packageProductDependencies = ( - 5BFF68582AD68C510099D333 /* ExyteMediaPicker */, + 13738D1B2AE272FA005BBAD5 /* ExyteMediaPicker */, ); productName = MediaPickerExample; productReference = 5BFF68312AD689C80099D333 /* MediaPickerExample.app */; @@ -138,6 +136,9 @@ Base, ); mainGroup = 5BFF68282AD689C80099D333; + packageReferences = ( + 13738D1A2AE272FA005BBAD5 /* XCLocalSwiftPackageReference ".." */, + ); productRefGroup = 5BFF68322AD689C80099D333 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -228,7 +229,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -285,7 +286,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; @@ -382,8 +383,15 @@ }; /* End XCConfigurationList section */ +/* Begin XCLocalSwiftPackageReference section */ + 13738D1A2AE272FA005BBAD5 /* XCLocalSwiftPackageReference ".." */ = { + isa = XCLocalSwiftPackageReference; + relativePath = ..; + }; +/* End XCLocalSwiftPackageReference section */ + /* Begin XCSwiftPackageProductDependency section */ - 5BFF68582AD68C510099D333 /* ExyteMediaPicker */ = { + 13738D1B2AE272FA005BBAD5 /* ExyteMediaPicker */ = { isa = XCSwiftPackageProductDependency; productName = ExyteMediaPicker; }; diff --git a/MediaPickerExample/MediaPickerExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/MediaPickerExample/MediaPickerExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/MediaPickerExample/MediaPickerExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Package.swift b/Package.swift index b49262b..19a103b 100644 --- a/Package.swift +++ b/Package.swift @@ -6,7 +6,7 @@ import PackageDescription let package = Package( name: "ExyteMediaPicker", platforms: [ - .iOS(.v16) + .iOS(.v15) ], products: [ .library( diff --git a/Sources/ExyteMediaPicker/Drivers/PhotoKit/PHAsset+Utils.swift b/Sources/ExyteMediaPicker/Drivers/PhotoKit/PHAsset+Utils.swift index 031e08e..4301aa7 100644 --- a/Sources/ExyteMediaPicker/Drivers/PhotoKit/PHAsset+Utils.swift +++ b/Sources/ExyteMediaPicker/Drivers/PhotoKit/PHAsset+Utils.swift @@ -41,6 +41,7 @@ extension PHAsset { if mediaType == .image { let options = PHContentEditingInputRequestOptions() + options.isNetworkAccessAllowed = true options.canHandleAdjustmentData = { _ -> Bool in return true } From a8c58abd7671c436220dc63259fa3cab256567c9 Mon Sep 17 00:00:00 2001 From: "Alex.M" Date: Wed, 25 Oct 2023 12:16:11 +0600 Subject: [PATCH 30/70] Add a new feature: deselect photo/video from camera; --- .../Selection/CameraSelectionService.swift | 37 +++++++++++++++++-- .../Camera/CameraSelectionContainer.swift | 37 ++++++++++++++----- .../Views/Pages/MediaPicker/MediaPicker.swift | 8 +++- 3 files changed, 67 insertions(+), 15 deletions(-) diff --git a/Sources/ExyteMediaPicker/Drivers/Selection/CameraSelectionService.swift b/Sources/ExyteMediaPicker/Drivers/Selection/CameraSelectionService.swift index 6a46dc2..da06aeb 100644 --- a/Sources/ExyteMediaPicker/Drivers/Selection/CameraSelectionService.swift +++ b/Sources/ExyteMediaPicker/Drivers/Selection/CameraSelectionService.swift @@ -12,6 +12,7 @@ final class CameraSelectionService: ObservableObject { var mediaSelectionLimit: Int? // if nill - unlimited var onChange: MediaPickerCompletionClosure? = nil + @Published private(set) var added: [URLMediaModel] = [] @Published private(set) var selected: [URLMediaModel] = [] var hasSelected: Bool { @@ -30,9 +31,14 @@ final class CameraSelectionService: ObservableObject { } func onSelect(media: URLMediaModel) { - if let index = selected.firstIndex(of: media) { - selected.remove(at: index) + if added.contains(media) { + if let index = selected.firstIndex(of: media) { + selected.remove(at: index) + } else if fitsSelectionLimit { + selected.append(media) + } } else { + added.append(media) if fitsSelectionLimit { selected.append(media) } @@ -40,8 +46,30 @@ final class CameraSelectionService: ObservableObject { onChange?(mapToMedia()) } - func index(of media: URLMediaModel) -> Int? { - selected.firstIndex(of: media) + func onSelect(index: Int) { + guard added.indices.contains(index) + else { return } + let media = added[index] + if let index = selected.firstIndex(of: media) { + selected.remove(at: index) + } else if fitsSelectionLimit { + selected.append(media) + } + onChange?(mapToMedia()) + } + + func isSelected(index: Int) -> Bool { + guard added.indices.contains(index) + else { return false } + return selected.contains(added[index]) + } + + func selectedIndex(fromAddedIndex index: Int) -> Int? { + guard added.indices.contains(index) + else { return nil } + + let media = added[index] + return selected.firstIndex(of: media) } func mapToMedia() -> [Media] { @@ -56,6 +84,7 @@ final class CameraSelectionService: ObservableObject { func removeAll() { selected.removeAll() + added.removeAll() onChange?([]) } } diff --git a/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraSelectionContainer.swift b/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraSelectionContainer.swift index b78db92..ab7dd95 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraSelectionContainer.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraSelectionContainer.swift @@ -10,12 +10,13 @@ import SwiftUI public struct CameraSelectionView: View { @EnvironmentObject private var cameraSelectionService: CameraSelectionService - @State private var index: Int = 0 + var selectionParamsHolder: SelectionParamsHolder + public var body: some View { TabView(selection: $index) { - ForEach(cameraSelectionService.selected.enumerated().map({ $0 }), id: \.offset) { (index, mediaModel) in + ForEach(cameraSelectionService.added.enumerated().map({ $0 }), id: \.offset) { (index, mediaModel) in FullscreenCell(viewModel: FullscreenCellViewModel(mediaModel: mediaModel)) .tag(index) .frame(maxHeight: .infinity) @@ -23,6 +24,21 @@ public struct CameraSelectionView: View { } } .tabViewStyle(.page(indexDisplayMode: .never)) + .overlay(alignment: .topTrailing) { + if selectionParamsHolder.selectionLimit != 1 { + SelectIndicatorView( + index: cameraSelectionService.selectedIndex(fromAddedIndex: index), + isFullscreen: true, + canSelect: true, + selectionParamsHolder: selectionParamsHolder + ) + .padding([.horizontal, .bottom], 20) + .contentShape(Rectangle()) + .onTapGesture { + cameraSelectionService.onSelect(index: index) + } + } + } } } @@ -34,6 +50,7 @@ struct DefaultCameraSelectionContainer: View { @ObservedObject var viewModel: MediaPickerViewModel @Binding var showingPicker: Bool + var selectionParamsHolder: SelectionParamsHolder var body: some View { VStack { @@ -46,19 +63,21 @@ struct DefaultCameraSelectionContainer: View { } .padding() - CameraSelectionView() + CameraSelectionView(selectionParamsHolder: selectionParamsHolder) HStack { Button("Done") { showingPicker = false } Spacer() - Button { - viewModel.setPickerMode(.camera) - } label: { - Image(systemName: "plus.app") - .resizable() - .frame(width: 30, height: 30) + if selectionParamsHolder.selectionLimit != 1 { + Button { + viewModel.setPickerMode(.camera) + } label: { + Image(systemName: "plus.app") + .resizable() + .frame(width: 30, height: 30) + } } } .foregroundColor(.white) diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift index 2511a2c..aae10b5 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift @@ -167,10 +167,14 @@ public struct MediaPicker Date: Wed, 25 Oct 2023 12:28:09 +0600 Subject: [PATCH 31/70] Correct code formatting; --- .../Drivers/Selection/CameraSelectionService.swift | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Sources/ExyteMediaPicker/Drivers/Selection/CameraSelectionService.swift b/Sources/ExyteMediaPicker/Drivers/Selection/CameraSelectionService.swift index da06aeb..3ffb547 100644 --- a/Sources/ExyteMediaPicker/Drivers/Selection/CameraSelectionService.swift +++ b/Sources/ExyteMediaPicker/Drivers/Selection/CameraSelectionService.swift @@ -47,8 +47,7 @@ final class CameraSelectionService: ObservableObject { } func onSelect(index: Int) { - guard added.indices.contains(index) - else { return } + guard added.indices.contains(index) else { return } let media = added[index] if let index = selected.firstIndex(of: media) { selected.remove(at: index) @@ -59,15 +58,12 @@ final class CameraSelectionService: ObservableObject { } func isSelected(index: Int) -> Bool { - guard added.indices.contains(index) - else { return false } + guard added.indices.contains(index) else { return false } return selected.contains(added[index]) } func selectedIndex(fromAddedIndex index: Int) -> Int? { - guard added.indices.contains(index) - else { return nil } - + guard added.indices.contains(index) else { return nil } let media = added[index] return selected.firstIndex(of: media) } From e8d1bc3de3ed7b2e5c697919e7b66ce4b85407f1 Mon Sep 17 00:00:00 2001 From: "Alex.M" Date: Wed, 25 Oct 2023 13:26:45 +0600 Subject: [PATCH 32/70] Fix a crash when the application does not have camera permission; --- .../Drivers/Permissions/PermissionsService.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Sources/ExyteMediaPicker/Drivers/Permissions/PermissionsService.swift b/Sources/ExyteMediaPicker/Drivers/Permissions/PermissionsService.swift index 0f4e866..6323c1b 100644 --- a/Sources/ExyteMediaPicker/Drivers/Permissions/PermissionsService.swift +++ b/Sources/ExyteMediaPicker/Drivers/Permissions/PermissionsService.swift @@ -8,8 +8,8 @@ import AVFoundation import Photos final class PermissionsService: ObservableObject { - @Published var cameraAction: CameraAction? - @Published var photoLibraryAction: PhotoLibraryAction? + @Published var cameraAction: CameraAction? = .authorize + @Published var photoLibraryAction: PhotoLibraryAction? = .authorize private var subscriptions = Set() @@ -25,6 +25,9 @@ final class PermissionsService: ObservableObject { self?.checkCameraAuthorizationStatus() } .store(in: &subscriptions) + + checkPhotoLibraryAuthorizationStatus() + checkCameraAuthorizationStatus() } func askLibraryPermissionIfNeeded() { From 7577cce4b46aedc9f01c06684e0ddea5f0a1c14d Mon Sep 17 00:00:00 2001 From: "Alex.M" Date: Wed, 25 Oct 2023 15:49:14 +0600 Subject: [PATCH 33/70] =?UTF-8?q?Apply=20the=20main=20text=20color=20from?= =?UTF-8?q?=20the=20theme=20for=20=E2=80=9CEmpty=20Data=E2=80=9D=20texts;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift | 1 + .../ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsView.swift | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift index 257050f..5630461 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift @@ -51,6 +51,7 @@ private extension AlbumView { } else if viewModel.assetMediaModels.isEmpty, !shouldShowLoadingCell { Text("Empty data") .font(.title3) + .foregroundColor(theme.main.text) } else { MediasGrid(viewModel.assetMediaModels) { #if !targetEnvironment(simulator) diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsView.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsView.swift index c294975..55e14c8 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsView.swift @@ -9,6 +9,7 @@ struct AlbumsView: View { @EnvironmentObject private var selectionService: SelectionService @EnvironmentObject private var permissionsService: PermissionsService + @Environment(\.mediaPickerTheme) private var theme @StateObject var viewModel: AlbumsViewModel @ObservedObject var mediaPickerViewModel: MediaPickerViewModel @@ -42,6 +43,7 @@ struct AlbumsView: View { } else if viewModel.albums.isEmpty { Text("Empty data") .font(.title3) + .foregroundColor(theme.main.text) } else { LazyVGrid(columns: columns, spacing: 0) { ForEach(viewModel.albums) { album in From 8d4f1d4d09e13df778a5f437c0519a72f60165cd Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Tue, 31 Oct 2023 18:19:33 +0700 Subject: [PATCH 34/70] Add show preview closure to camera builder --- .../CustomizedMediaPicker.swift | 13 +++++++++---- README.md | 16 +++++++++++----- .../Views/Pages/Camera/CameraView.swift | 3 ++- .../Views/Pages/MediaPicker/MediaPicker.swift | 3 ++- 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift b/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift index f85859b..023f31a 100644 --- a/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift +++ b/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift @@ -57,12 +57,17 @@ struct CustomizedMediaPicker: View { } .background(Color.black) }, - cameraViewBuilder: { cameraSheetView, cancelClosure, takePhotoClosure, startVideoCaptureClosure, stopVideoCaptureClosure, _, _ in + cameraViewBuilder: { cameraSheetView, cancelClosure, showPreviewClosure, takePhotoClosure, startVideoCaptureClosure, stopVideoCaptureClosure, _, _ in cameraSheetView .overlay(alignment: .topLeading) { - Button("Cancel") { cancelClosure() } - .foregroundColor(Color("CustomPurple")) - .padding() + HStack { + Button("Cancel") { cancelClosure() } + .foregroundColor(Color("CustomPurple")) + Spacer() + Button("Done") { showPreviewClosure() } + .foregroundColor(Color("CustomPurple")) + } + .padding() } .overlay(alignment: .bottom) { HStack { diff --git a/README.md b/README.md index 4c648b1..e951535 100644 --- a/README.md +++ b/README.md @@ -126,12 +126,17 @@ The last one is live camera screen MediaPicker( isPresented: $isPresented, onChange: { selectedMedia = $0 }, - cameraViewBuilder: { cameraSheetView, cancelClosure, takePhotoClosure, startVideoCaptureClosure, stopVideoCaptureClosure, toggleFlash, flipCamera in + cameraViewBuilder: { cameraSheetView, cancelClosure, showPreviewClosure, takePhotoClosure, startVideoCaptureClosure, stopVideoCaptureClosure, toggleFlash, flipCamera in cameraSheetView .overlay(alignment: .topLeading) { - Button("Cancel") { cancelClosure() } - .foregroundColor(Color("CustomPurple")) - .padding() + HStack { + Button("Cancel") { cancelClosure() } + .foregroundColor(Color("CustomPurple")) + Spacer() + Button("Done") { showPreviewClosure() } + .foregroundColor(Color("CustomPurple")) + } + .padding() } .overlay(alignment: .bottom) { HStack { @@ -152,7 +157,8 @@ MediaPicker( `cameraViewBuilder` live camera capture view and a lot of closures to do with as you please: - `cameraSheetView` - live camera capture view - `cancelClosure` - if you want to display "are you sure" before closing -- `takePhotoClosure` - takes a photo +- `showPreviewClosure` - shows preview of taken photos +- `cancelClosure` - if you want to display "are you sure" before closing - `startVideoCaptureClosure` - starts video capture, you'll need a bollean varialbe to track recording state - `stopVideoCaptureClosure` - stops video capture - `toggleFlash` - flash off/on diff --git a/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraView.swift b/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraView.swift index 8f8af70..a278f55 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraView.swift @@ -9,7 +9,7 @@ struct CustomCameraView: View { @EnvironmentObject private var cameraSelectionService: CameraSelectionService - public typealias CameraViewClosure = ((LiveCameraView, @escaping SimpleClosure, @escaping SimpleClosure, @escaping SimpleClosure, @escaping SimpleClosure, @escaping SimpleClosure, @escaping SimpleClosure) -> CameraViewContent) + public typealias CameraViewClosure = ((LiveCameraView, @escaping SimpleClosure, @escaping SimpleClosure, @escaping SimpleClosure, @escaping SimpleClosure, @escaping SimpleClosure, @escaping SimpleClosure, @escaping SimpleClosure) -> CameraViewContent) // params @ObservedObject var viewModel: MediaPickerViewModel @@ -33,6 +33,7 @@ struct CustomCameraView: View { didPressCancel() } }, + { viewModel.setPickerMode(.cameraSelection) }, // show preview of taken photos { cameraViewModel.takePhoto() }, // takePhoto { cameraViewModel.startVideoCapture() }, // start record video { cameraViewModel.stopVideoCapture() }, // stop record video diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift index aae10b5..081b6ad 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift @@ -22,12 +22,13 @@ public struct MediaPicker CameraViewContent) + public typealias CameraViewClosure = ((LiveCameraView, @escaping SimpleClosure, @escaping SimpleClosure, @escaping SimpleClosure, @escaping SimpleClosure, @escaping SimpleClosure, @escaping SimpleClosure, @escaping SimpleClosure) -> CameraViewContent) public typealias FilterClosure = (Media) async -> Media? public typealias MassFilterClosure = ([Media]) async -> [Media] From 3cb62be1813c1a53a9b6bd60b2ca31763efd623c Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Tue, 31 Oct 2023 18:21:26 +0700 Subject: [PATCH 35/70] Bump version --- ExyteMediaPicker.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ExyteMediaPicker.podspec b/ExyteMediaPicker.podspec index 2cef607..10fa904 100644 --- a/ExyteMediaPicker.podspec +++ b/ExyteMediaPicker.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "ExyteMediaPicker" - s.version = "2.1.1" + s.version = "2.2.1" s.summary = "MediaPicker is a customizable photo/video picker for iOS written in pure SwiftUI" s.homepage = 'https://github.com/exyte/MediaPicker.git' From 4ec0d232721949477c44dc3bb6112689cbf9baac Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Thu, 14 Dec 2023 16:13:32 +0700 Subject: [PATCH 36/70] Add more color customization --- ExyteMediaPicker.podspec | 2 +- .../Drivers/Theme/MediaPickerTheme.swift | 22 +++++- .../Modifiers/MediaPickerThemeModifier.swift | 13 ++-- .../Views/Pages/AlbumView/AlbumView.swift | 1 - .../Fullscreen/FullscreenContainer.swift | 71 +++++++++++-------- .../MediaPicker/AlbumSelectionView.swift | 3 - .../Views/Pages/MediaPicker/MediaPicker.swift | 10 ++- 7 files changed, 78 insertions(+), 44 deletions(-) diff --git a/ExyteMediaPicker.podspec b/ExyteMediaPicker.podspec index 10fa904..c4ba4c9 100644 --- a/ExyteMediaPicker.podspec +++ b/ExyteMediaPicker.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "ExyteMediaPicker" - s.version = "2.2.1" + s.version = "2.2.2" s.summary = "MediaPicker is a customizable photo/video picker for iOS written in pure SwiftUI" s.homepage = 'https://github.com/exyte/MediaPicker.git' diff --git a/Sources/ExyteMediaPicker/Drivers/Theme/MediaPickerTheme.swift b/Sources/ExyteMediaPicker/Drivers/Theme/MediaPickerTheme.swift index a82369c..18bb2d5 100644 --- a/Sources/ExyteMediaPicker/Drivers/Theme/MediaPickerTheme.swift +++ b/Sources/ExyteMediaPicker/Drivers/Theme/MediaPickerTheme.swift @@ -10,15 +10,18 @@ public struct MediaPickerTheme { public let selection: Selection public let cellStyle: CellStyle public let error: Error + public let defaultHeader: DefaultHeader public init(main: MediaPickerTheme.Main = .init(), selection: MediaPickerTheme.Selection = .init(), cellStyle: MediaPickerTheme.CellStyle = .init(), - error: MediaPickerTheme.Error = .init()) { + error: MediaPickerTheme.Error = .init(), + defaultHeader: MediaPickerTheme.DefaultHeader = .init()) { self.main = main self.selection = selection self.cellStyle = cellStyle self.error = error + self.defaultHeader = defaultHeader } } @@ -87,4 +90,21 @@ extension MediaPickerTheme { self.tint = tint } } + + public struct DefaultHeader { + public let background: Color + + public init(background: Color = Color(uiColor: .systemGroupedBackground), + segmentTintColor: Color = .white, + selectedSegmentTintColor: Color = .white, + selectedText: Color = .black, + unselectedText: Color = .black) { + self.background = background + + UISegmentedControl.appearance().backgroundColor = UIColor(segmentTintColor) + UISegmentedControl.appearance().selectedSegmentTintColor = UIColor(selectedSegmentTintColor) + UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor(selectedText)], for: .selected) + UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor(unselectedText)], for: .normal) + } + } } diff --git a/Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift b/Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift index 20e9447..3b2aecd 100644 --- a/Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift +++ b/Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift @@ -21,10 +21,13 @@ public extension View { self.environment(\.mediaPickerTheme, theme) } - func mediaPickerTheme(main: MediaPickerTheme.Main = .init(), - selection: MediaPickerTheme.Selection = .init(), - cellStyle: MediaPickerTheme.CellStyle = .init(), - error: MediaPickerTheme.Error = .init()) -> some View { - self.environment(\.mediaPickerTheme, MediaPickerTheme(main: main, selection: selection, cellStyle: cellStyle, error: error)) + func mediaPickerTheme( + main: MediaPickerTheme.Main = .init(), + selection: MediaPickerTheme.Selection = .init(), + cellStyle: MediaPickerTheme.CellStyle = .init(), + error: MediaPickerTheme.Error = .init(), + defaultHeader: MediaPickerTheme.DefaultHeader = .init() + ) -> some View { + self.environment(\.mediaPickerTheme, MediaPickerTheme(main: main, selection: selection, cellStyle: cellStyle, error: error, defaultHeader: defaultHeader)) } } diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift index 5630461..2d5a5be 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift @@ -14,7 +14,6 @@ struct AlbumView: View { @StateObject var viewModel: AlbumViewModel @Binding var showingCamera: Bool - @Binding var isInFullscreen: Bool @Binding var currentFullscreenMedia: Media? var shouldShowCamera: Bool diff --git a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift index 1eeb1ae..d0da9ff 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift @@ -38,12 +38,51 @@ struct FullscreenContainer: View { .tag(assetMediaModel.id) } } + .tabViewStyle(.page(indexDisplayMode: .never)) + .background { + theme.main.fullscreenPhotoBackground + .ignoresSafeArea() + } + .overlay(alignment: .top) { + controlsOverlay + } + .onAppear { + if let selectedMediaModel { + currentFullscreenMedia = Media(source: selectedMediaModel) + } + } + .onDisappear { + currentFullscreenMedia = nil + } .onChange(of: selection) { newValue in if let selectedMediaModel { currentFullscreenMedia = Media(source: selectedMediaModel) } } - .overlay(alignment: .topTrailing) { + .onTapGesture { + if keyboardHeightHelper.keyboardDisplayed { + dismissKeyboard() + } else { + if let selectedMediaModel = selectedMediaModel, selectedMediaModel.mediaType == .image { + selectionService.onSelect(assetMediaModel: selectedMediaModel) + } + } + } + } + + var controlsOverlay: some View { + HStack { + Image(systemName: "xmark") + .resizable() + .frame(width: 20, height: 20) + .padding([.horizontal, .bottom], 20) + .contentShape(Rectangle()) + .onTapGesture { + isPresented = false + } + + Spacer() + if let selectedMediaModel = selectedMediaModel { if selectionParamsHolder.selectionLimit == 1 { Button("Select") { @@ -61,34 +100,6 @@ struct FullscreenContainer: View { } } } - .onTapGesture { - if keyboardHeightHelper.keyboardDisplayed { - dismissKeyboard() - } else { - if let selectedMediaModel = selectedMediaModel, selectedMediaModel.mediaType == .image { - selectionService.onSelect(assetMediaModel: selectedMediaModel) - } - } - } - .overlay(closeButton) - .tabViewStyle(.page(indexDisplayMode: .never)) - .background( - theme.main.fullscreenPhotoBackground - .ignoresSafeArea() - ) - } - - var closeButton: some View { - Button { - isPresented = false - } label: { - Image(systemName: "xmark") - .resizable() - .frame(width: 20, height: 20) - .tint(theme.selection.fullscreenTint) - .padding(12) - } - .padding([.horizontal, .bottom], 8) - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + .foregroundStyle(theme.selection.fullscreenTint) } } diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/AlbumSelectionView.swift b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/AlbumSelectionView.swift index 158dd1f..170ec8c 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/AlbumSelectionView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/AlbumSelectionView.swift @@ -12,7 +12,6 @@ public struct AlbumSelectionView: View { @ObservedObject var viewModel: MediaPickerViewModel @Binding var showingCamera: Bool - @Binding var isInFullscreen: Bool @Binding var currentFullscreenMedia: Media? let showingLiveCameraCell: Bool @@ -31,7 +30,6 @@ public struct AlbumSelectionView: View { mediasProvider: AllPhotosProvider(selectionParamsHolder: selectionParamsHolder, filterClosure: filterClosure, massFilterClosure: massFilterClosure, showingLoadingCell: $showingLoadingCell) ), showingCamera: $showingCamera, - isInFullscreen: $isInFullscreen, currentFullscreenMedia: $currentFullscreenMedia, shouldShowCamera: showingLiveCameraCell, shouldShowLoadingCell: showingLoadingCell, @@ -60,7 +58,6 @@ public struct AlbumSelectionView: View { mediasProvider: AlbumMediasProvider(album: albumModel, selectionParamsHolder: selectionParamsHolder, filterClosure: filterClosure, massFilterClosure: massFilterClosure, showingLoadingCell: $showingLoadingCell) ), showingCamera: $showingCamera, - isInFullscreen: $isInFullscreen, currentFullscreenMedia: $currentFullscreenMedia, shouldShowCamera: false, shouldShowLoadingCell: showingLoadingCell, diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift index 081b6ad..16386d9 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift @@ -67,11 +67,14 @@ public struct MediaPicker, @@ -142,7 +145,7 @@ public struct MediaPicker Binding { From f2dbedb29cde2ba0105039727556cf6d40b9e490 Mon Sep 17 00:00:00 2001 From: lashlyn Date: Wed, 6 Mar 2024 14:01:07 +0700 Subject: [PATCH 37/70] fix showFullscreenPreview selection style --- Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift index 2d5a5be..89bb100 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift @@ -130,7 +130,7 @@ private extension AlbumView { .buttonStyle(MediaButtonStyle()) .contentShape(Rectangle()) - if selectionService.mediaSelectionLimit == 1 { + if selectionService.mediaSelectionLimit == 1 || !selectionParamsHolder.showFullscreenPreview { imageButton } else { SelectableView(selected: selectionService.index(of: assetMediaModel), isFullscreen: false, canSelect: selectionService.canSelect(assetMediaModel: assetMediaModel), selectionParamsHolder: selectionParamsHolder) { From b5741e6f551aef805e3060b1fe714a92402e81a6 Mon Sep 17 00:00:00 2001 From: lashlyn Date: Wed, 6 Mar 2024 14:29:05 +0700 Subject: [PATCH 38/70] fix comment --- .../ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift index 89bb100..80693fc 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift @@ -119,7 +119,9 @@ private extension AlbumView { } if !selectionParamsHolder.showFullscreenPreview { // select immediately selectionService.onSelect(assetMediaModel: assetMediaModel) - shouldDismiss() + if selectionService.mediaSelectionLimit == 1 { + shouldDismiss() + } } else if fullscreenItem == nil { fullscreenItem = assetMediaModel @@ -130,7 +132,7 @@ private extension AlbumView { .buttonStyle(MediaButtonStyle()) .contentShape(Rectangle()) - if selectionService.mediaSelectionLimit == 1 || !selectionParamsHolder.showFullscreenPreview { + if selectionService.mediaSelectionLimit == 1 { imageButton } else { SelectableView(selected: selectionService.index(of: assetMediaModel), isFullscreen: false, canSelect: selectionService.canSelect(assetMediaModel: assetMediaModel), selectionParamsHolder: selectionParamsHolder) { From f4f756dbdb53f087757bbd901d5eadc2be9c47e2 Mon Sep 17 00:00:00 2001 From: lashlyn Date: Mon, 11 Mar 2024 14:21:21 +0700 Subject: [PATCH 39/70] fix close action in camera stub view --- .../Views/Pages/MediaPicker/MediaPicker.swift | 4 +++- .../Views/Widgets/CameraStubView.swift | 10 ++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift index 16386d9..b28902b 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift @@ -297,7 +297,9 @@ public struct MediaPicker(), didPressCancel: @escaping ()->()) -> some View { #if targetEnvironment(simulator) - CameraStubView(isPresented: cameraBinding()) + CameraStubView { + didPressCancel() + } #elseif os(iOS) if let cameraViewBuilder = cameraViewBuilder { CustomCameraView(viewModel: viewModel, didTakePicture: didTakePicture, didPressCancel: didPressCancel, cameraViewBuilder: cameraViewBuilder) diff --git a/Sources/ExyteMediaPicker/Views/Widgets/CameraStubView.swift b/Sources/ExyteMediaPicker/Views/Widgets/CameraStubView.swift index 76cd613..94ef25f 100644 --- a/Sources/ExyteMediaPicker/Views/Widgets/CameraStubView.swift +++ b/Sources/ExyteMediaPicker/Views/Widgets/CameraStubView.swift @@ -7,8 +7,8 @@ import SwiftUI struct CameraStubView: View { - @Binding var isPresented: Bool - + let didPressCancel: () -> Void + var body: some View { ZStack { RoundedRectangle(cornerRadius: 20) @@ -22,7 +22,7 @@ struct CameraStubView: View { .font(.title3) .multilineTextAlignment(.center) Button("Close") { - isPresented = false + didPressCancel() } .padding() } @@ -32,7 +32,9 @@ struct CameraStubView: View { struct CameraStubView_Preview: PreviewProvider { static var previews: some View { - CameraStubView(isPresented: .constant(true)) + CameraStubView { + debugPrint("close") + } } } From 88769b1b69c2b5e5fa5b65522c08bc7b667a6cb8 Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Mon, 6 May 2024 16:49:55 +0700 Subject: [PATCH 40/70] Only ask permissions when needed --- ExyteMediaPicker.podspec | 2 +- .../Permissions/PermissionsService.swift | 65 +++++++------------ .../Albums/DefaultAlbumsProvider.swift | 2 +- .../PhotoKit/Medias/AlbumMediasProvider.swift | 2 +- .../PhotoKit/Medias/AllPhotosProvider.swift | 2 +- .../Views/Pages/MediaPicker/MediaPicker.swift | 22 +++++-- 6 files changed, 43 insertions(+), 52 deletions(-) diff --git a/ExyteMediaPicker.podspec b/ExyteMediaPicker.podspec index c4ba4c9..4846627 100644 --- a/ExyteMediaPicker.podspec +++ b/ExyteMediaPicker.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "ExyteMediaPicker" - s.version = "2.2.2" + s.version = "2.2.3" s.summary = "MediaPicker is a customizable photo/video picker for iOS written in pure SwiftUI" s.homepage = 'https://github.com/exyte/MediaPicker.git' diff --git a/Sources/ExyteMediaPicker/Drivers/Permissions/PermissionsService.swift b/Sources/ExyteMediaPicker/Drivers/Permissions/PermissionsService.swift index 6323c1b..ef0e830 100644 --- a/Sources/ExyteMediaPicker/Drivers/Permissions/PermissionsService.swift +++ b/Sources/ExyteMediaPicker/Drivers/Permissions/PermissionsService.swift @@ -25,85 +25,68 @@ final class PermissionsService: ObservableObject { self?.checkCameraAuthorizationStatus() } .store(in: &subscriptions) - - checkPhotoLibraryAuthorizationStatus() - checkCameraAuthorizationStatus() - } - - func askLibraryPermissionIfNeeded() { - checkPhotoLibraryAuthorizationStatus() } /// photoLibraryChangePermissionPublisher gets called multiple times even when nothing changed in photo library, so just use this one to make sure the closure runs exactly once - static func requestPermission(_ permissionGrantedClosure: @escaping ()->()) { + static func requestPhotoLibraryPermission(_ permissionGrantedClosure: @escaping ()->()) { PHPhotoLibrary.requestAuthorization { status in if status == .authorized || status == .limited { permissionGrantedClosure() } } } -} -private extension PermissionsService { - func checkCameraAuthorizationStatus() { - let status = AVCaptureDevice.authorizationStatus(for: .video) - handle(camera: status) + func requestCameraPermission() { + AVCaptureDevice.requestAccess(for: .video) { [weak self] _ in + self?.checkCameraAuthorizationStatus() + } } - func handle(camera status: AVAuthorizationStatus) { - var result: CameraAction? -#if targetEnvironment(simulator) - result = .unavailable -#else + func checkPhotoLibraryAuthorizationStatus() { + let status = PHPhotoLibrary.authorizationStatus(for: .readWrite) + + var result: PhotoLibraryAction? switch status { - case .notDetermined: - AVCaptureDevice.requestAccess(for: .video) { [weak self] _ in - self?.checkCameraAuthorizationStatus() - } case .restricted: + // TODO: Make sure that access can't change when status == .restricted result = .unavailable - case .denied: + case .notDetermined, .denied: result = .authorize case .authorized: // Do nothing break + case .limited: + result = .selectMore @unknown default: result = .unknown } -#endif + DispatchQueue.main.async { [weak self] in - self?.cameraAction = result + self?.photoLibraryAction = result } } - func checkPhotoLibraryAuthorizationStatus() { - let status = PHPhotoLibrary.authorizationStatus(for: .readWrite) - handle(photoLibrary: status) - } + func checkCameraAuthorizationStatus() { + let status = AVCaptureDevice.authorizationStatus(for: .video) - func handle(photoLibrary status: PHAuthorizationStatus) { - var result: PhotoLibraryAction? + var result: CameraAction? +#if targetEnvironment(simulator) + result = .unavailable +#else switch status { - case .notDetermined: - PHPhotoLibrary.requestAuthorization { [weak self] status in - self?.handle(photoLibrary: status) - } case .restricted: - // TODO: Make sure that access can't change when status == .restricted result = .unavailable - case .denied: + case .notDetermined, .denied: result = .authorize case .authorized: // Do nothing break - case .limited: - result = .selectMore @unknown default: result = .unknown } - +#endif DispatchQueue.main.async { [weak self] in - self?.photoLibraryAction = result + self?.cameraAction = result } } } diff --git a/Sources/ExyteMediaPicker/Drivers/PhotoKit/Albums/DefaultAlbumsProvider.swift b/Sources/ExyteMediaPicker/Drivers/PhotoKit/Albums/DefaultAlbumsProvider.swift index 28b4cbb..d4e6aee 100644 --- a/Sources/ExyteMediaPicker/Drivers/PhotoKit/Albums/DefaultAlbumsProvider.swift +++ b/Sources/ExyteMediaPicker/Drivers/PhotoKit/Albums/DefaultAlbumsProvider.swift @@ -19,7 +19,7 @@ final class DefaultAlbumsProvider: AlbumsProviderProtocol { var mediaSelectionType: MediaSelectionType = .photoAndVideo func reload() { - PermissionsService.requestPermission { [ weak self] in + PermissionsService.requestPhotoLibraryPermission { [ weak self] in self?.reloadInternal() } } diff --git a/Sources/ExyteMediaPicker/Drivers/PhotoKit/Medias/AlbumMediasProvider.swift b/Sources/ExyteMediaPicker/Drivers/PhotoKit/Medias/AlbumMediasProvider.swift index aba4f69..9dc970b 100644 --- a/Sources/ExyteMediaPicker/Drivers/PhotoKit/Medias/AlbumMediasProvider.swift +++ b/Sources/ExyteMediaPicker/Drivers/PhotoKit/Medias/AlbumMediasProvider.swift @@ -17,7 +17,7 @@ final class AlbumMediasProvider: BaseMediasProvider { } override func reload() { - PermissionsService.requestPermission { [ weak self] in + PermissionsService.requestPhotoLibraryPermission { [ weak self] in self?.reloadInternal() } } diff --git a/Sources/ExyteMediaPicker/Drivers/PhotoKit/Medias/AllPhotosProvider.swift b/Sources/ExyteMediaPicker/Drivers/PhotoKit/Medias/AllPhotosProvider.swift index 0c609c1..b745ea5 100644 --- a/Sources/ExyteMediaPicker/Drivers/PhotoKit/Medias/AllPhotosProvider.swift +++ b/Sources/ExyteMediaPicker/Drivers/PhotoKit/Medias/AllPhotosProvider.swift @@ -12,7 +12,7 @@ import SwiftUI final class AllPhotosProvider: BaseMediasProvider { override func reload() { - PermissionsService.requestPermission { [ weak self] in + PermissionsService.requestPhotoLibraryPermission { [ weak self] in self?.reloadInternal() } } diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift index b28902b..dd55a78 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift @@ -109,7 +109,10 @@ public struct MediaPicker(viewModel: viewModel, didTakePicture: didTakePicture, didPressCancel: didPressCancel, cameraViewBuilder: cameraViewBuilder) - .ignoresSafeArea() - } else { - StandardConrolsCameraView(viewModel: viewModel, didTakePicture: didTakePicture, didPressCancel: didPressCancel, selectionParamsHolder: selectionParamsHolder) - .ignoresSafeArea() + Group { + if let cameraViewBuilder = cameraViewBuilder { + CustomCameraView(viewModel: viewModel, didTakePicture: didTakePicture, didPressCancel: didPressCancel, cameraViewBuilder: cameraViewBuilder) + .ignoresSafeArea() + } else { + StandardConrolsCameraView(viewModel: viewModel, didTakePicture: didTakePicture, didPressCancel: didPressCancel, selectionParamsHolder: selectionParamsHolder) + .ignoresSafeArea() + } + } + .onAppear { + permissionService.requestCameraPermission() } #endif } From 2738cd0b90a09d9318c8a2244d87c8ee425996db Mon Sep 17 00:00:00 2001 From: "mylnikova.alisa" Date: Wed, 10 Jul 2024 13:31:26 +0700 Subject: [PATCH 41/70] Update README.md --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e951535..eb0e9f3 100644 --- a/README.md +++ b/README.md @@ -248,12 +248,15 @@ github "Exyte/MediaPicker" ## Our other open source SwiftUI libraries [PopupView](https://github.com/exyte/PopupView) - Toasts and popups library [Grid](https://github.com/exyte/Grid) - The most powerful Grid container -[ScalingHeaderScrollView](https://github.com/exyte/ScalingHeaderScrollView) - A scroll view with a sticky header which shrinks as you scroll -[AnimatedTabBar](https://github.com/exyte/AnimatedTabBar) - A tabbar with number of preset animations -[Chat](https://github.com/exyte/chat) - Chat UI framework with fully customizable message cells, input view, and a built-in media picker +[ScalingHeaderScrollView](https://github.com/exyte/ScalingHeaderScrollView) - A scroll view with a sticky header which shrinks as you scroll +[AnimatedTabBar](https://github.com/exyte/AnimatedTabBar) - A tabbar with a number of preset animations +[Chat](https://github.com/exyte/chat) - Chat UI framework with fully customizable message cells, input view, and a built-in media picker +[OpenAI](https://github.com/exyte/OpenAI) Wrapper lib for [OpenAI REST API](https://platform.openai.com/docs/api-reference/introduction) +[AnimatedGradient](https://github.com/exyte/AnimatedGradient) - Animated linear gradient [ConcentricOnboarding](https://github.com/exyte/ConcentricOnboarding) - Animated onboarding flow [FloatingButton](https://github.com/exyte/FloatingButton) - Floating button menu [ActivityIndicatorView](https://github.com/exyte/ActivityIndicatorView) - A number of animated loading indicators [ProgressIndicatorView](https://github.com/exyte/ProgressIndicatorView) - A number of animated progress indicators +[FlagAndCountryCode](https://github.com/exyte/FlagAndCountryCode) - Phone codes and flags for every country [SVGView](https://github.com/exyte/SVGView) - SVG parser [LiquidSwipe](https://github.com/exyte/LiquidSwipe) - Liquid navigation animation From 6f8745755530209462b60445ec4ba332c3058474 Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Fri, 21 Feb 2025 19:18:32 +0700 Subject: [PATCH 42/70] Use less geometry readers --- .../project.pbxproj | 27 +++++- .../xcschemes/MediaPickerExample.xcscheme | 92 +++++++++++++++++++ .../MediaPickerExample/ContentView.swift | 4 +- .../MediaPickerExample/MediaCell.swift | 33 ++++--- .../Modifiers/FrameGetter.swift | 35 ------- .../Views/Pages/AlbumView/AlbumView.swift | 8 +- .../Views/Pages/AlbumView/MediaCell.swift | 21 ++--- .../Pages/AlbumView/MediaViewModel.swift | 8 +- .../Views/Pages/AlbumsView/AlbumCell.swift | 15 ++- .../Pages/AlbumsView/AlbumCellViewModel.swift | 6 +- .../Views/Pages/AlbumsView/AlbumsView.swift | 25 +++-- .../Views/Widgets/MediasGrid.swift | 28 ++++-- .../Widgets/Thumbnail/ThumbnailView.swift | 13 ++- 13 files changed, 205 insertions(+), 110 deletions(-) create mode 100644 MediaPickerExample/MediaPickerExample.xcodeproj/xcshareddata/xcschemes/MediaPickerExample.xcscheme delete mode 100644 Sources/ExyteMediaPicker/Modifiers/FrameGetter.swift diff --git a/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj b/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj index f9f6c90..0ad7ea6 100644 --- a/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj +++ b/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj @@ -8,6 +8,9 @@ /* Begin PBXBuildFile section */ 13738D1C2AE272FA005BBAD5 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 13738D1B2AE272FA005BBAD5 /* ExyteMediaPicker */; }; + 5B4D33E12D689AF900E6C069 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B4D33E02D689AF900E6C069 /* ExyteMediaPicker */; }; + 5B4D33E42D689FA100E6C069 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B4D33E32D689FA100E6C069 /* ExyteMediaPicker */; }; + 5B4D33E72D68A66600E6C069 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B4D33E62D68A66600E6C069 /* ExyteMediaPicker */; }; 5BFF684D2AD68B990099D333 /* MediaCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFF68432AD68B990099D333 /* MediaCell.swift */; }; 5BFF684E2AD68B990099D333 /* FilterMediaPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFF68442AD68B990099D333 /* FilterMediaPicker.swift */; }; 5BFF684F2AD68B990099D333 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5BFF68452AD68B990099D333 /* Assets.xcassets */; }; @@ -35,7 +38,10 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 5B4D33E42D689FA100E6C069 /* ExyteMediaPicker in Frameworks */, + 5B4D33E72D68A66600E6C069 /* ExyteMediaPicker in Frameworks */, 13738D1C2AE272FA005BBAD5 /* ExyteMediaPicker in Frameworks */, + 5B4D33E12D689AF900E6C069 /* ExyteMediaPicker in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -107,6 +113,9 @@ name = MediaPickerExample; packageProductDependencies = ( 13738D1B2AE272FA005BBAD5 /* ExyteMediaPicker */, + 5B4D33E02D689AF900E6C069 /* ExyteMediaPicker */, + 5B4D33E32D689FA100E6C069 /* ExyteMediaPicker */, + 5B4D33E62D68A66600E6C069 /* ExyteMediaPicker */, ); productName = MediaPickerExample; productReference = 5BFF68312AD689C80099D333 /* MediaPickerExample.app */; @@ -137,7 +146,7 @@ ); mainGroup = 5BFF68282AD689C80099D333; packageReferences = ( - 13738D1A2AE272FA005BBAD5 /* XCLocalSwiftPackageReference ".." */, + 5B4D33E52D68A66600E6C069 /* XCLocalSwiftPackageReference "../../MediaPicker" */, ); productRefGroup = 5BFF68322AD689C80099D333 /* Products */; projectDirPath = ""; @@ -384,9 +393,9 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - 13738D1A2AE272FA005BBAD5 /* XCLocalSwiftPackageReference ".." */ = { + 5B4D33E52D68A66600E6C069 /* XCLocalSwiftPackageReference "../../MediaPicker" */ = { isa = XCLocalSwiftPackageReference; - relativePath = ..; + relativePath = ../../MediaPicker; }; /* End XCLocalSwiftPackageReference section */ @@ -395,6 +404,18 @@ isa = XCSwiftPackageProductDependency; productName = ExyteMediaPicker; }; + 5B4D33E02D689AF900E6C069 /* ExyteMediaPicker */ = { + isa = XCSwiftPackageProductDependency; + productName = ExyteMediaPicker; + }; + 5B4D33E32D689FA100E6C069 /* ExyteMediaPicker */ = { + isa = XCSwiftPackageProductDependency; + productName = ExyteMediaPicker; + }; + 5B4D33E62D68A66600E6C069 /* ExyteMediaPicker */ = { + isa = XCSwiftPackageProductDependency; + productName = ExyteMediaPicker; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 5BFF68292AD689C80099D333 /* Project object */; diff --git a/MediaPickerExample/MediaPickerExample.xcodeproj/xcshareddata/xcschemes/MediaPickerExample.xcscheme b/MediaPickerExample/MediaPickerExample.xcodeproj/xcshareddata/xcschemes/MediaPickerExample.xcscheme new file mode 100644 index 0000000..949dbd1 --- /dev/null +++ b/MediaPickerExample/MediaPickerExample.xcodeproj/xcshareddata/xcschemes/MediaPickerExample.xcscheme @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MediaPickerExample/MediaPickerExample/ContentView.swift b/MediaPickerExample/MediaPickerExample/ContentView.swift index 88b22b6..2018076 100644 --- a/MediaPickerExample/MediaPickerExample/ContentView.swift +++ b/MediaPickerExample/MediaPickerExample/ContentView.swift @@ -39,10 +39,11 @@ struct ContentView: View { } if !medias.isEmpty { + let size = UIScreen.main.bounds.width / 3 - 2 Section { LazyVGrid(columns: columns, spacing: 1) { ForEach(medias) { media in - MediaCell(viewModel: MediaCellViewModel(media: media)) + MediaCell(viewModel: MediaCellViewModel(media: media), size: size) .aspectRatio(1, contentMode: .fill) } } @@ -59,7 +60,6 @@ struct ContentView: View { isPresented: $showDefaultMediaPicker, onChange: { medias = $0 } ) - .showLiveCameraCell() .mediaSelectionLimit(1) .orientationHandler { switch $0 { diff --git a/MediaPickerExample/MediaPickerExample/MediaCell.swift b/MediaPickerExample/MediaPickerExample/MediaCell.swift index ef09dfc..08e5f41 100644 --- a/MediaPickerExample/MediaPickerExample/MediaCell.swift +++ b/MediaPickerExample/MediaPickerExample/MediaCell.swift @@ -13,29 +13,28 @@ import _AVKit_SwiftUI struct MediaCell: View { @StateObject var viewModel: MediaCellViewModel + var size: CGFloat var body: some View { - GeometryReader { g in - VStack { - if let url = viewModel.imageUrl { - AsyncImage(url: url) { phase in - if case let .success(image) = phase { - image - .resizable() - .scaledToFill() - } + VStack { + if let url = viewModel.imageUrl { + AsyncImage(url: url) { phase in + if case let .success(image) = phase { + image + .resizable() + .scaledToFill() } - } else if let player = viewModel.player { - VideoPlayer(player: player).onTapGesture { - viewModel.togglePlay() - } - } else { - ProgressView() } + } else if let player = viewModel.player { + VideoPlayer(player: player).onTapGesture { + viewModel.togglePlay() + } + } else { + ProgressView() } - .frame(width: g.size.width, height: g.size.height) - .clipped() } + .frame(width: size, height: size) + .clipped() .task { await viewModel.onStart() } diff --git a/Sources/ExyteMediaPicker/Modifiers/FrameGetter.swift b/Sources/ExyteMediaPicker/Modifiers/FrameGetter.swift deleted file mode 100644 index 2b27e19..0000000 --- a/Sources/ExyteMediaPicker/Modifiers/FrameGetter.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// FrameGetter.swift -// Example-iOS -// -// Created by Alisa Mylnikova on 23.08.2023. -// - -import SwiftUI - -struct FrameGetter: ViewModifier { - - @Binding var frame: CGRect - - func body(content: Content) -> some View { - content - .background( - GeometryReader { proxy -> AnyView in - DispatchQueue.main.async { - let rect = proxy.frame(in: .global) - // This avoids an infinite layout loop - if rect.integral != self.frame.integral { - self.frame = rect - } - } - return AnyView(EmptyView()) - } - ) - } -} - -internal extension View { - func frameGetter(_ frame: Binding) -> some View { - modifier(FrameGetter(frame: frame)) - } -} diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift index 80693fc..fc7557e 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift @@ -60,8 +60,8 @@ private extension AlbumView { } } #endif - } content: { assetMediaModel in - cellView(assetMediaModel) + } content: { assetMediaModel, cellSize in + cellView(assetMediaModel, size: cellSize) } loadingCell: { if shouldShowLoadingCell { ZStack { @@ -112,7 +112,7 @@ private extension AlbumView { } @ViewBuilder - func cellView(_ assetMediaModel: AssetMediaModel) -> some View { + func cellView(_ assetMediaModel: AssetMediaModel, size: CGFloat) -> some View { let imageButton = Button { if keyboardHeightHelper.keyboardDisplayed { dismissKeyboard() @@ -127,7 +127,7 @@ private extension AlbumView { fullscreenItem = assetMediaModel } } label: { - MediaCell(viewModel: MediaViewModel(assetMediaModel: assetMediaModel)) + MediaCell(viewModel: MediaViewModel(assetMediaModel: assetMediaModel), size: size) } .buttonStyle(MediaButtonStyle()) .contentShape(Rectangle()) diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/MediaCell.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/MediaCell.swift index 893eab9..a066de2 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/MediaCell.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/MediaCell.swift @@ -6,21 +6,20 @@ import SwiftUI struct MediaCell: View { - @StateObject var viewModel: MediaViewModel - @Environment(\.mediaPickerTheme) private var theme + @StateObject var viewModel: MediaViewModel + var size: CGFloat + var body: some View { ZStack { - GeometryReader { geometry in - ThumbnailView(preview: viewModel.preview) - .cornerRadius(theme.cellStyle.cornerRadius) - .onAppear { - viewModel.onStart(size: geometry.size) - } - } - .aspectRatio(1, contentMode: .fill) - .clipped() + ThumbnailView(preview: viewModel.preview, size: size) + .cornerRadius(theme.cellStyle.cornerRadius) + .onAppear { + viewModel.onStart(size: size) + } + .aspectRatio(1, contentMode: .fill) + .clipped() if let duration = viewModel.assetMediaModel.asset.formattedDuration { VStack { diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/MediaViewModel.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/MediaViewModel.swift index fb62e9e..ed2af45 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/MediaViewModel.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/MediaViewModel.swift @@ -22,10 +22,12 @@ class MediaViewModel: ObservableObject { // FIXME: Create preview for image/video for other platforms #endif - func onStart(size: CGSize) { + func onStart(size: CGFloat) { requestID = assetMediaModel.asset - .image(size: size) { - self.preview = $0 + .image(size: CGSize(width: size, height: size)) { image in + DispatchQueue.main.async { + self.preview = image + } } } diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumCell.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumCell.swift index 9ec23db..eabd1d5 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumCell.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumCell.swift @@ -6,21 +6,20 @@ import SwiftUI struct AlbumCell: View { + @Environment(\.mediaPickerTheme) private var theme + @StateObject var viewModel: AlbumCellViewModel + var size: CGFloat - @Environment(\.mediaPickerTheme) private var theme - var body: some View { VStack { Rectangle() .aspectRatio(1, contentMode: .fit) .overlay { - GeometryReader { geometry in - ThumbnailView(preview: viewModel.preview) - .onAppear { - viewModel.fetchPreview(size: geometry.size) - } - } + ThumbnailView(preview: viewModel.preview, size: size) + .onAppear { + viewModel.fetchPreview(size: CGSize(width: size, height: size)) + } } .clipped() .foregroundColor(theme.main.albumSelectionBackground) diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumCellViewModel.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumCellViewModel.swift index 16fcfd5..cb78a0f 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumCellViewModel.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumCellViewModel.swift @@ -26,8 +26,10 @@ class AlbumCellViewModel: ObservableObject { guard preview == nil else { return } requestID = album.preview?.asset - .image(size: size) { [weak self] in - self?.preview = $0 + .image(size: size) { [weak self] image in + DispatchQueue.main.async { + self?.preview = image + } } } diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsView.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsView.swift index 55e14c8..bed5442 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsView.swift @@ -23,8 +23,9 @@ struct AlbumsView: View { @State private var showingLoadingCell = false + let minColumnWidth = 100.0 private var columns: [GridItem] { - [GridItem(.adaptive(minimum: 100), spacing: 0, alignment: .top)] + [GridItem(.adaptive(minimum: minColumnWidth), spacing: 0, alignment: .top)] } private var cellPadding: EdgeInsets { @@ -45,14 +46,15 @@ struct AlbumsView: View { .font(.title3) .foregroundColor(theme.main.text) } else { - LazyVGrid(columns: columns, spacing: 0) { - ForEach(viewModel.albums) { album in - AlbumCell( - viewModel: AlbumCellViewModel(album: album) - ) - .padding(cellPadding) - .onTapGesture { - mediaPickerViewModel.setPickerMode(.album(album.toAlbum())) + GeometryReader { g in + let columnWidth = calculateColumnWidth(g.size.width) + LazyVGrid(columns: columns, spacing: 0) { + ForEach(viewModel.albums) { album in + AlbumCell(viewModel: AlbumCellViewModel(album: album), size: columnWidth) + .padding(cellPadding) + .onTapGesture { + mediaPickerViewModel.setPickerMode(.album(album.toAlbum())) + } } } } @@ -67,4 +69,9 @@ struct AlbumsView: View { viewModel.onStop() } } + + func calculateColumnWidth(_ gridWidth: CGFloat) -> CGFloat { + let wholeCount = CGFloat(Int(gridWidth / minColumnWidth)) + return gridWidth / wholeCount + } } diff --git a/Sources/ExyteMediaPicker/Views/Widgets/MediasGrid.swift b/Sources/ExyteMediaPicker/Views/Widgets/MediasGrid.swift index 228235d..378ac2f 100644 --- a/Sources/ExyteMediaPicker/Views/Widgets/MediasGrid.swift +++ b/Sources/ExyteMediaPicker/Views/Widgets/MediasGrid.swift @@ -9,16 +9,17 @@ public struct MediasGrid: View where Data: R public let data: Data public let camera: () -> Camera - public let content: (Data.Element) -> Content + public let content: (Data.Element, _ size: CGFloat) -> Content public let loadingCell: () -> LoadingCell @Environment(\.mediaPickerTheme) private var theme + let minColumnWidth: CGFloat = 100 private var columns: [GridItem] { - [GridItem(.adaptive(minimum: 100), spacing: theme.cellStyle.columnsSpacing, alignment: .top)] + [GridItem(.adaptive(minimum: minColumnWidth), spacing: theme.cellStyle.columnsSpacing, alignment: .top)] } - - public init(_ data: Data, @ViewBuilder camera: @escaping () -> Camera, @ViewBuilder content: @escaping (Data.Element) -> Content, @ViewBuilder loadingCell: @escaping () -> LoadingCell) { + + public init(_ data: Data, @ViewBuilder camera: @escaping () -> Camera, @ViewBuilder content: @escaping (Data.Element, CGFloat) -> Content, @ViewBuilder loadingCell: @escaping () -> LoadingCell) { self.data = data self.camera = camera self.content = content @@ -26,12 +27,21 @@ public struct MediasGrid: View where Data: R } public var body: some View { - LazyVGrid(columns: columns, spacing: theme.cellStyle.rowSpacing) { - camera() - ForEach(data) { item in - content(item) + GeometryReader { g in + let columnWidth = calculateColumnWidth(g.size.width) + LazyVGrid(columns: columns, spacing: theme.cellStyle.rowSpacing) { + camera() + ForEach(data) { item in + content(item, columnWidth) + } + loadingCell() } - loadingCell() } } + + func calculateColumnWidth(_ gridWidth: CGFloat) -> CGFloat { + let wholeCount = CGFloat(Int(gridWidth / minColumnWidth)) + let noSpaces = gridWidth - theme.cellStyle.columnsSpacing * (wholeCount - 1) + return noSpaces / wholeCount + } } diff --git a/Sources/ExyteMediaPicker/Views/Widgets/Thumbnail/ThumbnailView.swift b/Sources/ExyteMediaPicker/Views/Widgets/Thumbnail/ThumbnailView.swift index c1b1adb..76566ff 100644 --- a/Sources/ExyteMediaPicker/Views/Widgets/Thumbnail/ThumbnailView.swift +++ b/Sources/ExyteMediaPicker/Views/Widgets/Thumbnail/ThumbnailView.swift @@ -8,19 +8,18 @@ struct ThumbnailView: View { #if os(iOS) let preview: UIImage? + let size: CGFloat #else // FIXME: Create preview for image/video for other platforms #endif var body: some View { if let preview = preview { - GeometryReader { proxy in - Image(uiImage: preview) - .resizable() - .scaledToFill() - .frame(width: proxy.size.width, height: proxy.size.height) - .clipped() - } + Image(uiImage: preview) + .resizable() + .scaledToFill() + .frame(width: size, height: size) + .clipped() } else { ThumbnailPlaceholder() } From 7b88be4051bf601ec3bae35a873f6ea986babeef Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Wed, 26 Feb 2025 18:18:23 +0700 Subject: [PATCH 43/70] Refactor theme a bit; optimize fulscreen container --- .../project.pbxproj | 41 ++++++- .../MediaPickerExample/Application.swift | 1 + .../MediaPickerExample/ContentView.swift | 7 +- .../CustomizedMediaPicker.swift | 12 +- .../MediaPickerExample/MediaCell.swift | 31 ++--- .../Theme}/Bundle+.swift | 4 +- .../Drivers/Theme/MediaPickerTheme.swift | 116 ++++++++++++------ .../Drivers/UIKit/UIKit+Config.swift | 11 -- .../Extensions/Collection+.swift | 12 ++ .../ExyteMediaPicker/Extensions/View+.swift | 31 +++++ .../Extensions/View+Keyboard.swift | 14 --- .../Media.xcassets/Colors/Contents.json | 6 + .../Colors/cameraBG.colorset/Contents.json | 38 ++++++ .../Colors/cameraText.colorset/Contents.json | 38 ++++++ .../Colors/pickerBG.colorset/Contents.json | 38 ++++++ .../Colors/pickerText.colorset/Contents.json | 38 ++++++ .../Colors/selection.colorset/Contents.json | 38 ++++++ .../Media.xcassets/Images/Contents.json | 6 + .../FlashOff.imageset/Contents.json | 0 .../{ => Images}/FlashOff.imageset/Flash.pdf | Bin .../FlashOn.imageset/Contents.json | 0 .../FlashOn.imageset/Flash on.pdf | Bin .../FlipCamera.imageset/Change Camera.pdf | Bin .../FlipCamera.imageset/Contents.json | 0 .../Views/Pages/AlbumView/AlbumView.swift | 10 +- .../Views/Pages/AlbumsView/AlbumCell.swift | 6 +- .../Views/Pages/AlbumsView/AlbumsView.swift | 20 ++- .../Camera/CameraSelectionContainer.swift | 87 +++++++------ .../Views/Pages/Camera/CameraView.swift | 15 ++- .../Pages/Fullscreen/FullscreenCell.swift | 36 +++--- .../Fullscreen/FullscreenCellViewModel.swift | 3 +- .../Fullscreen/FullscreenContainer.swift | 68 ++++++---- .../MediaPicker/AlbumSelectionView.swift | 6 +- .../Views/Pages/MediaPicker/MediaPicker.swift | 6 +- .../Views/Widgets/CameraStubView.swift | 1 + .../Views/Widgets/MediasGrid.swift | 14 +-- .../Views/Widgets/SelectIndicatorView.swift | 75 ----------- .../Views/Widgets/SelectableView.swift | 19 ++- .../Widgets/SelectionIndicatorView.swift | 77 ++++++++++++ 39 files changed, 634 insertions(+), 291 deletions(-) rename Sources/ExyteMediaPicker/{Extensions => Drivers/Theme}/Bundle+.swift (88%) delete mode 100644 Sources/ExyteMediaPicker/Drivers/UIKit/UIKit+Config.swift create mode 100644 Sources/ExyteMediaPicker/Extensions/Collection+.swift create mode 100644 Sources/ExyteMediaPicker/Extensions/View+.swift delete mode 100644 Sources/ExyteMediaPicker/Extensions/View+Keyboard.swift create mode 100644 Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/Contents.json create mode 100644 Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/cameraBG.colorset/Contents.json create mode 100644 Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/cameraText.colorset/Contents.json create mode 100644 Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/pickerBG.colorset/Contents.json create mode 100644 Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/pickerText.colorset/Contents.json create mode 100644 Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/selection.colorset/Contents.json create mode 100644 Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/Contents.json rename Sources/ExyteMediaPicker/Resources/Media.xcassets/{ => Images}/FlashOff.imageset/Contents.json (100%) rename Sources/ExyteMediaPicker/Resources/Media.xcassets/{ => Images}/FlashOff.imageset/Flash.pdf (100%) rename Sources/ExyteMediaPicker/Resources/Media.xcassets/{ => Images}/FlashOn.imageset/Contents.json (100%) rename Sources/ExyteMediaPicker/Resources/Media.xcassets/{ => Images}/FlashOn.imageset/Flash on.pdf (100%) rename Sources/ExyteMediaPicker/Resources/Media.xcassets/{ => Images}/FlipCamera.imageset/Change Camera.pdf (100%) rename Sources/ExyteMediaPicker/Resources/Media.xcassets/{ => Images}/FlipCamera.imageset/Contents.json (100%) delete mode 100644 Sources/ExyteMediaPicker/Views/Widgets/SelectIndicatorView.swift create mode 100644 Sources/ExyteMediaPicker/Views/Widgets/SelectionIndicatorView.swift diff --git a/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj b/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj index 0ad7ea6..f507a25 100644 --- a/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj +++ b/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj @@ -8,9 +8,14 @@ /* Begin PBXBuildFile section */ 13738D1C2AE272FA005BBAD5 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 13738D1B2AE272FA005BBAD5 /* ExyteMediaPicker */; }; + 5B2A1A0F2D6DD583000A2E81 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B2A1A0E2D6DD583000A2E81 /* ExyteMediaPicker */; }; + 5B4C32F82D6F2080009844B6 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B4C32F72D6F2080009844B6 /* ExyteMediaPicker */; }; 5B4D33E12D689AF900E6C069 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B4D33E02D689AF900E6C069 /* ExyteMediaPicker */; }; 5B4D33E42D689FA100E6C069 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B4D33E32D689FA100E6C069 /* ExyteMediaPicker */; }; 5B4D33E72D68A66600E6C069 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B4D33E62D68A66600E6C069 /* ExyteMediaPicker */; }; + 5B9275C72D6F2DC700833284 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B9275C62D6F2DC700833284 /* ExyteMediaPicker */; }; + 5BC670572D6C8B250023E666 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5BC670562D6C8B250023E666 /* ExyteMediaPicker */; }; + 5BC6705A2D6C8B810023E666 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5BC670592D6C8B810023E666 /* ExyteMediaPicker */; }; 5BFF684D2AD68B990099D333 /* MediaCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFF68432AD68B990099D333 /* MediaCell.swift */; }; 5BFF684E2AD68B990099D333 /* FilterMediaPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFF68442AD68B990099D333 /* FilterMediaPicker.swift */; }; 5BFF684F2AD68B990099D333 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5BFF68452AD68B990099D333 /* Assets.xcassets */; }; @@ -38,9 +43,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 5B9275C72D6F2DC700833284 /* ExyteMediaPicker in Frameworks */, + 5B2A1A0F2D6DD583000A2E81 /* ExyteMediaPicker in Frameworks */, 5B4D33E42D689FA100E6C069 /* ExyteMediaPicker in Frameworks */, 5B4D33E72D68A66600E6C069 /* ExyteMediaPicker in Frameworks */, 13738D1C2AE272FA005BBAD5 /* ExyteMediaPicker in Frameworks */, + 5BC6705A2D6C8B810023E666 /* ExyteMediaPicker in Frameworks */, + 5B4C32F82D6F2080009844B6 /* ExyteMediaPicker in Frameworks */, + 5BC670572D6C8B250023E666 /* ExyteMediaPicker in Frameworks */, 5B4D33E12D689AF900E6C069 /* ExyteMediaPicker in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -116,6 +126,11 @@ 5B4D33E02D689AF900E6C069 /* ExyteMediaPicker */, 5B4D33E32D689FA100E6C069 /* ExyteMediaPicker */, 5B4D33E62D68A66600E6C069 /* ExyteMediaPicker */, + 5BC670562D6C8B250023E666 /* ExyteMediaPicker */, + 5BC670592D6C8B810023E666 /* ExyteMediaPicker */, + 5B2A1A0E2D6DD583000A2E81 /* ExyteMediaPicker */, + 5B4C32F72D6F2080009844B6 /* ExyteMediaPicker */, + 5B9275C62D6F2DC700833284 /* ExyteMediaPicker */, ); productName = MediaPickerExample; productReference = 5BFF68312AD689C80099D333 /* MediaPickerExample.app */; @@ -146,7 +161,7 @@ ); mainGroup = 5BFF68282AD689C80099D333; packageReferences = ( - 5B4D33E52D68A66600E6C069 /* XCLocalSwiftPackageReference "../../MediaPicker" */, + 5B9275C52D6F2DC700833284 /* XCLocalSwiftPackageReference "../../MediaPicker" */, ); productRefGroup = 5BFF68322AD689C80099D333 /* Products */; projectDirPath = ""; @@ -324,6 +339,7 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -356,6 +372,7 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -393,7 +410,7 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - 5B4D33E52D68A66600E6C069 /* XCLocalSwiftPackageReference "../../MediaPicker" */ = { + 5B9275C52D6F2DC700833284 /* XCLocalSwiftPackageReference "../../MediaPicker" */ = { isa = XCLocalSwiftPackageReference; relativePath = ../../MediaPicker; }; @@ -404,6 +421,14 @@ isa = XCSwiftPackageProductDependency; productName = ExyteMediaPicker; }; + 5B2A1A0E2D6DD583000A2E81 /* ExyteMediaPicker */ = { + isa = XCSwiftPackageProductDependency; + productName = ExyteMediaPicker; + }; + 5B4C32F72D6F2080009844B6 /* ExyteMediaPicker */ = { + isa = XCSwiftPackageProductDependency; + productName = ExyteMediaPicker; + }; 5B4D33E02D689AF900E6C069 /* ExyteMediaPicker */ = { isa = XCSwiftPackageProductDependency; productName = ExyteMediaPicker; @@ -416,6 +441,18 @@ isa = XCSwiftPackageProductDependency; productName = ExyteMediaPicker; }; + 5B9275C62D6F2DC700833284 /* ExyteMediaPicker */ = { + isa = XCSwiftPackageProductDependency; + productName = ExyteMediaPicker; + }; + 5BC670562D6C8B250023E666 /* ExyteMediaPicker */ = { + isa = XCSwiftPackageProductDependency; + productName = ExyteMediaPicker; + }; + 5BC670592D6C8B810023E666 /* ExyteMediaPicker */ = { + isa = XCSwiftPackageProductDependency; + productName = ExyteMediaPicker; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 5BFF68292AD689C80099D333 /* Project object */; diff --git a/MediaPickerExample/MediaPickerExample/Application.swift b/MediaPickerExample/MediaPickerExample/Application.swift index 9f3f296..c3dddd7 100644 --- a/MediaPickerExample/MediaPickerExample/Application.swift +++ b/MediaPickerExample/MediaPickerExample/Application.swift @@ -16,6 +16,7 @@ struct Application: App { WindowGroup { ContentView() .environmentObject(appDelegate) + .preferredColorScheme(.dark) } } } diff --git a/MediaPickerExample/MediaPickerExample/ContentView.swift b/MediaPickerExample/MediaPickerExample/ContentView.swift index 2018076..cbd8d71 100644 --- a/MediaPickerExample/MediaPickerExample/ContentView.swift +++ b/MediaPickerExample/MediaPickerExample/ContentView.swift @@ -39,11 +39,10 @@ struct ContentView: View { } if !medias.isEmpty { - let size = UIScreen.main.bounds.width / 3 - 2 Section { LazyVGrid(columns: columns, spacing: 1) { ForEach(medias) { media in - MediaCell(viewModel: MediaCellViewModel(media: media), size: size) + MediaCell(viewModel: MediaCellViewModel(media: media)) .aspectRatio(1, contentMode: .fill) } } @@ -60,7 +59,9 @@ struct ContentView: View { isPresented: $showDefaultMediaPicker, onChange: { medias = $0 } ) - .mediaSelectionLimit(1) + .showLiveCameraCell() + .mediaSelectionType(.photoAndVideo) + .mediaSelectionStyle(.count) .orientationHandler { switch $0 { case .lock: appDelegate.lockOrientationToPortrait() diff --git a/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift b/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift index 023f31a..abe271e 100644 --- a/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift +++ b/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift @@ -97,15 +97,15 @@ struct CustomizedMediaPicker: View { .mediaSelectionLimit(maxCount) .mediaPickerTheme( main: .init( - albumSelectionBackground: .black, + pickerBackground: .black, fullscreenPhotoBackground: .black ), selection: .init( - emptyTint: .white, - emptyBackground: .black.opacity(0.25), - selectedTint: Color("CustomPurple"), - fullscreenTint: .white - ) + cellSelectedBorder: .customPurple, + cellSelectedBackground: .customPurple, + fullscreenEmptyBorder: .customPurple, + fullscreenSelectedBorder: .customPurple, + fullscreenSelectedBackground: .customPurple) ) .overlay(alignment: .topLeading) { if showAlbumsDropDown { diff --git a/MediaPickerExample/MediaPickerExample/MediaCell.swift b/MediaPickerExample/MediaPickerExample/MediaCell.swift index 08e5f41..0fb8141 100644 --- a/MediaPickerExample/MediaPickerExample/MediaCell.swift +++ b/MediaPickerExample/MediaPickerExample/MediaCell.swift @@ -13,27 +13,28 @@ import _AVKit_SwiftUI struct MediaCell: View { @StateObject var viewModel: MediaCellViewModel - var size: CGFloat var body: some View { - VStack { - if let url = viewModel.imageUrl { - AsyncImage(url: url) { phase in - if case let .success(image) = phase { - image - .resizable() - .scaledToFill() + GeometryReader { g in + VStack { + if let url = viewModel.imageUrl { + AsyncImage(url: url) { phase in + if case let .success(image) = phase { + image + .resizable() + .scaledToFill() + } } + } else if let player = viewModel.player { + VideoPlayer(player: player).onTapGesture { + viewModel.togglePlay() + } + } else { + ProgressView() } - } else if let player = viewModel.player { - VideoPlayer(player: player).onTapGesture { - viewModel.togglePlay() - } - } else { - ProgressView() } + .frame(width: g.size.width, height: g.size.height) } - .frame(width: size, height: size) .clipped() .task { await viewModel.onStart() diff --git a/Sources/ExyteMediaPicker/Extensions/Bundle+.swift b/Sources/ExyteMediaPicker/Drivers/Theme/Bundle+.swift similarity index 88% rename from Sources/ExyteMediaPicker/Extensions/Bundle+.swift rename to Sources/ExyteMediaPicker/Drivers/Theme/Bundle+.swift index 9d1f8b2..b5f0ae3 100644 --- a/Sources/ExyteMediaPicker/Extensions/Bundle+.swift +++ b/Sources/ExyteMediaPicker/Drivers/Theme/Bundle+.swift @@ -1,5 +1,5 @@ // -// File.swift +// Bundle+.swift // // // Created by Alex.M on 07.07.2022. @@ -19,7 +19,7 @@ private final class BundleToken { private init() {} } -extension Bundle { +public extension Bundle { static var current: Bundle { BundleToken.bundle } diff --git a/Sources/ExyteMediaPicker/Drivers/Theme/MediaPickerTheme.swift b/Sources/ExyteMediaPicker/Drivers/Theme/MediaPickerTheme.swift index 18bb2d5..e517154 100644 --- a/Sources/ExyteMediaPicker/Drivers/Theme/MediaPickerTheme.swift +++ b/Sources/ExyteMediaPicker/Drivers/Theme/MediaPickerTheme.swift @@ -27,25 +27,89 @@ public struct MediaPickerTheme { extension MediaPickerTheme { public struct Main { - public let text: Color - public let albumSelectionBackground: Color + public let pickerText: Color + public let pickerBackground: Color public let fullscreenPhotoBackground: Color + public let cameraText: Color public let cameraBackground: Color + public let cameraSelectionText: Color public let cameraSelectionBackground: Color - public init(text: Color = Color(uiColor: .label), - albumSelectionBackground: Color = Color(uiColor: .systemGroupedBackground), - fullscreenPhotoBackground: Color = Color(uiColor: .systemGroupedBackground), - cameraBackground: Color = .black, - cameraSelectionBackground: Color = .black) { - self.text = text - self.albumSelectionBackground = albumSelectionBackground + public init( + pickerText: Color = Color("pickerText", bundle: .current), + pickerBackground: Color = Color("pickerBG", bundle: .current), + fullscreenPhotoBackground: Color = Color("pickerBG", bundle: .current), + cameraText: Color = Color("cameraText", bundle: .current), + cameraBackground: Color = Color("cameraBG", bundle: .current), + cameraSelectionText: Color = Color("cameraText", bundle: .current), + cameraSelectionBackground: Color = Color("cameraBG", bundle: .current) + ) { + self.pickerText = pickerText + self.pickerBackground = pickerBackground self.fullscreenPhotoBackground = fullscreenPhotoBackground + self.cameraText = cameraText self.cameraBackground = cameraBackground + self.cameraSelectionText = cameraSelectionText self.cameraSelectionBackground = cameraSelectionBackground } } - + + public struct Selection { + public let cellEmptyBorder: Color + public let cellEmptyBackground: Color + public let cellSelectedBorder: Color + public let cellSelectedBackground: Color + public let cellSelectedCheckmark: Color + public let fullscreenEmptyBorder: Color + public let fullscreenEmptyBackground: Color + public let fullscreenSelectedBorder: Color + public let fullscreenSelectedBackground: Color + public let fullscreenSelectedCheckmark: Color + + public init( + cellEmptyBorder: Color = .white, + cellEmptyBackground: Color = .black.opacity(0.25), + cellSelectedBorder: Color = .white, + cellSelectedBackground: Color = Color("selection", bundle: .current), + cellSelectedCheckmark: Color = .white, + fullscreenEmptyBorder: Color = Color("selection", bundle: .current), + fullscreenEmptyBackground: Color = .clear, + fullscreenSelectedBorder: Color = Color("selection", bundle: .current), + fullscreenSelectedBackground: Color = Color("selection", bundle: .current), + fullscreenSelectedCheckmark: Color = .white + ) { + self.cellEmptyBorder = cellEmptyBorder + self.cellEmptyBackground = cellEmptyBackground + self.cellSelectedBorder = cellSelectedBorder + self.cellSelectedBackground = cellSelectedBackground + self.cellSelectedCheckmark = cellSelectedCheckmark + self.fullscreenEmptyBorder = fullscreenEmptyBorder + self.fullscreenEmptyBackground = fullscreenEmptyBackground + self.fullscreenSelectedBorder = fullscreenSelectedBorder + self.fullscreenSelectedBackground = fullscreenSelectedBackground + self.fullscreenSelectedCheckmark = fullscreenSelectedCheckmark + } + + public init( + accent: Color, + tint: Color = .white, + background: Color = .black.opacity(0.25) + ) { + self.init( + cellEmptyBorder: tint, + cellEmptyBackground: background, + cellSelectedBorder: tint, + cellSelectedBackground: accent, + cellSelectedCheckmark: tint, + fullscreenEmptyBorder: accent, + fullscreenEmptyBackground: .clear, + fullscreenSelectedBorder: accent, + fullscreenSelectedBackground: accent, + fullscreenSelectedCheckmark: tint + ) + } + } + public struct CellStyle { public let columnsSpacing: CGFloat public let rowSpacing: CGFloat @@ -60,32 +124,12 @@ extension MediaPickerTheme { } } - public struct Selection { - public let emptyTint: Color - public let emptyBackground: Color - public let selectedTint: Color - public let selectedBackground: Color - public let fullscreenTint: Color - - public init(emptyTint: Color = .white, - emptyBackground: Color = .clear, - selectedTint: Color = .blue, - selectedBackground: Color = .white, - fullscreenTint: Color = .blue) { - self.emptyTint = emptyTint - self.emptyBackground = emptyBackground - self.selectedTint = selectedTint - self.selectedBackground = selectedBackground - self.fullscreenTint = fullscreenTint - } - } - public struct Error { public let background: Color public let tint: Color public init(background: Color = .red.opacity(0.7), - tint: Color = .white) { + tint: Color = Color("cameraText", bundle: .current)) { self.background = background self.tint = tint } @@ -94,11 +138,11 @@ extension MediaPickerTheme { public struct DefaultHeader { public let background: Color - public init(background: Color = Color(uiColor: .systemGroupedBackground), - segmentTintColor: Color = .white, - selectedSegmentTintColor: Color = .white, - selectedText: Color = .black, - unselectedText: Color = .black) { + public init(background: Color = Color("pickerBG", bundle: .current), + segmentTintColor: Color = Color("pickerBG", bundle: .current), + selectedSegmentTintColor: Color = Color("pickerBG", bundle: .current), + selectedText: Color = Color("pickerText", bundle: .current), + unselectedText: Color = Color("pickerText", bundle: .current)) { self.background = background UISegmentedControl.appearance().backgroundColor = UIColor(segmentTintColor) diff --git a/Sources/ExyteMediaPicker/Drivers/UIKit/UIKit+Config.swift b/Sources/ExyteMediaPicker/Drivers/UIKit/UIKit+Config.swift deleted file mode 100644 index da2b5be..0000000 --- a/Sources/ExyteMediaPicker/Drivers/UIKit/UIKit+Config.swift +++ /dev/null @@ -1,11 +0,0 @@ -// -// Created by Alex.M on 06.07.2022. -// - -import UIKit - -func setupNavigationBarAppearance() { - let appearance = UINavigationBarAppearance() - appearance.backgroundColor = .white - UINavigationBar.appearance().standardAppearance = appearance -} diff --git a/Sources/ExyteMediaPicker/Extensions/Collection+.swift b/Sources/ExyteMediaPicker/Extensions/Collection+.swift new file mode 100644 index 0000000..104ad59 --- /dev/null +++ b/Sources/ExyteMediaPicker/Extensions/Collection+.swift @@ -0,0 +1,12 @@ +// +// File.swift +// ExyteMediaPicker +// +// Created by Alisa Mylnikova on 25.02.2025. +// + +extension Collection { + subscript(safe index: Index) -> Element? { + indices.contains(index) ? self[index] : nil + } +} diff --git a/Sources/ExyteMediaPicker/Extensions/View+.swift b/Sources/ExyteMediaPicker/Extensions/View+.swift new file mode 100644 index 0000000..a5a1989 --- /dev/null +++ b/Sources/ExyteMediaPicker/Extensions/View+.swift @@ -0,0 +1,31 @@ +// +// File.swift +// +// +// Created by Alisa Mylnikova on 04.09.2023. +// + +import SwiftUI + +extension View { + func dismissKeyboard() { + UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) + } +} + +extension Shape { + func styled(_ foregroundColor: Color, border borderColor: Color = .clear, _ borderWidth: CGFloat = 0) -> some View { + self.foregroundStyle(foregroundColor) // Apply foreground color + .overlay { + self + .stroke(borderColor, lineWidth: borderWidth) // Apply border color and width + } + } +} + +extension View { + func padding(_ horizontal: CGFloat, _ vertical: CGFloat) -> some View { + self.padding(.horizontal, horizontal) + .padding(.vertical, vertical) + } +} diff --git a/Sources/ExyteMediaPicker/Extensions/View+Keyboard.swift b/Sources/ExyteMediaPicker/Extensions/View+Keyboard.swift deleted file mode 100644 index 229a605..0000000 --- a/Sources/ExyteMediaPicker/Extensions/View+Keyboard.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// File.swift -// -// -// Created by Alisa Mylnikova on 04.09.2023. -// - -import SwiftUI - -extension View { - func dismissKeyboard() { - UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) - } -} diff --git a/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/Contents.json b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/cameraBG.colorset/Contents.json b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/cameraBG.colorset/Contents.json new file mode 100644 index 0000000..be9d677 --- /dev/null +++ b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/cameraBG.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x00", + "green" : "0x00", + "red" : "0x00" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x00", + "green" : "0x00", + "red" : "0x00" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/cameraText.colorset/Contents.json b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/cameraText.colorset/Contents.json new file mode 100644 index 0000000..951b907 --- /dev/null +++ b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/cameraText.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFE" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFE" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/pickerBG.colorset/Contents.json b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/pickerBG.colorset/Contents.json new file mode 100644 index 0000000..9c0e331 --- /dev/null +++ b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/pickerBG.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x00", + "green" : "0x00", + "red" : "0x00" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/pickerText.colorset/Contents.json b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/pickerText.colorset/Contents.json new file mode 100644 index 0000000..3fe9b59 --- /dev/null +++ b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/pickerText.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x00", + "green" : "0x00", + "red" : "0x00" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFE" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/selection.colorset/Contents.json b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/selection.colorset/Contents.json new file mode 100644 index 0000000..7e19bd5 --- /dev/null +++ b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/selection.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0x71", + "red" : "0x5A" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0x71", + "red" : "0x5A" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/Contents.json b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/ExyteMediaPicker/Resources/Media.xcassets/FlashOff.imageset/Contents.json b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOff.imageset/Contents.json similarity index 100% rename from Sources/ExyteMediaPicker/Resources/Media.xcassets/FlashOff.imageset/Contents.json rename to Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOff.imageset/Contents.json diff --git a/Sources/ExyteMediaPicker/Resources/Media.xcassets/FlashOff.imageset/Flash.pdf b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOff.imageset/Flash.pdf similarity index 100% rename from Sources/ExyteMediaPicker/Resources/Media.xcassets/FlashOff.imageset/Flash.pdf rename to Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOff.imageset/Flash.pdf diff --git a/Sources/ExyteMediaPicker/Resources/Media.xcassets/FlashOn.imageset/Contents.json b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOn.imageset/Contents.json similarity index 100% rename from Sources/ExyteMediaPicker/Resources/Media.xcassets/FlashOn.imageset/Contents.json rename to Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOn.imageset/Contents.json diff --git a/Sources/ExyteMediaPicker/Resources/Media.xcassets/FlashOn.imageset/Flash on.pdf b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOn.imageset/Flash on.pdf similarity index 100% rename from Sources/ExyteMediaPicker/Resources/Media.xcassets/FlashOn.imageset/Flash on.pdf rename to Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOn.imageset/Flash on.pdf diff --git a/Sources/ExyteMediaPicker/Resources/Media.xcassets/FlipCamera.imageset/Change Camera.pdf b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlipCamera.imageset/Change Camera.pdf similarity index 100% rename from Sources/ExyteMediaPicker/Resources/Media.xcassets/FlipCamera.imageset/Change Camera.pdf rename to Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlipCamera.imageset/Change Camera.pdf diff --git a/Sources/ExyteMediaPicker/Resources/Media.xcassets/FlipCamera.imageset/Contents.json b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlipCamera.imageset/Contents.json similarity index 100% rename from Sources/ExyteMediaPicker/Resources/Media.xcassets/FlipCamera.imageset/Contents.json rename to Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlipCamera.imageset/Contents.json diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift index fc7557e..61ff2c4 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift @@ -19,7 +19,7 @@ struct AlbumView: View { var shouldShowCamera: Bool var shouldShowLoadingCell: Bool var selectionParamsHolder: SelectionParamsHolder - var shouldDismiss: ()->() + var dismiss: ()->() @State private var fullscreenItem: AssetMediaModel? @@ -50,7 +50,7 @@ private extension AlbumView { } else if viewModel.assetMediaModels.isEmpty, !shouldShowLoadingCell { Text("Empty data") .font(.title3) - .foregroundColor(theme.main.text) + .foregroundColor(theme.main.pickerText) } else { MediasGrid(viewModel.assetMediaModels) { #if !targetEnvironment(simulator) @@ -80,7 +80,7 @@ private extension AlbumView { } .frame(maxWidth: .infinity) } - .background(theme.main.albumSelectionBackground) + .background(theme.main.pickerBackground) .onTapGesture { if keyboardHeightHelper.keyboardDisplayed { dismissKeyboard() @@ -94,7 +94,7 @@ private extension AlbumView { assetMediaModels: viewModel.assetMediaModels, selection: item.id, selectionParamsHolder: selectionParamsHolder, - shouldDismiss: shouldDismiss + dismiss: dismiss ) } } @@ -120,7 +120,7 @@ private extension AlbumView { if !selectionParamsHolder.showFullscreenPreview { // select immediately selectionService.onSelect(assetMediaModel: assetMediaModel) if selectionService.mediaSelectionLimit == 1 { - shouldDismiss() + dismiss() } } else if fullscreenItem == nil { diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumCell.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumCell.swift index eabd1d5..d0acb28 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumCell.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumCell.swift @@ -22,13 +22,13 @@ struct AlbumCell: View { } } .clipped() - .foregroundColor(theme.main.albumSelectionBackground) - + .foregroundColor(theme.main.pickerBackground) + if let title = viewModel.album.title { Text(title) .lineLimit(2) .multilineTextAlignment(.center) - .foregroundColor(theme.main.text) + .foregroundColor(theme.main.pickerText) } } .onDisappear { diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsView.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsView.swift index bed5442..a91daa1 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsView.swift @@ -44,18 +44,16 @@ struct AlbumsView: View { } else if viewModel.albums.isEmpty { Text("Empty data") .font(.title3) - .foregroundColor(theme.main.text) + .foregroundColor(theme.main.pickerText) } else { - GeometryReader { g in - let columnWidth = calculateColumnWidth(g.size.width) - LazyVGrid(columns: columns, spacing: 0) { - ForEach(viewModel.albums) { album in - AlbumCell(viewModel: AlbumCellViewModel(album: album), size: columnWidth) - .padding(cellPadding) - .onTapGesture { - mediaPickerViewModel.setPickerMode(.album(album.toAlbum())) - } - } + let columnWidth = calculateColumnWidth(UIScreen.main.bounds.width) + LazyVGrid(columns: columns, spacing: 0) { + ForEach(viewModel.albums) { album in + AlbumCell(viewModel: AlbumCellViewModel(album: album), size: columnWidth) + .padding(cellPadding) + .onTapGesture { + mediaPickerViewModel.setPickerMode(.album(album.toAlbum())) + } } } } diff --git a/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraSelectionContainer.swift b/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraSelectionContainer.swift index ab7dd95..6023e9c 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraSelectionContainer.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraSelectionContainer.swift @@ -10,29 +10,49 @@ import SwiftUI public struct CameraSelectionView: View { @EnvironmentObject private var cameraSelectionService: CameraSelectionService - @State private var index: Int = 0 + @State private var index: Int? = 0 var selectionParamsHolder: SelectionParamsHolder public var body: some View { - TabView(selection: $index) { - ForEach(cameraSelectionService.added.enumerated().map({ $0 }), id: \.offset) { (index, mediaModel) in - FullscreenCell(viewModel: FullscreenCellViewModel(mediaModel: mediaModel)) - .tag(index) - .frame(maxHeight: .infinity) - .padding(.vertical) + GeometryReader { g in + let size = g.size + if #available(iOS 17.0, *) { + ScrollView(.horizontal, showsIndicators: false) { + LazyHStack(spacing: 0) { + ForEach(0..: View { struct StandardConrolsCameraView: View { @EnvironmentObject private var cameraSelectionService: CameraSelectionService - @Environment(\.safeAreaInsets) private var safeAreaInsets @Environment(\.mediaPickerTheme) private var theme @ObservedObject var viewModel: MediaPickerViewModel @@ -73,10 +72,8 @@ struct StandardConrolsCameraView: View { didPressCancel() } } - .foregroundColor(.white) - .padding(.top, safeAreaInsets.top) - .padding(.leading) - .padding(.bottom) + .foregroundColor(theme.main.cameraText) + .padding(12, 16) Spacer() } @@ -112,15 +109,17 @@ struct StandardConrolsCameraView: View { Spacer() Text("\(cameraSelectionService.selected.count)") .font(.system(size: 15)) + .foregroundStyle(theme.main.cameraText) .padding(8) .overlay(Circle() - .stroke(Color.white, lineWidth: 2)) + .stroke(theme.main.cameraText, lineWidth: 2)) } - .foregroundColor(.white) + .foregroundColor(theme.main.cameraText) .padding(.horizontal, 12) } else if selectionParamsHolder.mediaType.allowsVideo { photoVideoToggle + .padding(.bottom, 8) } HStack(spacing: 40) { @@ -146,7 +145,7 @@ struct StandardConrolsCameraView: View { } } .padding(.top, 24) - .padding(.bottom, safeAreaInsets.bottom + 50) + .padding(.bottom, 50) } .background(theme.main.cameraBackground) .onEnteredBackground(perform: cameraViewModel.stopSession) diff --git a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift index 80e83f4..2505ca1 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift @@ -13,27 +13,24 @@ struct FullscreenCell: View { @StateObject var viewModel: FullscreenCellViewModel @ObservedObject var keyboardHeightHelper = KeyboardHeightHelper.shared + var size: CGSize + var body: some View { - GeometryReader { g in - Group { - if let image = viewModel.image { - let useFill = g.size.width / g.size.height > image.size.width / image.size.height - ZoomableScrollView { - imageView(image: image, useFill: useFill) - } - } else if let player = viewModel.player { - let useFill = g.size.width / g.size.height > viewModel.videoSize.width / viewModel.videoSize.height - ZoomableScrollView { - videoView(player: player, useFill: useFill) - } - } else { - ProgressView() - .tint(.white) + Group { + if let image = viewModel.image { + ZoomableScrollView { + imageView(image: image, useFill: false) + } + } else if let player = viewModel.player { + ZoomableScrollView { + videoView(player: player, useFill: false) } + } else { + ProgressView() + .tint(.white) } - .allowsHitTesting(!keyboardHeightHelper.keyboardDisplayed) - .position(x: g.frame(in: .local).midX, y: g.frame(in: .local).midY) } + .allowsHitTesting(!keyboardHeightHelper.keyboardDisplayed) .task { await viewModel.onStart() } @@ -56,10 +53,13 @@ struct FullscreenCell: View { ZStack { Color.clear if !viewModel.isPlaying { + Circle().styled(.black.opacity(0.2)) + .frame(width: 70, height: 70) Image(systemName: "play.fill") .resizable() - .frame(width: 50, height: 50) .foregroundColor(.white.opacity(0.8)) + .frame(width: 30, height: 30) + .padding(.leading, 4) } } .contentShape(Rectangle()) diff --git a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCellViewModel.swift b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCellViewModel.swift index 6b21910..65100b1 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCellViewModel.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCellViewModel.swift @@ -32,8 +32,9 @@ final class FullscreenCellViewModel: ObservableObject { case .image: let data = try? await mediaModel.getData() // url is slow to load in UI, this way photos don't flicker when swiping guard let data = data else { return } + let result = UIImage(data: data) DispatchQueue.main.async { - self.image = UIImage(data: data) + self.image = result } case .video: let url = await mediaModel.getURL() diff --git a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift index d0da9ff..1d1f05c 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift @@ -15,9 +15,9 @@ struct FullscreenContainer: View { @Binding var isPresented: Bool @Binding var currentFullscreenMedia: Media? let assetMediaModels: [AssetMediaModel] - @State var selection: AssetMediaModel.ID + @State var selection: AssetMediaModel.ID? var selectionParamsHolder: SelectionParamsHolder - var shouldDismiss: ()->() + var dismiss: ()->() private var selectedMediaModel: AssetMediaModel? { assetMediaModels.first { $0.id == selection } @@ -29,23 +29,20 @@ struct FullscreenContainer: View { } return selectionService.index(of: selectedMediaModel) } - + var body: some View { - TabView(selection: $selection) { - ForEach(assetMediaModels, id: \.id) { assetMediaModel in - FullscreenCell(viewModel: FullscreenCellViewModel(mediaModel: assetMediaModel)) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .tag(assetMediaModel.id) + VStack { + controlsOverlay + GeometryReader { g in + let size = g.size + contentView(size) } } - .tabViewStyle(.page(indexDisplayMode: .never)) + .ignoresSafeArea() .background { theme.main.fullscreenPhotoBackground .ignoresSafeArea() } - .overlay(alignment: .top) { - controlsOverlay - } .onAppear { if let selectedMediaModel { currentFullscreenMedia = Media(source: selectedMediaModel) @@ -70,12 +67,43 @@ struct FullscreenContainer: View { } } + @ViewBuilder + func contentView(_ size: CGSize) -> some View { + if #available(iOS 17.0, *) { + ScrollViewReader { scrollReader in + ScrollView(.horizontal, showsIndicators: false) { + LazyHStack(spacing: 0) { + ForEach(assetMediaModels, id: \.id) { assetMediaModel in + FullscreenCell(viewModel: FullscreenCellViewModel(mediaModel: assetMediaModel), size: size) + .frame(width: size.width, height: size.height) + .id(assetMediaModel.id) + } + } + .scrollTargetLayout() + } + .scrollTargetBehavior(.viewAligned) + .scrollPosition(id: $selection) + .onAppear { + scrollReader.scrollTo(selection) + } + } + } else { + TabView(selection: $selection) { + ForEach(assetMediaModels, id: \.id) { assetMediaModel in + FullscreenCell(viewModel: FullscreenCellViewModel(mediaModel: assetMediaModel), size: size) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .tag(assetMediaModel.id) + } + } + } + } + var controlsOverlay: some View { HStack { Image(systemName: "xmark") .resizable() .frame(width: 20, height: 20) - .padding([.horizontal, .bottom], 20) + .padding(20, 16) .contentShape(Rectangle()) .onTapGesture { isPresented = false @@ -87,19 +115,15 @@ struct FullscreenContainer: View { if selectionParamsHolder.selectionLimit == 1 { Button("Select") { selectionService.onSelect(assetMediaModel: selectedMediaModel) - shouldDismiss() + dismiss() } - .padding([.horizontal, .bottom], 20) + .padding(.horizontal, 20) } else { - SelectIndicatorView(index: selectionServiceIndex, isFullscreen: true, canSelect: selectionService.canSelect(assetMediaModel: selectedMediaModel), selectionParamsHolder: selectionParamsHolder) - .padding([.horizontal, .bottom], 20) - .contentShape(Rectangle()) - .onTapGesture { - selectionService.onSelect(assetMediaModel: selectedMediaModel) - } + SelectionIndicatorView(index: selectionServiceIndex, isFullscreen: true, canSelect: selectionService.canSelect(assetMediaModel: selectedMediaModel), selectionParamsHolder: selectionParamsHolder) + .padding(.horizontal, 20) } } } - .foregroundStyle(theme.selection.fullscreenTint) + .foregroundStyle(theme.selection.fullscreenSelectedBackground) } } diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/AlbumSelectionView.swift b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/AlbumSelectionView.swift index 170ec8c..71b54e9 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/AlbumSelectionView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/AlbumSelectionView.swift @@ -18,7 +18,7 @@ public struct AlbumSelectionView: View { let selectionParamsHolder: SelectionParamsHolder let filterClosure: MediaPicker.FilterClosure? let massFilterClosure: MediaPicker.MassFilterClosure? - var shouldDismiss: ()->() + var dismiss: ()->() @State private var showingLoadingCell = false @@ -34,7 +34,7 @@ public struct AlbumSelectionView: View { shouldShowCamera: showingLiveCameraCell, shouldShowLoadingCell: showingLoadingCell, selectionParamsHolder: selectionParamsHolder, - shouldDismiss: shouldDismiss + dismiss: dismiss ) case .albums: AlbumsView( @@ -62,7 +62,7 @@ public struct AlbumSelectionView: View { shouldShowCamera: false, shouldShowLoadingCell: showingLoadingCell, selectionParamsHolder: selectionParamsHolder, - shouldDismiss: shouldDismiss + dismiss: dismiss ) .id(album.id) } diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift index dd55a78..c30266a 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift @@ -104,7 +104,7 @@ public struct MediaPicker: View where Data: R } public var body: some View { - GeometryReader { g in - let columnWidth = calculateColumnWidth(g.size.width) - LazyVGrid(columns: columns, spacing: theme.cellStyle.rowSpacing) { - camera() - ForEach(data) { item in - content(item, columnWidth) - } - loadingCell() + let columnWidth = calculateColumnWidth(UIScreen.main.bounds.width) + LazyVGrid(columns: columns, spacing: theme.cellStyle.rowSpacing) { + camera() + ForEach(data) { item in + content(item, columnWidth) } + loadingCell() } } diff --git a/Sources/ExyteMediaPicker/Views/Widgets/SelectIndicatorView.swift b/Sources/ExyteMediaPicker/Views/Widgets/SelectIndicatorView.swift deleted file mode 100644 index d672976..0000000 --- a/Sources/ExyteMediaPicker/Views/Widgets/SelectIndicatorView.swift +++ /dev/null @@ -1,75 +0,0 @@ -// -// Created by Alex.M on 27.05.2022. -// - -import SwiftUI - -struct SelectIndicatorView: View { - - @EnvironmentObject private var selectionService: SelectionService - - @Environment(\.mediaPickerTheme) var theme - - var index: Int? - var isFullscreen: Bool - var canSelect: Bool - var selectionParamsHolder: SelectionParamsHolder - - var body: some View { - Group { - switch selectionParamsHolder.selectionStyle { - case .checkmark: - checkView - case .count: - countView - } - } - .frame(width: 24, height: 24) - .padding(.top, isFullscreen ? 11 : -2) - } - - var checkView: some View { - Group { - if index != nil { - Image(systemName: "checkmark.circle.fill") - .resizable() - .foregroundColor(theme.selection.selectedTint) - .padding(2) - .background { - Circle() - .fill(theme.selection.selectedBackground) - } - } else if canSelect { - Image(systemName: "circle") - .resizable() - .foregroundColor(isFullscreen ? theme.selection.fullscreenTint : theme.selection.emptyTint) - .background { - Circle() - .fill(theme.selection.emptyBackground) - } - } - } - } - - var countView: some View { - Group { - if let index = index { - Image(systemName: "\(index + 1).circle.fill") - .resizable() - .foregroundColor(theme.selection.selectedTint) - .background { - Circle() - .fill(theme.selection.selectedBackground) - } - } else if canSelect { - Image(systemName: "circle") - .resizable() - .foregroundColor(isFullscreen ? theme.selection.fullscreenTint : theme.selection.emptyTint) - .background { - Circle() - .fill(theme.selection.emptyBackground) - } - } - } - } -} diff --git a/Sources/ExyteMediaPicker/Views/Widgets/SelectableView.swift b/Sources/ExyteMediaPicker/Views/Widgets/SelectableView.swift index 893b9eb..ba1b795 100644 --- a/Sources/ExyteMediaPicker/Views/Widgets/SelectableView.swift +++ b/Sources/ExyteMediaPicker/Views/Widgets/SelectableView.swift @@ -7,7 +7,6 @@ import SwiftUI struct SelectableView: View where Content: View { var selected: Int? - var paddings: CGFloat = 2 var isFullscreen: Bool var canSelect: Bool var selectionParamsHolder: SelectionParamsHolder @@ -15,15 +14,15 @@ struct SelectableView: View where Content: View { @ViewBuilder var content: () -> Content var body: some View { - content().overlay { - Button { - onSelect() - } label: { - SelectIndicatorView(index: selected, isFullscreen: isFullscreen, canSelect: canSelect, selectionParamsHolder: selectionParamsHolder) - .padding([.bottom, .leading], 10) + content() + .overlay(alignment: .topTrailing) { + SelectionIndicatorView(index: selected, isFullscreen: isFullscreen, canSelect: canSelect, selectionParamsHolder: selectionParamsHolder) + .padding([.bottom, .leading], 10) // extend tappable area where possible + .contentShape(Rectangle()) + .onTapGesture { + onSelect() + } + .padding(2) } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing) - .padding(paddings) - } } } diff --git a/Sources/ExyteMediaPicker/Views/Widgets/SelectionIndicatorView.swift b/Sources/ExyteMediaPicker/Views/Widgets/SelectionIndicatorView.swift new file mode 100644 index 0000000..a6eaf77 --- /dev/null +++ b/Sources/ExyteMediaPicker/Views/Widgets/SelectionIndicatorView.swift @@ -0,0 +1,77 @@ +// +// Created by Alex.M on 27.05.2022. +// + +import SwiftUI + +struct SelectionIndicatorView: View { + + @EnvironmentObject private var selectionService: SelectionService + + @Environment(\.mediaPickerTheme) var theme + + var index: Int? + var isFullscreen: Bool + var canSelect: Bool + var selectionParamsHolder: SelectionParamsHolder + + var size: CGFloat { isFullscreen ? 26 : 24 } + + var emptyBorder: Color { isFullscreen ? theme.selection.fullscreenEmptyBorder : theme.selection.cellEmptyBorder } + var emptyBackground: Color { isFullscreen ? theme.selection.fullscreenEmptyBackground : theme.selection.cellEmptyBackground } + var selectedBorder: Color { isFullscreen ? theme.selection.fullscreenSelectedBorder : theme.selection.cellSelectedBorder } + var selectedBackground: Color { isFullscreen ? theme.selection.fullscreenSelectedBackground : theme.selection.cellSelectedBackground } + var selectedCheckmark: Color { isFullscreen ? theme.selection.fullscreenSelectedCheckmark : theme.selection.cellSelectedCheckmark } + + var body: some View { + Group { + switch selectionParamsHolder.selectionStyle { + case .checkmark: + checkView + case .count: + countView + } + } + .frame(width: size, height: size) + } + + @ViewBuilder + var checkView: some View { + if canSelect { + let selected = index != nil + ZStack { + Circle().styled( + selected ? selectedBackground : emptyBackground, + border: selected ? selectedBorder : emptyBorder, 2 + ) + if let index { + Image(systemName: "checkmark") + .resizable() + .foregroundColor(selectedCheckmark) + .font(.system(size: 14, weight: .bold)) + .padding(7) + } + } + .animation(.easeOut(duration: 0.2), value: selected) + } + } + + @ViewBuilder + var countView: some View { + if canSelect { + let selected = index != nil + ZStack { + Circle().styled( + selected ? selectedBackground : emptyBackground, + border: selected ? selectedBorder : emptyBorder, 2 + ) + if let index { + Text("\(index + 1)") + .foregroundColor(selectedCheckmark) + .font(.system(size: 14, weight: .bold)) + } + } + .animation(.easeOut(duration: 0.2), value: selected) + } + } +} From 84f768b923f9d46fb79b63234b059c08e44270c9 Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Thu, 27 Feb 2025 15:09:57 +0700 Subject: [PATCH 44/70] Fix filter picker --- .../MediaPickerExample/FilterMediaPicker.swift | 8 ++++---- .../Drivers/PhotoKit/Medias/MediasProviderProtocol.swift | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/MediaPickerExample/MediaPickerExample/FilterMediaPicker.swift b/MediaPickerExample/MediaPickerExample/FilterMediaPicker.swift index 08b879f..cd734a4 100644 --- a/MediaPickerExample/MediaPickerExample/FilterMediaPicker.swift +++ b/MediaPickerExample/MediaPickerExample/FilterMediaPicker.swift @@ -19,18 +19,18 @@ struct FilterMediaPicker: View { isPresented: $isPresented, onChange: { medias = $0 } ) - .applyFilter { await isMostlyBlueAndGreen($0) } + .applyFilter { await isMostlyRed($0) } } - private func isMostlyBlueAndGreen(_ media: Media) async -> Media? { + private func isMostlyRed(_ media: Media) async -> Media? { guard let data = await media.getThumbnailData() else { return nil } guard let uiImage = UIImage(data: data) else { return nil } let color = uiImage.averageColor var red: CGFloat = 0.0, green: CGFloat = 0.0, blue: CGFloat = 0.0, alpha: CGFloat = 0.0 color?.getRed(&red, green: &green, blue: &blue, alpha: &alpha) - if blue > red, green > red { - return media + if red > blue, red > green { + return media } else { return nil } diff --git a/Sources/ExyteMediaPicker/Drivers/PhotoKit/Medias/MediasProviderProtocol.swift b/Sources/ExyteMediaPicker/Drivers/PhotoKit/Medias/MediasProviderProtocol.swift index c8b7075..d06ff31 100644 --- a/Sources/ExyteMediaPicker/Drivers/PhotoKit/Medias/MediasProviderProtocol.swift +++ b/Sources/ExyteMediaPicker/Drivers/PhotoKit/Medias/MediasProviderProtocol.swift @@ -48,6 +48,7 @@ class BaseMediasProvider: MediasProviderProtocol { var result = [AssetMediaModel]() await assets.asyncForEach { if cancellableTask?.isCancelled ?? false { + showLoading(false) return } if let media = await filterClosure(Media(source: $0)), let model = media.source as? AssetMediaModel { @@ -57,6 +58,7 @@ class BaseMediasProvider: MediasProviderProtocol { } } } + assetMediaModelsPublisher.send(result) showLoading(false) } } else if let massFilterClosure = massFilterClosure { From 7bb3011c48fc0aab9d24b1e31702d70aed6de2d7 Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Thu, 27 Feb 2025 15:25:10 +0700 Subject: [PATCH 45/70] Fix toggle video playing --- .../Views/Pages/Fullscreen/FullscreenCell.swift | 8 +++++--- .../Views/Pages/Fullscreen/FullscreenContainer.swift | 3 +++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift index 2505ca1..360cac0 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift @@ -63,9 +63,11 @@ struct FullscreenCell: View { } } .contentShape(Rectangle()) - .onTapGesture { - viewModel.togglePlay() - } + .simultaneousGesture( + TapGesture().onEnded { + viewModel.togglePlay() + } + ) } } } diff --git a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift index 1d1f05c..fe4c68c 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift @@ -121,6 +121,9 @@ struct FullscreenContainer: View { } else { SelectionIndicatorView(index: selectionServiceIndex, isFullscreen: true, canSelect: selectionService.canSelect(assetMediaModel: selectedMediaModel), selectionParamsHolder: selectionParamsHolder) .padding(.horizontal, 20) + .onTapGesture { + selectionService.onSelect(assetMediaModel: selectedMediaModel) // for video selection, since tap on video is toggle play + } } } } From 40eb7689d0288109a5193a2aac7918509ae99f35 Mon Sep 17 00:00:00 2001 From: "mylnikova.alisa" Date: Fri, 28 Feb 2025 15:08:53 +0700 Subject: [PATCH 46/70] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index eb0e9f3..9b4f0e2 100644 --- a/README.md +++ b/README.md @@ -247,6 +247,7 @@ github "Exyte/MediaPicker" ## Our other open source SwiftUI libraries [PopupView](https://github.com/exyte/PopupView) - Toasts and popups library +[AnchoredPopup](https://github.com/exyte/AnchoredPopup) - Anchored Popup grows "out" of a trigger view (similar to Hero animation) [Grid](https://github.com/exyte/Grid) - The most powerful Grid container [ScalingHeaderScrollView](https://github.com/exyte/ScalingHeaderScrollView) - A scroll view with a sticky header which shrinks as you scroll [AnimatedTabBar](https://github.com/exyte/AnimatedTabBar) - A tabbar with a number of preset animations From 7fa2782019b8bf0fa72c5bf4ac7d5a8a689b7553 Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Fri, 7 Mar 2025 17:00:23 +0700 Subject: [PATCH 47/70] Add swift 6 support; Add growing cells animations --- .../project.pbxproj | 62 +++++++- .../xcshareddata/WorkspaceSettings.xcsettings | 5 + .../MediaPickerExample/ContentView.swift | 2 +- .../MediaPickerExample/MediaCell.swift | 9 +- Package.swift | 15 +- README.md | 4 +- Sources/ExyteMediaPicker/Data/Media.swift | 2 +- .../Data/MediaModelProtocol.swift | 2 +- .../{FileManager => }/FileManager.swift | 0 .../LiveCameraViewModel.swift | 0 .../Medias/AlbumMediasProvider.swift | 10 +- .../Drivers/Medias/AllMediasProvider.swift | 33 +++++ .../Medias/AllPhotosProvider.swift | 0 .../Drivers/Medias/BaseMediasProvider.swift | 135 ++++++++++++++++++ .../Medias/DefaultAlbumsProvider.swift | 86 +++++++++++ .../{MotionManager => }/MotionManager.swift | 0 .../PermissionsService.swift | 23 ++- .../Albums/AlbumsProviderProtocol.swift | 12 -- .../Albums/DefaultAlbumsProvider.swift | 74 ---------- .../Medias/MediasProviderProtocol.swift | 109 -------------- .../Drivers/PhotoKit/PHAsset+Utils.swift | 1 + .../PhotoKit/PhotoLibraryChangeNotifier.swift | 15 -- .../Drivers/Theme/MediaPickerTheme.swift | 10 +- .../Modifiers/KeyboardHeightHelper.swift | 2 +- .../Modifiers/MediaPickerThemeModifier.swift | 9 +- .../Modifiers/SafeAreaEnvironmentValues.swift | 3 +- .../Views/Pages/AlbumView/AlbumView.swift | 116 ++++++++------- .../Pages/AlbumView/AlbumViewModel.swift | 39 ----- .../Pages/AlbumView/MediaViewModel.swift | 2 +- .../Pages/AlbumsView/AlbumCellViewModel.swift | 2 +- .../Pages/AlbumsView/AlbumsViewModel.swift | 37 ++--- .../Camera/CameraSelectionContainer.swift | 2 +- .../Views/Pages/Camera/CameraView.swift | 4 +- .../Fullscreen/FullscreenCellViewModel.swift | 4 + .../Fullscreen/FullscreenContainer.swift | 14 +- .../MediaPicker/AlbumSelectionView.swift | 13 +- .../Views/Pages/MediaPicker/MediaPicker.swift | 10 +- .../MediaPicker/MediaPickerViewModel.swift | 2 +- .../LimitedLibraryPickerProxyView.swift | 12 +- .../Views/Widgets/MediasGrid.swift | 18 ++- 40 files changed, 488 insertions(+), 410 deletions(-) create mode 100644 MediaPickerExample/MediaPickerExample.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename Sources/ExyteMediaPicker/Drivers/{FileManager => }/FileManager.swift (100%) rename Sources/ExyteMediaPicker/Drivers/{AVFoundation => }/LiveCameraViewModel.swift (100%) rename Sources/ExyteMediaPicker/Drivers/{PhotoKit => }/Medias/AlbumMediasProvider.swift (86%) create mode 100644 Sources/ExyteMediaPicker/Drivers/Medias/AllMediasProvider.swift rename Sources/ExyteMediaPicker/Drivers/{PhotoKit => }/Medias/AllPhotosProvider.swift (100%) create mode 100644 Sources/ExyteMediaPicker/Drivers/Medias/BaseMediasProvider.swift create mode 100644 Sources/ExyteMediaPicker/Drivers/Medias/DefaultAlbumsProvider.swift rename Sources/ExyteMediaPicker/Drivers/{MotionManager => }/MotionManager.swift (100%) rename Sources/ExyteMediaPicker/Drivers/{Permissions => }/PermissionsService.swift (80%) delete mode 100644 Sources/ExyteMediaPicker/Drivers/PhotoKit/Albums/AlbumsProviderProtocol.swift delete mode 100644 Sources/ExyteMediaPicker/Drivers/PhotoKit/Albums/DefaultAlbumsProvider.swift delete mode 100644 Sources/ExyteMediaPicker/Drivers/PhotoKit/Medias/MediasProviderProtocol.swift delete mode 100644 Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumViewModel.swift diff --git a/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj b/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj index f507a25..326061c 100644 --- a/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj +++ b/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj @@ -8,7 +8,12 @@ /* Begin PBXBuildFile section */ 13738D1C2AE272FA005BBAD5 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 13738D1B2AE272FA005BBAD5 /* ExyteMediaPicker */; }; + 5B0BE9372D7AF6030011FCDB /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B0BE9362D7AF6030011FCDB /* ExyteMediaPicker */; }; + 5B0BE93A2D7AF6470011FCDB /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B0BE9392D7AF6470011FCDB /* ExyteMediaPicker */; }; + 5B0BE93D2D7AF6940011FCDB /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B0BE93C2D7AF6940011FCDB /* ExyteMediaPicker */; }; 5B2A1A0F2D6DD583000A2E81 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B2A1A0E2D6DD583000A2E81 /* ExyteMediaPicker */; }; + 5B32A6692D79BA3100DF5348 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B32A6682D79BA3100DF5348 /* ExyteMediaPicker */; }; + 5B32A66C2D79BC6D00DF5348 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B32A66B2D79BC6D00DF5348 /* ExyteMediaPicker */; }; 5B4C32F82D6F2080009844B6 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B4C32F72D6F2080009844B6 /* ExyteMediaPicker */; }; 5B4D33E12D689AF900E6C069 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B4D33E02D689AF900E6C069 /* ExyteMediaPicker */; }; 5B4D33E42D689FA100E6C069 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B4D33E32D689FA100E6C069 /* ExyteMediaPicker */; }; @@ -16,6 +21,7 @@ 5B9275C72D6F2DC700833284 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B9275C62D6F2DC700833284 /* ExyteMediaPicker */; }; 5BC670572D6C8B250023E666 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5BC670562D6C8B250023E666 /* ExyteMediaPicker */; }; 5BC6705A2D6C8B810023E666 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5BC670592D6C8B810023E666 /* ExyteMediaPicker */; }; + 5BFD73DA2D70840200BD0F67 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5BFD73D92D70840200BD0F67 /* ExyteMediaPicker */; }; 5BFF684D2AD68B990099D333 /* MediaCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFF68432AD68B990099D333 /* MediaCell.swift */; }; 5BFF684E2AD68B990099D333 /* FilterMediaPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFF68442AD68B990099D333 /* FilterMediaPicker.swift */; }; 5BFF684F2AD68B990099D333 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5BFF68452AD68B990099D333 /* Assets.xcassets */; }; @@ -44,12 +50,18 @@ buildActionMask = 2147483647; files = ( 5B9275C72D6F2DC700833284 /* ExyteMediaPicker in Frameworks */, + 5B0BE9372D7AF6030011FCDB /* ExyteMediaPicker in Frameworks */, + 5B32A6692D79BA3100DF5348 /* ExyteMediaPicker in Frameworks */, + 5B0BE93D2D7AF6940011FCDB /* ExyteMediaPicker in Frameworks */, 5B2A1A0F2D6DD583000A2E81 /* ExyteMediaPicker in Frameworks */, 5B4D33E42D689FA100E6C069 /* ExyteMediaPicker in Frameworks */, + 5BFD73DA2D70840200BD0F67 /* ExyteMediaPicker in Frameworks */, 5B4D33E72D68A66600E6C069 /* ExyteMediaPicker in Frameworks */, + 5B0BE93A2D7AF6470011FCDB /* ExyteMediaPicker in Frameworks */, 13738D1C2AE272FA005BBAD5 /* ExyteMediaPicker in Frameworks */, 5BC6705A2D6C8B810023E666 /* ExyteMediaPicker in Frameworks */, 5B4C32F82D6F2080009844B6 /* ExyteMediaPicker in Frameworks */, + 5B32A66C2D79BC6D00DF5348 /* ExyteMediaPicker in Frameworks */, 5BC670572D6C8B250023E666 /* ExyteMediaPicker in Frameworks */, 5B4D33E12D689AF900E6C069 /* ExyteMediaPicker in Frameworks */, ); @@ -131,6 +143,12 @@ 5B2A1A0E2D6DD583000A2E81 /* ExyteMediaPicker */, 5B4C32F72D6F2080009844B6 /* ExyteMediaPicker */, 5B9275C62D6F2DC700833284 /* ExyteMediaPicker */, + 5BFD73D92D70840200BD0F67 /* ExyteMediaPicker */, + 5B32A6682D79BA3100DF5348 /* ExyteMediaPicker */, + 5B32A66B2D79BC6D00DF5348 /* ExyteMediaPicker */, + 5B0BE9362D7AF6030011FCDB /* ExyteMediaPicker */, + 5B0BE9392D7AF6470011FCDB /* ExyteMediaPicker */, + 5B0BE93C2D7AF6940011FCDB /* ExyteMediaPicker */, ); productName = MediaPickerExample; productReference = 5BFF68312AD689C80099D333 /* MediaPickerExample.app */; @@ -161,7 +179,7 @@ ); mainGroup = 5BFF68282AD689C80099D333; packageReferences = ( - 5B9275C52D6F2DC700833284 /* XCLocalSwiftPackageReference "../../MediaPicker" */, + 5B0BE93B2D7AF6940011FCDB /* XCLocalSwiftPackageReference "../../MediaPicker" */, ); productRefGroup = 5BFF68322AD689C80099D333 /* Products */; projectDirPath = ""; @@ -339,7 +357,7 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -347,9 +365,13 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = exyte.MediaPickerExample; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = 1; }; name = Debug; }; @@ -372,7 +394,7 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -380,9 +402,13 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = exyte.MediaPickerExample; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = 1; }; name = Release; }; @@ -410,7 +436,7 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - 5B9275C52D6F2DC700833284 /* XCLocalSwiftPackageReference "../../MediaPicker" */ = { + 5B0BE93B2D7AF6940011FCDB /* XCLocalSwiftPackageReference "../../MediaPicker" */ = { isa = XCLocalSwiftPackageReference; relativePath = ../../MediaPicker; }; @@ -421,10 +447,30 @@ isa = XCSwiftPackageProductDependency; productName = ExyteMediaPicker; }; + 5B0BE9362D7AF6030011FCDB /* ExyteMediaPicker */ = { + isa = XCSwiftPackageProductDependency; + productName = ExyteMediaPicker; + }; + 5B0BE9392D7AF6470011FCDB /* ExyteMediaPicker */ = { + isa = XCSwiftPackageProductDependency; + productName = ExyteMediaPicker; + }; + 5B0BE93C2D7AF6940011FCDB /* ExyteMediaPicker */ = { + isa = XCSwiftPackageProductDependency; + productName = ExyteMediaPicker; + }; 5B2A1A0E2D6DD583000A2E81 /* ExyteMediaPicker */ = { isa = XCSwiftPackageProductDependency; productName = ExyteMediaPicker; }; + 5B32A6682D79BA3100DF5348 /* ExyteMediaPicker */ = { + isa = XCSwiftPackageProductDependency; + productName = ExyteMediaPicker; + }; + 5B32A66B2D79BC6D00DF5348 /* ExyteMediaPicker */ = { + isa = XCSwiftPackageProductDependency; + productName = ExyteMediaPicker; + }; 5B4C32F72D6F2080009844B6 /* ExyteMediaPicker */ = { isa = XCSwiftPackageProductDependency; productName = ExyteMediaPicker; @@ -453,6 +499,10 @@ isa = XCSwiftPackageProductDependency; productName = ExyteMediaPicker; }; + 5BFD73D92D70840200BD0F67 /* ExyteMediaPicker */ = { + isa = XCSwiftPackageProductDependency; + productName = ExyteMediaPicker; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 5BFF68292AD689C80099D333 /* Project object */; diff --git a/MediaPickerExample/MediaPickerExample.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/MediaPickerExample/MediaPickerExample.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..0c67376 --- /dev/null +++ b/MediaPickerExample/MediaPickerExample.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,5 @@ + + + + + diff --git a/MediaPickerExample/MediaPickerExample/ContentView.swift b/MediaPickerExample/MediaPickerExample/ContentView.swift index cbd8d71..1e19a5c 100644 --- a/MediaPickerExample/MediaPickerExample/ContentView.swift +++ b/MediaPickerExample/MediaPickerExample/ContentView.swift @@ -54,7 +54,7 @@ struct ContentView: View { } // MARK: - Default media picker - .sheet(isPresented: $showDefaultMediaPicker) { + .fullScreenCover(isPresented: $showDefaultMediaPicker) { MediaPicker( isPresented: $showDefaultMediaPicker, onChange: { medias = $0 } diff --git a/MediaPickerExample/MediaPickerExample/MediaCell.swift b/MediaPickerExample/MediaPickerExample/MediaCell.swift index 0fb8141..b263ea5 100644 --- a/MediaPickerExample/MediaPickerExample/MediaCell.swift +++ b/MediaPickerExample/MediaPickerExample/MediaCell.swift @@ -45,6 +45,7 @@ struct MediaCell: View { } } +@MainActor final class MediaCellViewModel: ObservableObject { let media: Media @@ -64,12 +65,12 @@ final class MediaCellViewModel: ObservableObject { let url = await media.getURL() guard let url = url else { return } - DispatchQueue.main.async { - switch self.media.type { + DispatchQueue.main.async { [weak self, media] in + switch media.type { case .image: - self.imageUrl = url + self?.imageUrl = url case .video: - self.player = AVPlayer(url: url) + self?.player = AVPlayer(url: url) } } } diff --git a/Package.swift b/Package.swift index 19a103b..0a13f15 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.7 +// swift-tools-version: 6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -6,18 +6,25 @@ import PackageDescription let package = Package( name: "ExyteMediaPicker", platforms: [ - .iOS(.v15) + .iOS(.v17) ], products: [ .library( name: "ExyteMediaPicker", targets: ["ExyteMediaPicker"]), ], - dependencies: [], + dependencies: [ + .package( + url: "https://github.com/exyte/AnchoredPopup.git", + from: "1.0.0" + ) + ], targets: [ .target( name: "ExyteMediaPicker", - dependencies: [] + dependencies: [ + .product(name: "AnchoredPopup", package: "AnchoredPopup") + ] ), .testTarget( name: "MediaPickerTests", diff --git a/README.md b/README.md index eb0e9f3..a4b1a65 100644 --- a/README.md +++ b/README.md @@ -242,8 +242,8 @@ github "Exyte/MediaPicker" ## Requirements -* iOS 16+ -* Xcode 13+ +* iOS 17+ +* Xcode 15+ ## Our other open source SwiftUI libraries [PopupView](https://github.com/exyte/PopupView) - Toasts and popups library diff --git a/Sources/ExyteMediaPicker/Data/Media.swift b/Sources/ExyteMediaPicker/Data/Media.swift index 417ed80..eda5734 100644 --- a/Sources/ExyteMediaPicker/Data/Media.swift +++ b/Sources/ExyteMediaPicker/Data/Media.swift @@ -10,7 +10,7 @@ public enum MediaType { case video } -public struct Media: Identifiable, Equatable { +public struct Media: Identifiable, Equatable, Sendable { public var id = UUID() internal let source: MediaModelProtocol diff --git a/Sources/ExyteMediaPicker/Data/MediaModelProtocol.swift b/Sources/ExyteMediaPicker/Data/MediaModelProtocol.swift index 0e526c9..efeaf12 100644 --- a/Sources/ExyteMediaPicker/Data/MediaModelProtocol.swift +++ b/Sources/ExyteMediaPicker/Data/MediaModelProtocol.swift @@ -7,7 +7,7 @@ import SwiftUI -protocol MediaModelProtocol { +protocol MediaModelProtocol: Sendable { var mediaType: MediaType? { get } var duration: CGFloat? { get } diff --git a/Sources/ExyteMediaPicker/Drivers/FileManager/FileManager.swift b/Sources/ExyteMediaPicker/Drivers/FileManager.swift similarity index 100% rename from Sources/ExyteMediaPicker/Drivers/FileManager/FileManager.swift rename to Sources/ExyteMediaPicker/Drivers/FileManager.swift diff --git a/Sources/ExyteMediaPicker/Drivers/AVFoundation/LiveCameraViewModel.swift b/Sources/ExyteMediaPicker/Drivers/LiveCameraViewModel.swift similarity index 100% rename from Sources/ExyteMediaPicker/Drivers/AVFoundation/LiveCameraViewModel.swift rename to Sources/ExyteMediaPicker/Drivers/LiveCameraViewModel.swift diff --git a/Sources/ExyteMediaPicker/Drivers/PhotoKit/Medias/AlbumMediasProvider.swift b/Sources/ExyteMediaPicker/Drivers/Medias/AlbumMediasProvider.swift similarity index 86% rename from Sources/ExyteMediaPicker/Drivers/PhotoKit/Medias/AlbumMediasProvider.swift rename to Sources/ExyteMediaPicker/Drivers/Medias/AlbumMediasProvider.swift index 9dc970b..6558769 100644 --- a/Sources/ExyteMediaPicker/Drivers/PhotoKit/Medias/AlbumMediasProvider.swift +++ b/Sources/ExyteMediaPicker/Drivers/Medias/AlbumMediasProvider.swift @@ -11,9 +11,9 @@ final class AlbumMediasProvider: BaseMediasProvider { let album: AlbumModel - init(album: AlbumModel, selectionParamsHolder: SelectionParamsHolder, filterClosure: MediaPicker.FilterClosure? = nil, massFilterClosure: MediaPicker.MassFilterClosure? = nil, showingLoadingCell: Binding) { + init(album: AlbumModel, selectionParamsHolder: SelectionParamsHolder, filterClosure: MediaPicker.FilterClosure? = nil, massFilterClosure: MediaPicker.MassFilterClosure? = nil) { self.album = album - super.init(selectionParamsHolder: selectionParamsHolder, filterClosure: filterClosure, massFilterClosure: massFilterClosure, showingLoadingCell: showingLoadingCell) + super.init(selectionParamsHolder: selectionParamsHolder, filterClosure: filterClosure, massFilterClosure: massFilterClosure) } override func reload() { @@ -23,13 +23,17 @@ final class AlbumMediasProvider: BaseMediasProvider { } func reloadInternal() { + isLoading = true + defer { + isLoading = false + } let fetchOptions = PHFetchOptions() fetchOptions.sortDescriptors = [ NSSortDescriptor(key: "creationDate", ascending: false) ] let fetchResult = PHAsset.fetchAssets(in: album.source, options: fetchOptions) if fetchResult.count == 0 { - assetMediaModelsPublisher.send([]) + assetMediaModels = [] } let assets = MediasProvider.map(fetchResult: fetchResult, mediaSelectionType: selectionParamsHolder.mediaType) diff --git a/Sources/ExyteMediaPicker/Drivers/Medias/AllMediasProvider.swift b/Sources/ExyteMediaPicker/Drivers/Medias/AllMediasProvider.swift new file mode 100644 index 0000000..16de0bb --- /dev/null +++ b/Sources/ExyteMediaPicker/Drivers/Medias/AllMediasProvider.swift @@ -0,0 +1,33 @@ +// +// Created by Alex.M on 09.06.2022. +// + +import Foundation + +import Foundation +import Photos +import Combine +import SwiftUI + +final class AllMediasProvider: BaseMediasProvider { + + override func reload() { + PermissionsService.requestPhotoLibraryPermission { [ weak self] in + self?.reloadInternal() + } + } + + func reloadInternal() { + isLoading = true + defer { + isLoading = false + } + let allPhotosOptions = PHFetchOptions() + allPhotosOptions.sortDescriptors = [ + NSSortDescriptor(key: "creationDate", ascending: false) + ] + let allPhotos = PHAsset.fetchAssets(with: allPhotosOptions) + let assets = MediasProvider.map(fetchResult: allPhotos, mediaSelectionType: selectionParamsHolder.mediaType) + filterAndPublish(assets: assets) + } +} diff --git a/Sources/ExyteMediaPicker/Drivers/PhotoKit/Medias/AllPhotosProvider.swift b/Sources/ExyteMediaPicker/Drivers/Medias/AllPhotosProvider.swift similarity index 100% rename from Sources/ExyteMediaPicker/Drivers/PhotoKit/Medias/AllPhotosProvider.swift rename to Sources/ExyteMediaPicker/Drivers/Medias/AllPhotosProvider.swift diff --git a/Sources/ExyteMediaPicker/Drivers/Medias/BaseMediasProvider.swift b/Sources/ExyteMediaPicker/Drivers/Medias/BaseMediasProvider.swift new file mode 100644 index 0000000..653e13a --- /dev/null +++ b/Sources/ExyteMediaPicker/Drivers/Medias/BaseMediasProvider.swift @@ -0,0 +1,135 @@ +// +// Created by Alex.M on 09.06.2022. +// + +import Foundation +import Combine +import Photos +import SwiftUI + +@MainActor +class BaseMediasProvider: ObservableObject { + var selectionParamsHolder: SelectionParamsHolder + var filterClosure: MediaPicker.FilterClosure? + var massFilterClosure: MediaPicker.MassFilterClosure? + + @Published var assetMediaModels = [AssetMediaModel]() + private var privateAssetMediaModels: [AssetMediaModel] = [] + + @Published var isLoading: Bool = false + + private var timerTask: Task? + private var cancellableTask: Task? + private var cancellable: AnyCancellable? + + init(selectionParamsHolder: SelectionParamsHolder, filterClosure: MediaPicker.FilterClosure?, massFilterClosure: MediaPicker.MassFilterClosure?) { + self.selectionParamsHolder = selectionParamsHolder + self.filterClosure = filterClosure + self.massFilterClosure = massFilterClosure + + cancellable = NotificationCenter.default + .publisher(for: photoLibraryChangeLimitedPhotosNotification) + .map { _ in } + .share() + .receive(on: RunLoop.main) + .sink { [weak self] in + self?.reload() + } + } + + func filterAndPublish(assets: [AssetMediaModel]) { + isLoading = true + defer { + isLoading = false + } + + if let filterClosure = filterClosure { + startPublishing() + + cancellableTask = Task { [weak self] in + let serialQueue = DispatchQueue(label: "filterSerialQueue") + self?.privateAssetMediaModels = [AssetMediaModel]() + + await withTaskGroup(of: AssetMediaModel?.self) { group in + for asset in assets { + group.addTask { + if Task.isCancelled { return nil } + + let media = await Task.detached(priority: .userInitiated) { + return await filterClosure(Media(source: asset)) + }.value + + return media?.source as? AssetMediaModel + } + } + + for await filteredMedia in group { + if let model = filteredMedia { + serialQueue.sync { + self?.privateAssetMediaModels.append(model) + } + } + } + } + + self?.stopPublishing() + DispatchQueue.main.async { + self?.assetMediaModels = self?.privateAssetMediaModels ?? [] + } + } + } else if let massFilterClosure = massFilterClosure { + cancellableTask = Task { [weak self] in + let result = await massFilterClosure(assets.map { Media(source: $0) }) + self?.assetMediaModels = result.compactMap { $0.source as? AssetMediaModel } + } + } + else { + DispatchQueue.main.async { [weak self] in + self?.assetMediaModels = assets + } + } + } + + func startPublishing() { + // Start a task that runs every second + timerTask = Task { + while !Task.isCancelled { + try? await Task.sleep(nanoseconds: 1_000_000_000) // 1 second + + await MainActor.run { + self.assetMediaModels = self.privateAssetMediaModels + } + } + } + } + + func stopPublishing() { + timerTask?.cancel() + } + + func reload() { } + + func cancel() { + cancellableTask?.cancel() + stopPublishing() + } +} + +class MediasProvider { + + static func map(fetchResult: PHFetchResult, mediaSelectionType: MediaSelectionType) -> [AssetMediaModel] { + var assetMediaModels: [AssetMediaModel] = [] + + if fetchResult.count == 0 { + return assetMediaModels + } + + for index in 0...(fetchResult.count - 1) { + let asset = fetchResult[index] + if (asset.mediaType == .image && mediaSelectionType.allowsPhoto) || (asset.mediaType == .video && mediaSelectionType.allowsVideo) { + assetMediaModels.append(AssetMediaModel(asset: asset)) + } + } + return assetMediaModels + } +} diff --git a/Sources/ExyteMediaPicker/Drivers/Medias/DefaultAlbumsProvider.swift b/Sources/ExyteMediaPicker/Drivers/Medias/DefaultAlbumsProvider.swift new file mode 100644 index 0000000..ded447d --- /dev/null +++ b/Sources/ExyteMediaPicker/Drivers/Medias/DefaultAlbumsProvider.swift @@ -0,0 +1,86 @@ +// +// Created by Alex.M on 10.06.2022. +// + +import Foundation +import Photos + +import Photos +import SwiftUI + +@MainActor +final class DefaultAlbumsProvider: ObservableObject { + + @Published private(set) var albums: [AlbumModel] = [] + @Published private(set) var isLoading: Bool = false + + var mediaSelectionType: MediaSelectionType = .photoAndVideo + private var reloadTask: Task? + + func reload() { + cancelReload() + + PermissionsService.requestPhotoLibraryPermission { [weak self] in + guard let self = self else { return } + self.reloadTask = Task { + self.isLoading = true + await self.reloadInternal() + self.isLoading = false + } + } + } + + func cancelReload() { + reloadTask?.cancel() + reloadTask = nil + } + + private func reloadInternal() async { + let albumTypes: [PHAssetCollectionType] = [.album, .smartAlbum] + var allAlbums: [AlbumModel] = [] + + for type in albumTypes { + if Task.isCancelled { return } + let albums = fetchAlbums(type: type) + allAlbums.append(contentsOf: albums) + } + + if Task.isCancelled { return } + self.albums = allAlbums + } + + private func fetchAlbums(type: PHAssetCollectionType) -> [AlbumModel] { + let options = PHFetchOptions() + options.includeAssetSourceTypes = [.typeUserLibrary, .typeiTunesSynced, .typeCloudShared] + options.sortDescriptors = [NSSortDescriptor(key: "localizedTitle", ascending: true)] + + let collections = PHAssetCollection.fetchAssetCollections( + with: type, + subtype: .any, + options: options + ) + + guard collections.count > 0 else { return [] } + + var albums: [AlbumModel] = [] + + for index in 0.. 0 else { continue } + + let preview = MediasProvider.map(fetchResult: fetchResult, mediaSelectionType: mediaSelectionType).first + let album = AlbumModel(preview: preview, source: collection) + albums.append(album) + } + return albums + } +} diff --git a/Sources/ExyteMediaPicker/Drivers/MotionManager/MotionManager.swift b/Sources/ExyteMediaPicker/Drivers/MotionManager.swift similarity index 100% rename from Sources/ExyteMediaPicker/Drivers/MotionManager/MotionManager.swift rename to Sources/ExyteMediaPicker/Drivers/MotionManager.swift diff --git a/Sources/ExyteMediaPicker/Drivers/Permissions/PermissionsService.swift b/Sources/ExyteMediaPicker/Drivers/PermissionsService.swift similarity index 80% rename from Sources/ExyteMediaPicker/Drivers/Permissions/PermissionsService.swift rename to Sources/ExyteMediaPicker/Drivers/PermissionsService.swift index ef0e830..177d792 100644 --- a/Sources/ExyteMediaPicker/Drivers/Permissions/PermissionsService.swift +++ b/Sources/ExyteMediaPicker/Drivers/PermissionsService.swift @@ -7,6 +7,7 @@ import Combine import AVFoundation import Photos +@MainActor final class PermissionsService: ObservableObject { @Published var cameraAction: CameraAction? = .authorize @Published var photoLibraryAction: PhotoLibraryAction? = .authorize @@ -14,13 +15,19 @@ final class PermissionsService: ObservableObject { private var subscriptions = Set() init() { - photoLibraryChangePermissionPublisher + NotificationCenter.default + .publisher(for: photoLibraryChangePermissionNotification) + .map { _ in } + .share() .sink { [weak self] in self?.checkPhotoLibraryAuthorizationStatus() } .store(in: &subscriptions) - cameraChangePermissionPublisher + NotificationCenter.default + .publisher(for: cameraChangePermissionNotification) + .map { _ in } + .share() .sink { [weak self] in self?.checkCameraAuthorizationStatus() } @@ -29,16 +36,20 @@ final class PermissionsService: ObservableObject { /// photoLibraryChangePermissionPublisher gets called multiple times even when nothing changed in photo library, so just use this one to make sure the closure runs exactly once static func requestPhotoLibraryPermission(_ permissionGrantedClosure: @escaping ()->()) { - PHPhotoLibrary.requestAuthorization { status in + Task { + let status = await PHPhotoLibrary.requestAuthorization(for: .addOnly) if status == .authorized || status == .limited { - permissionGrantedClosure() + DispatchQueue.main.async { + permissionGrantedClosure() + } } } } func requestCameraPermission() { - AVCaptureDevice.requestAccess(for: .video) { [weak self] _ in - self?.checkCameraAuthorizationStatus() + Task { + await AVCaptureDevice.requestAccess(for: .video) + checkCameraAuthorizationStatus() } } diff --git a/Sources/ExyteMediaPicker/Drivers/PhotoKit/Albums/AlbumsProviderProtocol.swift b/Sources/ExyteMediaPicker/Drivers/PhotoKit/Albums/AlbumsProviderProtocol.swift deleted file mode 100644 index 7f47f20..0000000 --- a/Sources/ExyteMediaPicker/Drivers/PhotoKit/Albums/AlbumsProviderProtocol.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// Created by Alex.M on 09.06.2022. -// - -import Foundation -import Combine - -protocol AlbumsProviderProtocol { - var albums: AnyPublisher<[AlbumModel], Never> { get } - - func reload() -} diff --git a/Sources/ExyteMediaPicker/Drivers/PhotoKit/Albums/DefaultAlbumsProvider.swift b/Sources/ExyteMediaPicker/Drivers/PhotoKit/Albums/DefaultAlbumsProvider.swift deleted file mode 100644 index d4e6aee..0000000 --- a/Sources/ExyteMediaPicker/Drivers/PhotoKit/Albums/DefaultAlbumsProvider.swift +++ /dev/null @@ -1,74 +0,0 @@ -// -// Created by Alex.M on 10.06.2022. -// - -import Foundation -import Combine -import Photos - -final class DefaultAlbumsProvider: AlbumsProviderProtocol { - - private var subject = CurrentValueSubject<[AlbumModel], Never>([]) - private var albumsCancellable: AnyCancellable? - private var permissionCancellable: AnyCancellable? - - var albums: AnyPublisher<[AlbumModel], Never> { - subject.eraseToAnyPublisher() - } - - var mediaSelectionType: MediaSelectionType = .photoAndVideo - - func reload() { - PermissionsService.requestPhotoLibraryPermission { [ weak self] in - self?.reloadInternal() - } - } - - func reloadInternal() { - albumsCancellable = [PHAssetCollectionType.album, .smartAlbum] - .publisher - .map { fetchAlbums(type: $0) } - .scan([], +) - .receive(on: DispatchQueue.main) - .sink { [weak self] in - self?.subject.send($0) - } - } -} - -private extension DefaultAlbumsProvider { - - func fetchAlbums(type: PHAssetCollectionType) -> [AlbumModel] { - let options = PHFetchOptions() - options.includeAssetSourceTypes = [.typeUserLibrary, .typeiTunesSynced, .typeCloudShared] - options.sortDescriptors = [NSSortDescriptor(key: "localizedTitle", ascending: true)] - - let collections = PHAssetCollection.fetchAssetCollections( - with: type, - subtype: .any, - options: options - ) - - if collections.count == 0 { - return [] - } - var albums: [AlbumModel] = [] - - for index in 0...(collections.count - 1) { - let collection = collections[index] - let options = PHFetchOptions() - options.sortDescriptors = [ - NSSortDescriptor(key: "creationDate", ascending: false) - ] - options.fetchLimit = 1 - let fetchResult = PHAsset.fetchAssets(in: collection, options: options) - if fetchResult.count == 0 { - continue - } - let preview = MediasProvider.map(fetchResult: fetchResult, mediaSelectionType: mediaSelectionType).first - let album = AlbumModel(preview: preview, source: collection) - albums.append(album) - } - return albums - } -} diff --git a/Sources/ExyteMediaPicker/Drivers/PhotoKit/Medias/MediasProviderProtocol.swift b/Sources/ExyteMediaPicker/Drivers/PhotoKit/Medias/MediasProviderProtocol.swift deleted file mode 100644 index d06ff31..0000000 --- a/Sources/ExyteMediaPicker/Drivers/PhotoKit/Medias/MediasProviderProtocol.swift +++ /dev/null @@ -1,109 +0,0 @@ -// -// Created by Alex.M on 09.06.2022. -// - -import Foundation -import Combine -import Photos -import SwiftUI - -protocol MediasProviderProtocol { - func reload() - func cancel() - - var assetMediaModelsPublisher: PassthroughSubject<[AssetMediaModel], Never> { get } -} - -class BaseMediasProvider: MediasProviderProtocol { - var selectionParamsHolder: SelectionParamsHolder - var filterClosure: MediaPicker.FilterClosure? - var massFilterClosure: MediaPicker.MassFilterClosure? - - @Binding var showingLoadingCell: Bool - - var assetMediaModelsPublisher = PassthroughSubject<[AssetMediaModel], Never>() - - @Published var cancellableTask: Task? - - private var cancellable: AnyCancellable? - - init(selectionParamsHolder: SelectionParamsHolder, filterClosure: MediaPicker.FilterClosure?, massFilterClosure: MediaPicker.MassFilterClosure?, showingLoadingCell: Binding) { - self.selectionParamsHolder = selectionParamsHolder - self.filterClosure = filterClosure - self.massFilterClosure = massFilterClosure - self._showingLoadingCell = showingLoadingCell - - cancellable = photoLibraryChangeLimitedPhotosPublisher - .receive(on: RunLoop.main) - .sink { [weak self] in - self?.reload() - } - } - - func filterAndPublish(assets: [AssetMediaModel]) { - if let filterClosure = filterClosure { - showLoading(true) - cancellableTask = Task { - let serialQueue = DispatchQueue(label: "filterSerialQueue") - var result = [AssetMediaModel]() - await assets.asyncForEach { - if cancellableTask?.isCancelled ?? false { - showLoading(false) - return - } - if let media = await filterClosure(Media(source: $0)), let model = media.source as? AssetMediaModel { - serialQueue.sync { - result.append(model) - assetMediaModelsPublisher.send(result) - } - } - } - assetMediaModelsPublisher.send(result) - showLoading(false) - } - } else if let massFilterClosure = massFilterClosure { - showLoading(true) - cancellableTask = Task { - let result = await massFilterClosure(assets.map { Media(source: $0) }) - assetMediaModelsPublisher.send(result.compactMap { $0.source as? AssetMediaModel }) - showLoading(false) - } - } - else { - DispatchQueue.main.async { [weak self] in - self?.assetMediaModelsPublisher.send(assets) - } - } - } - - func showLoading(_ show: Bool) { - DispatchQueue.main.async { [weak self] in - self?.$showingLoadingCell.wrappedValue = show - } - } - - func reload() { } - - func cancel() { - cancellableTask?.cancel() - } -} - -class MediasProvider { - - static func map(fetchResult: PHFetchResult, mediaSelectionType: MediaSelectionType) -> [AssetMediaModel] { - var assetMediaModels: [AssetMediaModel] = [] - - if fetchResult.count == 0 { - return assetMediaModels - } - - for index in 0...(fetchResult.count - 1) { - let asset = fetchResult[index] - if (asset.mediaType == .image && mediaSelectionType.allowsPhoto) || (asset.mediaType == .video && mediaSelectionType.allowsVideo) { - assetMediaModels.append(AssetMediaModel(asset: asset)) - } - } - return assetMediaModels - } -} diff --git a/Sources/ExyteMediaPicker/Drivers/PhotoKit/PHAsset+Utils.swift b/Sources/ExyteMediaPicker/Drivers/PhotoKit/PHAsset+Utils.swift index 4301aa7..5acd04f 100644 --- a/Sources/ExyteMediaPicker/Drivers/PhotoKit/PHAsset+Utils.swift +++ b/Sources/ExyteMediaPicker/Drivers/PhotoKit/PHAsset+Utils.swift @@ -140,6 +140,7 @@ extension CGImage { #if os(iOS) extension PHAsset { + @MainActor func image(size: CGSize, resultClosure: @escaping (UIImage?)->()) -> PHImageRequestID { let requestSize = CGSize(width: size.width * UIScreen.main.scale, height: size.height * UIScreen.main.scale) diff --git a/Sources/ExyteMediaPicker/Drivers/PhotoKit/PhotoLibraryChangeNotifier.swift b/Sources/ExyteMediaPicker/Drivers/PhotoKit/PhotoLibraryChangeNotifier.swift index 4e662c7..d7bda83 100644 --- a/Sources/ExyteMediaPicker/Drivers/PhotoKit/PhotoLibraryChangeNotifier.swift +++ b/Sources/ExyteMediaPicker/Drivers/PhotoKit/PhotoLibraryChangeNotifier.swift @@ -12,21 +12,6 @@ let photoLibraryChangeLimitedPhotosNotification = Notification.Name(rawValue: "P let cameraChangePermissionNotification = Notification.Name(rawValue: "cameraChangePermissionNotification") -let photoLibraryChangePermissionPublisher = NotificationCenter.default - .publisher(for: photoLibraryChangePermissionNotification) - .map { _ in } - .share() - -let photoLibraryChangeLimitedPhotosPublisher = NotificationCenter.default - .publisher(for: photoLibraryChangeLimitedPhotosNotification) - .map { _ in } - .share() - -let cameraChangePermissionPublisher = NotificationCenter.default - .publisher(for: cameraChangePermissionNotification) - .map { _ in } - .share() - final class PhotoLibraryChangePermissionWatcher: NSObject, PHPhotoLibraryChangeObserver { override init() { super.init() diff --git a/Sources/ExyteMediaPicker/Drivers/Theme/MediaPickerTheme.swift b/Sources/ExyteMediaPicker/Drivers/Theme/MediaPickerTheme.swift index e517154..4f6e720 100644 --- a/Sources/ExyteMediaPicker/Drivers/Theme/MediaPickerTheme.swift +++ b/Sources/ExyteMediaPicker/Drivers/Theme/MediaPickerTheme.swift @@ -145,10 +145,12 @@ extension MediaPickerTheme { unselectedText: Color = Color("pickerText", bundle: .current)) { self.background = background - UISegmentedControl.appearance().backgroundColor = UIColor(segmentTintColor) - UISegmentedControl.appearance().selectedSegmentTintColor = UIColor(selectedSegmentTintColor) - UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor(selectedText)], for: .selected) - UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor(unselectedText)], for: .normal) + DispatchQueue.main.async { + UISegmentedControl.appearance().backgroundColor = UIColor(segmentTintColor) + UISegmentedControl.appearance().selectedSegmentTintColor = UIColor(selectedSegmentTintColor) + UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor(selectedText)], for: .selected) + UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor(unselectedText)], for: .normal) + } } } } diff --git a/Sources/ExyteMediaPicker/Modifiers/KeyboardHeightHelper.swift b/Sources/ExyteMediaPicker/Modifiers/KeyboardHeightHelper.swift index 08bcf38..936f816 100644 --- a/Sources/ExyteMediaPicker/Modifiers/KeyboardHeightHelper.swift +++ b/Sources/ExyteMediaPicker/Modifiers/KeyboardHeightHelper.swift @@ -9,7 +9,7 @@ import SwiftUI class KeyboardHeightHelper: ObservableObject { - static var shared = KeyboardHeightHelper() + @MainActor static let shared = KeyboardHeightHelper() @Published var keyboardHeight: CGFloat = 0 @Published var keyboardDisplayed: Bool = false diff --git a/Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift b/Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift index 3b2aecd..f28c8ec 100644 --- a/Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift +++ b/Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift @@ -5,15 +5,8 @@ import Foundation import SwiftUI -struct MediaPickerThemeKey: EnvironmentKey { - static var defaultValue: MediaPickerTheme = MediaPickerTheme() -} - extension EnvironmentValues { - public var mediaPickerTheme: MediaPickerTheme { - get { self[MediaPickerThemeKey.self] } - set { self[MediaPickerThemeKey.self] = newValue } - } + @Entry var mediaPickerTheme = MediaPickerTheme() } public extension View { diff --git a/Sources/ExyteMediaPicker/Modifiers/SafeAreaEnvironmentValues.swift b/Sources/ExyteMediaPicker/Modifiers/SafeAreaEnvironmentValues.swift index 087ad6e..f1745c0 100644 --- a/Sources/ExyteMediaPicker/Modifiers/SafeAreaEnvironmentValues.swift +++ b/Sources/ExyteMediaPicker/Modifiers/SafeAreaEnvironmentValues.swift @@ -23,7 +23,8 @@ extension UIApplication { } } -private struct SafeAreaInsetsKey: EnvironmentKey { +@MainActor +private struct SafeAreaInsetsKey: @preconcurrency EnvironmentKey { static var defaultValue: EdgeInsets { UIApplication.shared.keyWindow?.safeAreaInsets.swiftUiInsets ?? EdgeInsets() } diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift index 61ff2c4..8c90891 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift @@ -3,6 +3,7 @@ // import SwiftUI +import AnchoredPopup struct AlbumView: View { @@ -12,27 +13,35 @@ struct AlbumView: View { @ObservedObject var keyboardHeightHelper = KeyboardHeightHelper.shared - @StateObject var viewModel: AlbumViewModel + @StateObject var viewModel: BaseMediasProvider @Binding var showingCamera: Bool @Binding var currentFullscreenMedia: Media? + var title: String? var shouldShowCamera: Bool - var shouldShowLoadingCell: Bool var selectionParamsHolder: SelectionParamsHolder var dismiss: ()->() - @State private var fullscreenItem: AssetMediaModel? + @State private var fullscreenItem: AssetMediaModel.ID? + + private var shouldShowLoadingCell: Bool { + viewModel.isLoading && viewModel.assetMediaModels.count > 0 + } var body: some View { - if let title = viewModel.title { - content.navigationTitle(title) - } else { + VStack { + if let title = title { + Text(title) + } content } + .onAppear { + viewModel.reload() + } + .onDisappear { + viewModel.cancel() + } } -} - -private extension AlbumView { @ViewBuilder var content: some View { @@ -41,39 +50,20 @@ private extension AlbumView { if let action = permissionsService.photoLibraryAction { PermissionsActionView(action: .library(action)) } + if shouldShowCamera, let action = permissionsService.cameraAction { PermissionsActionView(action: .camera(action)) } - if viewModel.isLoading { + + if viewModel.isLoading, viewModel.assetMediaModels.isEmpty { ProgressView() .padding() - } else if viewModel.assetMediaModels.isEmpty, !shouldShowLoadingCell { + } else if !viewModel.isLoading, viewModel.assetMediaModels.isEmpty { Text("Empty data") .font(.title3) .foregroundColor(theme.main.pickerText) } else { - MediasGrid(viewModel.assetMediaModels) { -#if !targetEnvironment(simulator) - if shouldShowCamera && permissionsService.cameraAction == nil { - LiveCameraCell { - showingCamera = true - } - } -#endif - } content: { assetMediaModel, cellSize in - cellView(assetMediaModel, size: cellSize) - } loadingCell: { - if shouldShowLoadingCell { - ZStack { - Color.white.opacity(0.5) - ProgressView() - } - .aspectRatio(1, contentMode: .fit) - } - } - .onChange(of: viewModel.assetMediaModels) { newValue in - selectionService.updateSelection(with: newValue) - } + mediasGrid } Spacer() @@ -86,33 +76,35 @@ private extension AlbumView { dismissKeyboard() } } - .overlay { - if let item = fullscreenItem { - FullscreenContainer( - isPresented: fullscreenPresentedBinding(), - currentFullscreenMedia: $currentFullscreenMedia, - assetMediaModels: viewModel.assetMediaModels, - selection: item.id, - selectionParamsHolder: selectionParamsHolder, - dismiss: dismiss - ) - } - } } - func fullscreenPresentedBinding() -> Binding { - Binding( - get: { fullscreenItem != nil }, - set: { value in - if value == false { - fullscreenItem = nil + var mediasGrid: some View { + MediasGrid(viewModel.assetMediaModels) { +#if !targetEnvironment(simulator) + if shouldShowCamera && permissionsService.cameraAction == nil { + LiveCameraCell { + showingCamera = true } } - ) +#endif + } content: { assetMediaModel, index, cellSize in + cellView(assetMediaModel, index, cellSize) + } loadingCell: { + if shouldShowLoadingCell { + ZStack { + Color.white.opacity(0.5) + ProgressView() + } + .aspectRatio(1, contentMode: .fit) + } + } + .onChange(of: viewModel.assetMediaModels) { newValue in + selectionService.updateSelection(with: newValue) + } } @ViewBuilder - func cellView(_ assetMediaModel: AssetMediaModel, size: CGFloat) -> some View { + func cellView(_ assetMediaModel: AssetMediaModel, _ index: Int, _ size: CGFloat) -> some View { let imageButton = Button { if keyboardHeightHelper.keyboardDisplayed { dismissKeyboard() @@ -124,10 +116,25 @@ private extension AlbumView { } } else if fullscreenItem == nil { - fullscreenItem = assetMediaModel + fullscreenItem = assetMediaModel.id } } label: { + let id = "fullscreen_photo_\(index)" MediaCell(viewModel: MediaViewModel(assetMediaModel: assetMediaModel), size: size) + .useAsPopupAnchor(id: id) { + FullscreenContainer( + currentFullscreenMedia: $currentFullscreenMedia, + selection: $fullscreenItem, + animationID: id, + assetMediaModels: viewModel.assetMediaModels, + selectionParamsHolder: selectionParamsHolder, + dismiss: dismiss + ) + .environmentObject(selectionService) + } customize: { + $0.closeOnTap(false) + .animation(.easeIn(duration: 0.2)) + } } .buttonStyle(MediaButtonStyle()) .contentShape(Rectangle()) @@ -142,5 +149,4 @@ private extension AlbumView { } } } - } diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumViewModel.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumViewModel.swift deleted file mode 100644 index 01b1c9b..0000000 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumViewModel.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// Created by Alex.M on 09.06.2022. -// - -import Foundation -import Combine - -final class AlbumViewModel: ObservableObject { - - @Published var title: String? = nil - @Published var assetMediaModels: [AssetMediaModel] = [] - @Published var isLoading: Bool = false - - let mediasProvider: MediasProviderProtocol - - private var mediaCancellable: AnyCancellable? - - init(mediasProvider: MediasProviderProtocol) { - self.mediasProvider = mediasProvider - onStart() - } - - func onStart() { - isLoading = true - mediaCancellable = mediasProvider.assetMediaModelsPublisher - .receive(on: RunLoop.main) - .sink { [weak self] in - self?.assetMediaModels = $0 - self?.isLoading = false - } - - mediasProvider.reload() - } - - deinit { - mediasProvider.cancel() - mediaCancellable = nil - } -} diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/MediaViewModel.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/MediaViewModel.swift index ed2af45..3f8c3e4 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/MediaViewModel.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/MediaViewModel.swift @@ -22,7 +22,7 @@ class MediaViewModel: ObservableObject { // FIXME: Create preview for image/video for other platforms #endif - func onStart(size: CGFloat) { + @MainActor func onStart(size: CGFloat) { requestID = assetMediaModel.asset .image(size: CGSize(width: size, height: size)) { image in DispatchQueue.main.async { diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumCellViewModel.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumCellViewModel.swift index cb78a0f..c42102f 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumCellViewModel.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumCellViewModel.swift @@ -22,7 +22,7 @@ class AlbumCellViewModel: ObservableObject { // FIXME: Create preview for image/video for other platforms #endif - func fetchPreview(size: CGSize) { + @MainActor func fetchPreview(size: CGSize) { guard preview == nil else { return } requestID = album.preview?.asset diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsViewModel.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsViewModel.swift index 3ac2be8..415973d 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsViewModel.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsViewModel.swift @@ -3,38 +3,29 @@ // import Foundation -import Combine +@MainActor final class AlbumsViewModel: ObservableObject { - // MARK: - Values - // MARK: Public - @Published var albums: [AlbumModel] = [] - @Published var isLoading: Bool = false - - let albumsProvider: AlbumsProviderProtocol - // MARK: Private - private var albumsCancellable: AnyCancellable? - - // MARK: - Object life cycle - init(albumsProvider: AlbumsProviderProtocol) { + var albums: [AlbumModel] { + albumsProvider.albums + } + + var isLoading: Bool { + albumsProvider.isLoading + } + + private let albumsProvider: DefaultAlbumsProvider + + init(albumsProvider: DefaultAlbumsProvider) { self.albumsProvider = albumsProvider } - - // MARK: - Public methods + func onStart() { - isLoading = true - albumsCancellable = albumsProvider.albums - .receive(on: DispatchQueue.main) - .sink { [weak self] in - self?.albums = $0 - self?.isLoading = false - } - albumsProvider.reload() } func onStop() { - albumsCancellable = nil + albumsProvider.cancelReload() } } diff --git a/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraSelectionContainer.swift b/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraSelectionContainer.swift index 6023e9c..d924368 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraSelectionContainer.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraSelectionContainer.swift @@ -80,7 +80,7 @@ struct DefaultCameraSelectionContainer: View { viewModel.onCancelCameraSelection(cameraSelectionService.hasSelected) } .foregroundColor(theme.main.cameraText) - .padding(12, 16) + .padding(12, 18) } .overlay(alignment: .bottom) { HStack { diff --git a/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraView.swift b/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraView.swift index d95dd21..b44e696 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraView.swift @@ -51,6 +51,7 @@ struct StandardConrolsCameraView: View { @EnvironmentObject private var cameraSelectionService: CameraSelectionService @Environment(\.mediaPickerTheme) private var theme + @Environment(\.safeAreaInsets) private var safeArea @ObservedObject var viewModel: MediaPickerViewModel let didTakePicture: () -> Void @@ -73,10 +74,11 @@ struct StandardConrolsCameraView: View { } } .foregroundColor(theme.main.cameraText) - .padding(12, 16) + .padding(12, 18) Spacer() } + .safeAreaPadding(.top, safeArea.top) LiveCameraView( session: cameraViewModel.captureSession, diff --git a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCellViewModel.swift b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCellViewModel.swift index 65100b1..7775f8f 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCellViewModel.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCellViewModel.swift @@ -88,3 +88,7 @@ final class FullscreenCellViewModel: ObservableObject { } } } + +extension AVAssetTrack: @unchecked Sendable { + +} diff --git a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift index fe4c68c..a67cce1 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift @@ -4,18 +4,20 @@ import Foundation import SwiftUI +import AnchoredPopup struct FullscreenContainer: View { @EnvironmentObject private var selectionService: SelectionService @Environment(\.mediaPickerTheme) private var theme + @Environment(\.safeAreaInsets) private var safeArea @ObservedObject var keyboardHeightHelper = KeyboardHeightHelper.shared - @Binding var isPresented: Bool @Binding var currentFullscreenMedia: Media? + @Binding var selection: AssetMediaModel.ID? + let animationID: String let assetMediaModels: [AssetMediaModel] - @State var selection: AssetMediaModel.ID? var selectionParamsHolder: SelectionParamsHolder var dismiss: ()->() @@ -34,11 +36,10 @@ struct FullscreenContainer: View { VStack { controlsOverlay GeometryReader { g in - let size = g.size - contentView(size) + contentView(g.size) } } - .ignoresSafeArea() + .safeAreaPadding(.top, safeArea.top) .background { theme.main.fullscreenPhotoBackground .ignoresSafeArea() @@ -106,7 +107,8 @@ struct FullscreenContainer: View { .padding(20, 16) .contentShape(Rectangle()) .onTapGesture { - isPresented = false + selection = nil + AnchoredPopup.launchShrinkingAnimation(id: animationID) } Spacer() diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/AlbumSelectionView.swift b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/AlbumSelectionView.swift index 71b54e9..cd2f17b 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/AlbumSelectionView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/AlbumSelectionView.swift @@ -20,19 +20,14 @@ public struct AlbumSelectionView: View { let massFilterClosure: MediaPicker.MassFilterClosure? var dismiss: ()->() - @State private var showingLoadingCell = false - public var body: some View { switch viewModel.internalPickerMode { case .photos: AlbumView( - viewModel: AlbumViewModel( - mediasProvider: AllPhotosProvider(selectionParamsHolder: selectionParamsHolder, filterClosure: filterClosure, massFilterClosure: massFilterClosure, showingLoadingCell: $showingLoadingCell) - ), + viewModel: AllMediasProvider(selectionParamsHolder: selectionParamsHolder, filterClosure: filterClosure, massFilterClosure: massFilterClosure), showingCamera: $showingCamera, currentFullscreenMedia: $currentFullscreenMedia, shouldShowCamera: showingLiveCameraCell, - shouldShowLoadingCell: showingLoadingCell, selectionParamsHolder: selectionParamsHolder, dismiss: dismiss ) @@ -54,13 +49,11 @@ public struct AlbumSelectionView: View { case .album(let album): if let albumModel = viewModel.getAlbumModel(album) { AlbumView( - viewModel: AlbumViewModel( - mediasProvider: AlbumMediasProvider(album: albumModel, selectionParamsHolder: selectionParamsHolder, filterClosure: filterClosure, massFilterClosure: massFilterClosure, showingLoadingCell: $showingLoadingCell) - ), + viewModel: AlbumMediasProvider(album: albumModel, selectionParamsHolder: selectionParamsHolder, filterClosure: filterClosure, massFilterClosure: massFilterClosure), showingCamera: $showingCamera, currentFullscreenMedia: $currentFullscreenMedia, + title: album.title, shouldShowCamera: false, - shouldShowLoadingCell: showingLoadingCell, selectionParamsHolder: selectionParamsHolder, dismiss: dismiss ) diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift index c30266a..4bd4db9 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift @@ -30,8 +30,8 @@ public struct MediaPicker CameraViewContent) - public typealias FilterClosure = (Media) async -> Media? - public typealias MassFilterClosure = ([Media]) async -> [Media] + public typealias FilterClosure = @Sendable (Media) async -> Media? + public typealias MassFilterClosure = @Sendable ([Media]) async -> [Media] // MARK: - Parameters @@ -157,11 +157,7 @@ public struct MediaPicker() + var didDismiss: @Sendable ()->() func makeUIViewController(context: Context) -> UIViewController { let controller = UIViewController() @@ -27,11 +27,11 @@ struct LimitedLibraryPickerProxyView: UIViewControllerRepresentable { Coordinator(isPresented: $isPresented, didDismiss: didDismiss) } - class Coordinator: NSObject { - private var isPresented: Binding - var didDismiss: ()->() - - init(isPresented: Binding, didDismiss: @escaping ()->()) { + final class Coordinator: NSObject, Sendable { + private let isPresented: Binding + let didDismiss: @Sendable ()->() + + init(isPresented: Binding, didDismiss: @escaping @Sendable ()->()) { self.isPresented = isPresented self.didDismiss = didDismiss } diff --git a/Sources/ExyteMediaPicker/Views/Widgets/MediasGrid.swift b/Sources/ExyteMediaPicker/Views/Widgets/MediasGrid.swift index ea34e3e..b82878b 100644 --- a/Sources/ExyteMediaPicker/Views/Widgets/MediasGrid.swift +++ b/Sources/ExyteMediaPicker/Views/Widgets/MediasGrid.swift @@ -5,13 +5,14 @@ import Foundation import SwiftUI -public struct MediasGrid: View where Data: RandomAccessCollection, Data.Element: Identifiable, Camera: View, Content: View, LoadingCell: View { +public struct MediasGrid: View +where Element: Identifiable, Camera: View, Content: View, LoadingCell: View { - public let data: Data + public let data: [Element] public let camera: () -> Camera - public let content: (Data.Element, _ size: CGFloat) -> Content + public let content: (Element, _ index: Int, _ size: CGFloat) -> Content public let loadingCell: () -> LoadingCell - + @Environment(\.mediaPickerTheme) private var theme let minColumnWidth: CGFloat = 100 @@ -19,7 +20,10 @@ public struct MediasGrid: View where Data: R [GridItem(.adaptive(minimum: minColumnWidth), spacing: theme.cellStyle.columnsSpacing, alignment: .top)] } - public init(_ data: Data, @ViewBuilder camera: @escaping () -> Camera, @ViewBuilder content: @escaping (Data.Element, CGFloat) -> Content, @ViewBuilder loadingCell: @escaping () -> LoadingCell) { + public init(_ data: [Element], + @ViewBuilder camera: @escaping () -> Camera, + @ViewBuilder content: @escaping (Element, Int, CGFloat) -> Content, + @ViewBuilder loadingCell: @escaping () -> LoadingCell) { self.data = data self.camera = camera self.content = content @@ -30,8 +34,8 @@ public struct MediasGrid: View where Data: R let columnWidth = calculateColumnWidth(UIScreen.main.bounds.width) LazyVGrid(columns: columns, spacing: theme.cellStyle.rowSpacing) { camera() - ForEach(data) { item in - content(item, columnWidth) + ForEach(data.indices, id: \.self) { index in + content(data[index], index, columnWidth) } loadingCell() } From bb69eabbd24612fe41f1be9e7e1f81d6ce1efb8e Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Fri, 7 Mar 2025 17:41:00 +0700 Subject: [PATCH 48/70] Replace combine with swiftUI where possible --- .../project.pbxproj | 11 ++++++++-- .../MediaPickerExample/ContentView.swift | 1 - Sources/ExyteMediaPicker/Data/Media.swift | 1 - .../Drivers/Medias/AlbumMediasProvider.swift | 1 - .../Drivers/Medias/AllMediasProvider.swift | 1 - .../Drivers/Medias/AllPhotosProvider.swift | 1 - .../PhotoKit/PhotoLibraryChangeNotifier.swift | 19 ----------------- .../Drivers/Selection/SelectionService.swift | 21 ------------------- .../Extensions/Set+Cancellable.swift | 12 ----------- .../Views/Pages/AlbumView/AlbumView.swift | 18 ++++++---------- .../Views/Pages/AlbumsView/AlbumsView.swift | 1 - .../Views/Pages/Camera/CameraView.swift | 16 ++++++++------ .../Views/Pages/Camera/CameraViewModel.swift | 8 +++---- .../Fullscreen/FullscreenCellViewModel.swift | 1 - .../MediaPicker/AlbumSelectionView.swift | 1 - .../Views/Pages/MediaPicker/MediaPicker.swift | 1 - .../MediaPicker/MediaPickerViewModel.swift | 14 +++++-------- 17 files changed, 33 insertions(+), 95 deletions(-) delete mode 100644 Sources/ExyteMediaPicker/Extensions/Set+Cancellable.swift diff --git a/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj b/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj index 326061c..e157354 100644 --- a/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj +++ b/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 5B0BE9372D7AF6030011FCDB /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B0BE9362D7AF6030011FCDB /* ExyteMediaPicker */; }; 5B0BE93A2D7AF6470011FCDB /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B0BE9392D7AF6470011FCDB /* ExyteMediaPicker */; }; 5B0BE93D2D7AF6940011FCDB /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B0BE93C2D7AF6940011FCDB /* ExyteMediaPicker */; }; + 5B0BE9402D7AFCFE0011FCDB /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B0BE93F2D7AFCFE0011FCDB /* ExyteMediaPicker */; }; 5B2A1A0F2D6DD583000A2E81 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B2A1A0E2D6DD583000A2E81 /* ExyteMediaPicker */; }; 5B32A6692D79BA3100DF5348 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B32A6682D79BA3100DF5348 /* ExyteMediaPicker */; }; 5B32A66C2D79BC6D00DF5348 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B32A66B2D79BC6D00DF5348 /* ExyteMediaPicker */; }; @@ -62,6 +63,7 @@ 5BC6705A2D6C8B810023E666 /* ExyteMediaPicker in Frameworks */, 5B4C32F82D6F2080009844B6 /* ExyteMediaPicker in Frameworks */, 5B32A66C2D79BC6D00DF5348 /* ExyteMediaPicker in Frameworks */, + 5B0BE9402D7AFCFE0011FCDB /* ExyteMediaPicker in Frameworks */, 5BC670572D6C8B250023E666 /* ExyteMediaPicker in Frameworks */, 5B4D33E12D689AF900E6C069 /* ExyteMediaPicker in Frameworks */, ); @@ -149,6 +151,7 @@ 5B0BE9362D7AF6030011FCDB /* ExyteMediaPicker */, 5B0BE9392D7AF6470011FCDB /* ExyteMediaPicker */, 5B0BE93C2D7AF6940011FCDB /* ExyteMediaPicker */, + 5B0BE93F2D7AFCFE0011FCDB /* ExyteMediaPicker */, ); productName = MediaPickerExample; productReference = 5BFF68312AD689C80099D333 /* MediaPickerExample.app */; @@ -179,7 +182,7 @@ ); mainGroup = 5BFF68282AD689C80099D333; packageReferences = ( - 5B0BE93B2D7AF6940011FCDB /* XCLocalSwiftPackageReference "../../MediaPicker" */, + 5B0BE93E2D7AFCFE0011FCDB /* XCLocalSwiftPackageReference "../../MediaPicker" */, ); productRefGroup = 5BFF68322AD689C80099D333 /* Products */; projectDirPath = ""; @@ -436,7 +439,7 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - 5B0BE93B2D7AF6940011FCDB /* XCLocalSwiftPackageReference "../../MediaPicker" */ = { + 5B0BE93E2D7AFCFE0011FCDB /* XCLocalSwiftPackageReference "../../MediaPicker" */ = { isa = XCLocalSwiftPackageReference; relativePath = ../../MediaPicker; }; @@ -459,6 +462,10 @@ isa = XCSwiftPackageProductDependency; productName = ExyteMediaPicker; }; + 5B0BE93F2D7AFCFE0011FCDB /* ExyteMediaPicker */ = { + isa = XCSwiftPackageProductDependency; + productName = ExyteMediaPicker; + }; 5B2A1A0E2D6DD583000A2E81 /* ExyteMediaPicker */ = { isa = XCSwiftPackageProductDependency; productName = ExyteMediaPicker; diff --git a/MediaPickerExample/MediaPickerExample/ContentView.swift b/MediaPickerExample/MediaPickerExample/ContentView.swift index 1e19a5c..d8d656e 100644 --- a/MediaPickerExample/MediaPickerExample/ContentView.swift +++ b/MediaPickerExample/MediaPickerExample/ContentView.swift @@ -7,7 +7,6 @@ import SwiftUI import ExyteMediaPicker -import Combine struct ContentView: View { diff --git a/Sources/ExyteMediaPicker/Data/Media.swift b/Sources/ExyteMediaPicker/Data/Media.swift index eda5734..998cae9 100644 --- a/Sources/ExyteMediaPicker/Data/Media.swift +++ b/Sources/ExyteMediaPicker/Data/Media.swift @@ -3,7 +3,6 @@ // import Foundation -import Combine public enum MediaType { case image diff --git a/Sources/ExyteMediaPicker/Drivers/Medias/AlbumMediasProvider.swift b/Sources/ExyteMediaPicker/Drivers/Medias/AlbumMediasProvider.swift index 6558769..c04741f 100644 --- a/Sources/ExyteMediaPicker/Drivers/Medias/AlbumMediasProvider.swift +++ b/Sources/ExyteMediaPicker/Drivers/Medias/AlbumMediasProvider.swift @@ -3,7 +3,6 @@ // import Foundation -import Combine import Photos import SwiftUI diff --git a/Sources/ExyteMediaPicker/Drivers/Medias/AllMediasProvider.swift b/Sources/ExyteMediaPicker/Drivers/Medias/AllMediasProvider.swift index 16de0bb..569fd2e 100644 --- a/Sources/ExyteMediaPicker/Drivers/Medias/AllMediasProvider.swift +++ b/Sources/ExyteMediaPicker/Drivers/Medias/AllMediasProvider.swift @@ -6,7 +6,6 @@ import Foundation import Foundation import Photos -import Combine import SwiftUI final class AllMediasProvider: BaseMediasProvider { diff --git a/Sources/ExyteMediaPicker/Drivers/Medias/AllPhotosProvider.swift b/Sources/ExyteMediaPicker/Drivers/Medias/AllPhotosProvider.swift index b745ea5..c54f051 100644 --- a/Sources/ExyteMediaPicker/Drivers/Medias/AllPhotosProvider.swift +++ b/Sources/ExyteMediaPicker/Drivers/Medias/AllPhotosProvider.swift @@ -6,7 +6,6 @@ import Foundation import Foundation import Photos -import Combine import SwiftUI final class AllPhotosProvider: BaseMediasProvider { diff --git a/Sources/ExyteMediaPicker/Drivers/PhotoKit/PhotoLibraryChangeNotifier.swift b/Sources/ExyteMediaPicker/Drivers/PhotoKit/PhotoLibraryChangeNotifier.swift index d7bda83..1527ebc 100644 --- a/Sources/ExyteMediaPicker/Drivers/PhotoKit/PhotoLibraryChangeNotifier.swift +++ b/Sources/ExyteMediaPicker/Drivers/PhotoKit/PhotoLibraryChangeNotifier.swift @@ -3,7 +3,6 @@ // import Foundation -import Combine import Photos let photoLibraryChangePermissionNotification = Notification.Name(rawValue: "PhotoLibraryChangePermissionNotification") @@ -11,21 +10,3 @@ let photoLibraryChangePermissionNotification = Notification.Name(rawValue: "Phot let photoLibraryChangeLimitedPhotosNotification = Notification.Name(rawValue: "PhotoLibraryChangeLimitedPhotosNotification") let cameraChangePermissionNotification = Notification.Name(rawValue: "cameraChangePermissionNotification") - -final class PhotoLibraryChangePermissionWatcher: NSObject, PHPhotoLibraryChangeObserver { - override init() { - super.init() - PHPhotoLibrary.shared().register(self) - } - - deinit { - PHPhotoLibrary.shared().unregisterChangeObserver(self) - } - - func photoLibraryDidChange(_ changeInstance: PHChange) { - // gets called too often, even if nothing changed - a bug? - NotificationCenter.default.post( - name: photoLibraryChangePermissionNotification, - object: nil) - } -} diff --git a/Sources/ExyteMediaPicker/Drivers/Selection/SelectionService.swift b/Sources/ExyteMediaPicker/Drivers/Selection/SelectionService.swift index cffcead..7a73669 100644 --- a/Sources/ExyteMediaPicker/Drivers/Selection/SelectionService.swift +++ b/Sources/ExyteMediaPicker/Drivers/Selection/SelectionService.swift @@ -4,7 +4,6 @@ import Foundation import SwiftUI -import Combine import Photos final class SelectionService: ObservableObject { @@ -65,23 +64,3 @@ final class SelectionService: ObservableObject { } } } - -private extension SelectionService { - - func findAsset(identifier: String) -> Future { - Future { promise in - let options = PHFetchOptions() - let photos = PHAsset.fetchAssets(with: options) - - var result: PHAsset? - - photos.enumerateObjects { (asset, _, stop) in - if asset.localIdentifier == identifier { - result = asset - stop.pointee = true - } - } - promise(.success(result)) - } - } -} diff --git a/Sources/ExyteMediaPicker/Extensions/Set+Cancellable.swift b/Sources/ExyteMediaPicker/Extensions/Set+Cancellable.swift deleted file mode 100644 index 5472465..0000000 --- a/Sources/ExyteMediaPicker/Extensions/Set+Cancellable.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// Created by Alex.M on 07.06.2022. -// - -import Foundation -import Combine - -extension Set where Element == AnyCancellable { - mutating func cancelAll() { - self = Set() - } -} diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift index 8c90891..c217a2b 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift @@ -17,7 +17,6 @@ struct AlbumView: View { @Binding var showingCamera: Bool @Binding var currentFullscreenMedia: Media? - var title: String? var shouldShowCamera: Bool var selectionParamsHolder: SelectionParamsHolder var dismiss: ()->() @@ -29,18 +28,13 @@ struct AlbumView: View { } var body: some View { - VStack { - if let title = title { - Text(title) + content + .onAppear { + viewModel.reload() + } + .onDisappear { + viewModel.cancel() } - content - } - .onAppear { - viewModel.reload() - } - .onDisappear { - viewModel.cancel() - } } @ViewBuilder diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsView.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsView.swift index a91daa1..e69e2d6 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsView.swift @@ -3,7 +3,6 @@ // import SwiftUI -import Combine struct AlbumsView: View { diff --git a/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraView.swift b/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraView.swift index b44e696..6cc5a2a 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraView.swift @@ -40,9 +40,11 @@ struct CustomCameraView: View { { cameraViewModel.toggleFlash() }, // flash off/on { cameraViewModel.flipCamera() } // camera back/front ) - .onReceive(cameraViewModel.capturedPhotoPublisher) { - viewModel.pickedMediaUrl = $0 - didTakePicture() + .onReceive(cameraViewModel.$capturedPhoto) { + if let photo = $0 { + viewModel.pickedMediaUrl = photo + didTakePicture() + } } } } @@ -152,9 +154,11 @@ struct StandardConrolsCameraView: View { .background(theme.main.cameraBackground) .onEnteredBackground(perform: cameraViewModel.stopSession) .onEnteredForeground(perform: cameraViewModel.startSession) - .onReceive(cameraViewModel.capturedPhotoPublisher) { - viewModel.pickedMediaUrl = $0 - didTakePicture() + .onReceive(cameraViewModel.$capturedPhoto) { + if let photo = $0 { + viewModel.pickedMediaUrl = photo + didTakePicture() + } } } diff --git a/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraViewModel.swift b/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraViewModel.swift index dcc3c37..b67f14b 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraViewModel.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraViewModel.swift @@ -7,7 +7,6 @@ import Foundation import AVFoundation -import Combine import UIKit import SwiftUI @@ -22,15 +21,14 @@ final class CameraViewModel: NSObject, ObservableObject { @Published private(set) var flashEnabled = false @Published private(set) var snapOverlay = false + @Published private(set) var capturedPhoto: URL? let captureSession = AVCaptureSession() - var capturedPhotoPublisher: AnyPublisher { capturedPhotoSubject.eraseToAnyPublisher() } private let photoOutput = AVCapturePhotoOutput() private let videoOutput = AVCaptureMovieFileOutput() private let motionManager = MotionManager() private let sessionQueue = DispatchQueue(label: "LiveCameraQueue") - private let capturedPhotoSubject = PassthroughSubject() private var captureDevice: CaptureDevice? private var lastPhotoActualOrientation: UIDeviceOrientation? @@ -254,12 +252,12 @@ extension CameraViewModel: AVCapturePhotoCaptureDelegate { orientation: photoOrientation ).jpegData(compressionQuality: 0.8) else { return } - capturedPhotoSubject.send(FileManager.storeToTempDir(data: data)) + capturedPhoto = FileManager.storeToTempDir(data: data) } } extension CameraViewModel: AVCaptureFileOutputRecordingDelegate { func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) { - capturedPhotoSubject.send(outputFileURL) + capturedPhoto = outputFileURL } } diff --git a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCellViewModel.swift b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCellViewModel.swift index 7775f8f..718ce93 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCellViewModel.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCellViewModel.swift @@ -3,7 +3,6 @@ // import Foundation -import Combine import AVKit import UIKit.UIImage diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/AlbumSelectionView.swift b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/AlbumSelectionView.swift index cd2f17b..aba7527 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/AlbumSelectionView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/AlbumSelectionView.swift @@ -52,7 +52,6 @@ public struct AlbumSelectionView: View { viewModel: AlbumMediasProvider(album: albumModel, selectionParamsHolder: selectionParamsHolder, filterClosure: filterClosure, massFilterClosure: massFilterClosure), showingCamera: $showingCamera, currentFullscreenMedia: $currentFullscreenMedia, - title: album.title, shouldShowCamera: false, selectionParamsHolder: selectionParamsHolder, dismiss: dismiss diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift index 4bd4db9..6c24963 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift @@ -3,7 +3,6 @@ // import SwiftUI -import Combine public struct MediaPicker: View { diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPickerViewModel.swift b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPickerViewModel.swift index 56d79eb..fe1be83 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPickerViewModel.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPickerViewModel.swift @@ -4,7 +4,6 @@ import Foundation import SwiftUI -import Combine @MainActor final class MediaPickerViewModel: ObservableObject { @@ -14,20 +13,17 @@ final class MediaPickerViewModel: ObservableObject { @Published var pickedMediaUrl: URL? #endif + @Published private(set) var defaultAlbumsProvider = DefaultAlbumsProvider() @Published private(set) var internalPickerMode: MediaPickerMode = .photos - @Published private(set) var albums: [AlbumModel] = [] + + var albums: [AlbumModel] { + defaultAlbumsProvider.albums + } var shouldUpdatePickerMode: (MediaPickerMode)->() = {_ in} - let defaultAlbumsProvider = DefaultAlbumsProvider() - private let watcher = PhotoLibraryChangePermissionWatcher() - private var albumsCancellable: AnyCancellable? - func onStart() { defaultAlbumsProvider.reload() - albumsCancellable = defaultAlbumsProvider.$albums.sink { [weak self] albums in - self?.albums = albums - } } func getAlbumModel(_ album: Album) -> AlbumModel? { From 58067de5597f4e34f8c27d0f90ef83418a266efa Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Mon, 17 Mar 2025 17:23:37 +0700 Subject: [PATCH 49/70] Make theme public --- .../ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift b/Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift index f28c8ec..0f5cb20 100644 --- a/Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift +++ b/Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift @@ -5,7 +5,7 @@ import Foundation import SwiftUI -extension EnvironmentValues { +public extension EnvironmentValues { @Entry var mediaPickerTheme = MediaPickerTheme() } From 831fe42fc02e76f5e7e127e0241564c14532f86c Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Fri, 21 Mar 2025 17:16:22 +0700 Subject: [PATCH 50/70] Refactor permissions service --- .../Drivers/Medias/AlbumMediasProvider.swift | 6 +- .../Drivers/Medias/AllMediasProvider.swift | 6 +- .../Drivers/Medias/AllPhotosProvider.swift | 6 +- .../Drivers/Medias/BaseMediasProvider.swift | 9 -- .../Medias/DefaultAlbumsProvider.swift | 12 ++- .../Drivers/PermissionsService.swift | 93 ++++++++----------- .../PhotoKit/PhotoLibraryChangeNotifier.swift | 12 --- .../Modifiers/MediaPickerThemeModifier.swift | 3 + .../Views/Pages/AlbumView/AlbumView.swift | 14 ++- .../Views/Pages/AlbumsView/AlbumsView.swift | 7 +- .../Views/Pages/Camera/LiveCameraView.swift | 6 +- .../Views/Pages/MediaPicker/MediaPicker.swift | 22 +++-- .../Widgets/Errors/PermissionActionView.swift | 78 ++++++++++++++++ .../Errors/PermissionsActionView.swift | 84 ----------------- .../Widgets/Errors/PermissionsErrorView.swift | 1 + 15 files changed, 161 insertions(+), 198 deletions(-) delete mode 100644 Sources/ExyteMediaPicker/Drivers/PhotoKit/PhotoLibraryChangeNotifier.swift create mode 100644 Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionActionView.swift delete mode 100644 Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionsActionView.swift diff --git a/Sources/ExyteMediaPicker/Drivers/Medias/AlbumMediasProvider.swift b/Sources/ExyteMediaPicker/Drivers/Medias/AlbumMediasProvider.swift index c04741f..94d7a2e 100644 --- a/Sources/ExyteMediaPicker/Drivers/Medias/AlbumMediasProvider.swift +++ b/Sources/ExyteMediaPicker/Drivers/Medias/AlbumMediasProvider.swift @@ -16,8 +16,10 @@ final class AlbumMediasProvider: BaseMediasProvider { } override func reload() { - PermissionsService.requestPhotoLibraryPermission { [ weak self] in - self?.reloadInternal() + PermissionsService.shared.requestPhotoLibraryPermission { [weak self] in + DispatchQueue.main.async { + self?.reloadInternal() + } } } diff --git a/Sources/ExyteMediaPicker/Drivers/Medias/AllMediasProvider.swift b/Sources/ExyteMediaPicker/Drivers/Medias/AllMediasProvider.swift index 569fd2e..1690944 100644 --- a/Sources/ExyteMediaPicker/Drivers/Medias/AllMediasProvider.swift +++ b/Sources/ExyteMediaPicker/Drivers/Medias/AllMediasProvider.swift @@ -11,8 +11,10 @@ import SwiftUI final class AllMediasProvider: BaseMediasProvider { override func reload() { - PermissionsService.requestPhotoLibraryPermission { [ weak self] in - self?.reloadInternal() + PermissionsService.shared.requestPhotoLibraryPermission { [weak self] in + DispatchQueue.main.async { + self?.reloadInternal() + } } } diff --git a/Sources/ExyteMediaPicker/Drivers/Medias/AllPhotosProvider.swift b/Sources/ExyteMediaPicker/Drivers/Medias/AllPhotosProvider.swift index c54f051..08aa7e4 100644 --- a/Sources/ExyteMediaPicker/Drivers/Medias/AllPhotosProvider.swift +++ b/Sources/ExyteMediaPicker/Drivers/Medias/AllPhotosProvider.swift @@ -11,8 +11,10 @@ import SwiftUI final class AllPhotosProvider: BaseMediasProvider { override func reload() { - PermissionsService.requestPhotoLibraryPermission { [ weak self] in - self?.reloadInternal() + PermissionsService.shared.requestPhotoLibraryPermission { [weak self] in + DispatchQueue.main.async { + self?.reloadInternal() + } } } diff --git a/Sources/ExyteMediaPicker/Drivers/Medias/BaseMediasProvider.swift b/Sources/ExyteMediaPicker/Drivers/Medias/BaseMediasProvider.swift index 653e13a..2ab16bb 100644 --- a/Sources/ExyteMediaPicker/Drivers/Medias/BaseMediasProvider.swift +++ b/Sources/ExyteMediaPicker/Drivers/Medias/BaseMediasProvider.swift @@ -26,15 +26,6 @@ class BaseMediasProvider: ObservableObject { self.selectionParamsHolder = selectionParamsHolder self.filterClosure = filterClosure self.massFilterClosure = massFilterClosure - - cancellable = NotificationCenter.default - .publisher(for: photoLibraryChangeLimitedPhotosNotification) - .map { _ in } - .share() - .receive(on: RunLoop.main) - .sink { [weak self] in - self?.reload() - } } func filterAndPublish(assets: [AssetMediaModel]) { diff --git a/Sources/ExyteMediaPicker/Drivers/Medias/DefaultAlbumsProvider.swift b/Sources/ExyteMediaPicker/Drivers/Medias/DefaultAlbumsProvider.swift index ded447d..1d405b8 100644 --- a/Sources/ExyteMediaPicker/Drivers/Medias/DefaultAlbumsProvider.swift +++ b/Sources/ExyteMediaPicker/Drivers/Medias/DefaultAlbumsProvider.swift @@ -20,12 +20,14 @@ final class DefaultAlbumsProvider: ObservableObject { func reload() { cancelReload() - PermissionsService.requestPhotoLibraryPermission { [weak self] in + PermissionsService.shared.requestPhotoLibraryPermission { [weak self] in guard let self = self else { return } - self.reloadTask = Task { - self.isLoading = true - await self.reloadInternal() - self.isLoading = false + DispatchQueue.main.async { + self.reloadTask = Task { + self.isLoading = true + await self.reloadInternal() + self.isLoading = false + } } } } diff --git a/Sources/ExyteMediaPicker/Drivers/PermissionsService.swift b/Sources/ExyteMediaPicker/Drivers/PermissionsService.swift index 177d792..9e35378 100644 --- a/Sources/ExyteMediaPicker/Drivers/PermissionsService.swift +++ b/Sources/ExyteMediaPicker/Drivers/PermissionsService.swift @@ -9,39 +9,25 @@ import Photos @MainActor final class PermissionsService: ObservableObject { - @Published var cameraAction: CameraAction? = .authorize - @Published var photoLibraryAction: PhotoLibraryAction? = .authorize - private var subscriptions = Set() + static var shared = PermissionsService() - init() { - NotificationCenter.default - .publisher(for: photoLibraryChangePermissionNotification) - .map { _ in } - .share() - .sink { [weak self] in - self?.checkPhotoLibraryAuthorizationStatus() - } - .store(in: &subscriptions) - - NotificationCenter.default - .publisher(for: cameraChangePermissionNotification) - .map { _ in } - .share() - .sink { [weak self] in - self?.checkCameraAuthorizationStatus() - } - .store(in: &subscriptions) - } + @Published var cameraPermissionStatus: CameraPermissionStatus = .unknown + @Published var photoLibraryPermissionStatus: PhotoLibraryPermissionStatus = .unknown /// photoLibraryChangePermissionPublisher gets called multiple times even when nothing changed in photo library, so just use this one to make sure the closure runs exactly once - static func requestPhotoLibraryPermission(_ permissionGrantedClosure: @escaping ()->()) { + func requestPhotoLibraryPermission(_ permissionGrantedClosure: @Sendable @escaping ()->()) { Task { + let currentStatus = PHPhotoLibrary.authorizationStatus(for: .addOnly) + if currentStatus == .authorized || currentStatus == .limited { + permissionGrantedClosure() + return + } + let status = await PHPhotoLibrary.requestAuthorization(for: .addOnly) + updatePhotoLibraryAuthorizationStatus() if status == .authorized || status == .limited { - DispatchQueue.main.async { - permissionGrantedClosure() - } + permissionGrantedClosure() } } } @@ -49,69 +35,66 @@ final class PermissionsService: ObservableObject { func requestCameraPermission() { Task { await AVCaptureDevice.requestAccess(for: .video) - checkCameraAuthorizationStatus() + updateCameraAuthorizationStatus() } } - func checkPhotoLibraryAuthorizationStatus() { - let status = PHPhotoLibrary.authorizationStatus(for: .readWrite) + func updatePhotoLibraryAuthorizationStatus() { + let status = PHPhotoLibrary.authorizationStatus(for: .addOnly) - var result: PhotoLibraryAction? + let result: PhotoLibraryPermissionStatus switch status { - case .restricted: - // TODO: Make sure that access can't change when status == .restricted - result = .unavailable - case .notDetermined, .denied: - result = .authorize case .authorized: - // Do nothing - break + result = .authorized case .limited: - result = .selectMore - @unknown default: + result = .limited + case .restricted, .denied: + result = .unavailable + case .notDetermined: + result = .unknown + default: result = .unknown } DispatchQueue.main.async { [weak self] in - self?.photoLibraryAction = result + self?.photoLibraryPermissionStatus = result } } - func checkCameraAuthorizationStatus() { + func updateCameraAuthorizationStatus() { let status = AVCaptureDevice.authorizationStatus(for: .video) - var result: CameraAction? + let result: CameraPermissionStatus #if targetEnvironment(simulator) result = .unavailable #else switch status { - case .restricted: - result = .unavailable - case .notDetermined, .denied: - result = .authorize case .authorized: - // Do nothing - break - @unknown default: + result = .authorized + case .restricted, .denied: + result = .unavailable + case .notDetermined: + result = .unknown + default: result = .unknown } #endif DispatchQueue.main.async { [weak self] in - self?.cameraAction = result + self?.cameraPermissionStatus = result } } } extension PermissionsService { - enum CameraAction { - case authorize + enum CameraPermissionStatus { + case authorized case unavailable case unknown } - enum PhotoLibraryAction { - case selectMore - case authorize + enum PhotoLibraryPermissionStatus { + case limited + case authorized case unavailable case unknown } diff --git a/Sources/ExyteMediaPicker/Drivers/PhotoKit/PhotoLibraryChangeNotifier.swift b/Sources/ExyteMediaPicker/Drivers/PhotoKit/PhotoLibraryChangeNotifier.swift deleted file mode 100644 index 1527ebc..0000000 --- a/Sources/ExyteMediaPicker/Drivers/PhotoKit/PhotoLibraryChangeNotifier.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// Created by Alex.M on 08.06.2022. -// - -import Foundation -import Photos - -let photoLibraryChangePermissionNotification = Notification.Name(rawValue: "PhotoLibraryChangePermissionNotification") - -let photoLibraryChangeLimitedPhotosNotification = Notification.Name(rawValue: "PhotoLibraryChangeLimitedPhotosNotification") - -let cameraChangePermissionNotification = Notification.Name(rawValue: "cameraChangePermissionNotification") diff --git a/Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift b/Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift index 0f5cb20..a635d4d 100644 --- a/Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift +++ b/Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift @@ -7,11 +7,13 @@ import SwiftUI public extension EnvironmentValues { @Entry var mediaPickerTheme = MediaPickerTheme() + @Entry var mediaPickerThemeIsOverridden = false } public extension View { func mediaPickerTheme(_ theme: MediaPickerTheme) -> some View { self.environment(\.mediaPickerTheme, theme) + .environment(\.mediaPickerThemeIsOverridden, true) } func mediaPickerTheme( @@ -22,5 +24,6 @@ public extension View { defaultHeader: MediaPickerTheme.DefaultHeader = .init() ) -> some View { self.environment(\.mediaPickerTheme, MediaPickerTheme(main: main, selection: selection, cellStyle: cellStyle, error: error, defaultHeader: defaultHeader)) + .environment(\.mediaPickerThemeIsOverridden, true) } } diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift index c217a2b..1df444c 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift @@ -8,10 +8,10 @@ import AnchoredPopup struct AlbumView: View { @EnvironmentObject private var selectionService: SelectionService - @EnvironmentObject private var permissionsService: PermissionsService @Environment(\.mediaPickerTheme) private var theme @ObservedObject var keyboardHeightHelper = KeyboardHeightHelper.shared + @ObservedObject var permissionsService = PermissionsService.shared @StateObject var viewModel: BaseMediasProvider @Binding var showingCamera: Bool @@ -40,13 +40,11 @@ struct AlbumView: View { @ViewBuilder var content: some View { ScrollView { - VStack { - if let action = permissionsService.photoLibraryAction { - PermissionsActionView(action: .library(action)) - } + VStack(spacing: 0) { + PermissionActionView(type: .library(permissionsService.photoLibraryPermissionStatus)) - if shouldShowCamera, let action = permissionsService.cameraAction { - PermissionsActionView(action: .camera(action)) + if shouldShowCamera { + PermissionActionView(type: .camera(permissionsService.cameraPermissionStatus)) } if viewModel.isLoading, viewModel.assetMediaModels.isEmpty { @@ -75,7 +73,7 @@ struct AlbumView: View { var mediasGrid: some View { MediasGrid(viewModel.assetMediaModels) { #if !targetEnvironment(simulator) - if shouldShowCamera && permissionsService.cameraAction == nil { + if shouldShowCamera && permissionsService.cameraPermissionStatus == .authorized { LiveCameraCell { showingCamera = true } diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsView.swift b/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsView.swift index e69e2d6..baff1ff 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsView.swift @@ -7,11 +7,11 @@ import SwiftUI struct AlbumsView: View { @EnvironmentObject private var selectionService: SelectionService - @EnvironmentObject private var permissionsService: PermissionsService @Environment(\.mediaPickerTheme) private var theme @StateObject var viewModel: AlbumsViewModel @ObservedObject var mediaPickerViewModel: MediaPickerViewModel + @ObservedObject var permissionsService = PermissionsService.shared @Binding var showingCamera: Bool @Binding var currentFullscreenMedia: Media? @@ -34,9 +34,8 @@ struct AlbumsView: View { var body: some View { ScrollView { VStack { - if let action = permissionsService.photoLibraryAction { - PermissionsActionView(action: .library(action)) - } + PermissionActionView(type: .library(permissionsService.photoLibraryPermissionStatus)) + if viewModel.isLoading { ProgressView() .padding() diff --git a/Sources/ExyteMediaPicker/Views/Pages/Camera/LiveCameraView.swift b/Sources/ExyteMediaPicker/Views/Pages/Camera/LiveCameraView.swift index 6713d54..ae26395 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/Camera/LiveCameraView.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/Camera/LiveCameraView.swift @@ -15,11 +15,7 @@ public struct LiveCameraView: UIViewRepresentable { var orientation: UIDeviceOrientation = UIDevice.current.orientation public func makeUIView(context: Context) -> LiveVideoCaptureView { - NotificationCenter.default.post( - name: cameraChangePermissionNotification, - object: nil) - - return LiveVideoCaptureView( + LiveVideoCaptureView( session: session, videoGravity: videoGravity, orientation: orientation diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift index 6c24963..ce40286 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift +++ b/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift @@ -63,7 +63,6 @@ public struct MediaPicker some View { + switch status { + case .authorized, .unknown: + EmptyView() + case .limited: + PermissionsErrorView(text: "Setup Photos access to see more photos here") { + showSheet = true + } + case .unavailable: + goToSettingsButton(text: "Allow Photos access in settings to see photos here") + } + } + + @ViewBuilder + func buildCameraActionView(_ status: PermissionsService.CameraPermissionStatus) -> some View { + switch status { + case .authorized, .unknown: + EmptyView() + case .unavailable: + goToSettingsButton(text: "Allow Camera access in settings to see live preview") + } + } + + func goToSettingsButton(text: String) -> some View { + PermissionsErrorView( + text: text, + action: { + guard let url = URL(string: UIApplication.openSettingsURLString) + else { return } + if UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url) + } + } + ) + } +} diff --git a/Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionsActionView.swift b/Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionsActionView.swift deleted file mode 100644 index 65f585a..0000000 --- a/Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionsActionView.swift +++ /dev/null @@ -1,84 +0,0 @@ -// -// Created by Alex.M on 06.06.2022. -// - -import Foundation -import SwiftUI - -struct PermissionsActionView: View { - - let action: Action - - @State private var showSheet = false - - var body: some View { - ZStack { - if showSheet { - LimitedLibraryPickerProxyView(isPresented: $showSheet) { - NotificationCenter.default.post( - name: photoLibraryChangeLimitedPhotosNotification, - object: nil) - } - .frame(width: 1, height: 1) - } - - switch action { - case .library(let assetsLibraryAction): - buildLibraryAction(assetsLibraryAction) - case .camera(let cameraAction): - buildCameraAction(cameraAction) - } - } - } -} - -private extension PermissionsActionView { - - @ViewBuilder - func buildLibraryAction(_ action: PermissionsService.PhotoLibraryAction) -> some View { - switch action { - case .selectMore: - PermissionsErrorView(text: "Setup Photos access to see more photos here") { - showSheet = true - } - case .authorize: - goToSettingsButton(text: "Allow Photos access in settings to see photos here") - case .unavailable: - PermissionsErrorView(text: "Sorry, Photos are not available.", action: nil) - case .unknown: - fatalError("Unknown permission status.") - } - } - - @ViewBuilder - func buildCameraAction(_ action: PermissionsService.CameraAction) -> some View { - switch action { - case .authorize: - goToSettingsButton(text: "Allow Camera access in settings to see live preview") - case .unavailable: - PermissionsErrorView(text: "Sorry, Camera is not available.", action: nil) - case .unknown: - fatalError("Unknown permission status.") - } - } - - func goToSettingsButton(text: String) -> some View { - PermissionsErrorView( - text: text, - action: { - guard let url = URL(string: UIApplication.openSettingsURLString) - else { return } - if UIApplication.shared.canOpenURL(url) { - UIApplication.shared.open(url) - } - } - ) - } -} - -extension PermissionsActionView { - enum Action { - case library(PermissionsService.PhotoLibraryAction) - case camera(PermissionsService.CameraAction) - } -} diff --git a/Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionsErrorView.swift b/Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionsErrorView.swift index 8a3f9c0..31b2468 100644 --- a/Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionsErrorView.swift +++ b/Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionsErrorView.swift @@ -30,5 +30,6 @@ struct PermissionsErrorView: View { .background(theme.error.background) .cornerRadius(5) .padding(.horizontal, 20) + .padding(.bottom, 6) } } From 15f00080b41ed83c45148e5b43fce0c78f2356fa Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Fri, 21 Mar 2025 18:49:02 +0700 Subject: [PATCH 51/70] Deprecate cocoapods --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index cdab74b..3e3de45 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,7 @@ [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fexyte%2FMediaPicker%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/exyte/MediaPicker) [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fexyte%2FMediaPicker%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/exyte/MediaPicker) [![SPM Compatible](https://img.shields.io/badge/SwiftPM-Compatible-brightgreen.svg)](https://swiftpackageindex.com/exyte/MediaPicker) -[![Cocoapods Compatible](https://img.shields.io/badge/cocoapods-Compatible-brightgreen.svg)](https://cocoapods.org/pods/ExyteMediaPicker) -[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-brightgreen.svg?style=flat)](https://github.com/Carthage/Carthage) +[![Cocoapods Compatible](https://img.shields.io/badge/cocoapods-Deprecated%20after%202.2.3-yellow.svg)](https://cocoapods.org/pods/ExyteMediaPicker) [![License: MIT](https://img.shields.io/badge/License-MIT-black.svg)](https://opensource.org/licenses/MIT) # Features From 558293acb238b39e1cf94b80a402daf5d5675113 Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Tue, 25 Mar 2025 20:42:12 +0700 Subject: [PATCH 52/70] Reorganize the project --- ExyteMediaPicker.podspec | 23 ----------------- Package.swift => MediaPicker/Package.swift | 5 +++- .../Extensions/Collection+.swift | 0 .../Extensions/ColumnCalculation.swift | 19 ++++++++++++++ .../OrientationTransformationExtensions.swift | 0 .../Extensions/Sequence+asyncMap.swift | 0 .../Extensions/TimeInterval+Duration.swift | 0 .../ExyteMediaPicker/Extensions/View+.swift | 0 .../Extensions/View+NotificationCenter.swift | 0 .../Extensions/Zoom+ScrollView.swift | 0 .../Managers}/FileManager.swift | 0 .../Managers}/LiveCameraViewModel.swift | 0 .../Medias/AlbumMediasProvider.swift | 0 .../Managers}/Medias/AllMediasProvider.swift | 0 .../Managers}/Medias/AllPhotosProvider.swift | 0 .../Managers}/Medias/BaseMediasProvider.swift | 0 .../Medias/DefaultAlbumsProvider.swift | 0 .../Managers}/MotionManager.swift | 0 .../Managers}/PermissionsService.swift | 0 .../Managers}/PhotoKit/PHAsset+Utils.swift | 0 .../Managers}/PhotoKit/URL+Utils.swift | 0 .../Selection/CameraSelectionService.swift | 0 .../Selection/SelectionParamsHolder.swift | 0 .../Selection/SelectionService.swift | 0 .../ExyteMediaPicker/Model}/AlbumModel.swift | 0 .../Model}/AssetMediaModel.swift | 0 .../ExyteMediaPicker/Model}/Media.swift | 0 .../Model}/MediaModelProtocol.swift | 0 .../ExyteMediaPicker/Model}/Types.swift | 0 .../Model}/URLMediaModel.swift | 0 .../Modifiers/KeyboardHeightHelper.swift | 0 .../Modifiers/MediaButtonStyle.swift | 0 .../Modifiers/MediaPickerThemeModifier.swift | 0 .../Modifiers/SafeAreaEnvironmentValues.swift | 0 .../Media.xcassets/Colors/Contents.json | 0 .../Colors/cameraBG.colorset/Contents.json | 0 .../Colors/cameraText.colorset/Contents.json | 0 .../Colors/pickerBG.colorset/Contents.json | 0 .../Colors/pickerText.colorset/Contents.json | 0 .../Colors/selection.colorset/Contents.json | 0 .../Resources/Media.xcassets/Contents.json | 0 .../Media.xcassets/Images/Contents.json | 0 .../Images/FlashOff.imageset/Contents.json | 0 .../Images/FlashOff.imageset/Flash.pdf | Bin .../Images/FlashOn.imageset/Contents.json | 0 .../Images/FlashOn.imageset/Flash on.pdf | Bin .../FlipCamera.imageset/Change Camera.pdf | Bin .../Images/FlipCamera.imageset/Contents.json | 0 .../ExyteMediaPicker}/Theme/Bundle+.swift | 0 .../Theme/MediaPickerTheme.swift | 0 .../Views/Screens}/AlbumView/AlbumView.swift | 0 .../Views/Screens}/AlbumView/MediaCell.swift | 0 .../Screens}/AlbumView/MediaViewModel.swift | 0 .../Views/Screens}/AlbumsView/AlbumCell.swift | 0 .../AlbumsView/AlbumCellViewModel.swift | 0 .../Screens}/AlbumsView/AlbumsView.swift | 12 +-------- .../Screens}/AlbumsView/AlbumsViewModel.swift | 0 .../Camera/CameraSelectionContainer.swift | 0 .../Views/Screens}/Camera/CameraView.swift | 0 .../Screens}/Camera/CameraViewModel.swift | 0 .../Screens}/Camera/LiveCameraView.swift | 0 .../Screens}/Fullscreen/FullscreenCell.swift | 0 .../Fullscreen/FullscreenCellViewModel.swift | 0 .../Fullscreen/FullscreenContainer.swift | 0 .../MediaPicker/AlbumSelectionView.swift | 0 .../Screens}/MediaPicker/GenenricsTrick.swift | 0 .../Screens}/MediaPicker/MediaPicker.swift | 0 .../MediaPicker/MediaPickerMode.swift | 0 .../MediaPicker/MediaPickerViewModel.swift | 0 .../Views/Widgets/CameraStubView.swift | 0 .../Widgets/Errors/PermissionActionView.swift | 0 .../Widgets/Errors/PermissionsErrorView.swift | 0 .../LimitedLibraryPickerProxyView.swift | 0 .../Views/Widgets/LiveCameraCell.swift | 0 .../Views/Widgets/MediasGrid.swift | 13 +--------- .../Views/Widgets/PlayerUIView.swift | 0 .../Views/Widgets/SelectableView.swift | 0 .../Widgets/SelectionIndicatorView.swift | 0 .../Thumbnail/ThumbnailPlaceholder.swift | 0 .../Widgets/Thumbnail/ThumbnailView.swift | 0 .../Extensions/TimeInterval+Init.swift | 0 .../Tests/Extensions/TimeIntervalTests.swift | 0 .../project.pbxproj | 24 ++++-------------- README.md | 18 +++---------- 84 files changed, 33 insertions(+), 81 deletions(-) delete mode 100644 ExyteMediaPicker.podspec rename Package.swift => MediaPicker/Package.swift (85%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Extensions/Collection+.swift (100%) create mode 100644 MediaPicker/Sources/ExyteMediaPicker/Extensions/ColumnCalculation.swift rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Extensions/OrientationTransformationExtensions.swift (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Extensions/Sequence+asyncMap.swift (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Extensions/TimeInterval+Duration.swift (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Extensions/View+.swift (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Extensions/View+NotificationCenter.swift (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Extensions/Zoom+ScrollView.swift (100%) rename {Sources/ExyteMediaPicker/Drivers => MediaPicker/Sources/ExyteMediaPicker/Managers}/FileManager.swift (100%) rename {Sources/ExyteMediaPicker/Drivers => MediaPicker/Sources/ExyteMediaPicker/Managers}/LiveCameraViewModel.swift (100%) rename {Sources/ExyteMediaPicker/Drivers => MediaPicker/Sources/ExyteMediaPicker/Managers}/Medias/AlbumMediasProvider.swift (100%) rename {Sources/ExyteMediaPicker/Drivers => MediaPicker/Sources/ExyteMediaPicker/Managers}/Medias/AllMediasProvider.swift (100%) rename {Sources/ExyteMediaPicker/Drivers => MediaPicker/Sources/ExyteMediaPicker/Managers}/Medias/AllPhotosProvider.swift (100%) rename {Sources/ExyteMediaPicker/Drivers => MediaPicker/Sources/ExyteMediaPicker/Managers}/Medias/BaseMediasProvider.swift (100%) rename {Sources/ExyteMediaPicker/Drivers => MediaPicker/Sources/ExyteMediaPicker/Managers}/Medias/DefaultAlbumsProvider.swift (100%) rename {Sources/ExyteMediaPicker/Drivers => MediaPicker/Sources/ExyteMediaPicker/Managers}/MotionManager.swift (100%) rename {Sources/ExyteMediaPicker/Drivers => MediaPicker/Sources/ExyteMediaPicker/Managers}/PermissionsService.swift (100%) rename {Sources/ExyteMediaPicker/Drivers => MediaPicker/Sources/ExyteMediaPicker/Managers}/PhotoKit/PHAsset+Utils.swift (100%) rename {Sources/ExyteMediaPicker/Drivers => MediaPicker/Sources/ExyteMediaPicker/Managers}/PhotoKit/URL+Utils.swift (100%) rename {Sources/ExyteMediaPicker/Drivers => MediaPicker/Sources/ExyteMediaPicker/Managers}/Selection/CameraSelectionService.swift (100%) rename {Sources/ExyteMediaPicker/Drivers => MediaPicker/Sources/ExyteMediaPicker/Managers}/Selection/SelectionParamsHolder.swift (100%) rename {Sources/ExyteMediaPicker/Drivers => MediaPicker/Sources/ExyteMediaPicker/Managers}/Selection/SelectionService.swift (100%) rename {Sources/ExyteMediaPicker/Data => MediaPicker/Sources/ExyteMediaPicker/Model}/AlbumModel.swift (100%) rename {Sources/ExyteMediaPicker/Data => MediaPicker/Sources/ExyteMediaPicker/Model}/AssetMediaModel.swift (100%) rename {Sources/ExyteMediaPicker/Data => MediaPicker/Sources/ExyteMediaPicker/Model}/Media.swift (100%) rename {Sources/ExyteMediaPicker/Data => MediaPicker/Sources/ExyteMediaPicker/Model}/MediaModelProtocol.swift (100%) rename {Sources/ExyteMediaPicker/Data => MediaPicker/Sources/ExyteMediaPicker/Model}/Types.swift (100%) rename {Sources/ExyteMediaPicker/Data => MediaPicker/Sources/ExyteMediaPicker/Model}/URLMediaModel.swift (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Modifiers/KeyboardHeightHelper.swift (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Modifiers/MediaButtonStyle.swift (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Modifiers/SafeAreaEnvironmentValues.swift (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Resources/Media.xcassets/Colors/Contents.json (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Resources/Media.xcassets/Colors/cameraBG.colorset/Contents.json (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Resources/Media.xcassets/Colors/cameraText.colorset/Contents.json (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Resources/Media.xcassets/Colors/pickerBG.colorset/Contents.json (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Resources/Media.xcassets/Colors/pickerText.colorset/Contents.json (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Resources/Media.xcassets/Colors/selection.colorset/Contents.json (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Resources/Media.xcassets/Contents.json (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Resources/Media.xcassets/Images/Contents.json (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOff.imageset/Contents.json (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOff.imageset/Flash.pdf (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOn.imageset/Contents.json (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOn.imageset/Flash on.pdf (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Resources/Media.xcassets/Images/FlipCamera.imageset/Change Camera.pdf (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Resources/Media.xcassets/Images/FlipCamera.imageset/Contents.json (100%) rename {Sources/ExyteMediaPicker/Drivers => MediaPicker/Sources/ExyteMediaPicker}/Theme/Bundle+.swift (100%) rename {Sources/ExyteMediaPicker/Drivers => MediaPicker/Sources/ExyteMediaPicker}/Theme/MediaPickerTheme.swift (100%) rename {Sources/ExyteMediaPicker/Views/Pages => MediaPicker/Sources/ExyteMediaPicker/Views/Screens}/AlbumView/AlbumView.swift (100%) rename {Sources/ExyteMediaPicker/Views/Pages => MediaPicker/Sources/ExyteMediaPicker/Views/Screens}/AlbumView/MediaCell.swift (100%) rename {Sources/ExyteMediaPicker/Views/Pages => MediaPicker/Sources/ExyteMediaPicker/Views/Screens}/AlbumView/MediaViewModel.swift (100%) rename {Sources/ExyteMediaPicker/Views/Pages => MediaPicker/Sources/ExyteMediaPicker/Views/Screens}/AlbumsView/AlbumCell.swift (100%) rename {Sources/ExyteMediaPicker/Views/Pages => MediaPicker/Sources/ExyteMediaPicker/Views/Screens}/AlbumsView/AlbumCellViewModel.swift (100%) rename {Sources/ExyteMediaPicker/Views/Pages => MediaPicker/Sources/ExyteMediaPicker/Views/Screens}/AlbumsView/AlbumsView.swift (82%) rename {Sources/ExyteMediaPicker/Views/Pages => MediaPicker/Sources/ExyteMediaPicker/Views/Screens}/AlbumsView/AlbumsViewModel.swift (100%) rename {Sources/ExyteMediaPicker/Views/Pages => MediaPicker/Sources/ExyteMediaPicker/Views/Screens}/Camera/CameraSelectionContainer.swift (100%) rename {Sources/ExyteMediaPicker/Views/Pages => MediaPicker/Sources/ExyteMediaPicker/Views/Screens}/Camera/CameraView.swift (100%) rename {Sources/ExyteMediaPicker/Views/Pages => MediaPicker/Sources/ExyteMediaPicker/Views/Screens}/Camera/CameraViewModel.swift (100%) rename {Sources/ExyteMediaPicker/Views/Pages => MediaPicker/Sources/ExyteMediaPicker/Views/Screens}/Camera/LiveCameraView.swift (100%) rename {Sources/ExyteMediaPicker/Views/Pages => MediaPicker/Sources/ExyteMediaPicker/Views/Screens}/Fullscreen/FullscreenCell.swift (100%) rename {Sources/ExyteMediaPicker/Views/Pages => MediaPicker/Sources/ExyteMediaPicker/Views/Screens}/Fullscreen/FullscreenCellViewModel.swift (100%) rename {Sources/ExyteMediaPicker/Views/Pages => MediaPicker/Sources/ExyteMediaPicker/Views/Screens}/Fullscreen/FullscreenContainer.swift (100%) rename {Sources/ExyteMediaPicker/Views/Pages => MediaPicker/Sources/ExyteMediaPicker/Views/Screens}/MediaPicker/AlbumSelectionView.swift (100%) rename {Sources/ExyteMediaPicker/Views/Pages => MediaPicker/Sources/ExyteMediaPicker/Views/Screens}/MediaPicker/GenenricsTrick.swift (100%) rename {Sources/ExyteMediaPicker/Views/Pages => MediaPicker/Sources/ExyteMediaPicker/Views/Screens}/MediaPicker/MediaPicker.swift (100%) rename {Sources/ExyteMediaPicker/Views/Pages => MediaPicker/Sources/ExyteMediaPicker/Views/Screens}/MediaPicker/MediaPickerMode.swift (100%) rename {Sources/ExyteMediaPicker/Views/Pages => MediaPicker/Sources/ExyteMediaPicker/Views/Screens}/MediaPicker/MediaPickerViewModel.swift (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Views/Widgets/CameraStubView.swift (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Views/Widgets/Errors/PermissionActionView.swift (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Views/Widgets/Errors/PermissionsErrorView.swift (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Views/Widgets/LimitedLibraryPickerProxyView.swift (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Views/Widgets/LiveCameraCell.swift (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Views/Widgets/MediasGrid.swift (68%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Views/Widgets/PlayerUIView.swift (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Views/Widgets/SelectableView.swift (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Views/Widgets/SelectionIndicatorView.swift (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Views/Widgets/Thumbnail/ThumbnailPlaceholder.swift (100%) rename {Sources => MediaPicker/Sources}/ExyteMediaPicker/Views/Widgets/Thumbnail/ThumbnailView.swift (100%) rename {Tests => MediaPicker/Tests}/MediaPickerTests/Extensions/TimeInterval+Init.swift (100%) rename {Tests => MediaPicker/Tests}/MediaPickerTests/Tests/Extensions/TimeIntervalTests.swift (100%) diff --git a/ExyteMediaPicker.podspec b/ExyteMediaPicker.podspec deleted file mode 100644 index 4846627..0000000 --- a/ExyteMediaPicker.podspec +++ /dev/null @@ -1,23 +0,0 @@ -Pod::Spec.new do |s| - s.name = "ExyteMediaPicker" - s.version = "2.2.3" - s.summary = "MediaPicker is a customizable photo/video picker for iOS written in pure SwiftUI" - - s.homepage = 'https://github.com/exyte/MediaPicker.git' - s.license = 'MIT' - s.author = { 'Exyte' => 'info@exyte.com' } - s.source = { :git => 'https://github.com/exyte/MediaPicker.git', :tag => s.version.to_s } - s.social_media_url = 'http://exyte.com' - - s.ios.deployment_target = '15.0' - - s.requires_arc = true - s.swift_version = "5.2" - - s.source_files = [ - 'Sources/*.h', - 'Sources/*.swift', - 'Sources/**/*.swift' - ] - -end diff --git a/Package.swift b/MediaPicker/Package.swift similarity index 85% rename from Package.swift rename to MediaPicker/Package.swift index 0a13f15..d18485d 100644 --- a/Package.swift +++ b/MediaPicker/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 6.0 +// swift-tools-version: 5.9 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -24,6 +24,9 @@ let package = Package( name: "ExyteMediaPicker", dependencies: [ .product(name: "AnchoredPopup", package: "AnchoredPopup") + ], + swiftSettings: [ + .enableExperimentalFeature("StrictConcurrency") ] ), .testTarget( diff --git a/Sources/ExyteMediaPicker/Extensions/Collection+.swift b/MediaPicker/Sources/ExyteMediaPicker/Extensions/Collection+.swift similarity index 100% rename from Sources/ExyteMediaPicker/Extensions/Collection+.swift rename to MediaPicker/Sources/ExyteMediaPicker/Extensions/Collection+.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Extensions/ColumnCalculation.swift b/MediaPicker/Sources/ExyteMediaPicker/Extensions/ColumnCalculation.swift new file mode 100644 index 0000000..6a751b6 --- /dev/null +++ b/MediaPicker/Sources/ExyteMediaPicker/Extensions/ColumnCalculation.swift @@ -0,0 +1,19 @@ +// +// Untitled.swift +// ExyteMediaPicker +// +// Created by Alisa Mylnikova on 25.03.2025. +// + +import SwiftUI + +@MainActor +func calculateColumnWidth(spacing: CGFloat) -> (CGFloat, [GridItem]) { + let gridWidth = UIScreen.main.bounds.width + let minColumnWidth = 100.0 + let wholeCount = CGFloat(Int(gridWidth / minColumnWidth)) + let noSpaces = gridWidth - spacing * (wholeCount - 1) + let columnWidth = noSpaces / wholeCount + let columns = Array(repeating: GridItem(.fixed(columnWidth), spacing: spacing, alignment: .top), count: Int(wholeCount)) + return (columnWidth, columns) +} diff --git a/Sources/ExyteMediaPicker/Extensions/OrientationTransformationExtensions.swift b/MediaPicker/Sources/ExyteMediaPicker/Extensions/OrientationTransformationExtensions.swift similarity index 100% rename from Sources/ExyteMediaPicker/Extensions/OrientationTransformationExtensions.swift rename to MediaPicker/Sources/ExyteMediaPicker/Extensions/OrientationTransformationExtensions.swift diff --git a/Sources/ExyteMediaPicker/Extensions/Sequence+asyncMap.swift b/MediaPicker/Sources/ExyteMediaPicker/Extensions/Sequence+asyncMap.swift similarity index 100% rename from Sources/ExyteMediaPicker/Extensions/Sequence+asyncMap.swift rename to MediaPicker/Sources/ExyteMediaPicker/Extensions/Sequence+asyncMap.swift diff --git a/Sources/ExyteMediaPicker/Extensions/TimeInterval+Duration.swift b/MediaPicker/Sources/ExyteMediaPicker/Extensions/TimeInterval+Duration.swift similarity index 100% rename from Sources/ExyteMediaPicker/Extensions/TimeInterval+Duration.swift rename to MediaPicker/Sources/ExyteMediaPicker/Extensions/TimeInterval+Duration.swift diff --git a/Sources/ExyteMediaPicker/Extensions/View+.swift b/MediaPicker/Sources/ExyteMediaPicker/Extensions/View+.swift similarity index 100% rename from Sources/ExyteMediaPicker/Extensions/View+.swift rename to MediaPicker/Sources/ExyteMediaPicker/Extensions/View+.swift diff --git a/Sources/ExyteMediaPicker/Extensions/View+NotificationCenter.swift b/MediaPicker/Sources/ExyteMediaPicker/Extensions/View+NotificationCenter.swift similarity index 100% rename from Sources/ExyteMediaPicker/Extensions/View+NotificationCenter.swift rename to MediaPicker/Sources/ExyteMediaPicker/Extensions/View+NotificationCenter.swift diff --git a/Sources/ExyteMediaPicker/Extensions/Zoom+ScrollView.swift b/MediaPicker/Sources/ExyteMediaPicker/Extensions/Zoom+ScrollView.swift similarity index 100% rename from Sources/ExyteMediaPicker/Extensions/Zoom+ScrollView.swift rename to MediaPicker/Sources/ExyteMediaPicker/Extensions/Zoom+ScrollView.swift diff --git a/Sources/ExyteMediaPicker/Drivers/FileManager.swift b/MediaPicker/Sources/ExyteMediaPicker/Managers/FileManager.swift similarity index 100% rename from Sources/ExyteMediaPicker/Drivers/FileManager.swift rename to MediaPicker/Sources/ExyteMediaPicker/Managers/FileManager.swift diff --git a/Sources/ExyteMediaPicker/Drivers/LiveCameraViewModel.swift b/MediaPicker/Sources/ExyteMediaPicker/Managers/LiveCameraViewModel.swift similarity index 100% rename from Sources/ExyteMediaPicker/Drivers/LiveCameraViewModel.swift rename to MediaPicker/Sources/ExyteMediaPicker/Managers/LiveCameraViewModel.swift diff --git a/Sources/ExyteMediaPicker/Drivers/Medias/AlbumMediasProvider.swift b/MediaPicker/Sources/ExyteMediaPicker/Managers/Medias/AlbumMediasProvider.swift similarity index 100% rename from Sources/ExyteMediaPicker/Drivers/Medias/AlbumMediasProvider.swift rename to MediaPicker/Sources/ExyteMediaPicker/Managers/Medias/AlbumMediasProvider.swift diff --git a/Sources/ExyteMediaPicker/Drivers/Medias/AllMediasProvider.swift b/MediaPicker/Sources/ExyteMediaPicker/Managers/Medias/AllMediasProvider.swift similarity index 100% rename from Sources/ExyteMediaPicker/Drivers/Medias/AllMediasProvider.swift rename to MediaPicker/Sources/ExyteMediaPicker/Managers/Medias/AllMediasProvider.swift diff --git a/Sources/ExyteMediaPicker/Drivers/Medias/AllPhotosProvider.swift b/MediaPicker/Sources/ExyteMediaPicker/Managers/Medias/AllPhotosProvider.swift similarity index 100% rename from Sources/ExyteMediaPicker/Drivers/Medias/AllPhotosProvider.swift rename to MediaPicker/Sources/ExyteMediaPicker/Managers/Medias/AllPhotosProvider.swift diff --git a/Sources/ExyteMediaPicker/Drivers/Medias/BaseMediasProvider.swift b/MediaPicker/Sources/ExyteMediaPicker/Managers/Medias/BaseMediasProvider.swift similarity index 100% rename from Sources/ExyteMediaPicker/Drivers/Medias/BaseMediasProvider.swift rename to MediaPicker/Sources/ExyteMediaPicker/Managers/Medias/BaseMediasProvider.swift diff --git a/Sources/ExyteMediaPicker/Drivers/Medias/DefaultAlbumsProvider.swift b/MediaPicker/Sources/ExyteMediaPicker/Managers/Medias/DefaultAlbumsProvider.swift similarity index 100% rename from Sources/ExyteMediaPicker/Drivers/Medias/DefaultAlbumsProvider.swift rename to MediaPicker/Sources/ExyteMediaPicker/Managers/Medias/DefaultAlbumsProvider.swift diff --git a/Sources/ExyteMediaPicker/Drivers/MotionManager.swift b/MediaPicker/Sources/ExyteMediaPicker/Managers/MotionManager.swift similarity index 100% rename from Sources/ExyteMediaPicker/Drivers/MotionManager.swift rename to MediaPicker/Sources/ExyteMediaPicker/Managers/MotionManager.swift diff --git a/Sources/ExyteMediaPicker/Drivers/PermissionsService.swift b/MediaPicker/Sources/ExyteMediaPicker/Managers/PermissionsService.swift similarity index 100% rename from Sources/ExyteMediaPicker/Drivers/PermissionsService.swift rename to MediaPicker/Sources/ExyteMediaPicker/Managers/PermissionsService.swift diff --git a/Sources/ExyteMediaPicker/Drivers/PhotoKit/PHAsset+Utils.swift b/MediaPicker/Sources/ExyteMediaPicker/Managers/PhotoKit/PHAsset+Utils.swift similarity index 100% rename from Sources/ExyteMediaPicker/Drivers/PhotoKit/PHAsset+Utils.swift rename to MediaPicker/Sources/ExyteMediaPicker/Managers/PhotoKit/PHAsset+Utils.swift diff --git a/Sources/ExyteMediaPicker/Drivers/PhotoKit/URL+Utils.swift b/MediaPicker/Sources/ExyteMediaPicker/Managers/PhotoKit/URL+Utils.swift similarity index 100% rename from Sources/ExyteMediaPicker/Drivers/PhotoKit/URL+Utils.swift rename to MediaPicker/Sources/ExyteMediaPicker/Managers/PhotoKit/URL+Utils.swift diff --git a/Sources/ExyteMediaPicker/Drivers/Selection/CameraSelectionService.swift b/MediaPicker/Sources/ExyteMediaPicker/Managers/Selection/CameraSelectionService.swift similarity index 100% rename from Sources/ExyteMediaPicker/Drivers/Selection/CameraSelectionService.swift rename to MediaPicker/Sources/ExyteMediaPicker/Managers/Selection/CameraSelectionService.swift diff --git a/Sources/ExyteMediaPicker/Drivers/Selection/SelectionParamsHolder.swift b/MediaPicker/Sources/ExyteMediaPicker/Managers/Selection/SelectionParamsHolder.swift similarity index 100% rename from Sources/ExyteMediaPicker/Drivers/Selection/SelectionParamsHolder.swift rename to MediaPicker/Sources/ExyteMediaPicker/Managers/Selection/SelectionParamsHolder.swift diff --git a/Sources/ExyteMediaPicker/Drivers/Selection/SelectionService.swift b/MediaPicker/Sources/ExyteMediaPicker/Managers/Selection/SelectionService.swift similarity index 100% rename from Sources/ExyteMediaPicker/Drivers/Selection/SelectionService.swift rename to MediaPicker/Sources/ExyteMediaPicker/Managers/Selection/SelectionService.swift diff --git a/Sources/ExyteMediaPicker/Data/AlbumModel.swift b/MediaPicker/Sources/ExyteMediaPicker/Model/AlbumModel.swift similarity index 100% rename from Sources/ExyteMediaPicker/Data/AlbumModel.swift rename to MediaPicker/Sources/ExyteMediaPicker/Model/AlbumModel.swift diff --git a/Sources/ExyteMediaPicker/Data/AssetMediaModel.swift b/MediaPicker/Sources/ExyteMediaPicker/Model/AssetMediaModel.swift similarity index 100% rename from Sources/ExyteMediaPicker/Data/AssetMediaModel.swift rename to MediaPicker/Sources/ExyteMediaPicker/Model/AssetMediaModel.swift diff --git a/Sources/ExyteMediaPicker/Data/Media.swift b/MediaPicker/Sources/ExyteMediaPicker/Model/Media.swift similarity index 100% rename from Sources/ExyteMediaPicker/Data/Media.swift rename to MediaPicker/Sources/ExyteMediaPicker/Model/Media.swift diff --git a/Sources/ExyteMediaPicker/Data/MediaModelProtocol.swift b/MediaPicker/Sources/ExyteMediaPicker/Model/MediaModelProtocol.swift similarity index 100% rename from Sources/ExyteMediaPicker/Data/MediaModelProtocol.swift rename to MediaPicker/Sources/ExyteMediaPicker/Model/MediaModelProtocol.swift diff --git a/Sources/ExyteMediaPicker/Data/Types.swift b/MediaPicker/Sources/ExyteMediaPicker/Model/Types.swift similarity index 100% rename from Sources/ExyteMediaPicker/Data/Types.swift rename to MediaPicker/Sources/ExyteMediaPicker/Model/Types.swift diff --git a/Sources/ExyteMediaPicker/Data/URLMediaModel.swift b/MediaPicker/Sources/ExyteMediaPicker/Model/URLMediaModel.swift similarity index 100% rename from Sources/ExyteMediaPicker/Data/URLMediaModel.swift rename to MediaPicker/Sources/ExyteMediaPicker/Model/URLMediaModel.swift diff --git a/Sources/ExyteMediaPicker/Modifiers/KeyboardHeightHelper.swift b/MediaPicker/Sources/ExyteMediaPicker/Modifiers/KeyboardHeightHelper.swift similarity index 100% rename from Sources/ExyteMediaPicker/Modifiers/KeyboardHeightHelper.swift rename to MediaPicker/Sources/ExyteMediaPicker/Modifiers/KeyboardHeightHelper.swift diff --git a/Sources/ExyteMediaPicker/Modifiers/MediaButtonStyle.swift b/MediaPicker/Sources/ExyteMediaPicker/Modifiers/MediaButtonStyle.swift similarity index 100% rename from Sources/ExyteMediaPicker/Modifiers/MediaButtonStyle.swift rename to MediaPicker/Sources/ExyteMediaPicker/Modifiers/MediaButtonStyle.swift diff --git a/Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift b/MediaPicker/Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift similarity index 100% rename from Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift rename to MediaPicker/Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift diff --git a/Sources/ExyteMediaPicker/Modifiers/SafeAreaEnvironmentValues.swift b/MediaPicker/Sources/ExyteMediaPicker/Modifiers/SafeAreaEnvironmentValues.swift similarity index 100% rename from Sources/ExyteMediaPicker/Modifiers/SafeAreaEnvironmentValues.swift rename to MediaPicker/Sources/ExyteMediaPicker/Modifiers/SafeAreaEnvironmentValues.swift diff --git a/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/Contents.json b/MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/Contents.json similarity index 100% rename from Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/Contents.json rename to MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/Contents.json diff --git a/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/cameraBG.colorset/Contents.json b/MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/cameraBG.colorset/Contents.json similarity index 100% rename from Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/cameraBG.colorset/Contents.json rename to MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/cameraBG.colorset/Contents.json diff --git a/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/cameraText.colorset/Contents.json b/MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/cameraText.colorset/Contents.json similarity index 100% rename from Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/cameraText.colorset/Contents.json rename to MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/cameraText.colorset/Contents.json diff --git a/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/pickerBG.colorset/Contents.json b/MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/pickerBG.colorset/Contents.json similarity index 100% rename from Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/pickerBG.colorset/Contents.json rename to MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/pickerBG.colorset/Contents.json diff --git a/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/pickerText.colorset/Contents.json b/MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/pickerText.colorset/Contents.json similarity index 100% rename from Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/pickerText.colorset/Contents.json rename to MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/pickerText.colorset/Contents.json diff --git a/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/selection.colorset/Contents.json b/MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/selection.colorset/Contents.json similarity index 100% rename from Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/selection.colorset/Contents.json rename to MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/selection.colorset/Contents.json diff --git a/Sources/ExyteMediaPicker/Resources/Media.xcassets/Contents.json b/MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Contents.json similarity index 100% rename from Sources/ExyteMediaPicker/Resources/Media.xcassets/Contents.json rename to MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Contents.json diff --git a/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/Contents.json b/MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/Contents.json similarity index 100% rename from Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/Contents.json rename to MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/Contents.json diff --git a/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOff.imageset/Contents.json b/MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOff.imageset/Contents.json similarity index 100% rename from Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOff.imageset/Contents.json rename to MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOff.imageset/Contents.json diff --git a/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOff.imageset/Flash.pdf b/MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOff.imageset/Flash.pdf similarity index 100% rename from Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOff.imageset/Flash.pdf rename to MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOff.imageset/Flash.pdf diff --git a/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOn.imageset/Contents.json b/MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOn.imageset/Contents.json similarity index 100% rename from Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOn.imageset/Contents.json rename to MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOn.imageset/Contents.json diff --git a/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOn.imageset/Flash on.pdf b/MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOn.imageset/Flash on.pdf similarity index 100% rename from Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOn.imageset/Flash on.pdf rename to MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOn.imageset/Flash on.pdf diff --git a/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlipCamera.imageset/Change Camera.pdf b/MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlipCamera.imageset/Change Camera.pdf similarity index 100% rename from Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlipCamera.imageset/Change Camera.pdf rename to MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlipCamera.imageset/Change Camera.pdf diff --git a/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlipCamera.imageset/Contents.json b/MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlipCamera.imageset/Contents.json similarity index 100% rename from Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlipCamera.imageset/Contents.json rename to MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlipCamera.imageset/Contents.json diff --git a/Sources/ExyteMediaPicker/Drivers/Theme/Bundle+.swift b/MediaPicker/Sources/ExyteMediaPicker/Theme/Bundle+.swift similarity index 100% rename from Sources/ExyteMediaPicker/Drivers/Theme/Bundle+.swift rename to MediaPicker/Sources/ExyteMediaPicker/Theme/Bundle+.swift diff --git a/Sources/ExyteMediaPicker/Drivers/Theme/MediaPickerTheme.swift b/MediaPicker/Sources/ExyteMediaPicker/Theme/MediaPickerTheme.swift similarity index 100% rename from Sources/ExyteMediaPicker/Drivers/Theme/MediaPickerTheme.swift rename to MediaPicker/Sources/ExyteMediaPicker/Theme/MediaPickerTheme.swift diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/AlbumView/AlbumView.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Pages/AlbumView/AlbumView.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Screens/AlbumView/AlbumView.swift diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/MediaCell.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/AlbumView/MediaCell.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Pages/AlbumView/MediaCell.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Screens/AlbumView/MediaCell.swift diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumView/MediaViewModel.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/AlbumView/MediaViewModel.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Pages/AlbumView/MediaViewModel.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Screens/AlbumView/MediaViewModel.swift diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumCell.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumCell.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumCell.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumCell.swift diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumCellViewModel.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumCellViewModel.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumCellViewModel.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumCellViewModel.swift diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsView.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumsView.swift similarity index 82% rename from Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsView.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumsView.swift index baff1ff..3365b49 100644 --- a/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsView.swift +++ b/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumsView.swift @@ -21,11 +21,6 @@ struct AlbumsView: View { let massFilterClosure: MediaPicker.MassFilterClosure? @State private var showingLoadingCell = false - - let minColumnWidth = 100.0 - private var columns: [GridItem] { - [GridItem(.adaptive(minimum: minColumnWidth), spacing: 0, alignment: .top)] - } private var cellPadding: EdgeInsets { EdgeInsets(top: 2, leading: 2, bottom: 8, trailing: 2) @@ -44,7 +39,7 @@ struct AlbumsView: View { .font(.title3) .foregroundColor(theme.main.pickerText) } else { - let columnWidth = calculateColumnWidth(UIScreen.main.bounds.width) + let (columnWidth, columns) = calculateColumnWidth(spacing: 0) LazyVGrid(columns: columns, spacing: 0) { ForEach(viewModel.albums) { album in AlbumCell(viewModel: AlbumCellViewModel(album: album), size: columnWidth) @@ -65,9 +60,4 @@ struct AlbumsView: View { viewModel.onStop() } } - - func calculateColumnWidth(_ gridWidth: CGFloat) -> CGFloat { - let wholeCount = CGFloat(Int(gridWidth / minColumnWidth)) - return gridWidth / wholeCount - } } diff --git a/Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsViewModel.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumsViewModel.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Pages/AlbumsView/AlbumsViewModel.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumsViewModel.swift diff --git a/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraSelectionContainer.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/Camera/CameraSelectionContainer.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Pages/Camera/CameraSelectionContainer.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Screens/Camera/CameraSelectionContainer.swift diff --git a/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraView.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/Camera/CameraView.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Pages/Camera/CameraView.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Screens/Camera/CameraView.swift diff --git a/Sources/ExyteMediaPicker/Views/Pages/Camera/CameraViewModel.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/Camera/CameraViewModel.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Pages/Camera/CameraViewModel.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Screens/Camera/CameraViewModel.swift diff --git a/Sources/ExyteMediaPicker/Views/Pages/Camera/LiveCameraView.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/Camera/LiveCameraView.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Pages/Camera/LiveCameraView.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Screens/Camera/LiveCameraView.swift diff --git a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/Fullscreen/FullscreenCell.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCell.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Screens/Fullscreen/FullscreenCell.swift diff --git a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCellViewModel.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/Fullscreen/FullscreenCellViewModel.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenCellViewModel.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Screens/Fullscreen/FullscreenCellViewModel.swift diff --git a/Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/Fullscreen/FullscreenContainer.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Pages/Fullscreen/FullscreenContainer.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Screens/Fullscreen/FullscreenContainer.swift diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/AlbumSelectionView.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/AlbumSelectionView.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Pages/MediaPicker/AlbumSelectionView.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/AlbumSelectionView.swift diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/GenenricsTrick.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/GenenricsTrick.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Pages/MediaPicker/GenenricsTrick.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/GenenricsTrick.swift diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/MediaPicker.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPicker.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/MediaPicker.swift diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPickerMode.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/MediaPickerMode.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPickerMode.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/MediaPickerMode.swift diff --git a/Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPickerViewModel.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/MediaPickerViewModel.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Pages/MediaPicker/MediaPickerViewModel.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/MediaPickerViewModel.swift diff --git a/Sources/ExyteMediaPicker/Views/Widgets/CameraStubView.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/CameraStubView.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Widgets/CameraStubView.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/CameraStubView.swift diff --git a/Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionActionView.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionActionView.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionActionView.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionActionView.swift diff --git a/Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionsErrorView.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionsErrorView.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionsErrorView.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionsErrorView.swift diff --git a/Sources/ExyteMediaPicker/Views/Widgets/LimitedLibraryPickerProxyView.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/LimitedLibraryPickerProxyView.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Widgets/LimitedLibraryPickerProxyView.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/LimitedLibraryPickerProxyView.swift diff --git a/Sources/ExyteMediaPicker/Views/Widgets/LiveCameraCell.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/LiveCameraCell.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Widgets/LiveCameraCell.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/LiveCameraCell.swift diff --git a/Sources/ExyteMediaPicker/Views/Widgets/MediasGrid.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/MediasGrid.swift similarity index 68% rename from Sources/ExyteMediaPicker/Views/Widgets/MediasGrid.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/MediasGrid.swift index b82878b..e89da6d 100644 --- a/Sources/ExyteMediaPicker/Views/Widgets/MediasGrid.swift +++ b/MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/MediasGrid.swift @@ -15,11 +15,6 @@ where Element: Identifiable, Camera: View, Content: View, LoadingCell: View { @Environment(\.mediaPickerTheme) private var theme - let minColumnWidth: CGFloat = 100 - private var columns: [GridItem] { - [GridItem(.adaptive(minimum: minColumnWidth), spacing: theme.cellStyle.columnsSpacing, alignment: .top)] - } - public init(_ data: [Element], @ViewBuilder camera: @escaping () -> Camera, @ViewBuilder content: @escaping (Element, Int, CGFloat) -> Content, @@ -31,7 +26,7 @@ where Element: Identifiable, Camera: View, Content: View, LoadingCell: View { } public var body: some View { - let columnWidth = calculateColumnWidth(UIScreen.main.bounds.width) + let (columnWidth, columns) = calculateColumnWidth(spacing: theme.cellStyle.columnsSpacing) LazyVGrid(columns: columns, spacing: theme.cellStyle.rowSpacing) { camera() ForEach(data.indices, id: \.self) { index in @@ -40,10 +35,4 @@ where Element: Identifiable, Camera: View, Content: View, LoadingCell: View { loadingCell() } } - - func calculateColumnWidth(_ gridWidth: CGFloat) -> CGFloat { - let wholeCount = CGFloat(Int(gridWidth / minColumnWidth)) - let noSpaces = gridWidth - theme.cellStyle.columnsSpacing * (wholeCount - 1) - return noSpaces / wholeCount - } } diff --git a/Sources/ExyteMediaPicker/Views/Widgets/PlayerUIView.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/PlayerUIView.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Widgets/PlayerUIView.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/PlayerUIView.swift diff --git a/Sources/ExyteMediaPicker/Views/Widgets/SelectableView.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/SelectableView.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Widgets/SelectableView.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/SelectableView.swift diff --git a/Sources/ExyteMediaPicker/Views/Widgets/SelectionIndicatorView.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/SelectionIndicatorView.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Widgets/SelectionIndicatorView.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/SelectionIndicatorView.swift diff --git a/Sources/ExyteMediaPicker/Views/Widgets/Thumbnail/ThumbnailPlaceholder.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/Thumbnail/ThumbnailPlaceholder.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Widgets/Thumbnail/ThumbnailPlaceholder.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/Thumbnail/ThumbnailPlaceholder.swift diff --git a/Sources/ExyteMediaPicker/Views/Widgets/Thumbnail/ThumbnailView.swift b/MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/Thumbnail/ThumbnailView.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Widgets/Thumbnail/ThumbnailView.swift rename to MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/Thumbnail/ThumbnailView.swift diff --git a/Tests/MediaPickerTests/Extensions/TimeInterval+Init.swift b/MediaPicker/Tests/MediaPickerTests/Extensions/TimeInterval+Init.swift similarity index 100% rename from Tests/MediaPickerTests/Extensions/TimeInterval+Init.swift rename to MediaPicker/Tests/MediaPickerTests/Extensions/TimeInterval+Init.swift diff --git a/Tests/MediaPickerTests/Tests/Extensions/TimeIntervalTests.swift b/MediaPicker/Tests/MediaPickerTests/Tests/Extensions/TimeIntervalTests.swift similarity index 100% rename from Tests/MediaPickerTests/Tests/Extensions/TimeIntervalTests.swift rename to MediaPicker/Tests/MediaPickerTests/Tests/Extensions/TimeIntervalTests.swift diff --git a/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj b/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj index e157354..25fea2a 100644 --- a/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj +++ b/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 60; + objectVersion = 56; objects = { /* Begin PBXBuildFile section */ @@ -34,6 +34,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 5B4DBAA62D92E4A80067A006 /* MediaPicker */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = MediaPicker; path = ../MediaPicker; sourceTree = SOURCE_ROOT; }; 5BFF68312AD689C80099D333 /* MediaPickerExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MediaPickerExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 5BFF68432AD68B990099D333 /* MediaCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaCell.swift; sourceTree = ""; }; 5BFF68442AD68B990099D333 /* FilterMediaPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterMediaPicker.swift; sourceTree = ""; }; @@ -75,9 +76,9 @@ 5BFF68282AD689C80099D333 = { isa = PBXGroup; children = ( + 5B4DBAA62D92E4A80067A006 /* MediaPicker */, 5BFF68422AD68B990099D333 /* MediaPickerExample */, 5BFF68322AD689C80099D333 /* Products */, - 5BFF68572AD68C510099D333 /* Frameworks */, ); sourceTree = ""; }; @@ -112,13 +113,6 @@ path = "Preview Content"; sourceTree = ""; }; - 5BFF68572AD68C510099D333 /* Frameworks */ = { - isa = PBXGroup; - children = ( - ); - name = Frameworks; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -182,7 +176,6 @@ ); mainGroup = 5BFF68282AD689C80099D333; packageReferences = ( - 5B0BE93E2D7AFCFE0011FCDB /* XCLocalSwiftPackageReference "../../MediaPicker" */, ); productRefGroup = 5BFF68322AD689C80099D333 /* Products */; projectDirPath = ""; @@ -366,7 +359,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = exyte.MediaPickerExample; + PRODUCT_BUNDLE_IDENTIFIER = com.exyte.MediaPickerExample; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; @@ -403,7 +396,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = exyte.MediaPickerExample; + PRODUCT_BUNDLE_IDENTIFIER = com.exyte.MediaPickerExample; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; @@ -438,13 +431,6 @@ }; /* End XCConfigurationList section */ -/* Begin XCLocalSwiftPackageReference section */ - 5B0BE93E2D7AFCFE0011FCDB /* XCLocalSwiftPackageReference "../../MediaPicker" */ = { - isa = XCLocalSwiftPackageReference; - relativePath = ../../MediaPicker; - }; -/* End XCLocalSwiftPackageReference section */ - /* Begin XCSwiftPackageProductDependency section */ 13738D1B2AE272FA005BBAD5 /* ExyteMediaPicker */ = { isa = XCSwiftPackageProductDependency; diff --git a/README.md b/README.md index 3e3de45..6fd9d8d 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,8 @@ ![](https://img.shields.io/github/v/tag/exyte/MediaPicker?label=Version) [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fexyte%2FMediaPicker%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/exyte/MediaPicker) [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fexyte%2FMediaPicker%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/exyte/MediaPicker) -[![SPM Compatible](https://img.shields.io/badge/SwiftPM-Compatible-brightgreen.svg)](https://swiftpackageindex.com/exyte/MediaPicker) -[![Cocoapods Compatible](https://img.shields.io/badge/cocoapods-Deprecated%20after%202.2.3-yellow.svg)](https://cocoapods.org/pods/ExyteMediaPicker) +[![SPM](https://img.shields.io/badge/SPM-Compatible-brightgreen.svg)](https://swiftpackageindex.com/exyte/MediaPicker) +[![Cocoapods](https://img.shields.io/badge/Cocoapods-Deprecated%20after%202.2.3-yellow.svg)](https://cocoapods.org/pods/ExyteMediaPicker) [![License: MIT](https://img.shields.io/badge/License-MIT-black.svg)](https://opensource.org/licenses/MIT) # Features @@ -214,7 +214,7 @@ Here is an example of how you can customize colors and elements to create a cust To try out the MediaPicker examples: - Clone the repo `https://github.com/exyte/MediaPicker.git` -- Open `Examples/Examples.xcworkspace` in the Xcode +- Open `MediaPickerExample.xcodeproj` in the Xcode - Run it! ## Installation @@ -227,18 +227,6 @@ dependencies: [ ] ``` -### CocoaPods - -```ruby -pod 'ExyteMediaPicker' -``` - -### Carthage - -```ogdl -github "Exyte/MediaPicker" -``` - ## Requirements * iOS 17+ From 53eb9ccb74089412a4f812400b04d7698452f378 Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Tue, 25 Mar 2025 21:41:22 +0700 Subject: [PATCH 53/70] Restore the structure --- .../project.pbxproj | 19 +++++++++++++++--- MediaPicker/Package.swift => Package.swift | 0 .../Extensions/Collection+.swift | 0 .../Extensions/ColumnCalculation.swift | 0 .../OrientationTransformationExtensions.swift | 0 .../Extensions/Sequence+asyncMap.swift | 0 .../Extensions/TimeInterval+Duration.swift | 0 .../ExyteMediaPicker/Extensions/View+.swift | 0 .../Extensions/View+NotificationCenter.swift | 0 .../Extensions/Zoom+ScrollView.swift | 0 .../Managers/FileManager.swift | 0 .../Managers/LiveCameraViewModel.swift | 0 .../Managers/Medias/AlbumMediasProvider.swift | 0 .../Managers/Medias/AllMediasProvider.swift | 0 .../Managers/Medias/AllPhotosProvider.swift | 0 .../Managers/Medias/BaseMediasProvider.swift | 0 .../Medias/DefaultAlbumsProvider.swift | 0 .../Managers/MotionManager.swift | 0 .../Managers/PermissionsService.swift | 0 .../Managers/PhotoKit/PHAsset+Utils.swift | 0 .../Managers/PhotoKit/URL+Utils.swift | 0 .../Selection/CameraSelectionService.swift | 0 .../Selection/SelectionParamsHolder.swift | 0 .../Managers/Selection/SelectionService.swift | 0 .../ExyteMediaPicker/Model/AlbumModel.swift | 0 .../Model/AssetMediaModel.swift | 0 .../ExyteMediaPicker/Model/Media.swift | 0 .../Model/MediaModelProtocol.swift | 0 .../ExyteMediaPicker/Model/Types.swift | 0 .../Model/URLMediaModel.swift | 0 .../Modifiers/KeyboardHeightHelper.swift | 0 .../Modifiers/MediaButtonStyle.swift | 0 .../Modifiers/MediaPickerThemeModifier.swift | 0 .../Modifiers/SafeAreaEnvironmentValues.swift | 0 .../Media.xcassets/Colors/Contents.json | 0 .../Colors/cameraBG.colorset/Contents.json | 0 .../Colors/cameraText.colorset/Contents.json | 0 .../Colors/pickerBG.colorset/Contents.json | 0 .../Colors/pickerText.colorset/Contents.json | 0 .../Colors/selection.colorset/Contents.json | 0 .../Resources/Media.xcassets/Contents.json | 0 .../Media.xcassets/Images/Contents.json | 0 .../Images/FlashOff.imageset/Contents.json | 0 .../Images/FlashOff.imageset/Flash.pdf | Bin .../Images/FlashOn.imageset/Contents.json | 0 .../Images/FlashOn.imageset/Flash on.pdf | Bin .../FlipCamera.imageset/Change Camera.pdf | Bin .../Images/FlipCamera.imageset/Contents.json | 0 .../ExyteMediaPicker/Theme/Bundle+.swift | 0 .../Theme/MediaPickerTheme.swift | 0 .../Views/Screens/AlbumView/AlbumView.swift | 0 .../Views/Screens/AlbumView/MediaCell.swift | 0 .../Screens/AlbumView/MediaViewModel.swift | 0 .../Views/Screens/AlbumsView/AlbumCell.swift | 0 .../AlbumsView/AlbumCellViewModel.swift | 0 .../Views/Screens/AlbumsView/AlbumsView.swift | 0 .../Screens/AlbumsView/AlbumsViewModel.swift | 0 .../Camera/CameraSelectionContainer.swift | 0 .../Views/Screens/Camera/CameraView.swift | 0 .../Screens/Camera/CameraViewModel.swift | 0 .../Views/Screens/Camera/LiveCameraView.swift | 0 .../Screens/Fullscreen/FullscreenCell.swift | 0 .../Fullscreen/FullscreenCellViewModel.swift | 0 .../Fullscreen/FullscreenContainer.swift | 0 .../MediaPicker/AlbumSelectionView.swift | 0 .../Screens/MediaPicker/GenenricsTrick.swift | 0 .../Screens/MediaPicker/MediaPicker.swift | 0 .../Screens/MediaPicker/MediaPickerMode.swift | 0 .../MediaPicker/MediaPickerViewModel.swift | 0 .../Views/Widgets/CameraStubView.swift | 0 .../Widgets/Errors/PermissionActionView.swift | 0 .../Widgets/Errors/PermissionsErrorView.swift | 0 .../LimitedLibraryPickerProxyView.swift | 0 .../Views/Widgets/LiveCameraCell.swift | 0 .../Views/Widgets/MediasGrid.swift | 0 .../Views/Widgets/PlayerUIView.swift | 0 .../Views/Widgets/SelectableView.swift | 0 .../Widgets/SelectionIndicatorView.swift | 0 .../Thumbnail/ThumbnailPlaceholder.swift | 0 .../Widgets/Thumbnail/ThumbnailView.swift | 0 .../Extensions/TimeInterval+Init.swift | 0 .../Tests/Extensions/TimeIntervalTests.swift | 0 82 files changed, 16 insertions(+), 3 deletions(-) rename MediaPicker/Package.swift => Package.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Extensions/Collection+.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Extensions/ColumnCalculation.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Extensions/OrientationTransformationExtensions.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Extensions/Sequence+asyncMap.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Extensions/TimeInterval+Duration.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Extensions/View+.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Extensions/View+NotificationCenter.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Extensions/Zoom+ScrollView.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Managers/FileManager.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Managers/LiveCameraViewModel.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Managers/Medias/AlbumMediasProvider.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Managers/Medias/AllMediasProvider.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Managers/Medias/AllPhotosProvider.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Managers/Medias/BaseMediasProvider.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Managers/Medias/DefaultAlbumsProvider.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Managers/MotionManager.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Managers/PermissionsService.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Managers/PhotoKit/PHAsset+Utils.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Managers/PhotoKit/URL+Utils.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Managers/Selection/CameraSelectionService.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Managers/Selection/SelectionParamsHolder.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Managers/Selection/SelectionService.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Model/AlbumModel.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Model/AssetMediaModel.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Model/Media.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Model/MediaModelProtocol.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Model/Types.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Model/URLMediaModel.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Modifiers/KeyboardHeightHelper.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Modifiers/MediaButtonStyle.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Modifiers/SafeAreaEnvironmentValues.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Resources/Media.xcassets/Colors/Contents.json (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Resources/Media.xcassets/Colors/cameraBG.colorset/Contents.json (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Resources/Media.xcassets/Colors/cameraText.colorset/Contents.json (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Resources/Media.xcassets/Colors/pickerBG.colorset/Contents.json (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Resources/Media.xcassets/Colors/pickerText.colorset/Contents.json (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Resources/Media.xcassets/Colors/selection.colorset/Contents.json (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Resources/Media.xcassets/Contents.json (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Resources/Media.xcassets/Images/Contents.json (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOff.imageset/Contents.json (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOff.imageset/Flash.pdf (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOn.imageset/Contents.json (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOn.imageset/Flash on.pdf (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Resources/Media.xcassets/Images/FlipCamera.imageset/Change Camera.pdf (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Resources/Media.xcassets/Images/FlipCamera.imageset/Contents.json (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Theme/Bundle+.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Theme/MediaPickerTheme.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Screens/AlbumView/AlbumView.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Screens/AlbumView/MediaCell.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Screens/AlbumView/MediaViewModel.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumCell.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumCellViewModel.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumsView.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumsViewModel.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Screens/Camera/CameraSelectionContainer.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Screens/Camera/CameraView.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Screens/Camera/CameraViewModel.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Screens/Camera/LiveCameraView.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Screens/Fullscreen/FullscreenCell.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Screens/Fullscreen/FullscreenCellViewModel.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Screens/Fullscreen/FullscreenContainer.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Screens/MediaPicker/AlbumSelectionView.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Screens/MediaPicker/GenenricsTrick.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Screens/MediaPicker/MediaPicker.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Screens/MediaPicker/MediaPickerMode.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Screens/MediaPicker/MediaPickerViewModel.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Widgets/CameraStubView.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Widgets/Errors/PermissionActionView.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Widgets/Errors/PermissionsErrorView.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Widgets/LimitedLibraryPickerProxyView.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Widgets/LiveCameraCell.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Widgets/MediasGrid.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Widgets/PlayerUIView.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Widgets/SelectableView.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Widgets/SelectionIndicatorView.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Widgets/Thumbnail/ThumbnailPlaceholder.swift (100%) rename {MediaPicker/Sources => Sources}/ExyteMediaPicker/Views/Widgets/Thumbnail/ThumbnailView.swift (100%) rename {MediaPicker/Tests => Tests}/MediaPickerTests/Extensions/TimeInterval+Init.swift (100%) rename {MediaPicker/Tests => Tests}/MediaPickerTests/Tests/Extensions/TimeIntervalTests.swift (100%) diff --git a/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj b/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj index 25fea2a..213f62e 100644 --- a/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj +++ b/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 56; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ @@ -19,6 +19,7 @@ 5B4D33E12D689AF900E6C069 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B4D33E02D689AF900E6C069 /* ExyteMediaPicker */; }; 5B4D33E42D689FA100E6C069 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B4D33E32D689FA100E6C069 /* ExyteMediaPicker */; }; 5B4D33E72D68A66600E6C069 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B4D33E62D68A66600E6C069 /* ExyteMediaPicker */; }; + 5B4DBAD62D92F7D20067A006 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B4DBAD52D92F7D20067A006 /* ExyteMediaPicker */; }; 5B9275C72D6F2DC700833284 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B9275C62D6F2DC700833284 /* ExyteMediaPicker */; }; 5BC670572D6C8B250023E666 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5BC670562D6C8B250023E666 /* ExyteMediaPicker */; }; 5BC6705A2D6C8B810023E666 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5BC670592D6C8B810023E666 /* ExyteMediaPicker */; }; @@ -34,7 +35,6 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 5B4DBAA62D92E4A80067A006 /* MediaPicker */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = MediaPicker; path = ../MediaPicker; sourceTree = SOURCE_ROOT; }; 5BFF68312AD689C80099D333 /* MediaPickerExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MediaPickerExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 5BFF68432AD68B990099D333 /* MediaCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaCell.swift; sourceTree = ""; }; 5BFF68442AD68B990099D333 /* FilterMediaPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterMediaPicker.swift; sourceTree = ""; }; @@ -66,6 +66,7 @@ 5B32A66C2D79BC6D00DF5348 /* ExyteMediaPicker in Frameworks */, 5B0BE9402D7AFCFE0011FCDB /* ExyteMediaPicker in Frameworks */, 5BC670572D6C8B250023E666 /* ExyteMediaPicker in Frameworks */, + 5B4DBAD62D92F7D20067A006 /* ExyteMediaPicker in Frameworks */, 5B4D33E12D689AF900E6C069 /* ExyteMediaPicker in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -76,7 +77,6 @@ 5BFF68282AD689C80099D333 = { isa = PBXGroup; children = ( - 5B4DBAA62D92E4A80067A006 /* MediaPicker */, 5BFF68422AD68B990099D333 /* MediaPickerExample */, 5BFF68322AD689C80099D333 /* Products */, ); @@ -146,6 +146,7 @@ 5B0BE9392D7AF6470011FCDB /* ExyteMediaPicker */, 5B0BE93C2D7AF6940011FCDB /* ExyteMediaPicker */, 5B0BE93F2D7AFCFE0011FCDB /* ExyteMediaPicker */, + 5B4DBAD52D92F7D20067A006 /* ExyteMediaPicker */, ); productName = MediaPickerExample; productReference = 5BFF68312AD689C80099D333 /* MediaPickerExample.app */; @@ -176,6 +177,7 @@ ); mainGroup = 5BFF68282AD689C80099D333; packageReferences = ( + 5B4DBAD42D92F7D20067A006 /* XCLocalSwiftPackageReference "../../@MediaPicker" */, ); productRefGroup = 5BFF68322AD689C80099D333 /* Products */; projectDirPath = ""; @@ -431,6 +433,13 @@ }; /* End XCConfigurationList section */ +/* Begin XCLocalSwiftPackageReference section */ + 5B4DBAD42D92F7D20067A006 /* XCLocalSwiftPackageReference "../../@MediaPicker" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = "../../@MediaPicker"; + }; +/* End XCLocalSwiftPackageReference section */ + /* Begin XCSwiftPackageProductDependency section */ 13738D1B2AE272FA005BBAD5 /* ExyteMediaPicker */ = { isa = XCSwiftPackageProductDependency; @@ -480,6 +489,10 @@ isa = XCSwiftPackageProductDependency; productName = ExyteMediaPicker; }; + 5B4DBAD52D92F7D20067A006 /* ExyteMediaPicker */ = { + isa = XCSwiftPackageProductDependency; + productName = ExyteMediaPicker; + }; 5B9275C62D6F2DC700833284 /* ExyteMediaPicker */ = { isa = XCSwiftPackageProductDependency; productName = ExyteMediaPicker; diff --git a/MediaPicker/Package.swift b/Package.swift similarity index 100% rename from MediaPicker/Package.swift rename to Package.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Extensions/Collection+.swift b/Sources/ExyteMediaPicker/Extensions/Collection+.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Extensions/Collection+.swift rename to Sources/ExyteMediaPicker/Extensions/Collection+.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Extensions/ColumnCalculation.swift b/Sources/ExyteMediaPicker/Extensions/ColumnCalculation.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Extensions/ColumnCalculation.swift rename to Sources/ExyteMediaPicker/Extensions/ColumnCalculation.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Extensions/OrientationTransformationExtensions.swift b/Sources/ExyteMediaPicker/Extensions/OrientationTransformationExtensions.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Extensions/OrientationTransformationExtensions.swift rename to Sources/ExyteMediaPicker/Extensions/OrientationTransformationExtensions.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Extensions/Sequence+asyncMap.swift b/Sources/ExyteMediaPicker/Extensions/Sequence+asyncMap.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Extensions/Sequence+asyncMap.swift rename to Sources/ExyteMediaPicker/Extensions/Sequence+asyncMap.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Extensions/TimeInterval+Duration.swift b/Sources/ExyteMediaPicker/Extensions/TimeInterval+Duration.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Extensions/TimeInterval+Duration.swift rename to Sources/ExyteMediaPicker/Extensions/TimeInterval+Duration.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Extensions/View+.swift b/Sources/ExyteMediaPicker/Extensions/View+.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Extensions/View+.swift rename to Sources/ExyteMediaPicker/Extensions/View+.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Extensions/View+NotificationCenter.swift b/Sources/ExyteMediaPicker/Extensions/View+NotificationCenter.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Extensions/View+NotificationCenter.swift rename to Sources/ExyteMediaPicker/Extensions/View+NotificationCenter.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Extensions/Zoom+ScrollView.swift b/Sources/ExyteMediaPicker/Extensions/Zoom+ScrollView.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Extensions/Zoom+ScrollView.swift rename to Sources/ExyteMediaPicker/Extensions/Zoom+ScrollView.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Managers/FileManager.swift b/Sources/ExyteMediaPicker/Managers/FileManager.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Managers/FileManager.swift rename to Sources/ExyteMediaPicker/Managers/FileManager.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Managers/LiveCameraViewModel.swift b/Sources/ExyteMediaPicker/Managers/LiveCameraViewModel.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Managers/LiveCameraViewModel.swift rename to Sources/ExyteMediaPicker/Managers/LiveCameraViewModel.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Managers/Medias/AlbumMediasProvider.swift b/Sources/ExyteMediaPicker/Managers/Medias/AlbumMediasProvider.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Managers/Medias/AlbumMediasProvider.swift rename to Sources/ExyteMediaPicker/Managers/Medias/AlbumMediasProvider.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Managers/Medias/AllMediasProvider.swift b/Sources/ExyteMediaPicker/Managers/Medias/AllMediasProvider.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Managers/Medias/AllMediasProvider.swift rename to Sources/ExyteMediaPicker/Managers/Medias/AllMediasProvider.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Managers/Medias/AllPhotosProvider.swift b/Sources/ExyteMediaPicker/Managers/Medias/AllPhotosProvider.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Managers/Medias/AllPhotosProvider.swift rename to Sources/ExyteMediaPicker/Managers/Medias/AllPhotosProvider.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Managers/Medias/BaseMediasProvider.swift b/Sources/ExyteMediaPicker/Managers/Medias/BaseMediasProvider.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Managers/Medias/BaseMediasProvider.swift rename to Sources/ExyteMediaPicker/Managers/Medias/BaseMediasProvider.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Managers/Medias/DefaultAlbumsProvider.swift b/Sources/ExyteMediaPicker/Managers/Medias/DefaultAlbumsProvider.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Managers/Medias/DefaultAlbumsProvider.swift rename to Sources/ExyteMediaPicker/Managers/Medias/DefaultAlbumsProvider.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Managers/MotionManager.swift b/Sources/ExyteMediaPicker/Managers/MotionManager.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Managers/MotionManager.swift rename to Sources/ExyteMediaPicker/Managers/MotionManager.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Managers/PermissionsService.swift b/Sources/ExyteMediaPicker/Managers/PermissionsService.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Managers/PermissionsService.swift rename to Sources/ExyteMediaPicker/Managers/PermissionsService.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Managers/PhotoKit/PHAsset+Utils.swift b/Sources/ExyteMediaPicker/Managers/PhotoKit/PHAsset+Utils.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Managers/PhotoKit/PHAsset+Utils.swift rename to Sources/ExyteMediaPicker/Managers/PhotoKit/PHAsset+Utils.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Managers/PhotoKit/URL+Utils.swift b/Sources/ExyteMediaPicker/Managers/PhotoKit/URL+Utils.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Managers/PhotoKit/URL+Utils.swift rename to Sources/ExyteMediaPicker/Managers/PhotoKit/URL+Utils.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Managers/Selection/CameraSelectionService.swift b/Sources/ExyteMediaPicker/Managers/Selection/CameraSelectionService.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Managers/Selection/CameraSelectionService.swift rename to Sources/ExyteMediaPicker/Managers/Selection/CameraSelectionService.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Managers/Selection/SelectionParamsHolder.swift b/Sources/ExyteMediaPicker/Managers/Selection/SelectionParamsHolder.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Managers/Selection/SelectionParamsHolder.swift rename to Sources/ExyteMediaPicker/Managers/Selection/SelectionParamsHolder.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Managers/Selection/SelectionService.swift b/Sources/ExyteMediaPicker/Managers/Selection/SelectionService.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Managers/Selection/SelectionService.swift rename to Sources/ExyteMediaPicker/Managers/Selection/SelectionService.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Model/AlbumModel.swift b/Sources/ExyteMediaPicker/Model/AlbumModel.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Model/AlbumModel.swift rename to Sources/ExyteMediaPicker/Model/AlbumModel.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Model/AssetMediaModel.swift b/Sources/ExyteMediaPicker/Model/AssetMediaModel.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Model/AssetMediaModel.swift rename to Sources/ExyteMediaPicker/Model/AssetMediaModel.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Model/Media.swift b/Sources/ExyteMediaPicker/Model/Media.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Model/Media.swift rename to Sources/ExyteMediaPicker/Model/Media.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Model/MediaModelProtocol.swift b/Sources/ExyteMediaPicker/Model/MediaModelProtocol.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Model/MediaModelProtocol.swift rename to Sources/ExyteMediaPicker/Model/MediaModelProtocol.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Model/Types.swift b/Sources/ExyteMediaPicker/Model/Types.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Model/Types.swift rename to Sources/ExyteMediaPicker/Model/Types.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Model/URLMediaModel.swift b/Sources/ExyteMediaPicker/Model/URLMediaModel.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Model/URLMediaModel.swift rename to Sources/ExyteMediaPicker/Model/URLMediaModel.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Modifiers/KeyboardHeightHelper.swift b/Sources/ExyteMediaPicker/Modifiers/KeyboardHeightHelper.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Modifiers/KeyboardHeightHelper.swift rename to Sources/ExyteMediaPicker/Modifiers/KeyboardHeightHelper.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Modifiers/MediaButtonStyle.swift b/Sources/ExyteMediaPicker/Modifiers/MediaButtonStyle.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Modifiers/MediaButtonStyle.swift rename to Sources/ExyteMediaPicker/Modifiers/MediaButtonStyle.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift b/Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift rename to Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Modifiers/SafeAreaEnvironmentValues.swift b/Sources/ExyteMediaPicker/Modifiers/SafeAreaEnvironmentValues.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Modifiers/SafeAreaEnvironmentValues.swift rename to Sources/ExyteMediaPicker/Modifiers/SafeAreaEnvironmentValues.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/Contents.json b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/Contents.json similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/Contents.json rename to Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/Contents.json diff --git a/MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/cameraBG.colorset/Contents.json b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/cameraBG.colorset/Contents.json similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/cameraBG.colorset/Contents.json rename to Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/cameraBG.colorset/Contents.json diff --git a/MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/cameraText.colorset/Contents.json b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/cameraText.colorset/Contents.json similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/cameraText.colorset/Contents.json rename to Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/cameraText.colorset/Contents.json diff --git a/MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/pickerBG.colorset/Contents.json b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/pickerBG.colorset/Contents.json similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/pickerBG.colorset/Contents.json rename to Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/pickerBG.colorset/Contents.json diff --git a/MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/pickerText.colorset/Contents.json b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/pickerText.colorset/Contents.json similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/pickerText.colorset/Contents.json rename to Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/pickerText.colorset/Contents.json diff --git a/MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/selection.colorset/Contents.json b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/selection.colorset/Contents.json similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/selection.colorset/Contents.json rename to Sources/ExyteMediaPicker/Resources/Media.xcassets/Colors/selection.colorset/Contents.json diff --git a/MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Contents.json b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Contents.json similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Contents.json rename to Sources/ExyteMediaPicker/Resources/Media.xcassets/Contents.json diff --git a/MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/Contents.json b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/Contents.json similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/Contents.json rename to Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/Contents.json diff --git a/MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOff.imageset/Contents.json b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOff.imageset/Contents.json similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOff.imageset/Contents.json rename to Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOff.imageset/Contents.json diff --git a/MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOff.imageset/Flash.pdf b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOff.imageset/Flash.pdf similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOff.imageset/Flash.pdf rename to Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOff.imageset/Flash.pdf diff --git a/MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOn.imageset/Contents.json b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOn.imageset/Contents.json similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOn.imageset/Contents.json rename to Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOn.imageset/Contents.json diff --git a/MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOn.imageset/Flash on.pdf b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOn.imageset/Flash on.pdf similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOn.imageset/Flash on.pdf rename to Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlashOn.imageset/Flash on.pdf diff --git a/MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlipCamera.imageset/Change Camera.pdf b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlipCamera.imageset/Change Camera.pdf similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlipCamera.imageset/Change Camera.pdf rename to Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlipCamera.imageset/Change Camera.pdf diff --git a/MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlipCamera.imageset/Contents.json b/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlipCamera.imageset/Contents.json similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlipCamera.imageset/Contents.json rename to Sources/ExyteMediaPicker/Resources/Media.xcassets/Images/FlipCamera.imageset/Contents.json diff --git a/MediaPicker/Sources/ExyteMediaPicker/Theme/Bundle+.swift b/Sources/ExyteMediaPicker/Theme/Bundle+.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Theme/Bundle+.swift rename to Sources/ExyteMediaPicker/Theme/Bundle+.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Theme/MediaPickerTheme.swift b/Sources/ExyteMediaPicker/Theme/MediaPickerTheme.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Theme/MediaPickerTheme.swift rename to Sources/ExyteMediaPicker/Theme/MediaPickerTheme.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/AlbumView/AlbumView.swift b/Sources/ExyteMediaPicker/Views/Screens/AlbumView/AlbumView.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Screens/AlbumView/AlbumView.swift rename to Sources/ExyteMediaPicker/Views/Screens/AlbumView/AlbumView.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/AlbumView/MediaCell.swift b/Sources/ExyteMediaPicker/Views/Screens/AlbumView/MediaCell.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Screens/AlbumView/MediaCell.swift rename to Sources/ExyteMediaPicker/Views/Screens/AlbumView/MediaCell.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/AlbumView/MediaViewModel.swift b/Sources/ExyteMediaPicker/Views/Screens/AlbumView/MediaViewModel.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Screens/AlbumView/MediaViewModel.swift rename to Sources/ExyteMediaPicker/Views/Screens/AlbumView/MediaViewModel.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumCell.swift b/Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumCell.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumCell.swift rename to Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumCell.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumCellViewModel.swift b/Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumCellViewModel.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumCellViewModel.swift rename to Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumCellViewModel.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumsView.swift b/Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumsView.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumsView.swift rename to Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumsView.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumsViewModel.swift b/Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumsViewModel.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumsViewModel.swift rename to Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumsViewModel.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/Camera/CameraSelectionContainer.swift b/Sources/ExyteMediaPicker/Views/Screens/Camera/CameraSelectionContainer.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Screens/Camera/CameraSelectionContainer.swift rename to Sources/ExyteMediaPicker/Views/Screens/Camera/CameraSelectionContainer.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/Camera/CameraView.swift b/Sources/ExyteMediaPicker/Views/Screens/Camera/CameraView.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Screens/Camera/CameraView.swift rename to Sources/ExyteMediaPicker/Views/Screens/Camera/CameraView.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/Camera/CameraViewModel.swift b/Sources/ExyteMediaPicker/Views/Screens/Camera/CameraViewModel.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Screens/Camera/CameraViewModel.swift rename to Sources/ExyteMediaPicker/Views/Screens/Camera/CameraViewModel.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/Camera/LiveCameraView.swift b/Sources/ExyteMediaPicker/Views/Screens/Camera/LiveCameraView.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Screens/Camera/LiveCameraView.swift rename to Sources/ExyteMediaPicker/Views/Screens/Camera/LiveCameraView.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/Fullscreen/FullscreenCell.swift b/Sources/ExyteMediaPicker/Views/Screens/Fullscreen/FullscreenCell.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Screens/Fullscreen/FullscreenCell.swift rename to Sources/ExyteMediaPicker/Views/Screens/Fullscreen/FullscreenCell.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/Fullscreen/FullscreenCellViewModel.swift b/Sources/ExyteMediaPicker/Views/Screens/Fullscreen/FullscreenCellViewModel.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Screens/Fullscreen/FullscreenCellViewModel.swift rename to Sources/ExyteMediaPicker/Views/Screens/Fullscreen/FullscreenCellViewModel.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/Fullscreen/FullscreenContainer.swift b/Sources/ExyteMediaPicker/Views/Screens/Fullscreen/FullscreenContainer.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Screens/Fullscreen/FullscreenContainer.swift rename to Sources/ExyteMediaPicker/Views/Screens/Fullscreen/FullscreenContainer.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/AlbumSelectionView.swift b/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/AlbumSelectionView.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/AlbumSelectionView.swift rename to Sources/ExyteMediaPicker/Views/Screens/MediaPicker/AlbumSelectionView.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/GenenricsTrick.swift b/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/GenenricsTrick.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/GenenricsTrick.swift rename to Sources/ExyteMediaPicker/Views/Screens/MediaPicker/GenenricsTrick.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/MediaPicker.swift b/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/MediaPicker.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/MediaPicker.swift rename to Sources/ExyteMediaPicker/Views/Screens/MediaPicker/MediaPicker.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/MediaPickerMode.swift b/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/MediaPickerMode.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/MediaPickerMode.swift rename to Sources/ExyteMediaPicker/Views/Screens/MediaPicker/MediaPickerMode.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/MediaPickerViewModel.swift b/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/MediaPickerViewModel.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/MediaPickerViewModel.swift rename to Sources/ExyteMediaPicker/Views/Screens/MediaPicker/MediaPickerViewModel.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/CameraStubView.swift b/Sources/ExyteMediaPicker/Views/Widgets/CameraStubView.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/CameraStubView.swift rename to Sources/ExyteMediaPicker/Views/Widgets/CameraStubView.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionActionView.swift b/Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionActionView.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionActionView.swift rename to Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionActionView.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionsErrorView.swift b/Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionsErrorView.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionsErrorView.swift rename to Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionsErrorView.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/LimitedLibraryPickerProxyView.swift b/Sources/ExyteMediaPicker/Views/Widgets/LimitedLibraryPickerProxyView.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/LimitedLibraryPickerProxyView.swift rename to Sources/ExyteMediaPicker/Views/Widgets/LimitedLibraryPickerProxyView.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/LiveCameraCell.swift b/Sources/ExyteMediaPicker/Views/Widgets/LiveCameraCell.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/LiveCameraCell.swift rename to Sources/ExyteMediaPicker/Views/Widgets/LiveCameraCell.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/MediasGrid.swift b/Sources/ExyteMediaPicker/Views/Widgets/MediasGrid.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/MediasGrid.swift rename to Sources/ExyteMediaPicker/Views/Widgets/MediasGrid.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/PlayerUIView.swift b/Sources/ExyteMediaPicker/Views/Widgets/PlayerUIView.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/PlayerUIView.swift rename to Sources/ExyteMediaPicker/Views/Widgets/PlayerUIView.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/SelectableView.swift b/Sources/ExyteMediaPicker/Views/Widgets/SelectableView.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/SelectableView.swift rename to Sources/ExyteMediaPicker/Views/Widgets/SelectableView.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/SelectionIndicatorView.swift b/Sources/ExyteMediaPicker/Views/Widgets/SelectionIndicatorView.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/SelectionIndicatorView.swift rename to Sources/ExyteMediaPicker/Views/Widgets/SelectionIndicatorView.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/Thumbnail/ThumbnailPlaceholder.swift b/Sources/ExyteMediaPicker/Views/Widgets/Thumbnail/ThumbnailPlaceholder.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/Thumbnail/ThumbnailPlaceholder.swift rename to Sources/ExyteMediaPicker/Views/Widgets/Thumbnail/ThumbnailPlaceholder.swift diff --git a/MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/Thumbnail/ThumbnailView.swift b/Sources/ExyteMediaPicker/Views/Widgets/Thumbnail/ThumbnailView.swift similarity index 100% rename from MediaPicker/Sources/ExyteMediaPicker/Views/Widgets/Thumbnail/ThumbnailView.swift rename to Sources/ExyteMediaPicker/Views/Widgets/Thumbnail/ThumbnailView.swift diff --git a/MediaPicker/Tests/MediaPickerTests/Extensions/TimeInterval+Init.swift b/Tests/MediaPickerTests/Extensions/TimeInterval+Init.swift similarity index 100% rename from MediaPicker/Tests/MediaPickerTests/Extensions/TimeInterval+Init.swift rename to Tests/MediaPickerTests/Extensions/TimeInterval+Init.swift diff --git a/MediaPicker/Tests/MediaPickerTests/Tests/Extensions/TimeIntervalTests.swift b/Tests/MediaPickerTests/Tests/Extensions/TimeIntervalTests.swift similarity index 100% rename from MediaPicker/Tests/MediaPickerTests/Tests/Extensions/TimeIntervalTests.swift rename to Tests/MediaPickerTests/Tests/Extensions/TimeIntervalTests.swift From 7067c34b6460890bdeb43ba1ab01faf901860b3e Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Tue, 25 Mar 2025 21:49:41 +0700 Subject: [PATCH 54/70] Fix --- .../MediaPickerExample.xcodeproj/project.pbxproj | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj b/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj index 213f62e..75bd413 100644 --- a/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj +++ b/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 5B32A6692D79BA3100DF5348 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B32A6682D79BA3100DF5348 /* ExyteMediaPicker */; }; 5B32A66C2D79BC6D00DF5348 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B32A66B2D79BC6D00DF5348 /* ExyteMediaPicker */; }; 5B4C32F82D6F2080009844B6 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B4C32F72D6F2080009844B6 /* ExyteMediaPicker */; }; + 5B4D04FB2D92F9F1005262A1 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B4D04FA2D92F9F1005262A1 /* ExyteMediaPicker */; }; 5B4D33E12D689AF900E6C069 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B4D33E02D689AF900E6C069 /* ExyteMediaPicker */; }; 5B4D33E42D689FA100E6C069 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B4D33E32D689FA100E6C069 /* ExyteMediaPicker */; }; 5B4D33E72D68A66600E6C069 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B4D33E62D68A66600E6C069 /* ExyteMediaPicker */; }; @@ -60,6 +61,7 @@ 5BFD73DA2D70840200BD0F67 /* ExyteMediaPicker in Frameworks */, 5B4D33E72D68A66600E6C069 /* ExyteMediaPicker in Frameworks */, 5B0BE93A2D7AF6470011FCDB /* ExyteMediaPicker in Frameworks */, + 5B4D04FB2D92F9F1005262A1 /* ExyteMediaPicker in Frameworks */, 13738D1C2AE272FA005BBAD5 /* ExyteMediaPicker in Frameworks */, 5BC6705A2D6C8B810023E666 /* ExyteMediaPicker in Frameworks */, 5B4C32F82D6F2080009844B6 /* ExyteMediaPicker in Frameworks */, @@ -147,6 +149,7 @@ 5B0BE93C2D7AF6940011FCDB /* ExyteMediaPicker */, 5B0BE93F2D7AFCFE0011FCDB /* ExyteMediaPicker */, 5B4DBAD52D92F7D20067A006 /* ExyteMediaPicker */, + 5B4D04FA2D92F9F1005262A1 /* ExyteMediaPicker */, ); productName = MediaPickerExample; productReference = 5BFF68312AD689C80099D333 /* MediaPickerExample.app */; @@ -177,7 +180,7 @@ ); mainGroup = 5BFF68282AD689C80099D333; packageReferences = ( - 5B4DBAD42D92F7D20067A006 /* XCLocalSwiftPackageReference "../../@MediaPicker" */, + 5B4D04F92D92F9F1005262A1 /* XCLocalSwiftPackageReference "../../MediaPicker" */, ); productRefGroup = 5BFF68322AD689C80099D333 /* Products */; projectDirPath = ""; @@ -434,9 +437,9 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - 5B4DBAD42D92F7D20067A006 /* XCLocalSwiftPackageReference "../../@MediaPicker" */ = { + 5B4D04F92D92F9F1005262A1 /* XCLocalSwiftPackageReference "../../MediaPicker" */ = { isa = XCLocalSwiftPackageReference; - relativePath = "../../@MediaPicker"; + relativePath = ../../MediaPicker; }; /* End XCLocalSwiftPackageReference section */ @@ -477,6 +480,10 @@ isa = XCSwiftPackageProductDependency; productName = ExyteMediaPicker; }; + 5B4D04FA2D92F9F1005262A1 /* ExyteMediaPicker */ = { + isa = XCSwiftPackageProductDependency; + productName = ExyteMediaPicker; + }; 5B4D33E02D689AF900E6C069 /* ExyteMediaPicker */ = { isa = XCSwiftPackageProductDependency; productName = ExyteMediaPicker; From d349b68b27f43c384d5fa61bf6e265b96e379146 Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Wed, 26 Mar 2025 17:37:42 +0700 Subject: [PATCH 55/70] Update readme --- .../MediaPickerExample.xcodeproj/project.pbxproj | 6 ++++-- Package.swift | 1 - README.md | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj b/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj index 75bd413..782d511 100644 --- a/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj +++ b/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj @@ -280,6 +280,7 @@ SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 6.0; }; name = Debug; }; @@ -335,6 +336,7 @@ MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_VERSION = 6.0; VALIDATE_PRODUCT = YES; }; name = Release; @@ -371,7 +373,7 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = 1; }; name = Debug; @@ -408,7 +410,7 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = 1; }; name = Release; diff --git a/Package.swift b/Package.swift index d18485d..3288c9d 100644 --- a/Package.swift +++ b/Package.swift @@ -1,5 +1,4 @@ // swift-tools-version: 5.9 -// The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription diff --git a/README.md b/README.md index 6fd9d8d..2d7191e 100644 --- a/README.md +++ b/README.md @@ -215,7 +215,7 @@ Here is an example of how you can customize colors and elements to create a cust To try out the MediaPicker examples: - Clone the repo `https://github.com/exyte/MediaPicker.git` - Open `MediaPickerExample.xcodeproj` in the Xcode -- Run it! +- Try it! ## Installation From fab9865262047bb34b35e3c798cf2103f8f46ffd Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Thu, 3 Apr 2025 15:07:37 +0700 Subject: [PATCH 56/70] Fix xcode 15 warnings --- .../project.pbxproj | 163 +++--------------- .../MediaPickerExample/AppDelegate.swift | 24 ++- .../MediaPickerExample/ContentView.swift | 2 +- .../MediaPickerExample/Info.plist | 5 + .../Extensions/View+NotificationCenter.swift | 27 --- .../Managers/LiveCameraViewModel.swift | 61 ------- .../Managers/Medias/AlbumMediasProvider.swift | 4 +- .../Managers/Medias/AllMediasProvider.swift | 4 +- .../Managers/Medias/AllPhotosProvider.swift | 5 +- .../Managers/Medias/BaseMediasProvider.swift | 2 - Sources/ExyteMediaPicker/Model/Media.swift | 4 +- .../Model/MediaModelProtocol.swift | 2 +- .../Model/URLMediaModel.swift | 10 +- .../Modifiers/KeyboardHeightHelper.swift | 44 ----- .../Screens/AlbumView/AlbumView.swift | 7 +- .../Screens/AlbumView/MediaCell.swift | 0 .../Screens/AlbumView/MediaViewModel.swift | 0 .../Screens/AlbumsView/AlbumCell.swift | 0 .../AlbumsView/AlbumCellViewModel.swift | 0 .../Screens/AlbumsView/AlbumsView.swift | 0 .../Screens/AlbumsView/AlbumsViewModel.swift | 0 .../Camera/CameraSelectionContainer.swift | 2 +- .../Screens/Camera/CameraView.swift | 69 ++++---- .../Screens/Camera/CameraViewModel.swift | 155 ++++++++++------- .../Camera}/LiveCameraCell.swift | 17 +- .../Screens/Camera/LiveCameraView.swift | 17 +- .../Screens/Fullscreen/FullscreenCell.swift | 0 .../Fullscreen/FullscreenCellViewModel.swift | 12 +- .../Fullscreen/FullscreenContainer.swift | 5 +- .../MediaPicker/AlbumSelectionView.swift | 0 .../Screens/MediaPicker/GenenricsTrick.swift | 0 .../Screens/MediaPicker/MediaPicker.swift | 0 .../Screens/MediaPicker/MediaPickerMode.swift | 0 .../MediaPicker/MediaPickerViewModel.swift | 0 .../Theme/MediaPickerTheme.swift | 12 +- .../Errors/PermissionActionView.swift | 9 +- .../Errors/PermissionsErrorView.swift | 0 .../{ => Utils}/Extensions/Collection+.swift | 0 .../Extensions/ColumnCalculation.swift | 0 .../OrientationTransformationExtensions.swift | 12 -- .../Extensions/Sequence+asyncMap.swift | 0 .../Extensions/TimeInterval+Duration.swift | 0 .../{ => Utils}/Extensions/View+.swift | 13 +- .../Extensions/View+NotificationCenter.swift | 15 ++ .../Extensions/Zoom+ScrollView.swift | 0 .../Modifiers/KeyboardHeightHelper.swift | 64 +++++++ .../Modifiers/MediaButtonStyle.swift | 0 .../Modifiers/MediaPickerThemeModifier.swift | 23 +++ .../Modifiers/SafeAreaEnvironmentValues.swift | 17 +- .../Thumbnail/ThumbnailPlaceholder.swift | 0 .../Thumbnail/ThumbnailView.swift | 0 .../Utils/Widgets/AsyncButton.swift | 23 +++ .../Widgets/CameraStubView.swift | 0 .../LimitedLibraryPickerProxyView.swift | 37 ++++ .../{Views => Utils}/Widgets/MediasGrid.swift | 0 .../Widgets/PlayerUIView.swift | 0 .../Widgets/SelectableView.swift | 0 .../Widgets/SelectionIndicatorView.swift | 2 +- .../LimitedLibraryPickerProxyView.swift | 50 ------ 59 files changed, 417 insertions(+), 501 deletions(-) create mode 100644 MediaPickerExample/MediaPickerExample/Info.plist delete mode 100644 Sources/ExyteMediaPicker/Extensions/View+NotificationCenter.swift delete mode 100644 Sources/ExyteMediaPicker/Managers/LiveCameraViewModel.swift delete mode 100644 Sources/ExyteMediaPicker/Modifiers/KeyboardHeightHelper.swift rename Sources/ExyteMediaPicker/{Views => }/Screens/AlbumView/AlbumView.swift (95%) rename Sources/ExyteMediaPicker/{Views => }/Screens/AlbumView/MediaCell.swift (100%) rename Sources/ExyteMediaPicker/{Views => }/Screens/AlbumView/MediaViewModel.swift (100%) rename Sources/ExyteMediaPicker/{Views => }/Screens/AlbumsView/AlbumCell.swift (100%) rename Sources/ExyteMediaPicker/{Views => }/Screens/AlbumsView/AlbumCellViewModel.swift (100%) rename Sources/ExyteMediaPicker/{Views => }/Screens/AlbumsView/AlbumsView.swift (100%) rename Sources/ExyteMediaPicker/{Views => }/Screens/AlbumsView/AlbumsViewModel.swift (100%) rename Sources/ExyteMediaPicker/{Views => }/Screens/Camera/CameraSelectionContainer.swift (99%) rename Sources/ExyteMediaPicker/{Views => }/Screens/Camera/CameraView.swift (77%) rename Sources/ExyteMediaPicker/{Views => }/Screens/Camera/CameraViewModel.swift (63%) rename Sources/ExyteMediaPicker/{Views/Widgets => Screens/Camera}/LiveCameraCell.swift (54%) rename Sources/ExyteMediaPicker/{Views => }/Screens/Camera/LiveCameraView.swift (72%) rename Sources/ExyteMediaPicker/{Views => }/Screens/Fullscreen/FullscreenCell.swift (100%) rename Sources/ExyteMediaPicker/{Views => }/Screens/Fullscreen/FullscreenCellViewModel.swift (94%) rename Sources/ExyteMediaPicker/{Views => }/Screens/Fullscreen/FullscreenContainer.swift (96%) rename Sources/ExyteMediaPicker/{Views => }/Screens/MediaPicker/AlbumSelectionView.swift (100%) rename Sources/ExyteMediaPicker/{Views => }/Screens/MediaPicker/GenenricsTrick.swift (100%) rename Sources/ExyteMediaPicker/{Views => }/Screens/MediaPicker/MediaPicker.swift (100%) rename Sources/ExyteMediaPicker/{Views => }/Screens/MediaPicker/MediaPickerMode.swift (100%) rename Sources/ExyteMediaPicker/{Views => }/Screens/MediaPicker/MediaPickerViewModel.swift (100%) rename Sources/ExyteMediaPicker/{Views/Widgets => Utils}/Errors/PermissionActionView.swift (87%) rename Sources/ExyteMediaPicker/{Views/Widgets => Utils}/Errors/PermissionsErrorView.swift (100%) rename Sources/ExyteMediaPicker/{ => Utils}/Extensions/Collection+.swift (100%) rename Sources/ExyteMediaPicker/{ => Utils}/Extensions/ColumnCalculation.swift (100%) rename Sources/ExyteMediaPicker/{ => Utils}/Extensions/OrientationTransformationExtensions.swift (64%) rename Sources/ExyteMediaPicker/{ => Utils}/Extensions/Sequence+asyncMap.swift (100%) rename Sources/ExyteMediaPicker/{ => Utils}/Extensions/TimeInterval+Duration.swift (100%) rename Sources/ExyteMediaPicker/{ => Utils}/Extensions/View+.swift (66%) create mode 100644 Sources/ExyteMediaPicker/Utils/Extensions/View+NotificationCenter.swift rename Sources/ExyteMediaPicker/{ => Utils}/Extensions/Zoom+ScrollView.swift (100%) create mode 100644 Sources/ExyteMediaPicker/Utils/Modifiers/KeyboardHeightHelper.swift rename Sources/ExyteMediaPicker/{ => Utils}/Modifiers/MediaButtonStyle.swift (100%) rename Sources/ExyteMediaPicker/{ => Utils}/Modifiers/MediaPickerThemeModifier.swift (59%) rename Sources/ExyteMediaPicker/{ => Utils}/Modifiers/SafeAreaEnvironmentValues.swift (63%) rename Sources/ExyteMediaPicker/{Views/Widgets => Utils}/Thumbnail/ThumbnailPlaceholder.swift (100%) rename Sources/ExyteMediaPicker/{Views/Widgets => Utils}/Thumbnail/ThumbnailView.swift (100%) create mode 100644 Sources/ExyteMediaPicker/Utils/Widgets/AsyncButton.swift rename Sources/ExyteMediaPicker/{Views => Utils}/Widgets/CameraStubView.swift (100%) create mode 100644 Sources/ExyteMediaPicker/Utils/Widgets/LimitedLibraryPickerProxyView.swift rename Sources/ExyteMediaPicker/{Views => Utils}/Widgets/MediasGrid.swift (100%) rename Sources/ExyteMediaPicker/{Views => Utils}/Widgets/PlayerUIView.swift (100%) rename Sources/ExyteMediaPicker/{Views => Utils}/Widgets/SelectableView.swift (100%) rename Sources/ExyteMediaPicker/{Views => Utils}/Widgets/SelectionIndicatorView.swift (98%) delete mode 100644 Sources/ExyteMediaPicker/Views/Widgets/LimitedLibraryPickerProxyView.swift diff --git a/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj b/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj index 782d511..a9c3491 100644 --- a/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj +++ b/MediaPickerExample/MediaPickerExample.xcodeproj/project.pbxproj @@ -3,28 +3,11 @@ archiveVersion = 1; classes = { }; - objectVersion = 60; + objectVersion = 56; objects = { /* Begin PBXBuildFile section */ - 13738D1C2AE272FA005BBAD5 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 13738D1B2AE272FA005BBAD5 /* ExyteMediaPicker */; }; - 5B0BE9372D7AF6030011FCDB /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B0BE9362D7AF6030011FCDB /* ExyteMediaPicker */; }; - 5B0BE93A2D7AF6470011FCDB /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B0BE9392D7AF6470011FCDB /* ExyteMediaPicker */; }; - 5B0BE93D2D7AF6940011FCDB /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B0BE93C2D7AF6940011FCDB /* ExyteMediaPicker */; }; - 5B0BE9402D7AFCFE0011FCDB /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B0BE93F2D7AFCFE0011FCDB /* ExyteMediaPicker */; }; - 5B2A1A0F2D6DD583000A2E81 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B2A1A0E2D6DD583000A2E81 /* ExyteMediaPicker */; }; - 5B32A6692D79BA3100DF5348 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B32A6682D79BA3100DF5348 /* ExyteMediaPicker */; }; - 5B32A66C2D79BC6D00DF5348 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B32A66B2D79BC6D00DF5348 /* ExyteMediaPicker */; }; - 5B4C32F82D6F2080009844B6 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B4C32F72D6F2080009844B6 /* ExyteMediaPicker */; }; - 5B4D04FB2D92F9F1005262A1 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B4D04FA2D92F9F1005262A1 /* ExyteMediaPicker */; }; - 5B4D33E12D689AF900E6C069 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B4D33E02D689AF900E6C069 /* ExyteMediaPicker */; }; - 5B4D33E42D689FA100E6C069 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B4D33E32D689FA100E6C069 /* ExyteMediaPicker */; }; - 5B4D33E72D68A66600E6C069 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B4D33E62D68A66600E6C069 /* ExyteMediaPicker */; }; - 5B4DBAD62D92F7D20067A006 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B4DBAD52D92F7D20067A006 /* ExyteMediaPicker */; }; - 5B9275C72D6F2DC700833284 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B9275C62D6F2DC700833284 /* ExyteMediaPicker */; }; - 5BC670572D6C8B250023E666 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5BC670562D6C8B250023E666 /* ExyteMediaPicker */; }; - 5BC6705A2D6C8B810023E666 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5BC670592D6C8B810023E666 /* ExyteMediaPicker */; }; - 5BFD73DA2D70840200BD0F67 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5BFD73D92D70840200BD0F67 /* ExyteMediaPicker */; }; + 5B2068782D9BE32700485134 /* ExyteMediaPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 5B2068772D9BE32700485134 /* ExyteMediaPicker */; }; 5BFF684D2AD68B990099D333 /* MediaCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFF68432AD68B990099D333 /* MediaCell.swift */; }; 5BFF684E2AD68B990099D333 /* FilterMediaPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFF68442AD68B990099D333 /* FilterMediaPicker.swift */; }; 5BFF684F2AD68B990099D333 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5BFF68452AD68B990099D333 /* Assets.xcassets */; }; @@ -36,6 +19,8 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 5B2068742D9BE17100485134 /* MediaPicker */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = MediaPicker; path = ..; sourceTree = ""; }; + 5B2068752D9BE32400485134 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 5BFF68312AD689C80099D333 /* MediaPickerExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MediaPickerExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 5BFF68432AD68B990099D333 /* MediaCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaCell.swift; sourceTree = ""; }; 5BFF68442AD68B990099D333 /* FilterMediaPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterMediaPicker.swift; sourceTree = ""; }; @@ -52,35 +37,27 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5B9275C72D6F2DC700833284 /* ExyteMediaPicker in Frameworks */, - 5B0BE9372D7AF6030011FCDB /* ExyteMediaPicker in Frameworks */, - 5B32A6692D79BA3100DF5348 /* ExyteMediaPicker in Frameworks */, - 5B0BE93D2D7AF6940011FCDB /* ExyteMediaPicker in Frameworks */, - 5B2A1A0F2D6DD583000A2E81 /* ExyteMediaPicker in Frameworks */, - 5B4D33E42D689FA100E6C069 /* ExyteMediaPicker in Frameworks */, - 5BFD73DA2D70840200BD0F67 /* ExyteMediaPicker in Frameworks */, - 5B4D33E72D68A66600E6C069 /* ExyteMediaPicker in Frameworks */, - 5B0BE93A2D7AF6470011FCDB /* ExyteMediaPicker in Frameworks */, - 5B4D04FB2D92F9F1005262A1 /* ExyteMediaPicker in Frameworks */, - 13738D1C2AE272FA005BBAD5 /* ExyteMediaPicker in Frameworks */, - 5BC6705A2D6C8B810023E666 /* ExyteMediaPicker in Frameworks */, - 5B4C32F82D6F2080009844B6 /* ExyteMediaPicker in Frameworks */, - 5B32A66C2D79BC6D00DF5348 /* ExyteMediaPicker in Frameworks */, - 5B0BE9402D7AFCFE0011FCDB /* ExyteMediaPicker in Frameworks */, - 5BC670572D6C8B250023E666 /* ExyteMediaPicker in Frameworks */, - 5B4DBAD62D92F7D20067A006 /* ExyteMediaPicker in Frameworks */, - 5B4D33E12D689AF900E6C069 /* ExyteMediaPicker in Frameworks */, + 5B2068782D9BE32700485134 /* ExyteMediaPicker in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 5B2068762D9BE32700485134 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; 5BFF68282AD689C80099D333 = { isa = PBXGroup; children = ( + 5B2068742D9BE17100485134 /* MediaPicker */, 5BFF68422AD68B990099D333 /* MediaPickerExample */, 5BFF68322AD689C80099D333 /* Products */, + 5B2068762D9BE32700485134 /* Frameworks */, ); sourceTree = ""; }; @@ -95,6 +72,7 @@ 5BFF68422AD68B990099D333 /* MediaPickerExample */ = { isa = PBXGroup; children = ( + 5B2068752D9BE32400485134 /* Info.plist */, 5BFF684A2AD68B990099D333 /* Application.swift */, 5BFF68492AD68B990099D333 /* AppDelegate.swift */, 5BFF684B2AD68B990099D333 /* ContentView.swift */, @@ -132,24 +110,7 @@ ); name = MediaPickerExample; packageProductDependencies = ( - 13738D1B2AE272FA005BBAD5 /* ExyteMediaPicker */, - 5B4D33E02D689AF900E6C069 /* ExyteMediaPicker */, - 5B4D33E32D689FA100E6C069 /* ExyteMediaPicker */, - 5B4D33E62D68A66600E6C069 /* ExyteMediaPicker */, - 5BC670562D6C8B250023E666 /* ExyteMediaPicker */, - 5BC670592D6C8B810023E666 /* ExyteMediaPicker */, - 5B2A1A0E2D6DD583000A2E81 /* ExyteMediaPicker */, - 5B4C32F72D6F2080009844B6 /* ExyteMediaPicker */, - 5B9275C62D6F2DC700833284 /* ExyteMediaPicker */, - 5BFD73D92D70840200BD0F67 /* ExyteMediaPicker */, - 5B32A6682D79BA3100DF5348 /* ExyteMediaPicker */, - 5B32A66B2D79BC6D00DF5348 /* ExyteMediaPicker */, - 5B0BE9362D7AF6030011FCDB /* ExyteMediaPicker */, - 5B0BE9392D7AF6470011FCDB /* ExyteMediaPicker */, - 5B0BE93C2D7AF6940011FCDB /* ExyteMediaPicker */, - 5B0BE93F2D7AFCFE0011FCDB /* ExyteMediaPicker */, - 5B4DBAD52D92F7D20067A006 /* ExyteMediaPicker */, - 5B4D04FA2D92F9F1005262A1 /* ExyteMediaPicker */, + 5B2068772D9BE32700485134 /* ExyteMediaPicker */, ); productName = MediaPickerExample; productReference = 5BFF68312AD689C80099D333 /* MediaPickerExample.app */; @@ -180,7 +141,6 @@ ); mainGroup = 5BFF68282AD689C80099D333; packageReferences = ( - 5B4D04F92D92F9F1005262A1 /* XCLocalSwiftPackageReference "../../MediaPicker" */, ); productRefGroup = 5BFF68322AD689C80099D333 /* Products */; projectDirPath = ""; @@ -280,7 +240,7 @@ SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 6.0; + SWIFT_VERSION = ""; }; name = Debug; }; @@ -336,7 +296,7 @@ MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_VERSION = 6.0; + SWIFT_VERSION = ""; VALIDATE_PRODUCT = YES; }; name = Release; @@ -352,6 +312,7 @@ DEVELOPMENT_TEAM = FZXCM5CJ7P; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = MediaPickerExample/Info.plist; INFOPLIST_KEY_NSCameraUsageDescription = "Grant access to camera to be able to take photos and videos"; INFOPLIST_KEY_NSMicrophoneUsageDescription = "Grant access to microphone to be able to take videos"; INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "Grant access to photo library to be able to select photos"; @@ -360,7 +321,7 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 17.6; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -373,7 +334,7 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 6.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; }; name = Debug; @@ -389,6 +350,7 @@ DEVELOPMENT_TEAM = FZXCM5CJ7P; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = MediaPickerExample/Info.plist; INFOPLIST_KEY_NSCameraUsageDescription = "Grant access to camera to be able to take photos and videos"; INFOPLIST_KEY_NSMicrophoneUsageDescription = "Grant access to microphone to be able to take videos"; INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "Grant access to photo library to be able to select photos"; @@ -397,7 +359,7 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 17.6; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -410,7 +372,7 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 6.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; }; name = Release; @@ -438,83 +400,8 @@ }; /* End XCConfigurationList section */ -/* Begin XCLocalSwiftPackageReference section */ - 5B4D04F92D92F9F1005262A1 /* XCLocalSwiftPackageReference "../../MediaPicker" */ = { - isa = XCLocalSwiftPackageReference; - relativePath = ../../MediaPicker; - }; -/* End XCLocalSwiftPackageReference section */ - /* Begin XCSwiftPackageProductDependency section */ - 13738D1B2AE272FA005BBAD5 /* ExyteMediaPicker */ = { - isa = XCSwiftPackageProductDependency; - productName = ExyteMediaPicker; - }; - 5B0BE9362D7AF6030011FCDB /* ExyteMediaPicker */ = { - isa = XCSwiftPackageProductDependency; - productName = ExyteMediaPicker; - }; - 5B0BE9392D7AF6470011FCDB /* ExyteMediaPicker */ = { - isa = XCSwiftPackageProductDependency; - productName = ExyteMediaPicker; - }; - 5B0BE93C2D7AF6940011FCDB /* ExyteMediaPicker */ = { - isa = XCSwiftPackageProductDependency; - productName = ExyteMediaPicker; - }; - 5B0BE93F2D7AFCFE0011FCDB /* ExyteMediaPicker */ = { - isa = XCSwiftPackageProductDependency; - productName = ExyteMediaPicker; - }; - 5B2A1A0E2D6DD583000A2E81 /* ExyteMediaPicker */ = { - isa = XCSwiftPackageProductDependency; - productName = ExyteMediaPicker; - }; - 5B32A6682D79BA3100DF5348 /* ExyteMediaPicker */ = { - isa = XCSwiftPackageProductDependency; - productName = ExyteMediaPicker; - }; - 5B32A66B2D79BC6D00DF5348 /* ExyteMediaPicker */ = { - isa = XCSwiftPackageProductDependency; - productName = ExyteMediaPicker; - }; - 5B4C32F72D6F2080009844B6 /* ExyteMediaPicker */ = { - isa = XCSwiftPackageProductDependency; - productName = ExyteMediaPicker; - }; - 5B4D04FA2D92F9F1005262A1 /* ExyteMediaPicker */ = { - isa = XCSwiftPackageProductDependency; - productName = ExyteMediaPicker; - }; - 5B4D33E02D689AF900E6C069 /* ExyteMediaPicker */ = { - isa = XCSwiftPackageProductDependency; - productName = ExyteMediaPicker; - }; - 5B4D33E32D689FA100E6C069 /* ExyteMediaPicker */ = { - isa = XCSwiftPackageProductDependency; - productName = ExyteMediaPicker; - }; - 5B4D33E62D68A66600E6C069 /* ExyteMediaPicker */ = { - isa = XCSwiftPackageProductDependency; - productName = ExyteMediaPicker; - }; - 5B4DBAD52D92F7D20067A006 /* ExyteMediaPicker */ = { - isa = XCSwiftPackageProductDependency; - productName = ExyteMediaPicker; - }; - 5B9275C62D6F2DC700833284 /* ExyteMediaPicker */ = { - isa = XCSwiftPackageProductDependency; - productName = ExyteMediaPicker; - }; - 5BC670562D6C8B250023E666 /* ExyteMediaPicker */ = { - isa = XCSwiftPackageProductDependency; - productName = ExyteMediaPicker; - }; - 5BC670592D6C8B810023E666 /* ExyteMediaPicker */ = { - isa = XCSwiftPackageProductDependency; - productName = ExyteMediaPicker; - }; - 5BFD73D92D70840200BD0F67 /* ExyteMediaPicker */ = { + 5B2068772D9BE32700485134 /* ExyteMediaPicker */ = { isa = XCSwiftPackageProductDependency; productName = ExyteMediaPicker; }; diff --git a/MediaPickerExample/MediaPickerExample/AppDelegate.swift b/MediaPickerExample/MediaPickerExample/AppDelegate.swift index 37d1b51..34e976e 100644 --- a/MediaPickerExample/MediaPickerExample/AppDelegate.swift +++ b/MediaPickerExample/MediaPickerExample/AppDelegate.swift @@ -14,19 +14,27 @@ final class AppDelegate: NSObject, UIApplicationDelegate, ObservableObject { func lockOrientationToPortrait() { orientationLock = .portrait - if #available(iOS 16, *) { - if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene { - scene.requestGeometryUpdate(.iOS(interfaceOrientations: .portrait)) - } - UIViewController.attemptRotationToDeviceOrientation() - } else { - UIDevice.current.setValue(UIDeviceOrientation.portrait.rawValue, forKey: "orientation") + if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene { + scene.requestGeometryUpdate(.iOS(interfaceOrientations: .portrait)) } } func unlockOrientation() { orientationLock = .all - UIViewController.attemptRotationToDeviceOrientation() + if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene { + let currentOrientation = UIDevice.current.orientation + let newOrientation: UIInterfaceOrientationMask + + switch currentOrientation { + case .portrait: newOrientation = .portrait + case .portraitUpsideDown: newOrientation = .portraitUpsideDown + case .landscapeLeft: newOrientation = .landscapeLeft + case .landscapeRight: newOrientation = .landscapeRight + default: newOrientation = .all + } + + scene.requestGeometryUpdate(.iOS(interfaceOrientations: newOrientation)) + } } func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask { diff --git a/MediaPickerExample/MediaPickerExample/ContentView.swift b/MediaPickerExample/MediaPickerExample/ContentView.swift index d8d656e..2f96ad7 100644 --- a/MediaPickerExample/MediaPickerExample/ContentView.swift +++ b/MediaPickerExample/MediaPickerExample/ContentView.swift @@ -58,7 +58,7 @@ struct ContentView: View { isPresented: $showDefaultMediaPicker, onChange: { medias = $0 } ) - .showLiveCameraCell() + //.showLiveCameraCell() .mediaSelectionType(.photoAndVideo) .mediaSelectionStyle(.count) .orientationHandler { diff --git a/MediaPickerExample/MediaPickerExample/Info.plist b/MediaPickerExample/MediaPickerExample/Info.plist new file mode 100644 index 0000000..0c67376 --- /dev/null +++ b/MediaPickerExample/MediaPickerExample/Info.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/Sources/ExyteMediaPicker/Extensions/View+NotificationCenter.swift b/Sources/ExyteMediaPicker/Extensions/View+NotificationCenter.swift deleted file mode 100644 index 048c15b..0000000 --- a/Sources/ExyteMediaPicker/Extensions/View+NotificationCenter.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// View+NotificationCenter.swift -// -// -// Created by Alexandra Afonasova on 17.10.2022. -// - -import SwiftUI - -extension View { - - func onEnteredBackground(perform: @escaping () -> Void) -> some View { - let publisher = NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification) - return onReceive(publisher) { _ in perform() } - } - - func onEnteredForeground(perform: @escaping () -> Void) -> some View { - let publisher = NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification) - return onReceive(publisher) { _ in perform() } - } - - func onRotate(perform: @escaping (UIDeviceOrientation) -> Void) -> some View { - let publisher = NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification) - return onReceive(publisher) { _ in perform(UIDevice.current.orientation) } - } - -} diff --git a/Sources/ExyteMediaPicker/Managers/LiveCameraViewModel.swift b/Sources/ExyteMediaPicker/Managers/LiveCameraViewModel.swift deleted file mode 100644 index 30c3f51..0000000 --- a/Sources/ExyteMediaPicker/Managers/LiveCameraViewModel.swift +++ /dev/null @@ -1,61 +0,0 @@ -// -// Created by Alex.M on 07.06.2022. -// - -import Foundation -import AVFoundation -import CoreImage -import UIKit.UIImage - -/** - Object with live camera preview. Use `captureImage: UIImage` publisher. - - Used solution (with adapt for new iOs version): https://medium.com/ios-os-x-development/ios-camera-frames-extraction-d2c0f80ed05a - */ -class LiveCameraViewModel: NSObject, ObservableObject { - - private let quality = AVCaptureSession.Preset.medium - - private let sessionQueue = DispatchQueue(label: "LiveCameraPreviewQueue") - let captureSession = AVCaptureSession() - - @Published public var capturedImage: UIImage = UIImage() - - override init() { - super.init() - sessionQueue.async { [weak self] in - self?.configureSession() - self?.captureSession.startRunning() - } - } - - func startSession() { - sessionQueue.async { [weak self] in - self?.captureSession.startRunning() - } - } - - func stopSession() { - sessionQueue.async { [weak self] in - self?.captureSession.stopRunning() - } - } - - deinit { - captureSession.stopRunning() - } - - private func configureSession() { - captureSession.beginConfiguration() - captureSession.sessionPreset = quality - guard let captureDevice = captureDevice else { return } - guard let captureDeviceInput = try? AVCaptureDeviceInput(device: captureDevice) else { return } - guard captureSession.canAddInput(captureDeviceInput) else { return } - captureSession.addInput(captureDeviceInput) - captureSession.commitConfiguration() - } - - private var captureDevice: AVCaptureDevice? { - AVCaptureDevice.default(for: .video) - } -} diff --git a/Sources/ExyteMediaPicker/Managers/Medias/AlbumMediasProvider.swift b/Sources/ExyteMediaPicker/Managers/Medias/AlbumMediasProvider.swift index 94d7a2e..29eed7e 100644 --- a/Sources/ExyteMediaPicker/Managers/Medias/AlbumMediasProvider.swift +++ b/Sources/ExyteMediaPicker/Managers/Medias/AlbumMediasProvider.swift @@ -16,8 +16,8 @@ final class AlbumMediasProvider: BaseMediasProvider { } override func reload() { - PermissionsService.shared.requestPhotoLibraryPermission { [weak self] in - DispatchQueue.main.async { + PermissionsService.shared.requestPhotoLibraryPermission { + DispatchQueue.main.async { [weak self] in self?.reloadInternal() } } diff --git a/Sources/ExyteMediaPicker/Managers/Medias/AllMediasProvider.swift b/Sources/ExyteMediaPicker/Managers/Medias/AllMediasProvider.swift index 1690944..b032b28 100644 --- a/Sources/ExyteMediaPicker/Managers/Medias/AllMediasProvider.swift +++ b/Sources/ExyteMediaPicker/Managers/Medias/AllMediasProvider.swift @@ -11,8 +11,8 @@ import SwiftUI final class AllMediasProvider: BaseMediasProvider { override func reload() { - PermissionsService.shared.requestPhotoLibraryPermission { [weak self] in - DispatchQueue.main.async { + PermissionsService.shared.requestPhotoLibraryPermission { + DispatchQueue.main.async { [weak self] in self?.reloadInternal() } } diff --git a/Sources/ExyteMediaPicker/Managers/Medias/AllPhotosProvider.swift b/Sources/ExyteMediaPicker/Managers/Medias/AllPhotosProvider.swift index 08aa7e4..64e90ba 100644 --- a/Sources/ExyteMediaPicker/Managers/Medias/AllPhotosProvider.swift +++ b/Sources/ExyteMediaPicker/Managers/Medias/AllPhotosProvider.swift @@ -8,11 +8,12 @@ import Foundation import Photos import SwiftUI +@MainActor final class AllPhotosProvider: BaseMediasProvider { override func reload() { - PermissionsService.shared.requestPhotoLibraryPermission { [weak self] in - DispatchQueue.main.async { + PermissionsService.shared.requestPhotoLibraryPermission { + DispatchQueue.main.async { [weak self] in self?.reloadInternal() } } diff --git a/Sources/ExyteMediaPicker/Managers/Medias/BaseMediasProvider.swift b/Sources/ExyteMediaPicker/Managers/Medias/BaseMediasProvider.swift index 2ab16bb..cacd9ff 100644 --- a/Sources/ExyteMediaPicker/Managers/Medias/BaseMediasProvider.swift +++ b/Sources/ExyteMediaPicker/Managers/Medias/BaseMediasProvider.swift @@ -3,7 +3,6 @@ // import Foundation -import Combine import Photos import SwiftUI @@ -20,7 +19,6 @@ class BaseMediasProvider: ObservableObject { private var timerTask: Task? private var cancellableTask: Task? - private var cancellable: AnyCancellable? init(selectionParamsHolder: SelectionParamsHolder, filterClosure: MediaPicker.FilterClosure?, massFilterClosure: MediaPicker.MassFilterClosure?) { self.selectionParamsHolder = selectionParamsHolder diff --git a/Sources/ExyteMediaPicker/Model/Media.swift b/Sources/ExyteMediaPicker/Model/Media.swift index 998cae9..5eebaea 100644 --- a/Sources/ExyteMediaPicker/Model/Media.swift +++ b/Sources/ExyteMediaPicker/Model/Media.swift @@ -25,7 +25,9 @@ public extension Media { } var duration: CGFloat? { - source.duration + get async { + await source.duration + } } func getURL() async -> URL? { diff --git a/Sources/ExyteMediaPicker/Model/MediaModelProtocol.swift b/Sources/ExyteMediaPicker/Model/MediaModelProtocol.swift index efeaf12..ad86238 100644 --- a/Sources/ExyteMediaPicker/Model/MediaModelProtocol.swift +++ b/Sources/ExyteMediaPicker/Model/MediaModelProtocol.swift @@ -9,7 +9,7 @@ import SwiftUI protocol MediaModelProtocol: Sendable { var mediaType: MediaType? { get } - var duration: CGFloat? { get } + var duration: CGFloat? { get async } func getURL() async -> URL? func getThumbnailURL() async -> URL? diff --git a/Sources/ExyteMediaPicker/Model/URLMediaModel.swift b/Sources/ExyteMediaPicker/Model/URLMediaModel.swift index 85aa126..057df30 100644 --- a/Sources/ExyteMediaPicker/Model/URLMediaModel.swift +++ b/Sources/ExyteMediaPicker/Model/URLMediaModel.swift @@ -26,7 +26,15 @@ extension URLMediaModel: MediaModelProtocol { } var duration: CGFloat? { - CMTimeGetSeconds(AVURLAsset(url: url).duration) + get async { + let asset = AVURLAsset(url: url) + do { + let duration = try await asset.load(.duration) + return CGFloat(CMTimeGetSeconds(duration)) + } catch { + return nil + } + } } func getURL() async -> URL? { diff --git a/Sources/ExyteMediaPicker/Modifiers/KeyboardHeightHelper.swift b/Sources/ExyteMediaPicker/Modifiers/KeyboardHeightHelper.swift deleted file mode 100644 index 936f816..0000000 --- a/Sources/ExyteMediaPicker/Modifiers/KeyboardHeightHelper.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// KeyboardHeightHelper.swift -// Example-iOS -// -// Created by Alisa Mylnikova on 23.08.2023. -// - -import SwiftUI - -class KeyboardHeightHelper: ObservableObject { - - @MainActor static let shared = KeyboardHeightHelper() - - @Published var keyboardHeight: CGFloat = 0 - @Published var keyboardDisplayed: Bool = false - - init() { - self.listenForKeyboardNotifications() - } - - private func listenForKeyboardNotifications() { - NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: .main) { (notification) in - guard let userInfo = notification.userInfo, - let keyboardRect = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return } - - DispatchQueue.main.async { - self.keyboardHeight = keyboardRect.height - self.keyboardDisplayed = true - } - } - - NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: .main) { (notification) in - DispatchQueue.main.async { - self.keyboardHeight = 0 - } - } - - NotificationCenter.default.addObserver(forName: UIResponder.keyboardDidHideNotification, object: nil, queue: .main) { (notification) in - DispatchQueue.main.async { - self.keyboardDisplayed = false - } - } - } -} diff --git a/Sources/ExyteMediaPicker/Views/Screens/AlbumView/AlbumView.swift b/Sources/ExyteMediaPicker/Screens/AlbumView/AlbumView.swift similarity index 95% rename from Sources/ExyteMediaPicker/Views/Screens/AlbumView/AlbumView.swift rename to Sources/ExyteMediaPicker/Screens/AlbumView/AlbumView.swift index 1df444c..c89ae86 100644 --- a/Sources/ExyteMediaPicker/Views/Screens/AlbumView/AlbumView.swift +++ b/Sources/ExyteMediaPicker/Screens/AlbumView/AlbumView.swift @@ -90,7 +90,7 @@ struct AlbumView: View { .aspectRatio(1, contentMode: .fit) } } - .onChange(of: viewModel.assetMediaModels) { newValue in + .onChange(of: viewModel.assetMediaModels) { _ , newValue in selectionService.updateSelection(with: newValue) } } @@ -127,6 +127,11 @@ struct AlbumView: View { $0.closeOnTap(false) .animation(.easeIn(duration: 0.2)) } + .simultaneousGesture( + TapGesture().onEnded { + fullscreenItem = assetMediaModel.id + } + ) } .buttonStyle(MediaButtonStyle()) .contentShape(Rectangle()) diff --git a/Sources/ExyteMediaPicker/Views/Screens/AlbumView/MediaCell.swift b/Sources/ExyteMediaPicker/Screens/AlbumView/MediaCell.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Screens/AlbumView/MediaCell.swift rename to Sources/ExyteMediaPicker/Screens/AlbumView/MediaCell.swift diff --git a/Sources/ExyteMediaPicker/Views/Screens/AlbumView/MediaViewModel.swift b/Sources/ExyteMediaPicker/Screens/AlbumView/MediaViewModel.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Screens/AlbumView/MediaViewModel.swift rename to Sources/ExyteMediaPicker/Screens/AlbumView/MediaViewModel.swift diff --git a/Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumCell.swift b/Sources/ExyteMediaPicker/Screens/AlbumsView/AlbumCell.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumCell.swift rename to Sources/ExyteMediaPicker/Screens/AlbumsView/AlbumCell.swift diff --git a/Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumCellViewModel.swift b/Sources/ExyteMediaPicker/Screens/AlbumsView/AlbumCellViewModel.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumCellViewModel.swift rename to Sources/ExyteMediaPicker/Screens/AlbumsView/AlbumCellViewModel.swift diff --git a/Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumsView.swift b/Sources/ExyteMediaPicker/Screens/AlbumsView/AlbumsView.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumsView.swift rename to Sources/ExyteMediaPicker/Screens/AlbumsView/AlbumsView.swift diff --git a/Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumsViewModel.swift b/Sources/ExyteMediaPicker/Screens/AlbumsView/AlbumsViewModel.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Screens/AlbumsView/AlbumsViewModel.swift rename to Sources/ExyteMediaPicker/Screens/AlbumsView/AlbumsViewModel.swift diff --git a/Sources/ExyteMediaPicker/Views/Screens/Camera/CameraSelectionContainer.swift b/Sources/ExyteMediaPicker/Screens/Camera/CameraSelectionContainer.swift similarity index 99% rename from Sources/ExyteMediaPicker/Views/Screens/Camera/CameraSelectionContainer.swift rename to Sources/ExyteMediaPicker/Screens/Camera/CameraSelectionContainer.swift index d924368..5d9bc4e 100644 --- a/Sources/ExyteMediaPicker/Views/Screens/Camera/CameraSelectionContainer.swift +++ b/Sources/ExyteMediaPicker/Screens/Camera/CameraSelectionContainer.swift @@ -20,7 +20,7 @@ public struct CameraSelectionView: View { if #available(iOS 17.0, *) { ScrollView(.horizontal, showsIndicators: false) { LazyHStack(spacing: 0) { - ForEach(0..: View { } }, { viewModel.setPickerMode(.cameraSelection) }, // show preview of taken photos - { cameraViewModel.takePhoto() }, // takePhoto - { cameraViewModel.startVideoCapture() }, // start record video - { cameraViewModel.stopVideoCapture() }, // stop record video - { cameraViewModel.toggleFlash() }, // flash off/on - { cameraViewModel.flipCamera() } // camera back/front + { Task { await cameraViewModel.takePhoto() } }, // takePhoto + { Task { await cameraViewModel.startVideoCapture() } }, // start record video + { Task { await cameraViewModel.stopVideoCapture() } }, // stop record video + { Task { await cameraViewModel.toggleFlash() } }, // flash off/on + { Task { await cameraViewModel.flipCamera() } } // camera back/front ) - .onReceive(cameraViewModel.$capturedPhoto) { - if let photo = $0 { - viewModel.pickedMediaUrl = photo - didTakePicture() - } + .onChange(of: cameraViewModel.capturedPhoto) { _ , newValue in + viewModel.pickedMediaUrl = newValue + didTakePicture() } } } @@ -53,7 +51,7 @@ struct StandardConrolsCameraView: View { @EnvironmentObject private var cameraSelectionService: CameraSelectionService @Environment(\.mediaPickerTheme) private var theme - @Environment(\.safeAreaInsets) private var safeArea + @Environment(\.scenePhase) private var scenePhase @ObservedObject var viewModel: MediaPickerViewModel let didTakePicture: () -> Void @@ -80,7 +78,7 @@ struct StandardConrolsCameraView: View { Spacer() } - .safeAreaPadding(.top, safeArea.top) + .safeAreaPadding(.top, UIApplication.safeArea.top) LiveCameraView( session: cameraViewModel.captureSession, @@ -92,11 +90,13 @@ struct StandardConrolsCameraView: View { Rectangle() } } - .gesture( - MagnificationGesture() - .onChanged(cameraViewModel.zoomChanged(_:)) - .onEnded(cameraViewModel.zoomEnded(_:)) - ) + .applyIf(cameraViewModel.zoomAllowed) { + $0.gesture( + MagnificationGesture() + .onChanged(cameraViewModel.zoomChanged(_:)) + .onEnded(cameraViewModel.zoomEnded(_:)) + ) + } VStack(spacing: 10) { if cameraSelectionService.hasSelected { @@ -127,8 +127,8 @@ struct StandardConrolsCameraView: View { } HStack(spacing: 40) { - Button { - cameraViewModel.toggleFlash() + AsyncButton { + await cameraViewModel.toggleFlash() } label: { Image(cameraViewModel.flashEnabled ? "FlashOn" : "FlashOff", bundle: .current) } @@ -141,8 +141,8 @@ struct StandardConrolsCameraView: View { stopVideoCaptureButton } - Button { - cameraViewModel.flipCamera() + AsyncButton { + await cameraViewModel.flipCamera() } label: { Image("FlipCamera", bundle: .current) } @@ -152,10 +152,17 @@ struct StandardConrolsCameraView: View { .padding(.bottom, 50) } .background(theme.main.cameraBackground) - .onEnteredBackground(perform: cameraViewModel.stopSession) - .onEnteredForeground(perform: cameraViewModel.startSession) - .onReceive(cameraViewModel.$capturedPhoto) { - if let photo = $0 { + .onChange(of: scenePhase) { + Task { + if scenePhase == .background { + await cameraViewModel.stopSession() + } else if scenePhase == .active { + await cameraViewModel.startSession() + } + } + } + .onChange(of: cameraViewModel.capturedPhoto) { _ , newValue in + if let photo = newValue { viewModel.pickedMediaUrl = photo didTakePicture() } @@ -183,7 +190,9 @@ struct StandardConrolsCameraView: View { .frame(width: 72, height: 72) Button { - cameraViewModel.takePhoto() + Task { + await cameraViewModel.takePhoto() + } } label: { Circle() .foregroundColor(.white) @@ -198,8 +207,8 @@ struct StandardConrolsCameraView: View { .stroke(Color.white.opacity(0.4), lineWidth: 6) .frame(width: 72, height: 72) - Button { - cameraViewModel.startVideoCapture() + AsyncButton { + await cameraViewModel.startVideoCapture() videoCaptureInProgress = true } label: { Circle() @@ -215,8 +224,8 @@ struct StandardConrolsCameraView: View { .stroke(Color.white.opacity(0.4), lineWidth: 6) .frame(width: 72, height: 72) - Button { - cameraViewModel.stopVideoCapture() + AsyncButton { + await cameraViewModel.stopVideoCapture() videoCaptureInProgress = false } label: { RoundedRectangle(cornerRadius: 10) diff --git a/Sources/ExyteMediaPicker/Views/Screens/Camera/CameraViewModel.swift b/Sources/ExyteMediaPicker/Screens/Camera/CameraViewModel.swift similarity index 63% rename from Sources/ExyteMediaPicker/Views/Screens/Camera/CameraViewModel.swift rename to Sources/ExyteMediaPicker/Screens/Camera/CameraViewModel.swift index b67f14b..3c838f1 100644 --- a/Sources/ExyteMediaPicker/Views/Screens/Camera/CameraViewModel.swift +++ b/Sources/ExyteMediaPicker/Screens/Camera/CameraViewModel.swift @@ -9,8 +9,15 @@ import Foundation import AVFoundation import UIKit import SwiftUI +import Combine -final class CameraViewModel: NSObject, ObservableObject { +#if compiler(>=6.0) +extension AVCaptureSession: @retroactive @unchecked Sendable { } +#else +extension AVCaptureSession: @unchecked Sendable { } +#endif + +final actor CameraViewModel: NSObject, ObservableObject { struct CaptureDevice { let device: AVCaptureDevice @@ -19,16 +26,16 @@ final class CameraViewModel: NSObject, ObservableObject { let maxZoom: CGFloat } - @Published private(set) var flashEnabled = false - @Published private(set) var snapOverlay = false - @Published private(set) var capturedPhoto: URL? + @MainActor @Published private(set) var flashEnabled = false + @MainActor @Published private(set) var snapOverlay = false + @MainActor @Published private(set) var zoomAllowed = false + @MainActor @Published private(set) var capturedPhoto: URL? let captureSession = AVCaptureSession() private let photoOutput = AVCapturePhotoOutput() private let videoOutput = AVCaptureMovieFileOutput() private let motionManager = MotionManager() - private let sessionQueue = DispatchQueue(label: "LiveCameraQueue") private var captureDevice: CaptureDevice? private var lastPhotoActualOrientation: UIDeviceOrientation? @@ -37,44 +44,49 @@ final class CameraViewModel: NSObject, ObservableObject { private let dualCameraMaxScale: CGFloat = 8 private let tripleCameraMaxScale: CGFloat = 12 private var lastScale: CGFloat = 1 - private var zoomAllowed: Bool { captureDevice?.position == .back } override init() { super.init() - sessionQueue.async { [weak self] in - self?.configureSession() - self?.captureSession.startRunning() + Task { + await configureSession() + captureSession.startRunning() } } - deinit { - captureSession.stopRunning() - } - func startSession() { - sessionQueue.async { [weak self] in - self?.captureSession.startRunning() - } + captureSession.startRunning() } func stopSession() { - sessionQueue.async { [weak self] in - self?.captureSession.stopRunning() + captureSession.stopRunning() + } + + func setCapturedPhoto(_ photo: URL?) { + DispatchQueue.main.async { + self.capturedPhoto = photo } } - func takePhoto() { + func takePhoto() async { let settings = AVCapturePhotoSettings() - settings.flashMode = flashEnabled ? .on : .off + settings.flashMode = await flashEnabled ? .on : .off photoOutput.capturePhoto(with: settings, delegate: self) lastPhotoActualOrientation = motionManager.orientation - withAnimation(.linear(duration: 0.1)) { snapOverlay = true } - withAnimation(.linear(duration: 0.1).delay(0.1)) { snapOverlay = false } + withAnimation(.linear(duration: 0.1)) { + DispatchQueue.main.async { + self.snapOverlay = true + } + } + withAnimation(.linear(duration: 0.1).delay(0.1)) { + DispatchQueue.main.async { + self.snapOverlay = false + } + } } - func startVideoCapture() { - setVideoTorchMode(flashEnabled ? .on : .off) + func startVideoCapture() async { + setVideoTorchMode(await flashEnabled ? .on : .off) let videoUrl = FileManager.getTempUrl() videoOutput.startRecording(to: videoUrl, recordingDelegate: self) @@ -94,34 +106,39 @@ final class CameraViewModel: NSObject, ObservableObject { } func flipCamera() { - sessionQueue.async { [weak self] in - guard let session = self?.captureSession, let input = session.inputs.first else { - return - } - - let newPosition: AVCaptureDevice.Position = self?.captureDevice?.position == .back ? .front : .back - - session.beginConfiguration() - session.removeInput(input) - self?.addInput(to: session, for: newPosition) - session.commitConfiguration() + let session = captureSession + guard let input = session.inputs.first else { + return } + let newPosition: AVCaptureDevice.Position = captureDevice?.position == .back ? .front : .back + + session.beginConfiguration() + session.removeInput(input) + addInput(to: session, for: newPosition) + session.commitConfiguration() } func toggleFlash() { - flashEnabled.toggle() + DispatchQueue.main.async { + self.flashEnabled.toggle() + } } - func zoomChanged(_ scale: CGFloat) { - if !zoomAllowed { return } - zoomCamera(resolveScale(scale)) + nonisolated func zoomChanged(_ scale: CGFloat) { + Task { + await zoomCamera(await resolveScale(scale)) + } } - func zoomEnded(_ scale: CGFloat) { - if !zoomAllowed { return } + nonisolated func zoomEnded(_ scale: CGFloat) { + Task { + await setLastScale(await resolveScale(scale)) + await zoomCamera(lastScale) + } + } - lastScale = resolveScale(scale) - zoomCamera(lastScale) + private func setLastScale(_ scale: CGFloat) { + self.lastScale = scale } private func resolveScale(_ gestureScale: CGFloat) -> CGFloat { @@ -148,6 +165,10 @@ final class CameraViewModel: NSObject, ObservableObject { private func addInput(to session: AVCaptureSession, for position: AVCaptureDevice.Position = .back) { guard let captureDevice = selectCaptureDevice(for: position) else { return } + let zoomAllowed = captureDevice.position == .back + Task { @MainActor in + self.zoomAllowed = zoomAllowed + } guard let captureDeviceInput = try? AVCaptureDeviceInput(device: captureDevice) else { return } guard session.canAddInput(captureDeviceInput) else { return } session.addInput(captureDeviceInput) @@ -189,12 +210,15 @@ final class CameraViewModel: NSObject, ObservableObject { guard session.canAddOutput(videoOutput) else { return } session.addOutput(videoOutput) - updateOutputOrientation() + updateOutputOrientation(photoOutput) + updateOutputOrientation(videoOutput) } - private func updateOutputOrientation() { - guard let connection = photoOutput.connection(with: .video), connection.isVideoOrientationSupported else { return } - connection.videoOrientation = .portrait + private func updateOutputOrientation(_ output: AVCaptureOutput) { + guard let connection = output.connection(with: .video) else { return } + if connection.isVideoRotationAngleSupported(0) { + connection.videoRotationAngle = 0 + } } private func selectCaptureDevice(for position: AVCaptureDevice.Position) -> AVCaptureDevice? { @@ -222,42 +246,45 @@ final class CameraViewModel: NSObject, ObservableObject { private func selectAudioCaptureDevice() -> AVCaptureDevice? { let session = AVCaptureDevice.DiscoverySession( - deviceTypes: [.builtInMicrophone], + deviceTypes: [.microphone], mediaType: .audio, position: .unspecified) return session.devices.first } - } extension CameraViewModel: AVCapturePhotoCaptureDelegate { - func photoOutput( + nonisolated func photoOutput( _ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error? ) { guard let cgImage = photo.cgImageRepresentation() else { return } - let photoOrientation: UIImage.Orientation - if let orientation = lastPhotoActualOrientation { - photoOrientation = UIImage.Orientation(orientation) - } else { - photoOrientation = UIImage.Orientation.default - } + Task { + let photoOrientation: UIImage.Orientation + if let orientation = await lastPhotoActualOrientation { + photoOrientation = UIImage.Orientation(orientation) + } else { + photoOrientation = UIImage.Orientation.default + } - guard let data = UIImage( - cgImage: cgImage, - scale: 1, - orientation: photoOrientation - ).jpegData(compressionQuality: 0.8) else { return } + guard let data = UIImage( + cgImage: cgImage, + scale: 1, + orientation: photoOrientation + ).jpegData(compressionQuality: 0.8) else { return } - capturedPhoto = FileManager.storeToTempDir(data: data) + await setCapturedPhoto(FileManager.storeToTempDir(data: data)) + } } } extension CameraViewModel: AVCaptureFileOutputRecordingDelegate { - func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) { - capturedPhoto = outputFileURL + nonisolated func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) { + Task { + await setCapturedPhoto(outputFileURL) + } } } diff --git a/Sources/ExyteMediaPicker/Views/Widgets/LiveCameraCell.swift b/Sources/ExyteMediaPicker/Screens/Camera/LiveCameraCell.swift similarity index 54% rename from Sources/ExyteMediaPicker/Views/Widgets/LiveCameraCell.swift rename to Sources/ExyteMediaPicker/Screens/Camera/LiveCameraCell.swift index f64b4db..7b1c10a 100644 --- a/Sources/ExyteMediaPicker/Views/Widgets/LiveCameraCell.swift +++ b/Sources/ExyteMediaPicker/Screens/Camera/LiveCameraCell.swift @@ -5,10 +5,12 @@ import SwiftUI struct LiveCameraCell: View { + + @Environment(\.scenePhase) private var scenePhase let action: () -> Void - @StateObject private var liveCameraViewModel = LiveCameraViewModel() + @StateObject private var cameraViewModel = CameraViewModel() @State private var orientation = UIDevice.current.orientation var body: some View { @@ -16,7 +18,7 @@ struct LiveCameraCell: View { action() } label: { LiveCameraView( - session: liveCameraViewModel.captureSession, + session: cameraViewModel.captureSession, videoGravity: .resizeAspectFill, orientation: orientation ) @@ -25,8 +27,15 @@ struct LiveCameraCell: View { .foregroundColor(.white) ) } - .onEnteredBackground(perform: liveCameraViewModel.stopSession) - .onEnteredForeground(perform: liveCameraViewModel.startSession) + .onChange(of: scenePhase) { + Task { + if scenePhase == .background { + await cameraViewModel.stopSession() + } else if scenePhase == .active { + await cameraViewModel.startSession() + } + } + } .onRotate { orientation = $0 } } } diff --git a/Sources/ExyteMediaPicker/Views/Screens/Camera/LiveCameraView.swift b/Sources/ExyteMediaPicker/Screens/Camera/LiveCameraView.swift similarity index 72% rename from Sources/ExyteMediaPicker/Views/Screens/Camera/LiveCameraView.swift rename to Sources/ExyteMediaPicker/Screens/Camera/LiveCameraView.swift index ae26395..db4daae 100644 --- a/Sources/ExyteMediaPicker/Views/Screens/Camera/LiveCameraView.swift +++ b/Sources/ExyteMediaPicker/Screens/Camera/LiveCameraView.swift @@ -8,11 +8,12 @@ import SwiftUI import AVFoundation +@MainActor public struct LiveCameraView: UIViewRepresentable { let session: AVCaptureSession - var videoGravity: AVLayerVideoGravity = .resizeAspect - var orientation: UIDeviceOrientation = UIDevice.current.orientation + let videoGravity: AVLayerVideoGravity + let orientation: UIDeviceOrientation public func makeUIView(context: Context) -> LiveVideoCaptureView { LiveVideoCaptureView( @@ -22,10 +23,7 @@ public struct LiveCameraView: UIViewRepresentable { ) } - public func updateUIView(_ uiView: LiveVideoCaptureView, context: Context) { - uiView.updateOrientation(orientation) - } - + public func updateUIView(_ uiView: LiveVideoCaptureView, context: Context) { } } public final class LiveVideoCaptureView: UIView { @@ -56,12 +54,5 @@ public final class LiveVideoCaptureView: UIView { super.init(frame: frame) self.session = session videoLayer.videoGravity = videoGravity - updateOrientation(orientation) } - - func updateOrientation(_ orientation: UIDeviceOrientation) { - guard let connection = videoLayer.connection, connection.isVideoOrientationSupported else { return } - connection.videoOrientation = AVCaptureVideoOrientation(orientation) - } - } diff --git a/Sources/ExyteMediaPicker/Views/Screens/Fullscreen/FullscreenCell.swift b/Sources/ExyteMediaPicker/Screens/Fullscreen/FullscreenCell.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Screens/Fullscreen/FullscreenCell.swift rename to Sources/ExyteMediaPicker/Screens/Fullscreen/FullscreenCell.swift diff --git a/Sources/ExyteMediaPicker/Views/Screens/Fullscreen/FullscreenCellViewModel.swift b/Sources/ExyteMediaPicker/Screens/Fullscreen/FullscreenCellViewModel.swift similarity index 94% rename from Sources/ExyteMediaPicker/Views/Screens/Fullscreen/FullscreenCellViewModel.swift rename to Sources/ExyteMediaPicker/Screens/Fullscreen/FullscreenCellViewModel.swift index 718ce93..49fcb15 100644 --- a/Sources/ExyteMediaPicker/Views/Screens/Fullscreen/FullscreenCellViewModel.swift +++ b/Sources/ExyteMediaPicker/Screens/Fullscreen/FullscreenCellViewModel.swift @@ -3,9 +3,15 @@ // import Foundation -import AVKit +@preconcurrency import AVKit import UIKit.UIImage +#if compiler(>=6.0) +extension AVAssetTrack: @retroactive @unchecked Sendable { } +#else +extension AVAssetTrack: @unchecked Sendable { } +#endif + @MainActor final class FullscreenCellViewModel: ObservableObject { @@ -87,7 +93,3 @@ final class FullscreenCellViewModel: ObservableObject { } } } - -extension AVAssetTrack: @unchecked Sendable { - -} diff --git a/Sources/ExyteMediaPicker/Views/Screens/Fullscreen/FullscreenContainer.swift b/Sources/ExyteMediaPicker/Screens/Fullscreen/FullscreenContainer.swift similarity index 96% rename from Sources/ExyteMediaPicker/Views/Screens/Fullscreen/FullscreenContainer.swift rename to Sources/ExyteMediaPicker/Screens/Fullscreen/FullscreenContainer.swift index a67cce1..818fb3e 100644 --- a/Sources/ExyteMediaPicker/Views/Screens/Fullscreen/FullscreenContainer.swift +++ b/Sources/ExyteMediaPicker/Screens/Fullscreen/FullscreenContainer.swift @@ -10,7 +10,6 @@ struct FullscreenContainer: View { @EnvironmentObject private var selectionService: SelectionService @Environment(\.mediaPickerTheme) private var theme - @Environment(\.safeAreaInsets) private var safeArea @ObservedObject var keyboardHeightHelper = KeyboardHeightHelper.shared @@ -39,7 +38,7 @@ struct FullscreenContainer: View { contentView(g.size) } } - .safeAreaPadding(.top, safeArea.top) + .safeAreaPadding(.top, UIApplication.safeArea.top) .background { theme.main.fullscreenPhotoBackground .ignoresSafeArea() @@ -52,7 +51,7 @@ struct FullscreenContainer: View { .onDisappear { currentFullscreenMedia = nil } - .onChange(of: selection) { newValue in + .onChange(of: selection) { if let selectedMediaModel { currentFullscreenMedia = Media(source: selectedMediaModel) } diff --git a/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/AlbumSelectionView.swift b/Sources/ExyteMediaPicker/Screens/MediaPicker/AlbumSelectionView.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Screens/MediaPicker/AlbumSelectionView.swift rename to Sources/ExyteMediaPicker/Screens/MediaPicker/AlbumSelectionView.swift diff --git a/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/GenenricsTrick.swift b/Sources/ExyteMediaPicker/Screens/MediaPicker/GenenricsTrick.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Screens/MediaPicker/GenenricsTrick.swift rename to Sources/ExyteMediaPicker/Screens/MediaPicker/GenenricsTrick.swift diff --git a/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/MediaPicker.swift b/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Screens/MediaPicker/MediaPicker.swift rename to Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift diff --git a/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/MediaPickerMode.swift b/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPickerMode.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Screens/MediaPicker/MediaPickerMode.swift rename to Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPickerMode.swift diff --git a/Sources/ExyteMediaPicker/Views/Screens/MediaPicker/MediaPickerViewModel.swift b/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPickerViewModel.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Screens/MediaPicker/MediaPickerViewModel.swift rename to Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPickerViewModel.swift diff --git a/Sources/ExyteMediaPicker/Theme/MediaPickerTheme.swift b/Sources/ExyteMediaPicker/Theme/MediaPickerTheme.swift index 4f6e720..e7dc723 100644 --- a/Sources/ExyteMediaPicker/Theme/MediaPickerTheme.swift +++ b/Sources/ExyteMediaPicker/Theme/MediaPickerTheme.swift @@ -5,7 +5,7 @@ import Foundation import SwiftUI -public struct MediaPickerTheme { +public struct MediaPickerTheme: Sendable { public let main: Main public let selection: Selection public let cellStyle: CellStyle @@ -26,7 +26,7 @@ public struct MediaPickerTheme { } extension MediaPickerTheme { - public struct Main { + public struct Main: Sendable { public let pickerText: Color public let pickerBackground: Color public let fullscreenPhotoBackground: Color @@ -54,7 +54,7 @@ extension MediaPickerTheme { } } - public struct Selection { + public struct Selection: Sendable { public let cellEmptyBorder: Color public let cellEmptyBackground: Color public let cellSelectedBorder: Color @@ -110,7 +110,7 @@ extension MediaPickerTheme { } } - public struct CellStyle { + public struct CellStyle: Sendable { public let columnsSpacing: CGFloat public let rowSpacing: CGFloat public let cornerRadius: CGFloat @@ -124,7 +124,7 @@ extension MediaPickerTheme { } } - public struct Error { + public struct Error: Sendable { public let background: Color public let tint: Color @@ -135,7 +135,7 @@ extension MediaPickerTheme { } } - public struct DefaultHeader { + public struct DefaultHeader: Sendable { public let background: Color public init(background: Color = Color("pickerBG", bundle: .current), diff --git a/Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionActionView.swift b/Sources/ExyteMediaPicker/Utils/Errors/PermissionActionView.swift similarity index 87% rename from Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionActionView.swift rename to Sources/ExyteMediaPicker/Utils/Errors/PermissionActionView.swift index 7dfb60a..2a21bee 100644 --- a/Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionActionView.swift +++ b/Sources/ExyteMediaPicker/Utils/Errors/PermissionActionView.swift @@ -67,10 +67,11 @@ private extension PermissionActionView { PermissionsErrorView( text: text, action: { - guard let url = URL(string: UIApplication.openSettingsURLString) - else { return } - if UIApplication.shared.canOpenURL(url) { - UIApplication.shared.open(url) + DispatchQueue.main.async { + guard let url = URL(string: UIApplication.openSettingsURLString) else { return } + if UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url) + } } } ) diff --git a/Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionsErrorView.swift b/Sources/ExyteMediaPicker/Utils/Errors/PermissionsErrorView.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Widgets/Errors/PermissionsErrorView.swift rename to Sources/ExyteMediaPicker/Utils/Errors/PermissionsErrorView.swift diff --git a/Sources/ExyteMediaPicker/Extensions/Collection+.swift b/Sources/ExyteMediaPicker/Utils/Extensions/Collection+.swift similarity index 100% rename from Sources/ExyteMediaPicker/Extensions/Collection+.swift rename to Sources/ExyteMediaPicker/Utils/Extensions/Collection+.swift diff --git a/Sources/ExyteMediaPicker/Extensions/ColumnCalculation.swift b/Sources/ExyteMediaPicker/Utils/Extensions/ColumnCalculation.swift similarity index 100% rename from Sources/ExyteMediaPicker/Extensions/ColumnCalculation.swift rename to Sources/ExyteMediaPicker/Utils/Extensions/ColumnCalculation.swift diff --git a/Sources/ExyteMediaPicker/Extensions/OrientationTransformationExtensions.swift b/Sources/ExyteMediaPicker/Utils/Extensions/OrientationTransformationExtensions.swift similarity index 64% rename from Sources/ExyteMediaPicker/Extensions/OrientationTransformationExtensions.swift rename to Sources/ExyteMediaPicker/Utils/Extensions/OrientationTransformationExtensions.swift index 9125cc2..ccacf6c 100644 --- a/Sources/ExyteMediaPicker/Extensions/OrientationTransformationExtensions.swift +++ b/Sources/ExyteMediaPicker/Utils/Extensions/OrientationTransformationExtensions.swift @@ -21,15 +21,3 @@ extension UIImage.Orientation { static var `default`: UIImage.Orientation { .right } } - -extension AVCaptureVideoOrientation { - - init(_ orientation: UIDeviceOrientation) { - switch orientation { - case .landscapeLeft: self = .landscapeRight - case .landscapeRight: self = .landscapeLeft - default: self = .portrait - } - } - -} diff --git a/Sources/ExyteMediaPicker/Extensions/Sequence+asyncMap.swift b/Sources/ExyteMediaPicker/Utils/Extensions/Sequence+asyncMap.swift similarity index 100% rename from Sources/ExyteMediaPicker/Extensions/Sequence+asyncMap.swift rename to Sources/ExyteMediaPicker/Utils/Extensions/Sequence+asyncMap.swift diff --git a/Sources/ExyteMediaPicker/Extensions/TimeInterval+Duration.swift b/Sources/ExyteMediaPicker/Utils/Extensions/TimeInterval+Duration.swift similarity index 100% rename from Sources/ExyteMediaPicker/Extensions/TimeInterval+Duration.swift rename to Sources/ExyteMediaPicker/Utils/Extensions/TimeInterval+Duration.swift diff --git a/Sources/ExyteMediaPicker/Extensions/View+.swift b/Sources/ExyteMediaPicker/Utils/Extensions/View+.swift similarity index 66% rename from Sources/ExyteMediaPicker/Extensions/View+.swift rename to Sources/ExyteMediaPicker/Utils/Extensions/View+.swift index a5a1989..5fc3791 100644 --- a/Sources/ExyteMediaPicker/Extensions/View+.swift +++ b/Sources/ExyteMediaPicker/Utils/Extensions/View+.swift @@ -9,7 +9,9 @@ import SwiftUI extension View { func dismissKeyboard() { - UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) + DispatchQueue.main.async { + UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) + } } } @@ -28,4 +30,13 @@ extension View { self.padding(.horizontal, horizontal) .padding(.vertical, vertical) } + + @ViewBuilder + func applyIf(_ condition: Bool, apply: (Self) -> T) -> some View { + if condition { + apply(self) + } else { + self + } + } } diff --git a/Sources/ExyteMediaPicker/Utils/Extensions/View+NotificationCenter.swift b/Sources/ExyteMediaPicker/Utils/Extensions/View+NotificationCenter.swift new file mode 100644 index 0000000..fe38084 --- /dev/null +++ b/Sources/ExyteMediaPicker/Utils/Extensions/View+NotificationCenter.swift @@ -0,0 +1,15 @@ +// +// View+NotificationCenter.swift +// +// +// Created by Alexandra Afonasova on 17.10.2022. +// + +import SwiftUI + +extension View { + @MainActor func onRotate(perform: @escaping (UIDeviceOrientation) -> Void) -> some View { + let publisher = NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification) + return onReceive(publisher) { _ in perform(UIDevice.current.orientation) } + } +} diff --git a/Sources/ExyteMediaPicker/Extensions/Zoom+ScrollView.swift b/Sources/ExyteMediaPicker/Utils/Extensions/Zoom+ScrollView.swift similarity index 100% rename from Sources/ExyteMediaPicker/Extensions/Zoom+ScrollView.swift rename to Sources/ExyteMediaPicker/Utils/Extensions/Zoom+ScrollView.swift diff --git a/Sources/ExyteMediaPicker/Utils/Modifiers/KeyboardHeightHelper.swift b/Sources/ExyteMediaPicker/Utils/Modifiers/KeyboardHeightHelper.swift new file mode 100644 index 0000000..92ca3d6 --- /dev/null +++ b/Sources/ExyteMediaPicker/Utils/Modifiers/KeyboardHeightHelper.swift @@ -0,0 +1,64 @@ +// +// KeyboardHeightHelper.swift +// Example-iOS +// +// Created by Alisa Mylnikova on 23.08.2023. +// + +import SwiftUI + +#if compiler(>=6.0) +extension Notification: @retroactive @unchecked Sendable { } +#else +extension Notification: @unchecked Sendable { } +#endif + +@MainActor +class KeyboardHeightHelper: ObservableObject { + + static let shared = KeyboardHeightHelper() + + @Published var keyboardHeight: CGFloat = 0 + @Published var keyboardDisplayed: Bool = false + + init() { + self.listenForKeyboardNotifications() + } + + private func listenForKeyboardNotifications() { + NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: .main) { [weak self] notification in + guard let self = self else { return } + + Task { @MainActor in + // Safely extract the data from the notification on the main actor + guard let userInfo = notification.userInfo, + let keyboardRect = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return } + + // Now update the UI on the main actor + self.keyboardHeight = keyboardRect.height + self.keyboardDisplayed = true + } + } + + NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: .main) { (notification) in + DispatchQueue.main.async { + self.keyboardHeight = 0 + } + } + + NotificationCenter.default.addObserver(forName: UIResponder.keyboardDidHideNotification, object: nil, queue: .main) { (notification) in + DispatchQueue.main.async { + self.keyboardDisplayed = false + } + } + } + + private func handleKeyboardWillShow(_ notification: Notification) { + guard let userInfo = notification.userInfo, + let keyboardRect = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { + return + } + self.keyboardHeight = keyboardRect.height + self.keyboardDisplayed = true + } +} diff --git a/Sources/ExyteMediaPicker/Modifiers/MediaButtonStyle.swift b/Sources/ExyteMediaPicker/Utils/Modifiers/MediaButtonStyle.swift similarity index 100% rename from Sources/ExyteMediaPicker/Modifiers/MediaButtonStyle.swift rename to Sources/ExyteMediaPicker/Utils/Modifiers/MediaButtonStyle.swift diff --git a/Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift b/Sources/ExyteMediaPicker/Utils/Modifiers/MediaPickerThemeModifier.swift similarity index 59% rename from Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift rename to Sources/ExyteMediaPicker/Utils/Modifiers/MediaPickerThemeModifier.swift index a635d4d..99d3233 100644 --- a/Sources/ExyteMediaPicker/Modifiers/MediaPickerThemeModifier.swift +++ b/Sources/ExyteMediaPicker/Utils/Modifiers/MediaPickerThemeModifier.swift @@ -6,9 +6,32 @@ import Foundation import SwiftUI public extension EnvironmentValues { + #if swift(>=6.0) @Entry var mediaPickerTheme = MediaPickerTheme() @Entry var mediaPickerThemeIsOverridden = false + #else + var mediaPickerTheme: MediaPickerTheme { + get { self[MediaPickerThemeKey.self] } + set { self[MediaPickerThemeKey.self] = newValue } + } + + var mediaPickerThemeIsOverridden: Bool { + get { self[MediaPickerThemeIsOverriddenKey.self] } + set { self[MediaPickerThemeIsOverriddenKey.self] = newValue } + } + #endif +} + +// Define keys only for older versions +#if swift(<6.0) +@preconcurrency public struct MediaPickerThemeKey: EnvironmentKey { + public static let defaultValue = MediaPickerTheme() +} + +public struct MediaPickerThemeIsOverriddenKey: EnvironmentKey { + public static let defaultValue = false } +#endif public extension View { func mediaPickerTheme(_ theme: MediaPickerTheme) -> some View { diff --git a/Sources/ExyteMediaPicker/Modifiers/SafeAreaEnvironmentValues.swift b/Sources/ExyteMediaPicker/Utils/Modifiers/SafeAreaEnvironmentValues.swift similarity index 63% rename from Sources/ExyteMediaPicker/Modifiers/SafeAreaEnvironmentValues.swift rename to Sources/ExyteMediaPicker/Utils/Modifiers/SafeAreaEnvironmentValues.swift index f1745c0..a5a1b61 100644 --- a/Sources/ExyteMediaPicker/Modifiers/SafeAreaEnvironmentValues.swift +++ b/Sources/ExyteMediaPicker/Utils/Modifiers/SafeAreaEnvironmentValues.swift @@ -21,29 +21,14 @@ extension UIApplication { $0.isKeyWindow } } -} -@MainActor -private struct SafeAreaInsetsKey: @preconcurrency EnvironmentKey { - static var defaultValue: EdgeInsets { + static var safeArea: EdgeInsets { UIApplication.shared.keyWindow?.safeAreaInsets.swiftUiInsets ?? EdgeInsets() } } -extension EnvironmentValues { - var safeAreaInsets: EdgeInsets { - self[SafeAreaInsetsKey.self] - } -} - private extension UIEdgeInsets { var swiftUiInsets: EdgeInsets { EdgeInsets(top: top, leading: left, bottom: bottom, trailing: right) } } - -private extension UIEdgeInsets { - var insets: EdgeInsets { - EdgeInsets(top: top, leading: left, bottom: bottom, trailing: right) - } -} diff --git a/Sources/ExyteMediaPicker/Views/Widgets/Thumbnail/ThumbnailPlaceholder.swift b/Sources/ExyteMediaPicker/Utils/Thumbnail/ThumbnailPlaceholder.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Widgets/Thumbnail/ThumbnailPlaceholder.swift rename to Sources/ExyteMediaPicker/Utils/Thumbnail/ThumbnailPlaceholder.swift diff --git a/Sources/ExyteMediaPicker/Views/Widgets/Thumbnail/ThumbnailView.swift b/Sources/ExyteMediaPicker/Utils/Thumbnail/ThumbnailView.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Widgets/Thumbnail/ThumbnailView.swift rename to Sources/ExyteMediaPicker/Utils/Thumbnail/ThumbnailView.swift diff --git a/Sources/ExyteMediaPicker/Utils/Widgets/AsyncButton.swift b/Sources/ExyteMediaPicker/Utils/Widgets/AsyncButton.swift new file mode 100644 index 0000000..58f8fac --- /dev/null +++ b/Sources/ExyteMediaPicker/Utils/Widgets/AsyncButton.swift @@ -0,0 +1,23 @@ +// +// SwiftUIView.swift +// +// +// Created by Alisa Mylnikova on 01.04.2025. +// + +import SwiftUI + +struct AsyncButton: View { + var action: () async -> () + var label: (()->Content) + + var body: some View { + Button { + Task { + await action() + } + } label: { + label() + } + } +} diff --git a/Sources/ExyteMediaPicker/Views/Widgets/CameraStubView.swift b/Sources/ExyteMediaPicker/Utils/Widgets/CameraStubView.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Widgets/CameraStubView.swift rename to Sources/ExyteMediaPicker/Utils/Widgets/CameraStubView.swift diff --git a/Sources/ExyteMediaPicker/Utils/Widgets/LimitedLibraryPickerProxyView.swift b/Sources/ExyteMediaPicker/Utils/Widgets/LimitedLibraryPickerProxyView.swift new file mode 100644 index 0000000..c58ae28 --- /dev/null +++ b/Sources/ExyteMediaPicker/Utils/Widgets/LimitedLibraryPickerProxyView.swift @@ -0,0 +1,37 @@ +// +// Created by Alex.M on 02.06.2022. +// + +import SwiftUI +import UIKit +import PhotosUI + +@MainActor +struct LimitedLibraryPickerProxyView: UIViewControllerRepresentable { + @Binding var isPresented: Bool + var didDismiss: @Sendable ()->() + + func makeUIViewController(context: Context) -> UIViewController { + let controller = UIViewController() + + DispatchQueue.main.async { [controller] in + PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: controller) + trackCompletion(in: controller) + } + + return controller + } + + func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} + + func trackCompletion(in controller: UIViewController) { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak controller] in + if controller?.presentedViewController == nil { + self.$isPresented.wrappedValue = false + self.didDismiss() + } else if let controller = controller { + self.trackCompletion(in: controller) + } + } + } +} diff --git a/Sources/ExyteMediaPicker/Views/Widgets/MediasGrid.swift b/Sources/ExyteMediaPicker/Utils/Widgets/MediasGrid.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Widgets/MediasGrid.swift rename to Sources/ExyteMediaPicker/Utils/Widgets/MediasGrid.swift diff --git a/Sources/ExyteMediaPicker/Views/Widgets/PlayerUIView.swift b/Sources/ExyteMediaPicker/Utils/Widgets/PlayerUIView.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Widgets/PlayerUIView.swift rename to Sources/ExyteMediaPicker/Utils/Widgets/PlayerUIView.swift diff --git a/Sources/ExyteMediaPicker/Views/Widgets/SelectableView.swift b/Sources/ExyteMediaPicker/Utils/Widgets/SelectableView.swift similarity index 100% rename from Sources/ExyteMediaPicker/Views/Widgets/SelectableView.swift rename to Sources/ExyteMediaPicker/Utils/Widgets/SelectableView.swift diff --git a/Sources/ExyteMediaPicker/Views/Widgets/SelectionIndicatorView.swift b/Sources/ExyteMediaPicker/Utils/Widgets/SelectionIndicatorView.swift similarity index 98% rename from Sources/ExyteMediaPicker/Views/Widgets/SelectionIndicatorView.swift rename to Sources/ExyteMediaPicker/Utils/Widgets/SelectionIndicatorView.swift index a6eaf77..8945bd0 100644 --- a/Sources/ExyteMediaPicker/Views/Widgets/SelectionIndicatorView.swift +++ b/Sources/ExyteMediaPicker/Utils/Widgets/SelectionIndicatorView.swift @@ -44,7 +44,7 @@ struct SelectionIndicatorView: View { selected ? selectedBackground : emptyBackground, border: selected ? selectedBorder : emptyBorder, 2 ) - if let index { + if index != nil { Image(systemName: "checkmark") .resizable() .foregroundColor(selectedCheckmark) diff --git a/Sources/ExyteMediaPicker/Views/Widgets/LimitedLibraryPickerProxyView.swift b/Sources/ExyteMediaPicker/Views/Widgets/LimitedLibraryPickerProxyView.swift deleted file mode 100644 index 5eeb512..0000000 --- a/Sources/ExyteMediaPicker/Views/Widgets/LimitedLibraryPickerProxyView.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// Created by Alex.M on 02.06.2022. -// - -import SwiftUI -import UIKit -import PhotosUI - -struct LimitedLibraryPickerProxyView: UIViewControllerRepresentable { - @Binding var isPresented: Bool - var didDismiss: @Sendable ()->() - - func makeUIViewController(context: Context) -> UIViewController { - let controller = UIViewController() - - DispatchQueue.main.async { [controller] in - PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: controller) - context.coordinator.trackCompletion(in: controller) - } - - return controller - } - - func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} - - func makeCoordinator() -> Coordinator { - Coordinator(isPresented: $isPresented, didDismiss: didDismiss) - } - - final class Coordinator: NSObject, Sendable { - private let isPresented: Binding - let didDismiss: @Sendable ()->() - - init(isPresented: Binding, didDismiss: @escaping @Sendable ()->()) { - self.isPresented = isPresented - self.didDismiss = didDismiss - } - - func trackCompletion(in controller: UIViewController) { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self, weak controller] in - if controller?.presentedViewController == nil { - self?.isPresented.wrappedValue = false - self?.didDismiss() - } else if let controller = controller { - self?.trackCompletion(in: controller) - } - } - } - } -} From c287ee410e5d9e9d642ed9c715042830f174a1d6 Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Fri, 4 Apr 2025 16:38:24 +0700 Subject: [PATCH 57/70] Fix showFullscreenPreview --- .../Screens/AlbumView/AlbumView.swift | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/Sources/ExyteMediaPicker/Screens/AlbumView/AlbumView.swift b/Sources/ExyteMediaPicker/Screens/AlbumView/AlbumView.swift index c89ae86..334c970 100644 --- a/Sources/ExyteMediaPicker/Screens/AlbumView/AlbumView.swift +++ b/Sources/ExyteMediaPicker/Screens/AlbumView/AlbumView.swift @@ -113,25 +113,27 @@ struct AlbumView: View { } label: { let id = "fullscreen_photo_\(index)" MediaCell(viewModel: MediaViewModel(assetMediaModel: assetMediaModel), size: size) - .useAsPopupAnchor(id: id) { - FullscreenContainer( - currentFullscreenMedia: $currentFullscreenMedia, - selection: $fullscreenItem, - animationID: id, - assetMediaModels: viewModel.assetMediaModels, - selectionParamsHolder: selectionParamsHolder, - dismiss: dismiss + .applyIf(selectionParamsHolder.showFullscreenPreview) { + $0.useAsPopupAnchor(id: id) { + FullscreenContainer( + currentFullscreenMedia: $currentFullscreenMedia, + selection: $fullscreenItem, + animationID: id, + assetMediaModels: viewModel.assetMediaModels, + selectionParamsHolder: selectionParamsHolder, + dismiss: dismiss + ) + .environmentObject(selectionService) + } customize: { + $0.closeOnTap(false) + .animation(.easeIn(duration: 0.2)) + } + .simultaneousGesture( + TapGesture().onEnded { + fullscreenItem = assetMediaModel.id + } ) - .environmentObject(selectionService) - } customize: { - $0.closeOnTap(false) - .animation(.easeIn(duration: 0.2)) } - .simultaneousGesture( - TapGesture().onEnded { - fullscreenItem = assetMediaModel.id - } - ) } .buttonStyle(MediaButtonStyle()) .contentShape(Rectangle()) From 05a4cdc22cbe2828f4896cbb139bbb5bb6b975e0 Mon Sep 17 00:00:00 2001 From: Fred Bowker Date: Thu, 28 Aug 2025 11:10:16 +0100 Subject: [PATCH 58/70] Added editorconfig for standardised formatting --- .editorconfig | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..51de011 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,3 @@ +[*.{swift}] +indent_style = space +indent_size = 4 \ No newline at end of file From dd9d41c2e39cf9cef18b0bd508a71ada4570051b Mon Sep 17 00:00:00 2001 From: Fred Bowker Date: Thu, 28 Aug 2025 11:21:52 +0100 Subject: [PATCH 59/70] changed to have fixed 3 column width instead of dynamic width --- .../ExyteMediaPicker/Utils/Extensions/ColumnCalculation.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/ExyteMediaPicker/Utils/Extensions/ColumnCalculation.swift b/Sources/ExyteMediaPicker/Utils/Extensions/ColumnCalculation.swift index 6a751b6..054e111 100644 --- a/Sources/ExyteMediaPicker/Utils/Extensions/ColumnCalculation.swift +++ b/Sources/ExyteMediaPicker/Utils/Extensions/ColumnCalculation.swift @@ -10,8 +10,7 @@ import SwiftUI @MainActor func calculateColumnWidth(spacing: CGFloat) -> (CGFloat, [GridItem]) { let gridWidth = UIScreen.main.bounds.width - let minColumnWidth = 100.0 - let wholeCount = CGFloat(Int(gridWidth / minColumnWidth)) + let wholeCount = 3.0 let noSpaces = gridWidth - spacing * (wholeCount - 1) let columnWidth = noSpaces / wholeCount let columns = Array(repeating: GridItem(.fixed(columnWidth), spacing: spacing, alignment: .top), count: Int(wholeCount)) From deff05802339ef5676a408b3c611b6dac657f01b Mon Sep 17 00:00:00 2001 From: FireLord Date: Fri, 29 Aug 2025 19:13:35 +0530 Subject: [PATCH 60/70] fix(camera): correctly switch between front and back cameras The old flipCamera() assumed the first session input was the video device, which failed if other inputs (e.g. audio) were present. This caused camera flipping to break or remove the wrong input. The guard in addInput() would fail when the audio input was already added, preventing the zoom configuration from running for back cameras. This caused the camera to default to the ultra-wide-angle lens instead of preserving the wide angle behavior based on the zoom value. --- .../Screens/Camera/CameraViewModel.swift | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Sources/ExyteMediaPicker/Screens/Camera/CameraViewModel.swift b/Sources/ExyteMediaPicker/Screens/Camera/CameraViewModel.swift index 3c838f1..8e21fc9 100644 --- a/Sources/ExyteMediaPicker/Screens/Camera/CameraViewModel.swift +++ b/Sources/ExyteMediaPicker/Screens/Camera/CameraViewModel.swift @@ -107,7 +107,9 @@ final actor CameraViewModel: NSObject, ObservableObject { func flipCamera() { let session = captureSession - guard let input = session.inputs.first else { + guard let input = session.inputs + .compactMap({ $0 as? AVCaptureDeviceInput }) + .first(where: { $0.device.hasMediaType(.video) }) else { return } let newPosition: AVCaptureDevice.Position = captureDevice?.position == .back ? .front : .back @@ -173,10 +175,13 @@ final actor CameraViewModel: NSObject, ObservableObject { guard session.canAddInput(captureDeviceInput) else { return } session.addInput(captureDeviceInput) - guard let captureAudioDevice = selectAudioCaptureDevice() else { return } - guard let captureAudioDeviceInput = try? AVCaptureDeviceInput(device: captureAudioDevice) else { return } - guard session.canAddInput(captureAudioDeviceInput) else { return } - session.addInput(captureAudioDeviceInput) + let hasAudioInput = session.inputs.contains { ($0 as? AVCaptureDeviceInput)?.device.hasMediaType(.audio) == true } + if !hasAudioInput { + guard let captureAudioDevice = selectAudioCaptureDevice() else { return } + guard let captureAudioDeviceInput = try? AVCaptureDeviceInput(device: captureAudioDevice) else { return } + guard session.canAddInput(captureAudioDeviceInput) else { return } + session.addInput(captureAudioDeviceInput) + } let defaultZoom = CGFloat(truncating: captureDevice.virtualDeviceSwitchOverVideoZoomFactors.first ?? minScale as NSNumber) From fa8601904572a7abad529734ff17daf4418cb172 Mon Sep 17 00:00:00 2001 From: FireLord Date: Fri, 29 Aug 2025 20:30:02 +0530 Subject: [PATCH 61/70] fix(camera): remove applyIf by inlining zoom gesture condition Using `.applyIf` (or wrapping with `Group`) to conditionally attach the `MagnificationGesture` introduced a noticeable delay when camera opens because the view is recreated. This commit replaces the higher-order conditional wrapper with a direct `isZoomSupported` check inside the gesture handlers. The zoom gesture is now always attached, but only processes events when zoom is supported. This removes the delay while still preventing unsupported devices from triggering zoom. --- .../Screens/Camera/CameraView.swift | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Sources/ExyteMediaPicker/Screens/Camera/CameraView.swift b/Sources/ExyteMediaPicker/Screens/Camera/CameraView.swift index ee4779d..a4edefd 100644 --- a/Sources/ExyteMediaPicker/Screens/Camera/CameraView.swift +++ b/Sources/ExyteMediaPicker/Screens/Camera/CameraView.swift @@ -90,13 +90,19 @@ struct StandardConrolsCameraView: View { Rectangle() } } - .applyIf(cameraViewModel.zoomAllowed) { - $0.gesture( - MagnificationGesture() - .onChanged(cameraViewModel.zoomChanged(_:)) - .onEnded(cameraViewModel.zoomEnded(_:)) - ) - } + .gesture( + MagnificationGesture() + .onChanged { value in + if cameraViewModel.zoomAllowed { + cameraViewModel.zoomChanged(value) + } + } + .onEnded { value in + if cameraViewModel.zoomAllowed { + cameraViewModel.zoomEnded(value) + } + } + ) VStack(spacing: 10) { if cameraSelectionService.hasSelected { From 56c940617453a5b4eadab8adb90ad0f97e575fd2 Mon Sep 17 00:00:00 2001 From: Fred Bowker Date: Thu, 11 Sep 2025 09:18:49 +0100 Subject: [PATCH 62/70] added functionality for showing camera cell prominent, 2 cell height --- .../CustomizedMediaPicker.swift | 2 +- README.md | 2 +- .../Screens/AlbumView/AlbumView.swift | 26 ++++++-- .../MediaPicker/AlbumSelectionView.swift | 6 +- .../Screens/MediaPicker/MediaPicker.swift | 10 +-- .../Utils/Widgets/MediasGrid.swift | 64 +++++++++++++++++-- 6 files changed, 90 insertions(+), 20 deletions(-) diff --git a/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift b/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift index abe271e..a2942ef 100644 --- a/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift +++ b/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift @@ -83,7 +83,7 @@ struct CustomizedMediaPicker: View { } } ) - .showLiveCameraCell() + .liveCameraCell(.small) .albums($albums) .pickerMode($mediaPickerMode) .currentFullscreenMedia($currentFullscreenMedia) diff --git a/README.md b/README.md index 2d7191e..ae039fc 100644 --- a/README.md +++ b/README.md @@ -164,7 +164,7 @@ MediaPicker( - `flipCamera` - camera back/front ## Available modifiers -`showLiveCameraCell` - show live camera feed cell in the top left corner of the gallery grid +`liveCameraCell` - show live camera feed cell in the top left corner of the gallery grid, type .none, .small, .prominant (2 cell height) defaults to .small `mediaSelectionType` - limit displayed media type: .photo, .video or both `mediaSelectionStyle` - a way to display selected/unselected media state: a counter or a simple checkmark `mediaSelectionLimit` - the maximum selection quantity allowed, 'nil' for unlimited selection diff --git a/Sources/ExyteMediaPicker/Screens/AlbumView/AlbumView.swift b/Sources/ExyteMediaPicker/Screens/AlbumView/AlbumView.swift index 334c970..1f92f2a 100644 --- a/Sources/ExyteMediaPicker/Screens/AlbumView/AlbumView.swift +++ b/Sources/ExyteMediaPicker/Screens/AlbumView/AlbumView.swift @@ -2,6 +2,12 @@ // Created by Alex.M on 27.05.2022. // +public enum LiveCameraCellStyle { + case none + case small + case prominant +} + import SwiftUI import AnchoredPopup @@ -17,7 +23,7 @@ struct AlbumView: View { @Binding var showingCamera: Bool @Binding var currentFullscreenMedia: Media? - var shouldShowCamera: Bool + var liveCameraCell: LiveCameraCellStyle var selectionParamsHolder: SelectionParamsHolder var dismiss: ()->() @@ -43,7 +49,7 @@ struct AlbumView: View { VStack(spacing: 0) { PermissionActionView(type: .library(permissionsService.photoLibraryPermissionStatus)) - if shouldShowCamera { + if liveCameraCell != .none { PermissionActionView(type: .camera(permissionsService.cameraPermissionStatus)) } @@ -69,11 +75,23 @@ struct AlbumView: View { } } } + + private func getLiveCameraCell() -> LiveCameraCellStyle { + #if targetEnvironment(simulator) + return .none + #else + return if permissionsService.cameraPermissionStatus != .authorized { + .none + } else { + liveCameraCell + } + #endif + } var mediasGrid: some View { - MediasGrid(viewModel.assetMediaModels) { + MediasGrid(viewModel.assetMediaModels, liveCameraCell: getLiveCameraCell()) { #if !targetEnvironment(simulator) - if shouldShowCamera && permissionsService.cameraPermissionStatus == .authorized { + if getLiveCameraCell() != .none && permissionsService.cameraPermissionStatus == .authorized { LiveCameraCell { showingCamera = true } diff --git a/Sources/ExyteMediaPicker/Screens/MediaPicker/AlbumSelectionView.swift b/Sources/ExyteMediaPicker/Screens/MediaPicker/AlbumSelectionView.swift index aba7527..dbf6ebf 100644 --- a/Sources/ExyteMediaPicker/Screens/MediaPicker/AlbumSelectionView.swift +++ b/Sources/ExyteMediaPicker/Screens/MediaPicker/AlbumSelectionView.swift @@ -14,7 +14,7 @@ public struct AlbumSelectionView: View { @Binding var showingCamera: Bool @Binding var currentFullscreenMedia: Media? - let showingLiveCameraCell: Bool + let liveCameraCell: LiveCameraCellStyle let selectionParamsHolder: SelectionParamsHolder let filterClosure: MediaPicker.FilterClosure? let massFilterClosure: MediaPicker.MassFilterClosure? @@ -27,7 +27,7 @@ public struct AlbumSelectionView: View { viewModel: AllMediasProvider(selectionParamsHolder: selectionParamsHolder, filterClosure: filterClosure, massFilterClosure: massFilterClosure), showingCamera: $showingCamera, currentFullscreenMedia: $currentFullscreenMedia, - shouldShowCamera: showingLiveCameraCell, + liveCameraCell: liveCameraCell, selectionParamsHolder: selectionParamsHolder, dismiss: dismiss ) @@ -52,7 +52,7 @@ public struct AlbumSelectionView: View { viewModel: AlbumMediasProvider(album: albumModel, selectionParamsHolder: selectionParamsHolder, filterClosure: filterClosure, massFilterClosure: massFilterClosure), showingCamera: $showingCamera, currentFullscreenMedia: $currentFullscreenMedia, - shouldShowCamera: false, + liveCameraCell: .none, selectionParamsHolder: selectionParamsHolder, dismiss: dismiss ) diff --git a/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift b/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift index ce40286..bd88aff 100644 --- a/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift +++ b/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift @@ -49,7 +49,7 @@ public struct MediaPicker? - private var showingLiveCameraCell: Bool = false + private var liveCameraCell: LiveCameraCellStyle = LiveCameraCellStyle.none private var didPressCancelCamera: (() -> Void)? private var orientationHandler: MediaPickerOrientationHandler = {_ in} private var filterClosure: FilterClosure? @@ -108,7 +108,7 @@ public struct MediaPicker MediaPicker { + func liveCameraCell(_ style: LiveCameraCellStyle = .small) -> MediaPicker { var mediaPicker = self - mediaPicker.showingLiveCameraCell = show + mediaPicker.liveCameraCell = style return mediaPicker } diff --git a/Sources/ExyteMediaPicker/Utils/Widgets/MediasGrid.swift b/Sources/ExyteMediaPicker/Utils/Widgets/MediasGrid.swift index e89da6d..3f21cae 100644 --- a/Sources/ExyteMediaPicker/Utils/Widgets/MediasGrid.swift +++ b/Sources/ExyteMediaPicker/Utils/Widgets/MediasGrid.swift @@ -9,6 +9,7 @@ public struct MediasGrid: View where Element: Identifiable, Camera: View, Content: View, LoadingCell: View { public let data: [Element] + public let liveCameraCell: LiveCameraCellStyle public let camera: () -> Camera public let content: (Element, _ index: Int, _ size: CGFloat) -> Content public let loadingCell: () -> LoadingCell @@ -16,23 +17,74 @@ where Element: Identifiable, Camera: View, Content: View, LoadingCell: View { @Environment(\.mediaPickerTheme) private var theme public init(_ data: [Element], + liveCameraCell: LiveCameraCellStyle, @ViewBuilder camera: @escaping () -> Camera, @ViewBuilder content: @escaping (Element, Int, CGFloat) -> Content, @ViewBuilder loadingCell: @escaping () -> LoadingCell) { self.data = data + self.liveCameraCell = liveCameraCell self.camera = camera self.content = content self.loadingCell = loadingCell } public var body: some View { - let (columnWidth, columns) = calculateColumnWidth(spacing: theme.cellStyle.columnsSpacing) - LazyVGrid(columns: columns, spacing: theme.cellStyle.rowSpacing) { - camera() - ForEach(data.indices, id: \.self) { index in - content(data[index], index, columnWidth) + let (columnWidth, columns) = calculateColumnWidth( + spacing: theme.cellStyle.columnsSpacing + ) + let spacing = theme.cellStyle.rowSpacing + + switch liveCameraCell { + case .prominant: + let columnCount = columns.count + let indexedData = Array(data.enumerated()) + let itemsToTake = (columnCount - 1) * 2 + let topData = indexedData.prefix(itemsToTake) + let remainingData = indexedData.dropFirst(itemsToTake) + + VStack(alignment: .leading, spacing: 0) { + HStack(alignment: .top, spacing: spacing) { + camera() + .frame(width: columnWidth, height: columnWidth * 2 + spacing) + .clipped() + + let topColumns = Array( + repeating: GridItem(.fixed(columnWidth), spacing: spacing,alignment: .top), + count: columnCount - 1 + ) + + LazyVGrid(columns: topColumns, spacing: spacing) { + ForEach(topData, id: \.element.id) { index, element in + content(element, index, columnWidth) + } + } + } + .padding(.bottom, spacing) + + LazyVGrid(columns: columns, spacing: spacing) { + ForEach(remainingData, id: \.element.id) { index, element in + content(element, index, columnWidth) + } + loadingCell() + } + } + + case .small: + LazyVGrid(columns: columns, spacing: theme.cellStyle.rowSpacing) { + camera() + ForEach(data.indices, id: \.self) { index in + content(data[index], index, columnWidth) + } + loadingCell() + } + + case .none: + LazyVGrid(columns: columns, spacing: spacing) { + ForEach(Array(data.enumerated()), id: \.element.id) { index, element in + content(element, index, columnWidth) + } + loadingCell() } - loadingCell() } } } From 2dc3aec92b127eb97dd350e39b31c28a155da749 Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Fri, 12 Sep 2025 14:44:38 +0700 Subject: [PATCH 63/70] Restore camera cell method with deprecated annotation --- Sources/ExyteMediaPicker/Screens/AlbumView/AlbumView.swift | 4 ++-- .../ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Sources/ExyteMediaPicker/Screens/AlbumView/AlbumView.swift b/Sources/ExyteMediaPicker/Screens/AlbumView/AlbumView.swift index 1f92f2a..fbb8181 100644 --- a/Sources/ExyteMediaPicker/Screens/AlbumView/AlbumView.swift +++ b/Sources/ExyteMediaPicker/Screens/AlbumView/AlbumView.swift @@ -4,8 +4,8 @@ public enum LiveCameraCellStyle { case none - case small - case prominant + case small // 1 cell in photos grid + case prominant // 2 cell height } import SwiftUI diff --git a/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift b/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift index bd88aff..d43b13d 100644 --- a/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift +++ b/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift @@ -327,6 +327,13 @@ public extension MediaPicker { return mediaPicker } + @available(*, deprecated, message: "use liveCameraCell instead") + func showLiveCameraCell(_ show: Bool = true) -> MediaPicker { + var mediaPicker = self + mediaPicker.liveCameraCell = .small + return mediaPicker + } + func mediaSelectionType(_ type: MediaSelectionType) -> MediaPicker { selectionParamsHolder.mediaType = type return self From a4d1c69807d80af1350073eac6aa2bc25d5e55fb Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Fri, 12 Sep 2025 15:05:19 +0700 Subject: [PATCH 64/70] Small fix --- Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift b/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift index d43b13d..e100e07 100644 --- a/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift +++ b/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift @@ -330,7 +330,7 @@ public extension MediaPicker { @available(*, deprecated, message: "use liveCameraCell instead") func showLiveCameraCell(_ show: Bool = true) -> MediaPicker { var mediaPicker = self - mediaPicker.liveCameraCell = .small + mediaPicker.liveCameraCell = show ? .small : .none return mediaPicker } From 36019ee7e7073954ecdaf755fd1b17790859885b Mon Sep 17 00:00:00 2001 From: Fred Bowker Date: Thu, 16 Oct 2025 12:57:44 +0100 Subject: [PATCH 65/70] setMediaPickerParameters with parameter liveCameraCell allows external libraries to set the liveCameraCell style --- .../MediaPickerExample/ContentView.swift | 1 - .../CustomizedMediaPicker.swift | 2 +- .../Screens/AlbumView/AlbumView.swift | 6 +++--- .../Screens/AlbumsView/AlbumsView.swift | 1 + .../MediaPicker/AlbumSelectionView.swift | 9 +++++---- .../Screens/MediaPicker/MediaPicker.swift | 17 +++++++++++------ .../MediaPicker/MediaPickerParamsHolder.swift | 8 ++++++++ 7 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPickerParamsHolder.swift diff --git a/MediaPickerExample/MediaPickerExample/ContentView.swift b/MediaPickerExample/MediaPickerExample/ContentView.swift index 2f96ad7..5ae8aae 100644 --- a/MediaPickerExample/MediaPickerExample/ContentView.swift +++ b/MediaPickerExample/MediaPickerExample/ContentView.swift @@ -58,7 +58,6 @@ struct ContentView: View { isPresented: $showDefaultMediaPicker, onChange: { medias = $0 } ) - //.showLiveCameraCell() .mediaSelectionType(.photoAndVideo) .mediaSelectionStyle(.count) .orientationHandler { diff --git a/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift b/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift index a2942ef..8f47832 100644 --- a/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift +++ b/MediaPickerExample/MediaPickerExample/CustomizedMediaPicker.swift @@ -83,7 +83,7 @@ struct CustomizedMediaPicker: View { } } ) - .liveCameraCell(.small) + .liveCameraCell(.prominant) .albums($albums) .pickerMode($mediaPickerMode) .currentFullscreenMedia($currentFullscreenMedia) diff --git a/Sources/ExyteMediaPicker/Screens/AlbumView/AlbumView.swift b/Sources/ExyteMediaPicker/Screens/AlbumView/AlbumView.swift index 1f92f2a..fe9d295 100644 --- a/Sources/ExyteMediaPicker/Screens/AlbumView/AlbumView.swift +++ b/Sources/ExyteMediaPicker/Screens/AlbumView/AlbumView.swift @@ -23,8 +23,8 @@ struct AlbumView: View { @Binding var showingCamera: Bool @Binding var currentFullscreenMedia: Media? - var liveCameraCell: LiveCameraCellStyle var selectionParamsHolder: SelectionParamsHolder + var mediaPickerParamsHolder: MediaPickerParamsHolder var dismiss: ()->() @State private var fullscreenItem: AssetMediaModel.ID? @@ -49,7 +49,7 @@ struct AlbumView: View { VStack(spacing: 0) { PermissionActionView(type: .library(permissionsService.photoLibraryPermissionStatus)) - if liveCameraCell != .none { + if mediaPickerParamsHolder.liveCameraCell != .none { PermissionActionView(type: .camera(permissionsService.cameraPermissionStatus)) } @@ -83,7 +83,7 @@ struct AlbumView: View { return if permissionsService.cameraPermissionStatus != .authorized { .none } else { - liveCameraCell + mediaPickerParamsHolder.liveCameraCell } #endif } diff --git a/Sources/ExyteMediaPicker/Screens/AlbumsView/AlbumsView.swift b/Sources/ExyteMediaPicker/Screens/AlbumsView/AlbumsView.swift index 3365b49..ddd4c0f 100644 --- a/Sources/ExyteMediaPicker/Screens/AlbumsView/AlbumsView.swift +++ b/Sources/ExyteMediaPicker/Screens/AlbumsView/AlbumsView.swift @@ -17,6 +17,7 @@ struct AlbumsView: View { @Binding var currentFullscreenMedia: Media? let selectionParamsHolder: SelectionParamsHolder + let mediaPickerParamsHolder: MediaPickerParamsHolder let filterClosure: MediaPicker.FilterClosure? let massFilterClosure: MediaPicker.MassFilterClosure? diff --git a/Sources/ExyteMediaPicker/Screens/MediaPicker/AlbumSelectionView.swift b/Sources/ExyteMediaPicker/Screens/MediaPicker/AlbumSelectionView.swift index dbf6ebf..8034743 100644 --- a/Sources/ExyteMediaPicker/Screens/MediaPicker/AlbumSelectionView.swift +++ b/Sources/ExyteMediaPicker/Screens/MediaPicker/AlbumSelectionView.swift @@ -14,8 +14,8 @@ public struct AlbumSelectionView: View { @Binding var showingCamera: Bool @Binding var currentFullscreenMedia: Media? - let liveCameraCell: LiveCameraCellStyle let selectionParamsHolder: SelectionParamsHolder + let mediaPickerParamsHolder: MediaPickerParamsHolder let filterClosure: MediaPicker.FilterClosure? let massFilterClosure: MediaPicker.MassFilterClosure? var dismiss: ()->() @@ -27,8 +27,8 @@ public struct AlbumSelectionView: View { viewModel: AllMediasProvider(selectionParamsHolder: selectionParamsHolder, filterClosure: filterClosure, massFilterClosure: massFilterClosure), showingCamera: $showingCamera, currentFullscreenMedia: $currentFullscreenMedia, - liveCameraCell: liveCameraCell, selectionParamsHolder: selectionParamsHolder, + mediaPickerParamsHolder: mediaPickerParamsHolder, dismiss: dismiss ) case .albums: @@ -40,6 +40,7 @@ public struct AlbumSelectionView: View { showingCamera: $showingCamera, currentFullscreenMedia: $currentFullscreenMedia, selectionParamsHolder: selectionParamsHolder, + mediaPickerParamsHolder: mediaPickerParamsHolder, filterClosure: filterClosure, massFilterClosure: massFilterClosure ) @@ -49,11 +50,11 @@ public struct AlbumSelectionView: View { case .album(let album): if let albumModel = viewModel.getAlbumModel(album) { AlbumView( - viewModel: AlbumMediasProvider(album: albumModel, selectionParamsHolder: selectionParamsHolder, filterClosure: filterClosure, massFilterClosure: massFilterClosure), + viewModel: AlbumMediasProvider(album: albumModel, selectionParamsHolder: SelectionParamsHolder(), filterClosure: filterClosure, massFilterClosure: massFilterClosure), showingCamera: $showingCamera, currentFullscreenMedia: $currentFullscreenMedia, - liveCameraCell: .none, selectionParamsHolder: selectionParamsHolder, + mediaPickerParamsHolder: MediaPickerParamsHolder(liveCameraCell: .none), dismiss: dismiss ) .id(album.id) diff --git a/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift b/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift index bd88aff..0c13750 100644 --- a/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift +++ b/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift @@ -49,12 +49,12 @@ public struct MediaPicker? - private var liveCameraCell: LiveCameraCellStyle = LiveCameraCellStyle.none private var didPressCancelCamera: (() -> Void)? private var orientationHandler: MediaPickerOrientationHandler = {_ in} private var filterClosure: FilterClosure? private var massFilterClosure: MassFilterClosure? private var selectionParamsHolder = SelectionParamsHolder() + private var mediaPickerParamsHolder = MediaPickerParamsHolder() // MARK: - Inner values @@ -108,7 +108,7 @@ public struct MediaPicker MediaPicker { - var mediaPicker = self - mediaPicker.liveCameraCell = style - return mediaPicker + mediaPickerParamsHolder.liveCameraCell = style + return self } func mediaSelectionType(_ type: MediaSelectionType) -> MediaPicker { @@ -347,6 +346,12 @@ public extension MediaPicker { return self } + func setMediaPickerParameters(_ params: MediaPickerParamsHolder) -> MediaPicker { + var mediaPicker = self + mediaPicker.mediaPickerParamsHolder = params + return mediaPicker + } + func setSelectionParameters(_ params: SelectionParamsHolder?) -> MediaPicker { guard let params = params else { return self diff --git a/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPickerParamsHolder.swift b/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPickerParamsHolder.swift new file mode 100644 index 0000000..040e015 --- /dev/null +++ b/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPickerParamsHolder.swift @@ -0,0 +1,8 @@ +public class MediaPickerParamsHolder { + + var liveCameraCell: LiveCameraCellStyle + + init(liveCameraCell: LiveCameraCellStyle = LiveCameraCellStyle.small) { + self.liveCameraCell = liveCameraCell + } +} From 1e41f2a4efe46f7f306d36d9a1e7b2104c14f671 Mon Sep 17 00:00:00 2001 From: AlisaMylnikova Date: Fri, 17 Oct 2025 13:16:34 +0700 Subject: [PATCH 66/70] Fix compile error --- .../ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift b/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift index 65c9368..bf57f33 100644 --- a/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift +++ b/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift @@ -328,9 +328,8 @@ public extension MediaPicker { @available(*, deprecated, message: "use liveCameraCell instead") func showLiveCameraCell(_ show: Bool = true) -> MediaPicker { - var mediaPicker = self - mediaPicker.liveCameraCell = show ? .small : .none - return mediaPicker + mediaPickerParamsHolder.liveCameraCell = show ? .small : .none + return self } func mediaSelectionType(_ type: MediaSelectionType) -> MediaPicker { From 39e9b63c3026890e87c4e77880d81ca656bcb477 Mon Sep 17 00:00:00 2001 From: Fred Bowker Date: Sun, 19 Oct 2025 22:53:49 +0100 Subject: [PATCH 67/70] changing MediaPickerParamsHolder to public --- .../ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift | 5 ++++- .../Screens/MediaPicker/MediaPickerParamsHolder.swift | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift b/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift index bf57f33..ebbad49 100644 --- a/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift +++ b/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPicker.swift @@ -352,7 +352,10 @@ public extension MediaPicker { return self } - func setMediaPickerParameters(_ params: MediaPickerParamsHolder) -> MediaPicker { + func setMediaPickerParameters(_ params: MediaPickerParamsHolder?) -> MediaPicker { + guard let params = params else { + return self + } var mediaPicker = self mediaPicker.mediaPickerParamsHolder = params return mediaPicker diff --git a/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPickerParamsHolder.swift b/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPickerParamsHolder.swift index 040e015..dda203d 100644 --- a/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPickerParamsHolder.swift +++ b/Sources/ExyteMediaPicker/Screens/MediaPicker/MediaPickerParamsHolder.swift @@ -2,7 +2,7 @@ public class MediaPickerParamsHolder { var liveCameraCell: LiveCameraCellStyle - init(liveCameraCell: LiveCameraCellStyle = LiveCameraCellStyle.small) { + public init(liveCameraCell: LiveCameraCellStyle = LiveCameraCellStyle.small) { self.liveCameraCell = liveCameraCell } } From f398c403bb18e7e5e615ad6842bd92ebbe828366 Mon Sep 17 00:00:00 2001 From: Fred Bowker Date: Sat, 1 Nov 2025 13:08:58 +0000 Subject: [PATCH 68/70] update to create compressed thumbnail for image types in getThumbnailData --- .../Managers/PhotoKit/PHAsset+Utils.swift | 57 ++++++++++++++----- .../Model/URLMediaModel.swift | 4 +- 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/Sources/ExyteMediaPicker/Managers/PhotoKit/PHAsset+Utils.swift b/Sources/ExyteMediaPicker/Managers/PhotoKit/PHAsset+Utils.swift index 5acd04f..aad3265 100644 --- a/Sources/ExyteMediaPicker/Managers/PhotoKit/PHAsset+Utils.swift +++ b/Sources/ExyteMediaPicker/Managers/PhotoKit/PHAsset+Utils.swift @@ -102,28 +102,33 @@ extension PHAsset { func getThumbnailURL() async -> URL? { guard let url = await getURL() else { return nil } - if mediaType == .image { - return url - } - - let asset: AVAsset = AVAsset(url: url) - if let thumbnailData = asset.generateThumbnail() { + switch mediaType { + case .image: + guard let image = UIImage.from(url: url), + let thumbnailData = image.generateThumbnail() + else { return nil } return FileManager.storeToTempDir(data: thumbnailData) + case .video: + let asset = AVAsset(url: url) + guard let thumbnailData = asset.generateThumbnail() else { return nil } + return FileManager.storeToTempDir(data: thumbnailData) + default: return nil } - - return nil } func getThumbnailData() async -> Data? { - if mediaType == .image { - return try? await self.getData() - } - else if mediaType == .video { + switch mediaType { + case .image: + guard let data = try? await getData(), + let image = UIImage(data: data) + else { return nil } + return image.generateThumbnail() + case .video: guard let url = await getURL() else { return nil } let asset: AVAsset = AVAsset(url: url) return asset.generateThumbnail() + default: return nil } - return nil } } @@ -219,6 +224,32 @@ extension AVAsset { } } +extension UIImage { + + enum ThumbnailQuality { + case low + case medium + case high + + var compressionValue: CGFloat { + switch self { + case .low: return 0.3 + case .medium: return 0.6 + case .high: return 0.8 + } + } + } + + static func from(url: URL) -> UIImage? { + guard let data = try? Data(contentsOf: url) else { return nil } + return UIImage(data: data) + } + + func generateThumbnail(quality: ThumbnailQuality = .medium) -> Data? { + jpegData(compressionQuality: quality.compressionValue) + } +} + enum AssetFetchError: Error { case noImageData case unknownType diff --git a/Sources/ExyteMediaPicker/Model/URLMediaModel.swift b/Sources/ExyteMediaPicker/Model/URLMediaModel.swift index 057df30..5586ca0 100644 --- a/Sources/ExyteMediaPicker/Model/URLMediaModel.swift +++ b/Sources/ExyteMediaPicker/Model/URLMediaModel.swift @@ -44,7 +44,7 @@ extension URLMediaModel: MediaModelProtocol { func getThumbnailURL() async -> URL? { switch mediaType { case .image: - return url + return await url.getThumbnailURL() case .video: return await url.getThumbnailURL() case .none: @@ -59,7 +59,7 @@ extension URLMediaModel: MediaModelProtocol { func getThumbnailData() async -> Data? { switch mediaType { case .image: - return try? Data(contentsOf: url) + return await url.getThumbnailData() case .video: return await url.getThumbnailData() case .none: From eeec61127676d369a1eb938f3ff8c17ad67ea16a Mon Sep 17 00:00:00 2001 From: Denis Obukhov Date: Tue, 9 Dec 2025 16:30:37 +0700 Subject: [PATCH 69/70] Add public initializer for Media struct Fixes #35 --- Sources/ExyteMediaPicker/Model/Media.swift | 6 +++++- Sources/ExyteMediaPicker/Model/MediaModelProtocol.swift | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Sources/ExyteMediaPicker/Model/Media.swift b/Sources/ExyteMediaPicker/Model/Media.swift index 5eebaea..017f09c 100644 --- a/Sources/ExyteMediaPicker/Model/Media.swift +++ b/Sources/ExyteMediaPicker/Model/Media.swift @@ -11,7 +11,11 @@ public enum MediaType { public struct Media: Identifiable, Equatable, Sendable { public var id = UUID() - internal let source: MediaModelProtocol + public let source: MediaModelProtocol + + public init(source: MediaModelProtocol) { + self.source = source + } public static func == (lhs: Media, rhs: Media) -> Bool { lhs.id == rhs.id diff --git a/Sources/ExyteMediaPicker/Model/MediaModelProtocol.swift b/Sources/ExyteMediaPicker/Model/MediaModelProtocol.swift index ad86238..bcea99f 100644 --- a/Sources/ExyteMediaPicker/Model/MediaModelProtocol.swift +++ b/Sources/ExyteMediaPicker/Model/MediaModelProtocol.swift @@ -7,7 +7,7 @@ import SwiftUI -protocol MediaModelProtocol: Sendable { +public protocol MediaModelProtocol: Sendable { var mediaType: MediaType? { get } var duration: CGFloat? { get async } From 7de947d8f418c8554b089f7f05563c05df4f139a Mon Sep 17 00:00:00 2001 From: Denis Obukhov Date: Tue, 9 Dec 2025 19:01:58 +0700 Subject: [PATCH 70/70] Fix not dismissing AnchoredPopup with mediaSelectionLimit(1) Fixes #40 --- .../Screens/Fullscreen/FullscreenContainer.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/ExyteMediaPicker/Screens/Fullscreen/FullscreenContainer.swift b/Sources/ExyteMediaPicker/Screens/Fullscreen/FullscreenContainer.swift index 818fb3e..69e7057 100644 --- a/Sources/ExyteMediaPicker/Screens/Fullscreen/FullscreenContainer.swift +++ b/Sources/ExyteMediaPicker/Screens/Fullscreen/FullscreenContainer.swift @@ -115,6 +115,7 @@ struct FullscreenContainer: View { if let selectedMediaModel = selectedMediaModel { if selectionParamsHolder.selectionLimit == 1 { Button("Select") { + AnchoredPopup.launchShrinkingAnimation(id: animationID) selectionService.onSelect(assetMediaModel: selectedMediaModel) dismiss() }