summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-02-18 12:29:25 -0800
committerFuwn <[email protected]>2026-02-18 12:29:29 -0800
commit0917354dc0b79d1ac414392ae036f784eb397113 (patch)
treeede2a5b3935eed05607f6a493d8a34ce74d9834d
parentperf: eliminate repeated favorites sort map and grid recompute (diff)
downloadsora-testing-0917354dc0b79d1ac414392ae036f784eb397113.tar.xz
sora-testing-0917354dc0b79d1ac414392ae036f784eb397113.zip
refactor: share folder menu hierarchy across views
-rw-r--r--Sora/Views/BookmarkMenuButtonView.swift64
-rw-r--r--Sora/Views/FavoriteMenuButtonView.swift66
-rw-r--r--Sora/Views/FavoritesView.swift58
-rw-r--r--Sora/Views/Generic/GenericListView.swift58
-rw-r--r--Sora/Views/Post/Grid/PostGridView.swift38
-rw-r--r--Sora/Views/Shared/FolderMenuView.swift72
-rw-r--r--SoraTests/ViewDerivedDataTests.swift25
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"#,