summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFuwn <[email protected]>2025-07-11 08:24:34 -0700
committerFuwn <[email protected]>2025-07-11 08:24:34 -0700
commitf7d66a9190526f2c6e715fcddf10ca65c919140c (patch)
tree0e90e992aeb6facc5d70fc2adade4c80e2aea18e
parentfeat: Development commit (diff)
downloadsora-testing-f7d66a9190526f2c6e715fcddf10ca65c919140c.tar.xz
sora-testing-f7d66a9190526f2c6e715fcddf10ca65c919140c.zip
feat: Development commit
-rw-r--r--Sora/Views/Post/Grid/PostGridView.swift498
1 files changed, 251 insertions, 247 deletions
diff --git a/Sora/Views/Post/Grid/PostGridView.swift b/Sora/Views/Post/Grid/PostGridView.swift
index c57e296..d0f6473 100644
--- a/Sora/Views/Post/Grid/PostGridView.swift
+++ b/Sora/Views/Post/Grid/PostGridView.swift
@@ -33,53 +33,26 @@ struct PostGridView: View { // swiftlint:disable:this type_body_length
}
var body: some View {
- ScrollView {
- Group {
- if let error = manager.error {
- ContentUnavailableView(
- "Provider Error",
- systemImage: "exclamationmark.triangle.fill",
- description: Text(error.localizedDescription)
- )
- }
-
- if activePosts.isEmpty, manager.isLoading {
- let gridItems = Array(
- repeating: GridItem(.flexible()),
- count: settings.thumbnailGridColumns
- )
-
- LazyVGrid(columns: gridItems) {
- ForEach(0..<(50 / settings.thumbnailGridColumns), id: \.self) { _ in
- PostGridThumbnailPlaceholderView()
- }
+ ScrollViewReader { proxy in
+ ScrollView {
+ Group {
+ if let error = manager.error {
+ ContentUnavailableView(
+ "Provider Error",
+ systemImage: "exclamationmark.triangle.fill",
+ description: Text(error.localizedDescription)
+ )
}
- #if os(macOS)
- .padding(8)
- #else
- .padding(.horizontal)
- #endif
- .transition(.opacity)
- } else {
- let columnCount = settings.thumbnailGridColumns
- if settings.alternativeThumbnailGrid {
- let columnsData = (0..<columnCount).map { columnIndex in
- activePosts.enumerated().compactMap { index, post in
- index % columnCount == columnIndex ? post : nil
- }
- }
+ if activePosts.isEmpty, manager.isLoading {
+ let gridItems = Array(
+ repeating: GridItem(.flexible()),
+ count: settings.thumbnailGridColumns
+ )
- HStack(alignment: .top) {
- ForEach(0..<columnCount, id: \.self) { columnIndex in
- LazyVStack {
- ForEach(columnsData[columnIndex], id: \.id) { post in
- waterfallGridContent(post: post)
- .id(post.id)
- }
- }
- .transaction { $0.animation = nil }
- .scrollTargetLayout()
+ LazyVGrid(columns: gridItems) {
+ ForEach(0..<(50 / settings.thumbnailGridColumns), id: \.self) { _ in
+ PostGridThumbnailPlaceholderView()
}
}
#if os(macOS)
@@ -89,260 +62,291 @@ struct PostGridView: View { // swiftlint:disable:this type_body_length
#endif
.transition(.opacity)
} else {
- WaterfallGrid(activePosts, id: \.id) { post in
- waterfallGridContent(post: post)
- .id(post.id)
+ let columnCount = settings.thumbnailGridColumns
+
+ if settings.alternativeThumbnailGrid {
+ let columnsData = (0..<columnCount).map { columnIndex in
+ activePosts.enumerated().compactMap { index, post in
+ index % columnCount == columnIndex ? post : nil
+ }
+ }
+
+ HStack(alignment: .top) {
+ ForEach(0..<columnCount, id: \.self) { columnIndex in
+ LazyVStack {
+ ForEach(columnsData[columnIndex], id: \.id) { post in
+ waterfallGridContent(post: post)
+ .id(post.id)
+ }
+ }
+ .transaction { $0.animation = nil }
+ .scrollTargetLayout()
+ }
+ }
+ #if os(macOS)
+ .padding(8)
+ #else
+ .padding(.horizontal)
+ #endif
+ .transition(.opacity)
+ } else {
+ WaterfallGrid(activePosts, id: \.id) { post in
+ waterfallGridContent(post: post)
+ .id(post.id)
+ }
+ .gridStyle(columns: columnCount)
+ .transaction { $0.animation = nil }
+ .scrollTargetLayout()
+ #if os(macOS)
+ .padding(8)
+ #else
+ .padding(.horizontal)
+ #endif
+ .transition(.opacity)
}
- .gridStyle(columns: columnCount)
- .transaction { $0.animation = nil }
- .scrollTargetLayout()
- #if os(macOS)
- .padding(8)
- #else
- .padding(.horizontal)
- #endif
- .transition(.opacity)
}
}
+ .id(queryID)
+ .transition(.opacity)
}
- .id(queryID)
- .transition(.opacity)
- }
- #if os(iOS)
- .searchable(
- text: $manager.searchText,
- isPresented: $isSearchablePresented,
- placement: .navigationBarDrawer(displayMode: .automatic),
- prompt: "Tags"
- )
- #else
- .searchable(
- text: $manager.searchText,
- isPresented: $isSearchablePresented,
- prompt: "Tags"
- )
- #endif
- .searchSuggestions {
- if settings.searchSuggestionsMode != .disabled {
- SearchSuggestionsView(
- items: searchSuggestionsItems(),
- searchText: $manager.searchText,
- suppressNextSearchSubmit: $suppressNextSearchSubmit
+ #if os(iOS)
+ .searchable(
+ text: $manager.searchText,
+ isPresented: $isSearchablePresented,
+ placement: .navigationBarDrawer(displayMode: .automatic),
+ prompt: "Tags"
+ )
+ #else
+ .searchable(
+ text: $manager.searchText,
+ isPresented: $isSearchablePresented,
+ prompt: "Tags"
)
+ #endif
+ .searchSuggestions {
+ if settings.searchSuggestionsMode != .disabled {
+ SearchSuggestionsView(
+ items: searchSuggestionsItems(),
+ searchText: $manager.searchText,
+ suppressNextSearchSubmit: $suppressNextSearchSubmit
+ )
+ }
}
- }
- .onChange(of: manager.searchText) { _, newValue in
- if settings.searchSuggestionsMode == .tags {
- searchTask?.cancel()
+ .onChange(of: manager.searchText) { _, newValue in
+ if settings.searchSuggestionsMode == .tags {
+ searchTask?.cancel()
- searchTask = Task {
- try? await Task.sleep(nanoseconds: 300_000_000)
+ searchTask = Task {
+ try? await Task.sleep(nanoseconds: 300_000_000)
- guard !Task.isCancelled else { return }
+ guard !Task.isCancelled else { return }
- let searchTag = newValue.split(separator: " ").last.map(String.init) ?? ""
+ let searchTag = newValue.split(separator: " ").last.map(String.init) ?? ""
- if !searchTag.isEmpty {
- suggestions = await manager.searchTags(name: searchTag)
- } else {
- suggestions = []
+ if !searchTag.isEmpty {
+ suggestions = await manager.searchTags(name: searchTag)
+ } else {
+ suggestions = []
+ }
}
}
}
- }
- .onSubmit(of: .search) {
- if suppressNextSearchSubmit {
- suppressNextSearchSubmit = false
+ .onSubmit(of: .search) {
+ if suppressNextSearchSubmit {
+ suppressNextSearchSubmit = false
- return
- }
+ return
+ }
- Task(priority: .userInitiated) {
- await manager.performSearch(settings: settings)
+ Task(priority: .userInitiated) {
+ await manager.performSearch(settings: settings)
+ }
}
- }
- .navigationDestination(for: BooruPost.self) { post in
- PostDetailsView(post: post)
- }
- .onChange(of: isSearchablePresented) { _, isPresented in
- if !isPresented, manager.searchText.isEmpty, !manager.isNavigatingHistory {
- Task(priority: .userInitiated) { await manager.performSearch() }
+ .navigationDestination(for: BooruPost.self) { post in
+ PostDetailsView(post: post)
}
- }
- .onChange(of: manager.posts) { _, newPosts in
- if manager.historyIndex >= 0 && manager.historyIndex < manager.searchHistory.count {
- updateViewState(for: queryID, posts: newPosts)
+ .onChange(of: isSearchablePresented) { _, isPresented in
+ if !isPresented, manager.searchText.isEmpty, !manager.isNavigatingHistory {
+ Task(priority: .userInitiated) { await manager.performSearch() }
+ }
}
- }
- .onChange(of: manager.currentPage) { _, newPage in
- if manager.historyIndex >= 0 && manager.historyIndex < manager.searchHistory.count {
- let queryID = manager.searchHistory[manager.historyIndex].id
-
- updateViewState(for: queryID, currentPage: newPage)
+ .onChange(of: manager.posts) { _, newPosts in
+ if manager.historyIndex >= 0 && manager.historyIndex < manager.searchHistory.count {
+ updateViewState(for: queryID, posts: newPosts)
+ }
}
- }
- .onChange(of: manager.selectedPost) { _, newPost in
- let queryID = manager.searchHistory.last { $0.tags == manager.tags }?.id ?? UUID()
+ .onChange(of: manager.currentPage) { _, newPage in
+ if manager.historyIndex >= 0 && manager.historyIndex < manager.searchHistory.count {
+ let queryID = manager.searchHistory[manager.historyIndex].id
- updateViewState(for: queryID, selectedPost: newPost, resetSelectedPost: newPost == nil)
- }
- .onChange(of: manager.historyIndex) { _, newIndex in
- guard newIndex >= 0 && newIndex < manager.searchHistory.count else { return }
+ updateViewState(for: queryID, currentPage: newPage)
+ }
+ }
+ .onChange(of: manager.selectedPost) { _, newPost in
+ let queryID = manager.searchHistory.last { $0.tags == manager.tags }?.id ?? UUID()
- let queryID = manager.searchHistory[newIndex].id
+ updateViewState(for: queryID, selectedPost: newPost, resetSelectedPost: newPost == nil)
+ }
+ .onChange(of: manager.historyIndex) { _, newIndex in
+ guard newIndex >= 0 && newIndex < manager.searchHistory.count else { return }
- if let state = viewStates[queryID] {
- manager.posts = state.posts
- manager.currentPage = state.currentPage
- manager.selectedPost = state.selectedPost
- } else {
- manager.posts = []
- manager.currentPage = 1
+ let queryID = manager.searchHistory[newIndex].id
- Task(priority: .userInitiated) {
- await manager.fetchPosts(
- page: 1,
- tags: manager.searchHistory[newIndex].tags,
- replace: true
- )
+ if let state = viewStates[queryID] {
+ manager.posts = state.posts
+ manager.currentPage = state.currentPage
+ manager.selectedPost = state.selectedPost
+ } else {
+ manager.posts = []
+ manager.currentPage = 1
+
+ Task(priority: .userInitiated) {
+ await manager.fetchPosts(
+ page: 1,
+ tags: manager.searchHistory[newIndex].tags,
+ replace: true
+ )
+ }
}
+
+ proxy.scrollTo(queryID, anchor: .top)
}
- }
- .toolbar {
- #if os(macOS)
- ToolbarItem {
- Button(action: {
- Task {
- await manager.fetchPosts(page: 1, tags: manager.tags, replace: true)
+ .toolbar {
+ #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)
- PlatformSpecificToolbarItem {
- Button(action: { Task { isSearchHistoryPresented.toggle() } }) {
- Label("Search History", systemImage: "clock.arrow.circlepath")
+ #if !os(macOS)
+ PlatformSpecificToolbarItem {
+ Button(action: { Task { isSearchHistoryPresented.toggle() } }) {
+ Label("Search History", systemImage: "clock.arrow.circlepath")
+ }
}
- }
- if #available(iOS 26, *), manager.isLoading || manager.isNavigatingHistory {
- ToolbarItem(placement: .status) { ProgressView() }
- }
- #endif
+ if #available(iOS 26, *), manager.isLoading || manager.isNavigatingHistory {
+ ToolbarItem(placement: .status) { ProgressView() }
+ }
+ #endif
- PlatformSpecificToolbarItem {
- PostGridBookmarkButtonView()
- .disabled(manager.tags.isEmpty)
- }
+ PlatformSpecificToolbarItem {
+ PostGridBookmarkButtonView()
+ .disabled(manager.tags.isEmpty)
+ }
- PlatformSpecificToolbarItem {
- Button(
- action: {
- Task(priority: .userInitiated) {
- await manager.loadNextPage()
+ PlatformSpecificToolbarItem {
+ Button(
+ action: {
+ Task(priority: .userInitiated) {
+ await manager.loadNextPage()
+ }
}
+ ) {
+ Label(
+ "Manually Load Next Page",
+ systemImage: "arrow.down.to.line"
+ )
}
- ) {
- Label(
- "Manually Load Next Page",
- systemImage: "arrow.down.to.line"
- )
- }
- .disabled(manager.isLoading)
- }
-
- #if !os(macOS)
- if #unavailable(iOS 26), manager.isLoading || manager.isNavigatingHistory {
- ToolbarItem(placement: .topBarTrailing) { ProgressView() }
+ .disabled(manager.isLoading)
}
- #endif
- PlatformSpecificToolbarItem(placement: .navigation) {
- Menu {
- ForEach(
- Array(manager.searchHistory.enumerated().filter { $0.offset < manager.historyIndex }),
- id: \.offset
- ) { offset, query in
- Button(action: {
- manager.historyIndex = offset
- }) {
- Text(query.tags.isEmpty ? "No Tags" : query.tags.joined(separator: " "))
- }
+ #if !os(macOS)
+ if #unavailable(iOS 26), manager.isLoading || manager.isNavigatingHistory {
+ ToolbarItem(placement: .topBarTrailing) { ProgressView() }
}
- } label: {
- Label("Previous Search", systemImage: "chevron.left")
- } primaryAction: {
- withAnimation {
- manager.goBackInHistory()
+ #endif
+
+ PlatformSpecificToolbarItem(placement: .navigation) {
+ Menu {
+ ForEach(
+ Array(manager.searchHistory.enumerated().filter { $0.offset < manager.historyIndex }),
+ id: \.offset
+ ) { offset, query in
+ Button(action: {
+ manager.historyIndex = offset
+ }) {
+ Text(query.tags.isEmpty ? "No Tags" : query.tags.joined(separator: " "))
+ }
+ }
+ } label: {
+ Label("Previous Search", systemImage: "chevron.left")
+ } primaryAction: {
+ withAnimation {
+ manager.goBackInHistory()
+ }
}
+ .disabled(!manager.canGoBackInHistory)
}
- .disabled(!manager.canGoBackInHistory)
- }
- PlatformSpecificToolbarItem(placement: .navigation) {
- Menu {
- ForEach(
- Array(manager.searchHistory.enumerated().filter { $0.offset > manager.historyIndex }),
- id: \.offset
- ) { offset, query in
- Button(action: {
- manager.historyIndex = offset
- }) {
- Text(query.tags.isEmpty ? "No Tags" : query.tags.joined(separator: " "))
+ PlatformSpecificToolbarItem(placement: .navigation) {
+ Menu {
+ ForEach(
+ Array(manager.searchHistory.enumerated().filter { $0.offset > manager.historyIndex }),
+ id: \.offset
+ ) { offset, query in
+ Button(action: {
+ manager.historyIndex = offset
+ }) {
+ Text(query.tags.isEmpty ? "No Tags" : query.tags.joined(separator: " "))
+ }
+ }
+ } label: {
+ Label("Next Search", systemImage: "chevron.right")
+ } primaryAction: {
+ withAnimation {
+ manager.goForwardInHistory()
}
}
- } label: {
- Label("Next Search", systemImage: "chevron.right")
- } primaryAction: {
- withAnimation {
- manager.goForwardInHistory()
- }
+ .disabled(!manager.canGoForwardInHistory)
}
- .disabled(!manager.canGoForwardInHistory)
}
- }
- .navigationTitle("Posts")
- .refreshable {
- manager.clearCachedPages()
+ .navigationTitle("Posts")
+ .refreshable {
+ manager.clearCachedPages()
- Task(priority: .userInitiated) {
- await manager.fetchPosts(page: 1, tags: manager.tags, replace: true)
+ Task(priority: .userInitiated) {
+ await manager.fetchPosts(page: 1, tags: manager.tags, replace: true)
+ }
}
- }
- .sheet(isPresented: $isSearchHistoryPresented) {
- PostGridSearchHistoryView(
- selectedTab: $selectedTab,
- isPresented: $isSearchHistoryPresented
- )
- }
- #if os(iOS)
- .gesture(
- DragGesture()
- .onEnded { value in
- if value.startLocation.x < 50 && value.translation.width > 100 {
- withAnimation {
- manager.goBackInHistory()
+ .sheet(isPresented: $isSearchHistoryPresented) {
+ PostGridSearchHistoryView(
+ selectedTab: $selectedTab,
+ isPresented: $isSearchHistoryPresented
+ )
+ }
+ #if os(iOS)
+ .gesture(
+ DragGesture()
+ .onEnded { value in
+ if value.startLocation.x < 50 && value.translation.width > 100 {
+ withAnimation {
+ manager.goBackInHistory()
+ }
+
+ debugPrint("ContentView: Swipe left, \(manager.searchHistory)")
}
- debugPrint("ContentView: Swipe left, \(manager.searchHistory)")
- }
+ if value.startLocation.x > (UIScreen.main.bounds.width - 50)
+ && value.translation.width < -100
+ {
+ withAnimation {
+ manager.goForwardInHistory()
+ }
- if value.startLocation.x > (UIScreen.main.bounds.width - 50)
- && value.translation.width < -100
- {
- withAnimation {
- manager.goForwardInHistory()
+ debugPrint("ContentView: Swipe right, \(manager.searchHistory)")
}
-
- debugPrint("ContentView: Swipe right, \(manager.searchHistory)")
}
- }
- )
- #endif
+ )
+ #endif
+ }
}
private func waterfallGridContent(post: BooruPost) -> some View {