Skip to content

Commit

Permalink
Merge pull request #182 from cocoatype/34-albums-list-does-not-refresh
Browse files Browse the repository at this point in the history
Fix refreshing of albums and images
  • Loading branch information
Arclite authored Jul 2, 2024
2 parents 33d069c + 81bbfbf commit a305326
Show file tree
Hide file tree
Showing 44 changed files with 268 additions and 247 deletions.
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

0 comments on commit a305326

Please sign in to comment.