From aeb074b5f308172ba50ac9ae0f5ac6e3159cacb7 Mon Sep 17 00:00:00 2001 From: Fuwn Date: Sun, 2 Mar 2025 06:11:03 -0800 Subject: feat: Development commit --- Localizable.xcstrings | 12 +-- Sora/Data/GenericItem.swift | 11 +++ Sora/Views/BookmarksView.swift | 90 +++--------------- Sora/Views/Generic/GenericItemView.swift | 40 ++++++++ Sora/Views/Generic/GenericListView.swift | 105 +++++++++++++++++++++ Sora/Views/GenericItemView.swift | 40 -------- .../Post/Grid/PostGridSearchHistoryView.swift | 97 +++---------------- 7 files changed, 188 insertions(+), 207 deletions(-) create mode 100644 Sora/Data/GenericItem.swift create mode 100644 Sora/Views/Generic/GenericItemView.swift create mode 100644 Sora/Views/Generic/GenericListView.swift delete mode 100644 Sora/Views/GenericItemView.swift diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 0be1197..f263594 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -118,17 +118,11 @@ } } } - }, - "No bookmarks match your search" : { - }, "No History" : { }, - "No matching %@ found" : { - - }, - "No matching history found" : { + "No matching items found" : { }, "On %@ from %@" : { @@ -199,10 +193,10 @@ "Remove" : { }, - "Remove All Bookmarks" : { + "Remove All" : { }, - "Remove All Search History" : { + "Remove All Bookmarks" : { }, "Remove All Searches" : { diff --git a/Sora/Data/GenericItem.swift b/Sora/Data/GenericItem.swift new file mode 100644 index 0000000..af15e55 --- /dev/null +++ b/Sora/Data/GenericItem.swift @@ -0,0 +1,11 @@ +import Foundation + +protocol GenericItem { + var id: UUID { get } + var tags: [String] { get } + var date: Date { get } + var provider: BooruProvider { get } +} + +extension SettingsBookmark: GenericItem {} +extension BooruSearchQuery: GenericItem {} diff --git a/Sora/Views/BookmarksView.swift b/Sora/Views/BookmarksView.swift index ccef3f1..fa7734e 100644 --- a/Sora/Views/BookmarksView.swift +++ b/Sora/Views/BookmarksView.swift @@ -2,85 +2,23 @@ import SwiftUI struct BookmarksView: View { @EnvironmentObject var settings: SettingsManager - @EnvironmentObject var manager: BooruManager @Binding var selectedTab: Int - @State private var bookmarksSearchText: String = "" - @State private var isShowingRemoveAllConfirmation = false - - var filteredBookmarks: [SettingsBookmark] { - guard !bookmarksSearchText.isEmpty else { - return settings.bookmarks - } - - return settings.bookmarks - .filter { bookmark in - bookmark.tags.joined(separator: " ").lowercased().contains(bookmarksSearchText.lowercased()) - } - } var body: some View { - NavigationStack { - VStack { - if settings.bookmarks.isEmpty { - ContentUnavailableView( - "No Bookmarks", - systemImage: "bookmark", - description: Text("Tap the bookmark button on a search page to add a bookmark.") - ) - } else { - List { - if filteredBookmarks.isEmpty, !bookmarksSearchText.isEmpty { - Text("No bookmarks match your search") - } - - ForEach( - filteredBookmarks.sorted { $0.date > $1.date }, - id: \.self - ) { bookmark in - Button(action: { - let previousProvider = settings.preferredBooru - - settings.preferredBooru = bookmark.provider - manager.searchText = bookmark.tags.joined(separator: " ") - selectedTab = 0 - - if previousProvider == settings.preferredBooru { - manager.performSearch(settings: settings) - } - }) { - GenericItemView( - item: bookmark, - removeAction: settings.removeBookmark - ) - } - #if os(macOS) - .buttonStyle(.plain) - #endif - } - .onDelete(perform: settings.removeBookmark) - } - } - } - } - .navigationTitle("Bookmarks") - .searchable(text: $bookmarksSearchText) - .toolbar { - ToolbarItem { - Button(action: { - isShowingRemoveAllConfirmation = true - }) { - Label("Remove All Bookmarks", systemImage: "trash") - } - } - } - .confirmationDialog( - "Are you sure you want to remove all bookmarks? This action cannot be undone.", - isPresented: $isShowingRemoveAllConfirmation - ) { - Button("Remove All Bookmarks") { - settings.bookmarks.removeAll() - } - } + GenericListView( + selectedTab: $selectedTab, + isPresented: .constant(false), + title: "Bookmarks", + emptyMessage: "No Bookmarks", + emptyIcon: "bookmark", + emptyDescription: "Tap the bookmark button on a search page to add a bookmark.", + removeAllMessage: + "Are you sure you want to remove all bookmarks? This action cannot be undone.", + removeAllButtonText: "Remove All Bookmarks", + items: settings.bookmarks, + removeAction: settings.removeBookmark, + removeActionUUID: settings.removeBookmark + ) { settings.bookmarks.removeAll() } } } diff --git a/Sora/Views/Generic/GenericItemView.swift b/Sora/Views/Generic/GenericItemView.swift new file mode 100644 index 0000000..ec8deaa --- /dev/null +++ b/Sora/Views/Generic/GenericItemView.swift @@ -0,0 +1,40 @@ +import SwiftUI + +struct GenericItemView: View { + @EnvironmentObject var settings: SettingsManager + let item: T + let removeAction: (UUID) -> Void + + var body: some View { + #if os(macOS) + HStack { + VStack(alignment: .leading) { + Text(item.tags.joined(separator: ", ").lowercased()) + + Spacer() + + Text("On \(item.date.formatted()) from \(item.provider.rawValue)") + .font(.caption) + .foregroundStyle(Color.secondary) + + Spacer() + } + } + .contextMenu { + Button { + removeAction(item.id) + } label: { + Label("Remove", systemImage: "trash") + } + } + #else + VStack(alignment: .leading) { + Text(item.tags.joined(separator: ", ").lowercased()) + + Text("On \(item.date.formatted()) from \(item.provider.rawValue)") + .font(.caption) + .foregroundStyle(Color.secondary) + } + #endif + } +} diff --git a/Sora/Views/Generic/GenericListView.swift b/Sora/Views/Generic/GenericListView.swift new file mode 100644 index 0000000..0642c6e --- /dev/null +++ b/Sora/Views/Generic/GenericListView.swift @@ -0,0 +1,105 @@ +import SwiftUI + +struct GenericListView: View { + @EnvironmentObject private var settings: SettingsManager + @EnvironmentObject private var manager: BooruManager + @Binding var selectedTab: Int + @State private var searchText: String = "" + @State private var isShowingRemoveAllConfirmation = false + @Binding var isPresented: Bool + + let title: String + let emptyMessage: String + let emptyIcon: String + let emptyDescription: String + let removeAllMessage: String + let removeAllButtonText: String + let items: [T] + let removeAction: (IndexSet) -> Void + let removeActionUUID: (UUID) -> Void + let removeAllAction: () -> Void + + var filteredItems: [T] { + guard !searchText.isEmpty else { + return items + } + + return items.filter { item in + item.tags + .joined(separator: " ") + .lowercased() + .contains(searchText.lowercased()) + } + } + + var body: some View { + NavigationStack { + VStack { + if items.isEmpty { + ContentUnavailableView( + emptyMessage, + systemImage: emptyIcon, + description: Text(emptyDescription) + ) + } else { + List { + if filteredItems.isEmpty, !searchText.isEmpty { + Text("No matching items found") + } + + ForEach( + filteredItems.sorted { $0.date > $1.date }, + id: \.id + ) { item in + itemButtonContent(item: item) + } + .onDelete(perform: removeAction) + } + } + } + } + .navigationTitle(title) + .searchable(text: $searchText) + .toolbar { + ToolbarItem { + Button(action: { + isShowingRemoveAllConfirmation = true + }) { + Label("Remove All", systemImage: "trash") + } + } + } + .confirmationDialog( + removeAllMessage, + isPresented: $isShowingRemoveAllConfirmation + ) { + Button(removeAllButtonText) { + removeAllAction() + } + } + } + + func itemButtonContent(item: T) -> some View { + Button(action: { + let previousProvider = settings.preferredBooru + + settings.preferredBooru = item.provider + manager.searchText = item.tags.joined(separator: " ") + selectedTab = 0 + + isPresented.toggle() + + if previousProvider == settings.preferredBooru { + manager.performSearch(settings: settings) + } + }) { + GenericItemView( + item: item, + removeAction: removeActionUUID + ) + } + #if os(macOS) + .buttonStyle(.plain) + #endif + } +} diff --git a/Sora/Views/GenericItemView.swift b/Sora/Views/GenericItemView.swift deleted file mode 100644 index ec8deaa..0000000 --- a/Sora/Views/GenericItemView.swift +++ /dev/null @@ -1,40 +0,0 @@ -import SwiftUI - -struct GenericItemView: View { - @EnvironmentObject var settings: SettingsManager - let item: T - let removeAction: (UUID) -> Void - - var body: some View { - #if os(macOS) - HStack { - VStack(alignment: .leading) { - Text(item.tags.joined(separator: ", ").lowercased()) - - Spacer() - - Text("On \(item.date.formatted()) from \(item.provider.rawValue)") - .font(.caption) - .foregroundStyle(Color.secondary) - - Spacer() - } - } - .contextMenu { - Button { - removeAction(item.id) - } label: { - Label("Remove", systemImage: "trash") - } - } - #else - VStack(alignment: .leading) { - Text(item.tags.joined(separator: ", ").lowercased()) - - Text("On \(item.date.formatted()) from \(item.provider.rawValue)") - .font(.caption) - .foregroundStyle(Color.secondary) - } - #endif - } -} diff --git a/Sora/Views/Post/Grid/PostGridSearchHistoryView.swift b/Sora/Views/Post/Grid/PostGridSearchHistoryView.swift index 30f7808..ae33c53 100644 --- a/Sora/Views/Post/Grid/PostGridSearchHistoryView.swift +++ b/Sora/Views/Post/Grid/PostGridSearchHistoryView.swift @@ -1,92 +1,25 @@ import SwiftUI struct PostGridSearchHistoryView: View { - @EnvironmentObject private var manager: BooruManager - @EnvironmentObject private var settings: SettingsManager - @State private var searchText: String = "" + @EnvironmentObject var settings: SettingsManager @Binding var selectedTab: Int @Binding var isPresented: Bool - @State private var isShowingRemoveAllConfirmation = false - - var filteredHistory: [BooruSearchQuery] { - guard !searchText.isEmpty else { - return settings.searchHistory - } - - return settings.searchHistory - .filter { query in - query.tags - .joined(separator: " ") - .lowercased() - .contains(searchText.lowercased()) - } - } var body: some View { - NavigationStack { - VStack { - if settings.searchHistory.isEmpty { - ContentUnavailableView( - "No History", - systemImage: "magnifyingglass", - description: Text("Recent searches will appear here.") - ) - } else { - List { - if filteredHistory.isEmpty, !searchText.isEmpty { - Text("No matching history found") - } - - ForEach( - filteredHistory.sorted { $0.date > $1.date }, - id: \.id - ) { query in - Button(action: { - let previousProvider = settings.preferredBooru - - settings.preferredBooru = query.provider - manager.searchText = query.tags.joined(separator: " ") - selectedTab = 0 - - isPresented.toggle() - - if previousProvider == settings.preferredBooru { - manager.performSearch() - } - }) { - GenericItemView( - item: query, - removeAction: settings.removeSearchHistoryEntry - ) - } - #if os(macOS) - .buttonStyle(.plain) - #endif - } - .onDelete(perform: settings.removeSearchHistoryEntry) - } - } - } - } - .navigationTitle("Search History") - .searchable(text: $searchText) - .toolbar { - ToolbarItem { - Button(action: { - isShowingRemoveAllConfirmation = true - }) { - Label("Remove All Search History", systemImage: "trash") - } - } - } - .confirmationDialog( - "Are you sure you want to remove all searches? This action cannot be undone.", - isPresented: $isShowingRemoveAllConfirmation - ) { - Button("Remove All Searches") { - settings.bookmarks.removeAll() - } - } + GenericListView( + selectedTab: $selectedTab, + isPresented: $isPresented, + title: "Search History", + emptyMessage: "No History", + emptyIcon: "magnifyingglass", + emptyDescription: "Recent searches will appear here.", + removeAllMessage: + "Are you sure you want to remove all searches? This action cannot be undone.", + removeAllButtonText: "Remove All Searches", + items: settings.searchHistory, + removeAction: settings.removeSearchHistoryEntry, + removeActionUUID: settings.removeSearchHistoryEntry + ) { settings.searchHistory.removeAll() } } } -- cgit v1.2.3