Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix refreshing of albums and images #182

Merged
merged 4 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions App/Resources/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@

"AlbumsBarButtonItem.standardAccessibilityLabel" = "Albums";

"AlbumsViewController.navigationTitle" = "Albums";

"AppDelegate.preferencesMenuTitle" = "Preferences";
"AppDelegate.preferencesMenuItemTitle" = "Preferences…";
"AppDelegate.saveMenuTitle" = "Save";
Expand All @@ -20,9 +18,6 @@
"BasePhotoEditingViewController.undoKeyCommandDiscoverabilityTitle" = "Undo Redaction";
"BasePhotoEditingViewController.redoKeyCommandDiscoverabilityTitle" = "Redo Redaction";

"CollectionsDataSource.userAlbumsHeader" = "Albums";
"CollectionsDataSource.smartAlbumsHeader" = "Library";

"ColorPickerBarButtonItem.accessibilityLabel" = "Color Picker";

"ContactMailViewController.emailSubject" = "Hello!";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
Localizable.strings
Highlighter

Created by Geoff Pado on 7/1/24.
Copyright © 2024 Cocoatype, LLC. All rights reserved.
*/

// Header for the user albums section in the albums list
"PhotoCollectionsDataSource.userAlbumsHeader" = "Albums";

// Header for the smart albums section in the albums list
"PhotoCollectionsDataSource.smartAlbumsHeader" = "Library";
Original file line number Diff line number Diff line change
@@ -1,30 +1,18 @@
// Created by Geoff Pado on 5/16/20.
// Copyright © 2020 Cocoatype, LLC. All rights reserved.
// Created by Geoff Pado on 7/1/24.
// Copyright © 2024 Cocoatype, LLC. All rights reserved.

import DesignSystem
import Photos
import UIKit

struct CollectionSection {
let title: String
let collections: [Collection]
}

protocol Collection {
var title: String? { get }
var icon: String { get }
var identifier: String { get }
}

