diff options
| -rw-r--r-- | Sora/Views/BookmarkMenuButtonView.swift | 64 | ||||
| -rw-r--r-- | Sora/Views/FavoriteMenuButtonView.swift | 66 | ||||
| -rw-r--r-- | Sora/Views/FavoritesView.swift | 58 | ||||
| -rw-r--r-- | Sora/Views/Generic/GenericListView.swift | 58 | ||||
| -rw-r--r-- | Sora/Views/Post/Grid/PostGridView.swift | 38 | ||||
| -rw-r--r-- | Sora/Views/Shared/FolderMenuView.swift | 72 | ||||
| -rw-r--r-- | SoraTests/ViewDerivedDataTests.swift | 25 |
7 files changed, 162 insertions, 219 deletions
diff --git a/Sora/Views/BookmarkMenuButtonView.swift b/Sora/Views/BookmarkMenuButtonView.swift index 5e737ff..8446539 100644 --- a/Sora/Views/BookmarkMenuButtonView.swift +++ b/Sora/Views/BookmarkMenuButtonView.swift @@ -14,60 +14,24 @@ struct BookmarkMenuButtonView: View { let isBookmarked = settings.bookmarks.contains { bookmark in Set(bookmark.tags) == Set(tags) } + let folderHierarchy = FolderHierarchy(folders: settings.folders) Menu { - ForEach(settings.folders.filter { $0.topLevelName == nil }, id: \.id) { folder in - Button(action: { - settings.addBookmark(provider: provider, tags: tags, folder: folder.id) - }) { - Label(folder.name, systemImage: "folder") - } - .disabled(isBookmarkedInFolder(folderId: folder.id)) - } - - let topLevelFolders = settings.folders - .reduce(into: [String: [SettingsFolder]]()) { result, folder in - guard let topLevelName = folder.topLevelName else { return } - - result[topLevelName, default: []].append(folder) - } + FolderMenuView( + folderHierarchy: folderHierarchy, + onSelectFolder: { folderIdentifier in + settings.addBookmark(provider: provider, tags: tags, folder: folderIdentifier) + }, + onCreateTopLevelUncategorized: { topLevelName in + let newFolder = SettingsFolder(name: topLevelName) - ForEach(topLevelFolders.keys.sorted(), id: \.self) { topLevelName in - Menu { - let topLevelFolder = settings.folders.first { $0.name == topLevelName } - - if let topLevelFolder { - Button(action: { - settings.addBookmark(provider: provider, tags: tags, folder: topLevelFolder.id) - }) { - Text("Uncategorized") - } - .disabled(isBookmarkedInFolder(folderId: topLevelFolder.id)) - } else { - Button(action: { - let newFolder = SettingsFolder(name: topLevelName) - - settings.folders.append(newFolder) - settings.addBookmark(provider: provider, tags: tags, folder: newFolder.id) - }) { - Text("Uncategorized") - } - } - - Divider() - - ForEach(topLevelFolders[topLevelName] ?? [], id: \.id) { folder in - Button(action: { - settings.addBookmark(provider: provider, tags: tags, folder: folder.id) - }) { - Text(folder.shortName) - } - .disabled(isBookmarkedInFolder(folderId: folder.id)) - } - } label: { - Text(topLevelName) + settings.folders.append(newFolder) + settings.addBookmark(provider: provider, tags: tags, folder: newFolder.id) + }, + isFolderDisabled: { folderIdentifier in + isBookmarkedInFolder(folderId: folderIdentifier) } - } + ) Button(action: { isNewCollectionAlertPresented = true diff --git a/Sora/Views/FavoriteMenuButtonView.swift b/Sora/Views/FavoriteMenuButtonView.swift index 5cfecbd..a29b2b0 100644 --- a/Sora/Views/FavoriteMenuButtonView.swift +++ b/Sora/Views/FavoriteMenuButtonView.swift @@ -12,62 +12,24 @@ struct FavoriteMenuButtonView: View { var body: some View { let isFavorited = settings.isFavorite(postId: post.id, provider: manager.provider) + let folderHierarchy = FolderHierarchy(folders: settings.folders) Menu { - ForEach(settings.folders.filter { $0.topLevelName == nil }, id: \.id) { folder in - Button(action: { - settings.addFavorite(post: post, provider: manager.provider, folder: folder.id) - }) { - Label(folder.name, systemImage: "folder") - } - .disabled(isFavoritedInFolder(folderId: folder.id)) - } - - let topLevelFolders = settings.folders - .reduce(into: [String: [SettingsFolder]]()) { result, folder in - guard let topLevelName = folder.topLevelName else { return } - - result[topLevelName, default: []].append(folder) - } + FolderMenuView( + folderHierarchy: folderHierarchy, + onSelectFolder: { folderIdentifier in + settings.addFavorite(post: post, provider: manager.provider, folder: folderIdentifier) + }, + onCreateTopLevelUncategorized: { topLevelName in + let newFolder = SettingsFolder(name: topLevelName) - ForEach(topLevelFolders.keys.sorted(), id: \.self) { topLevelName in - Menu { - let topLevelFolder = settings.folders.first { $0.name == topLevelName } - - if let topLevelFolder { - Button(action: { - settings.addFavorite( - post: post, provider: manager.provider, folder: topLevelFolder.id - ) - }) { - Text("Uncategorized") - } - .disabled(isFavoritedInFolder(folderId: topLevelFolder.id)) - } else { - Button(action: { - let newFolder = SettingsFolder(name: topLevelName) - - settings.folders.append(newFolder) - settings.addFavorite(post: post, provider: manager.provider, folder: newFolder.id) - }) { - Text("Uncategorized") - } - } - - Divider() - - ForEach(topLevelFolders[topLevelName] ?? [], id: \.id) { folder in - Button(action: { - settings.addFavorite(post: post, provider: manager.provider, folder: folder.id) - }) { - Text(folder.shortName) - } - .disabled(isFavoritedInFolder(folderId: folder.id)) - } - } label: { - Text(topLevelName) + settings.folders.append(newFolder) + settings.addFavorite(post: post, provider: manager.provider, folder: newFolder.id) + }, + isFolderDisabled: { folderIdentifier in + isFavoritedInFolder(folderId: folderIdentifier) } - } + ) Button(action: { isNewCollectionAlertPresented = true diff --git a/Sora/Views/FavoritesView.swift b/Sora/Views/FavoritesView.swift index 6295b23..12f1bc0 100644 --- a/Sora/Views/FavoritesView.swift +++ b/Sora/Views/FavoritesView.swift @@ -556,51 +556,21 @@ struct FavoritesView: View { // swiftlint:disable:this type_body_length } Menu { - ForEach(folderHierarchy.rootFolders, id: \.id) { folder in - Button(action: { - settings.updateFavoriteFolder(withID: favorite.id, folder: folder.id) - }) { - Label(folder.name, systemImage: "folder") - } - .disabled(favorite.folder == folder.id) - } - - ForEach(folderHierarchy.sortedTopLevelNames, id: \.self) { topLevelName in - Menu { - let topLevelFolder = folderHierarchy.rootFolders.first { $0.name == topLevelName } - - if let topLevelFolder { - Button(action: { - settings.updateFavoriteFolder(withID: favorite.id, folder: topLevelFolder.id) - }) { - Text("Uncategorized") - } - .disabled(favorite.folder == topLevelFolder.id) - } else { - Button(action: { - let newFolder = SettingsFolder(name: topLevelName) - - settings.folders.append(newFolder) - settings.updateFavoriteFolder(withID: favorite.id, folder: newFolder.id) - }) { - Text("Uncategorized") - } - } - - Divider() - - ForEach(folderHierarchy.folders(forTopLevelName: topLevelName), id: \.id) { folder in - Button(action: { - settings.updateFavoriteFolder(withID: favorite.id, folder: folder.id) - }) { - Text(folder.shortName) - } - .disabled(favorite.folder == folder.id) - } - } label: { - Text(topLevelName) + FolderMenuView( + folderHierarchy: folderHierarchy, + onSelectFolder: { folderIdentifier in + settings.updateFavoriteFolder(withID: favorite.id, folder: folderIdentifier) + }, + onCreateTopLevelUncategorized: { topLevelName in + let newFolder = SettingsFolder(name: topLevelName) + + settings.folders.append(newFolder) + settings.updateFavoriteFolder(withID: favorite.id, folder: newFolder.id) + }, + isFolderDisabled: { folderIdentifier in + favorite.folder == folderIdentifier } - } + ) Button(action: { itemPendingCollectionAssignment = favorite.id diff --git a/Sora/Views/Generic/GenericListView.swift b/Sora/Views/Generic/GenericListView.swift index 3195624..a829856 100644 --- a/Sora/Views/Generic/GenericListView.swift +++ b/Sora/Views/Generic/GenericListView.swift @@ -477,51 +477,21 @@ struct GenericListView<T: Identifiable & Hashable & GenericItem>: View { } Menu { - ForEach(folderHierarchy.rootFolders, id: \.id) { folder in - Button(action: { - settings.updateBookmarkFolder(withID: item.id, folder: folder.id) - }) { - Label(folder.name, systemImage: "folder") - } - .disabled(item.folder == folder.id) - } - - ForEach(folderHierarchy.sortedTopLevelNames, id: \.self) { topLevelName in - Menu { - let topLevelFolder = folderHierarchy.rootFolders.first { $0.name == topLevelName } - - if let topLevelFolder { - Button(action: { - settings.updateBookmarkFolder(withID: item.id, folder: topLevelFolder.id) - }) { - Text("Uncategorized") - } - .disabled(item.folder == topLevelFolder.id) - } else { - Button(action: { - let newFolder = SettingsFolder(name: topLevelName) - - settings.folders.append(newFolder) - settings.updateBookmarkFolder(withID: item.id, folder: newFolder.id) - }) { - Text("Uncategorized") - } - } - - Divider() - - ForEach(folderHierarchy.folders(forTopLevelName: topLevelName), id: \.id) { folder in - Button(action: { - settings.updateBookmarkFolder(withID: item.id, folder: folder.id) - }) { - Text(folder.shortName) - } - .disabled(item.folder == folder.id) - } - } label: { - Text(topLevelName) + FolderMenuView( + folderHierarchy: folderHierarchy, + onSelectFolder: { folderIdentifier in + settings.updateBookmarkFolder(withID: item.id, folder: folderIdentifier) + }, + onCreateTopLevelUncategorized: { topLevelName in + let newFolder = SettingsFolder(name: topLevelName) + + settings.folders.append(newFolder) + settings.updateBookmarkFolder(withID: item.id, folder: newFolder.id) + }, + isFolderDisabled: { folderIdentifier in + item.folder == folderIdentifier } - } + ) Button(action: { itemPendingCollectionAssignment = item.id diff --git a/Sora/Views/Post/Grid/PostGridView.swift b/Sora/Views/Post/Grid/PostGridView.swift index 7c0a1aa..9c24932 100644 --- a/Sora/Views/Post/Grid/PostGridView.swift +++ b/Sora/Views/Post/Grid/PostGridView.swift @@ -512,36 +512,16 @@ struct PostGridView: View { // swiftlint:disable:this type_body_length } Menu { - ForEach(settings.folders.filter { $0.topLevelName == nil }, id: \.id) { folder in - Button(action: { - settings.addFavorite(post: post, provider: manager.provider, folder: folder.id) - }) { - Label(folder.name, systemImage: "folder") - } - .disabled(isFavoritedInFolder(post: post, folderId: folder.id)) - } - - let topLevelFolders = settings.folders - .reduce(into: [String: [SettingsFolder]]()) { result, folder in - guard let topLevelName = folder.topLevelName else { return } - - result[topLevelName, default: []].append(folder) - } - - ForEach(topLevelFolders.keys.sorted(), id: \.self) { topLevelName in - Menu { - ForEach(topLevelFolders[topLevelName] ?? [], id: \.id) { folder in - Button(action: { - settings.addFavorite(post: post, provider: manager.provider, folder: folder.id) - }) { - Text(folder.shortName) - } - .disabled(isFavoritedInFolder(post: post, folderId: folder.id)) - } - } label: { - Text(topLevelName) + FolderMenuView( + folderHierarchy: FolderHierarchy(folders: settings.folders), + showsTopLevelUncategorized: false, + onSelectFolder: { folderIdentifier in + settings.addFavorite(post: post, provider: manager.provider, folder: folderIdentifier) + }, + isFolderDisabled: { folderIdentifier in + isFavoritedInFolder(post: post, folderId: folderIdentifier) } - } + ) } label: { Label("Add to Collection", systemImage: "folder.badge.plus") } diff --git a/Sora/Views/Shared/FolderMenuView.swift b/Sora/Views/Shared/FolderMenuView.swift new file mode 100644 index 0000000..b068985 --- /dev/null +++ b/Sora/Views/Shared/FolderMenuView.swift @@ -0,0 +1,72 @@ +import SwiftUI + +struct FolderMenuView: View { + let folderHierarchy: FolderHierarchy + let showsTopLevelUncategorized: Bool + let onSelectFolder: (UUID) -> Void + let onCreateTopLevelUncategorized: (String) -> Void + let isFolderDisabled: (UUID) -> Bool + + init( + folderHierarchy: FolderHierarchy, + showsTopLevelUncategorized: Bool = true, + onSelectFolder: @escaping (UUID) -> Void, + onCreateTopLevelUncategorized: @escaping (String) -> Void = { topLevelName in + _ = topLevelName + }, + isFolderDisabled: @escaping (UUID) -> Bool = { _ in false } + ) { + self.folderHierarchy = folderHierarchy + self.showsTopLevelUncategorized = showsTopLevelUncategorized + self.onSelectFolder = onSelectFolder + self.onCreateTopLevelUncategorized = onCreateTopLevelUncategorized + self.isFolderDisabled = isFolderDisabled + } + + var body: some View { + ForEach(folderHierarchy.rootFolders, id: \.id) { folder in + Button(action: { + onSelectFolder(folder.id) + }) { + Text(folder.name) + } + .disabled(isFolderDisabled(folder.id)) + } + + ForEach(folderHierarchy.sortedTopLevelNames, id: \.self) { topLevelName in + Menu { + if showsTopLevelUncategorized { + let topLevelFolder = folderHierarchy.rootFolders.first { $0.name == topLevelName } + + if let topLevelFolder { + Button(action: { + onSelectFolder(topLevelFolder.id) + }) { + Text("Uncategorized") + } + .disabled(isFolderDisabled(topLevelFolder.id)) + } else { + Button(action: { + onCreateTopLevelUncategorized(topLevelName) + }) { + Text("Uncategorized") + } + } + + Divider() + } + + ForEach(folderHierarchy.folders(forTopLevelName: topLevelName), id: \.id) { folder in + Button(action: { + onSelectFolder(folder.id) + }) { + Text(folder.shortName) + } + .disabled(isFolderDisabled(folder.id)) + } + } label: { + Text(topLevelName) + } + } + } +} diff --git a/SoraTests/ViewDerivedDataTests.swift b/SoraTests/ViewDerivedDataTests.swift index da5ad0d..eb426f7 100644 --- a/SoraTests/ViewDerivedDataTests.swift +++ b/SoraTests/ViewDerivedDataTests.swift @@ -102,6 +102,31 @@ final class ViewDerivedDataTests: XCTestCase { ) } + func testFolderMenuHierarchyIsSharedAcrossConsumers() throws { + let consumerPaths = [ + "Sora/Views/Generic/GenericListView.swift", + "Sora/Views/FavoritesView.swift", + "Sora/Views/Post/Grid/PostGridView.swift", + "Sora/Views/BookmarkMenuButtonView.swift", + "Sora/Views/FavoriteMenuButtonView.swift", + ] + + for consumerPath in consumerPaths { + let source = try loadSource(at: consumerPath) + let sharedMenuUsages = tokenCount( + matching: #"\bFolderMenuView\s*\("#, + in: source + ) + + // swiftlint:disable:next prefer_nimble + XCTAssertGreaterThan( + sharedMenuUsages, + 0, + "\(consumerPath) should use FolderMenuView for folder hierarchy rendering." + ) + } + } + private func referenceCount(for symbol: String, in source: String) -> Int { let totalMatches = tokenCount( matching: #"\b\#(symbol)\b"#, |