summaryrefslogtreecommitdiff
path: root/Sora/Views/Post
diff options
context:
space:
mode:
authorFuwn <[email protected]>2025-03-12 01:49:11 -0700
committerFuwn <[email protected]>2025-03-12 01:49:11 -0700
commita30ee55976b5e80f48826e3e8e490761ce0a2410 (patch)
tree2c3b27971bff80749a80ee48263b751ec852c747 /Sora/Views/Post
parentfeat: Development commit (diff)
downloadsora-testing-a30ee55976b5e80f48826e3e8e490761ce0a2410.tar.xz
sora-testing-a30ee55976b5e80f48826e3e8e490761ce0a2410.zip
feat: Development commit
Diffstat (limited to 'Sora/Views/Post')
-rw-r--r--Sora/Views/Post/Details/PostDetailsView.swift2
-rw-r--r--Sora/Views/Post/Grid/PostGridView.swift131
-rw-r--r--Sora/Views/Post/Grid/Tab/PostGridTabButtonView.swift30
-rw-r--r--Sora/Views/Post/Grid/Tab/PostGridTabSwitcherContentView.swift22
-rw-r--r--Sora/Views/Post/Grid/Tab/PostGridTabSwitcherView.swift128
5 files changed, 254 insertions, 59 deletions
diff --git a/Sora/Views/Post/Details/PostDetailsView.swift b/Sora/Views/Post/Details/PostDetailsView.swift
index e040bc3..3b6ef49 100644
--- a/Sora/Views/Post/Details/PostDetailsView.swift
+++ b/Sora/Views/Post/Details/PostDetailsView.swift
@@ -1,9 +1,9 @@
import SwiftUI
struct PostDetailsView: View {
- @EnvironmentObject var manager: BooruManager
@EnvironmentObject var settings: SettingsManager
let post: BooruPost
+ let manager: BooruManager
@State private var loadingStage: BooruPostLoadingState = .loadingPreview
private var imageURL: URL? {
switch settings.detailViewQuality {
diff --git a/Sora/Views/Post/Grid/PostGridView.swift b/Sora/Views/Post/Grid/PostGridView.swift
index dc6546d..a03463b 100644
--- a/Sora/Views/Post/Grid/PostGridView.swift
+++ b/Sora/Views/Post/Grid/PostGridView.swift
@@ -3,9 +3,11 @@ import WaterfallGrid
struct PostGridView: View {
@EnvironmentObject var settings: SettingsManager
- @EnvironmentObject var manager: BooruManager
+ @ObservedObject var manager: BooruManager
@State private var isSearchHistoryPresented = false
@Binding var selectedTab: Int
+ let isActive: Bool
+ @Binding var selectedPost: (post: BooruPost?, manager: BooruManager?)
@Environment(\.isSearching)
private var isSearching
@@ -29,77 +31,84 @@ struct PostGridView: View {
.gridStyle(columns: settings.thumbnailGridColumns)
.padding(8)
}
- .searchable(text: $manager.searchText, prompt: "Tags")
- .searchSuggestions {
- if settings.searchSuggestionsMode != .disabled {
- SearchSuggestionsView(
- items: searchSuggestionsItems(),
- searchText: $manager.searchText
- )
+ #if os(macOS)
+ .searchable(text: $manager.searchText, prompt: "Tags")
+ .searchSuggestions {
+ if settings.searchSuggestionsMode != .disabled {
+ SearchSuggestionsView(
+ items: searchSuggestionsItems(),
+ searchText: $manager.searchText
+ )
+ }
}
- }
- .onSubmit(of: .search) {
- manager.performSearch(settings: settings)
- }
- .navigationDestination(for: BooruPost.self) { post in
- PostDetailsView(post: post)
- }
- .onChange(of: manager.searchText) { _, _ in
- if manager.searchText.isEmpty, !isSearching {
- Task { manager.performSearch() }
+ .onSubmit(of: .search) {
+ manager.performSearch(settings: settings)
}
- }
+ .navigationDestination(for: BooruPost.self) { post in
+ PostDetailsView(post: post, manager: manager)
+ }
+ .onChange(of: manager.searchText) { _, _ in
+ if manager.searchText.isEmpty, !isSearching {
+ Task { manager.performSearch() }
+ }
+ }
+ #endif
.toolbar {
- #if os(macOS)
- ToolbarItem {
- Button(action: {
- Task {
- await manager.fetchPosts(page: 1, tags: manager.tags, replace: true)
+ if isActive {
+ #if os(macOS)
+ ToolbarItem {
+ Button(action: {
+ Task {
+ await manager.fetchPosts(page: 1, tags: manager.tags, replace: true)
+ }
+ }) {
+ Label("Refresh", systemImage: "arrow.clockwise")
}
- }) {
- Label("Refresh", systemImage: "arrow.clockwise")
+ .disabled(manager.isLoading)
}
- .disabled(manager.isLoading)
- }
- #endif
+ #endif
- #if os(macOS)
- ToolbarItem {
- Button(action: { Task { manager.loadNextPage() } }) {
- Label("Manually Load Next Page", systemImage: "arrow.triangle.2.circlepath")
+ #if os(macOS)
+ ToolbarItem {
+ Button(action: { Task { manager.loadNextPage() } }) {
+ Label("Manually Load Next Page", systemImage: "arrow.triangle.2.circlepath")
+ }
+ .disabled(manager.isLoading)
}
- .disabled(manager.isLoading)
- }
- #else
- ToolbarItem(placement: .bottomBar) {
- Button(action: { Task { manager.loadNextPage() } }) {
- Label("Manually Load Next Page", systemImage: "arrow.triangle.2.circlepath")
+ #else
+ ToolbarItem(placement: .bottomBar) {
+ Button(action: { Task { manager.loadNextPage() } }) {
+ Label("Manually Load Next Page", systemImage: "arrow.triangle.2.circlepath")
+ }
+ .disabled(manager.isLoading)
}
- .disabled(manager.isLoading)
- }
- ToolbarItem(placement: .bottomBar) {
- Button(action: { Task { isSearchHistoryPresented.toggle() } }) {
- Label("Search History", systemImage: "clock.arrow.circlepath")
+ ToolbarItem(placement: .bottomBar) {
+ Button(action: { Task { isSearchHistoryPresented.toggle() } }) {
+ Label("Search History", systemImage: "clock.arrow.circlepath")
+ }
}
- }
- if manager.isLoading {
- ToolbarItem {
- ProgressView()
+ if manager.isLoading {
+ ToolbarItem {
+ ProgressView()
+ }
}
- }
- #endif
-
- if !manager.tags.isEmpty {
- #if os(macOS)
- ToolbarItem { PostGridBookmarkButtonView() }
- #else
- ToolbarItem(placement: .bottomBar) { PostGridBookmarkButtonView() }
#endif
+
+ if !manager.tags.isEmpty {
+ #if os(macOS)
+ ToolbarItem { PostGridBookmarkButtonView() }
+ #else
+ ToolbarItem(placement: .bottomBar) { PostGridBookmarkButtonView() }
+ #endif
+ }
}
}
.navigationTitle("Posts")
+ #if !os(macOS)
+ .navigationBarTitleDisplayMode(.inline)
+ #endif
.refreshable {
await manager.fetchPosts(page: 1, tags: manager.tags, replace: true)
}
@@ -116,13 +125,19 @@ struct PostGridView: View {
private func waterfallGridContent(post: BooruPost) -> some View {
Button {
- manager.selectedPost = post
+ if isActive {
+ selectedPost.post = post
+ selectedPost.manager = manager
+ }
} label: {
PostGridThumbnailView(post: post, posts: filteredPosts)
}
.buttonStyle(PlainButtonStyle())
.contextMenu {
- Button(action: { manager.selectedPost = post }) {
+ Button(action: {
+ selectedPost.post = post
+ selectedPost.manager = manager
+ }) {
Label("Select Post", systemImage: "arrow.right.circle")
}
}
diff --git a/Sora/Views/Post/Grid/Tab/PostGridTabButtonView.swift b/Sora/Views/Post/Grid/Tab/PostGridTabButtonView.swift
new file mode 100644
index 0000000..9162ff0
--- /dev/null
+++ b/Sora/Views/Post/Grid/Tab/PostGridTabButtonView.swift
@@ -0,0 +1,30 @@
+import SwiftUI
+
+struct PostGridTabButtonView: View {
+ let title: String
+ let isSelected: Bool
+ let onSelect: () -> Void
+ let onClose: () -> Void
+
+ var body: some View {
+ HStack {
+ Button(action: onClose) {
+ Image(systemName: "xmark.square.fill")
+ .foregroundColor(.secondary)
+ }
+ .buttonStyle(PlainButtonStyle())
+
+ Text(title)
+ .lineLimit(1)
+ }
+ .padding(.horizontal, 12)
+ .padding(.vertical, 8)
+ #if !os(macOS)
+ .background(isSelected ? Color(.systemGray5) : Color(.systemGray3))
+ #endif
+ .opacity(isSelected ? 1 : 0.advanced(by: 0.3))
+ .clipShape(RoundedRectangle(cornerRadius: 9))
+ .onTapGesture(perform: onSelect)
+ .accessibilityAddTraits(.isButton)
+ }
+}
diff --git a/Sora/Views/Post/Grid/Tab/PostGridTabSwitcherContentView.swift b/Sora/Views/Post/Grid/Tab/PostGridTabSwitcherContentView.swift
new file mode 100644
index 0000000..6a42460
--- /dev/null
+++ b/Sora/Views/Post/Grid/Tab/PostGridTabSwitcherContentView.swift
@@ -0,0 +1,22 @@
+import SwiftUI
+
+struct PostGridTabSwitcherContentView: View {
+ @Binding var selectedTabID: UUID?
+ @Binding var selectedPost: (post: BooruPost?, manager: BooruManager?)
+ @Binding var selectedTab: Int
+ @Binding var tabs: [PostGridTab]
+
+ var body: some View {
+ ForEach(tabs) { tab in
+ PostGridView(
+ manager: tab.manager,
+ selectedTab: $selectedTab,
+ isActive: tab.id == selectedTabID,
+ selectedPost: $selectedPost
+ )
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
+ .opacity(tab.id == selectedTabID ? 1 : 0)
+ .allowsHitTesting(tab.id == selectedTabID)
+ }
+ }
+}
diff --git a/Sora/Views/Post/Grid/Tab/PostGridTabSwitcherView.swift b/Sora/Views/Post/Grid/Tab/PostGridTabSwitcherView.swift
new file mode 100644
index 0000000..4f21470
--- /dev/null
+++ b/Sora/Views/Post/Grid/Tab/PostGridTabSwitcherView.swift
@@ -0,0 +1,128 @@
+import SwiftUI
+
+struct PostGridTabSwitcherView: View {
+ @EnvironmentObject var settings: SettingsManager
+ @State private var tabs: [PostGridTab] = []
+ @State private var selectedTabID: UUID?
+ @Binding private var selectedTab: Int
+ @State private var searchText: String = ""
+ @Binding private var selectedPost: (post: BooruPost?, manager: BooruManager?)
+
+ init(selectedTab: Binding<Int>, selectedPost: Binding<(post: BooruPost?, manager: BooruManager?)>)
+ {
+ let initialTab = PostGridTab(manager: BooruManager())
+
+ _tabs = State(initialValue: [initialTab])
+ _selectedTabID = State(initialValue: initialTab.id)
+ _selectedTab = selectedTab
+ _selectedPost = selectedPost
+
+ Task { await initialTab.manager.fetchPosts(page: 1, tags: [], replace: true) }
+ }
+
+ private var activeManager: BooruManager? {
+ if let selectedID = selectedTabID,
+ let activeTab = tabs.first(where: { $0.id == selectedID })
+ {
+ return activeTab.manager
+ }
+
+ return nil
+ }
+
+ var body: some View {
+ VStack(spacing: 0) {
+ #if !os(macOS)
+ ScrollView(.horizontal, showsIndicators: false) {
+ HStack {
+ ForEach(tabs) { tab in
+ PostGridTabButtonView(
+ title: tab.manager.searchText.isEmpty ? "New Tab" : tab.manager.searchText,
+ isSelected: tab.id == selectedTabID,
+ onSelect: { selectedTabID = tab.id },
+ onClose: {
+ if tabs.count > 1 {
+ tabs.removeAll { $0.id == tab.id }
+
+ if selectedTabID == tab.id {
+ selectedTabID = tabs.first!.id
+ }
+ }
+ }
+ )
+ }
+
+ Button(
+ action: {
+ let newTab = PostGridTab(manager: BooruManager())
+
+ tabs.append(newTab)
+
+ selectedTabID = newTab.id
+
+ Task { await newTab.manager.fetchPosts(page: 1, tags: [], replace: true) }
+ }
+ ) {
+ Image(systemName: "plus")
+ }
+ }
+ .padding(.horizontal)
+ }
+ .background(Color(.systemGray6))
+
+ HStack {
+ TextField(
+ "Tags",
+ text: $searchText
+ ) {
+ if let manager = activeManager {
+ manager.searchText = searchText
+
+ Task { manager.performSearch(settings: settings) }
+ }
+ }
+ .textFieldStyle(PlainTextFieldStyle())
+ .padding(.vertical, 8)
+ .padding(.horizontal, 12)
+ .background(Color(.systemGray5))
+ .clipShape(RoundedRectangle(cornerRadius: 9))
+
+ if !searchText.isEmpty {
+ Button(action: {
+ searchText = ""
+
+ if let manager = activeManager {
+ manager.searchText = ""
+
+ Task { manager.performSearch(settings: settings) }
+ }
+ }) {
+ Image(systemName: "xmark.circle.fill")
+ .foregroundColor(.secondary)
+ }
+ .buttonStyle(PlainButtonStyle())
+ }
+ }
+ .padding(.horizontal)
+ .padding(.vertical, 12)
+ .background(Color(.systemGray6))
+ #endif
+
+ ZStack {
+ PostGridTabSwitcherContentView(
+ selectedTabID: $selectedTabID,
+ selectedPost: $selectedPost,
+ selectedTab: $selectedTab,
+ tabs: $tabs
+ )
+ }
+ }
+ .onChange(of: selectedTabID) { _, newValue in
+ if let selectedID = newValue,
+ let activeTab = tabs.first(where: { $0.id == selectedID })
+ {
+ searchText = activeTab.manager.searchText
+ }
+ }
+ }
+}