struct AssetCollection: Collection {
var assets: PHFetchResult<PHAsset> {
public struct AssetCollection: PhotoCollection {
public var assets: PHFetchResult<PHAsset> {
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
return PHAsset.fetchAssets(in: assetCollection, options: fetchOptions)
}
var assetCount: Int { return assets.count }
var assetCollectionSubtype: PHAssetCollectionSubtype { assetCollection.assetCollectionSubtype }
var icon: String {
public var icon: String {
switch assetCollection.assetCollectionSubtype {
case .smartAlbumFavorites: return Icons.favoritesCollection
case .smartAlbumRecentlyAdded, .smartAlbumUserLibrary: return Icons.recentsCollection
Expand All @@ -33,18 +21,12 @@ struct AssetCollection: Collection {
}
}
var keyAssets: PHFetchResult<PHAsset> { return PHAsset.fetchKeyAssets(in: assetCollection, options: nil) ?? PHFetchResult<PHAsset>() }
var identifier: String { return assetCollection.localIdentifier }
var title: String? { return assetCollection.localizedTitle }
public var identifier: String { return assetCollection.localIdentifier }
public var title: String? { return assetCollection.localizedTitle }

init(_ assetCollection: PHAssetCollection) {
self.assetCollection = assetCollection
}

private let assetCollection: PHAssetCollection
}

struct EmptyCollection: Collection {
var title: String? { nil }
var icon: String { Icons.standardCollection }
var identifier: String { "" }
}
10 changes: 10 additions & 0 deletions Modules/Capabilities/AlbumsData/Sources/EmptyCollection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Created by Geoff Pado on 7/1/24.
// Copyright © 2024 Cocoatype, LLC. All rights reserved.

import DesignSystem

struct EmptyCollection: PhotoCollection {
var title: String? { nil }
var icon: String { Icons.standardCollection }
var identifier: String { "" }
}
8 changes: 8 additions & 0 deletions Modules/Capabilities/AlbumsData/Sources/PhotoCollection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Created by Geoff Pado on 5/16/20.
// Copyright © 2020 Cocoatype, LLC. All rights reserved.

public protocol PhotoCollection {
var title: String? { get }
var icon: String { get }
var identifier: String { get }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Created by Geoff Pado on 7/1/24.
// Copyright © 2024 Cocoatype, LLC. All rights reserved.

public struct PhotoCollectionSection {
public let title: String
public let collections: [PhotoCollection]

public init(title: String, collections: [PhotoCollection]) {
self.title = title
self.collections = collections
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import Photos

enum CollectionType {
public enum PhotoCollectionType {
case favorites
case library
case recents
Expand Down Expand Up @@ -31,7 +31,7 @@ enum CollectionType {
return PHAssetCollection.fetchAssetCollections(with: assetCollectionType, subtype: assetCollectionSubtype, options: nil)
}

var defaultCollection: Collection {
public var defaultCollection: PhotoCollection {
guard let defaultCollection = fetchResult.firstObject else {
assertionFailure("Did not return a default collection for type: \(self)")
return AssetCollection(PHAssetCollection())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Created by Geoff Pado on 5/16/20.
// Copyright © 2020 Cocoatype, LLC. All rights reserved.

import ErrorHandling
import Photos
import UIKit

public class PhotoCollectionsDataSource: NSObject, ObservableObject, PHPhotoLibraryChangeObserver {
@Published public var collectionsData: [PhotoCollectionSection]

public override init() {
collectionsData = Self.allSections()
super.init()

PHPhotoLibrary.shared().register(self)
}

// MARK: Change Observer

public func photoLibraryDidChange(_ changeInstance: PHChange) {
Task { @MainActor in
collectionsData = Self.allSections()
}
}

// MARK: Boilerplate

private static func allSections() -> [PhotoCollectionSection] {
return [
Self.section(title: AlbumsDataStrings.PhotoCollectionsDataSource.smartAlbumsHeader, types: [.library, .screenshots, .favorites]),
Self.section(title: AlbumsDataStrings.PhotoCollectionsDataSource.userAlbumsHeader, types: [.userAlbum]),
]
}

private static func section(title: String, types: [PhotoCollectionType]) -> PhotoCollectionSection {
PhotoCollectionSection(
title: title,
collections: types
.map { $0.fetchResult }
.flatMap { $0.objects(at: IndexSet(integersIn: 0..<$0.count)) }
.map(AssetCollection.init)
)
}
}
7 changes: 7 additions & 0 deletions Modules/Capabilities/AlbumsData/Tests/AlbumsDataTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//
// AlbumsDataTest.swift
// Highlighter
//
// Created by Geoff Pado on 7/1/24.
// Copyright © 2024 Cocoatype, LLC. All rights reserved.
//
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
Localizable.strings
Highlighter

Created by Geoff Pado on 7/1/24.
Copyright © 2024 Cocoatype, LLC. All rights reserved.
*/

// Navigation title for the albums list
"AlbumsViewController.navigationTitle" = "Albums";
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
// Created by Geoff Pado on 7/15/20.
// Copyright © 2020 Cocoatype, LLC. All rights reserved.

import AlbumsData
import AppNavigation
import SwiftUI

struct AlbumsList: View {
public struct AlbumsList: View {
@State private var selectedCollectionIdentifier: String?
@StateObject private var dataSource = PhotoCollectionsDataSource()

var navigationWrapper = NavigationWrapper.empty
let data: [CollectionSection]
init(data: [CollectionSection], selectedCollectionIdentifier: String? = CollectionType.library.defaultCollection.identifier) {
self.data = data
init(selectedCollectionIdentifier: String? = PhotoCollectionType.library.defaultCollection.identifier) {
self.selectedCollectionIdentifier = selectedCollectionIdentifier
}

var body: some View {
return List(selection: $selectedCollectionIdentifier) {
ForEach(data, id: \.title) { section in
public var body: some View {
List(selection: $selectedCollectionIdentifier) {
ForEach(dataSource.collectionsData, id: \.title) { section in
Section(header: AlbumsSectionHeader(section.title)) {
ForEach(section.collections, id: \.identifier) { collection in
AlbumsRow(collection, selection: $selectedCollectionIdentifier)
Expand All @@ -23,29 +25,29 @@ struct AlbumsList: View {
}
}
.listStyle(SidebarListStyle())
.navigationTitle("AlbumsViewController.navigationTitle")
.navigationTitle(AlbumsUIStrings.AlbumsViewController.navigationTitle)
.environmentObject(navigationWrapper)
.albumsListBackground()
}
}

enum AlbumsList_Previews: PreviewProvider {
static let fakeData = [
CollectionSection(title: "Smart Collections", collections: [
PhotoCollectionSection(title: "Smart Collections", collections: [
DummyCollection(title: "Recent Photos", iconName: "clock"),
DummyCollection(title: "Screenshots", iconName: "camera.viewfinder"),
DummyCollection(title: "Favorites", iconName: "suit.heart"),
]),
CollectionSection(title: "User Collections", collections: []),
PhotoCollectionSection(title: "User Collections", collections: []),
]

static var previews: some View {
AlbumsList(data: fakeData, selectedCollectionIdentifier: nil)
AlbumsList(selectedCollectionIdentifier: nil)
.preferredColorScheme(.dark)
}
}

struct DummyCollection: Collection {
struct DummyCollection: PhotoCollection {
let title: String?
let icon: String
let identifier: String
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
// Created by Geoff Pado on 8/30/21.
// Copyright © 2021 Cocoatype, LLC. All rights reserved.

import AlbumsData
import AppNavigation
import SwiftUI

struct AlbumsRow: View {
@Binding var selection: String?
private let collection: Collection
init(_ collection: Collection, selection: Binding<String?>) {
private let collection: PhotoCollection
init(_ collection: PhotoCollection, selection: Binding<String?>) {
self.collection = collection
self._selection = selection
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,60 +1,54 @@
// Created by Geoff Pado on 7/15/20.
// Copyright © 2020 Cocoatype, LLC. All rights reserved.

import Editing
import AlbumsData
import AppNavigation
import Photos
import Redactions
import SwiftUI

class AlbumsViewController: UIHostingController<AlbumsList>, NavigationWrapper.NavigationObject {
init() {
let albumsDataSource = CollectionsDataSource()
self.albumsDataSource = albumsDataSource

var albumsList = AlbumsList(data: albumsDataSource.collectionsData)
public class AlbumsViewController: UIHostingController<AlbumsList>, NavigationWrapper.NavigationObject {
public init() {
var albumsList = AlbumsList()
super.init(rootView: albumsList)

view.tintColor = .primaryDark

if let navigationObject = navigationObject {
navigationItem.title = Self.navigationTitle
if let navigationObject {
navigationItem.title = AlbumsUIStrings.AlbumsViewController.navigationTitle
albumsList.navigationWrapper = NavigationWrapper(navigationObject: navigationObject)
self.rootView = albumsList
}
}

// MARK: NavigationObject

func presentSettingsViewController() {
public func presentSettingsViewController() {
next?.settingsPresenter?.presentSettingsViewController()
}

func presentPhotoEditingViewController(for asset: PHAsset, redactions: [Redaction]?, animated: Bool) {
public func presentPhotoEditingViewController(for asset: PHAsset, redactions: [Redaction]?, animated: Bool) {
next?.photoEditorPresenter?.presentPhotoEditingViewController(for: asset, redactions: redactions, animated: animated)
}

func presentPhotoEditingViewController(for image: UIImage, redactions: [Redaction]?, animated: Bool, completionHandler: ((UIImage) -> Void)?) {
public func presentPhotoEditingViewController(for image: UIImage, redactions: [Redaction]?, animated: Bool, completionHandler: ((UIImage) -> Void)?) {
next?.photoEditorPresenter?.presentPhotoEditingViewController(for: image, redactions: nil, animated: true, completionHandler: completionHandler)
}

func presentDocumentCameraViewController() {
public func presentDocumentCameraViewController() {
next?.documentScannerPresenter?.presentDocumentCameraViewController()
}

func present(_ collection: Collection) {
public func present(_ collection: PhotoCollection) {
next?.collectionPresenter?.present(collection)
}

func presentLimitedLibrary() {
public func presentLimitedLibrary() {
next?.limitedLibraryPresenter?.presentLimitedLibrary()
}

// MARK: Boilerplate

private static let navigationTitle = NSLocalizedString("AlbumsViewController.navigationTitle", comment: "Navigation title for the albums list")

private let albumsDataSource: CollectionsDataSource

@available(*, unavailable)
required init(coder: NSCoder) {
let typeName = NSStringFromClass(type(of: self))
Expand Down
7 changes: 7 additions & 0 deletions Modules/Capabilities/AlbumsUI/Tests/AlbumsTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//
// AlbumsTests.swift
// Highlighter
//
// Created by Geoff Pado on 7/1/24.
// Copyright © 2024 Cocoatype, LLC. All rights reserved.
//
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Created by Geoff Pado on 10/2/20.
// Copyright © 2020 Cocoatype, LLC. All rights reserved.

import AlbumsData
import UIKit

public protocol PhotoCollectionPresenting {
func present(_ collection: PhotoCollection)
}

extension UIResponder {
public var collectionPresenter: PhotoCollectionPresenting? {
if let presenter = (self as? PhotoCollectionPresenting) {
return presenter
}

return next?.collectionPresenter
}
}
Loading