From a43bdd31d3e053de100773dfaa34e7225dcf49a8 Mon Sep 17 00:00:00 2001 From: Fuwn Date: Tue, 8 Jul 2025 09:47:39 -0700 Subject: feat: Development commit --- .../Data/PostGridViewState/PostGridViewState.swift | 1 + Sora/Data/Scroll/ScrollPositionPreference.swift | 6 + Sora/Data/Scroll/ScrollPositionPreferenceKey.swift | 19 ++ Sora/Views/Post/Grid/PostGridView.swift | 197 +++++++++++++-------- 4 files changed, 145 insertions(+), 78 deletions(-) create mode 100644 Sora/Data/Scroll/ScrollPositionPreference.swift create mode 100644 Sora/Data/Scroll/ScrollPositionPreferenceKey.swift diff --git a/Sora/Data/PostGridViewState/PostGridViewState.swift b/Sora/Data/PostGridViewState/PostGridViewState.swift index 266d05c..ff7a32a 100644 --- a/Sora/Data/PostGridViewState/PostGridViewState.swift +++ b/Sora/Data/PostGridViewState/PostGridViewState.swift @@ -4,5 +4,6 @@ struct PostGridViewState: Equatable { var posts: [BooruPost] = [] var currentPage: Int = 1 var selectedPost: BooruPost? + var scrollPosition: BooruPost.ID? let createdAt = Date() } diff --git a/Sora/Data/Scroll/ScrollPositionPreference.swift b/Sora/Data/Scroll/ScrollPositionPreference.swift new file mode 100644 index 0000000..fb36bb1 --- /dev/null +++ b/Sora/Data/Scroll/ScrollPositionPreference.swift @@ -0,0 +1,6 @@ +import SwiftUI + +struct ScrollPositionPreference: Equatable { + let id: BooruPost.ID + let yPosition: CGFloat +} diff --git a/Sora/Data/Scroll/ScrollPositionPreferenceKey.swift b/Sora/Data/Scroll/ScrollPositionPreferenceKey.swift new file mode 100644 index 0000000..6b78bdc --- /dev/null +++ b/Sora/Data/Scroll/ScrollPositionPreferenceKey.swift @@ -0,0 +1,19 @@ +import SwiftUI + +struct ScrollPositionPreferenceKey: PreferenceKey { + typealias Value = ScrollPositionPreference? + + static var defaultValue: Value = nil + + static func reduce(value: inout Value, nextValue: () -> Value) { + guard let next = nextValue() else { return } + + if let current = value { + if abs(next.yPosition) < abs(current.yPosition) { + value = next + } + } else { + value = next + } + } +} diff --git a/Sora/Views/Post/Grid/PostGridView.swift b/Sora/Views/Post/Grid/PostGridView.swift index 97604e5..5b48e1e 100644 --- a/Sora/Views/Post/Grid/PostGridView.swift +++ b/Sora/Views/Post/Grid/PostGridView.swift @@ -1,3 +1,5 @@ +// swiftlint:disable file_length + import SwiftUI import WaterfallGrid @@ -13,100 +15,120 @@ struct PostGridView: View { // swiftlint:disable:this type_body_length @State private var suppressNextSearchSubmit = false @State private var searchTask: Task? @State private var suggestions: [BooruTag] = [] + @State private var topItemID: BooruPost.ID? + @State private var debounceTask: Task? @Environment(\.isSearching) private var isSearching - var filteredPosts: [BooruPost] { - manager.posts - .filter { settings.displayRatings.contains($0.rating) } + private var activePosts: [BooruPost] { + guard manager.historyIndex >= 0 && manager.historyIndex < manager.searchHistory.count else { + return [] + } + + let queryID = manager.searchHistory[manager.historyIndex].id + + return viewStates[queryID]?.posts + .filter { settings.displayRatings.contains($0.rating) } ?? [] } var body: some View { - ScrollViewReader { _ in - ZStack { - ForEach(Array(manager.searchHistory.enumerated()), id: \.element.id) { index, query in - let isActive = index == manager.historyIndex - let filteredPosts = - viewStates[query.id]?.posts - .filter { settings.displayRatings.contains($0.rating) } ?? [] - - ScrollView { - Group { - if let error = manager.error { - ContentUnavailableView( - "Provider Error", - systemImage: "exclamationmark.triangle.fill", - description: Text(error.localizedDescription) - ) - } + ScrollViewReader { proxy in + ScrollView { + Group { + if let error = manager.error { + ContentUnavailableView( + "Provider Error", + systemImage: "exclamationmark.triangle.fill", + description: Text(error.localizedDescription) + ) + } - if filteredPosts.isEmpty, isActive, manager.isLoading { - let gridItems = Array( - repeating: GridItem(.flexible()), - count: settings.thumbnailGridColumns - ) + 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() - } + LazyVGrid(columns: gridItems) { + ForEach(0..<(50 / settings.thumbnailGridColumns), id: \.self) { _ in + PostGridThumbnailPlaceholderView() + } + } + #if os(macOS) + .padding(8) + #else + .padding(.horizontal) + #endif + .transition(.opacity) + } else { + let columnCount = settings.thumbnailGridColumns + + if settings.alternativeThumbnailGrid { + let columnsData = (0..= 0, + manager.historyIndex < manager.searchHistory.count + else { return } + + let queryID = manager.searchHistory[manager.historyIndex].id + + updateViewState(for: queryID, scrollPosition: newID) + } catch { + return + } } } .animation(.easeInOut, value: manager.historyIndex) @@ -199,6 +221,12 @@ struct PostGridView: View { // swiftlint:disable:this type_body_length manager.posts = state.posts manager.currentPage = state.currentPage manager.selectedPost = state.selectedPost + + if let scrollPosition = state.scrollPosition { + DispatchQueue.main.async { + proxy.scrollTo(scrollPosition, anchor: .top) + } + } } else { manager.posts = [] manager.currentPage = 1 @@ -344,7 +372,17 @@ struct PostGridView: View { // swiftlint:disable:this type_body_length Button { if !manager.isLoading { manager.selectedPost = post } } label: { - PostGridThumbnailView(post: post, posts: filteredPosts) + PostGridThumbnailView(post: post, posts: activePosts) + .background( + GeometryReader { geometry in + let frame = geometry.frame(in: .named("scrollview")) + + Color.clear.preference( + key: ScrollPositionPreferenceKey.self, + value: ScrollPositionPreference(id: post.id, yPosition: frame.minY) + ) + } + ) } .buttonStyle(PlainButtonStyle()) .contextMenu { @@ -372,7 +410,8 @@ struct PostGridView: View { // swiftlint:disable:this type_body_length posts: [BooruPost] = [], currentPage: Int? = nil, selectedPost: BooruPost? = nil, - resetSelectedPost: Bool = false + resetSelectedPost: Bool = false, + scrollPosition: BooruPost.ID? = nil ) { let wasNewlyCreated = viewStates[queryID] == nil var state = viewStates[queryID] ?? PostGridViewState() @@ -381,6 +420,8 @@ struct PostGridView: View { // swiftlint:disable:this type_body_length if let currentPage { state.currentPage = currentPage } + if let scrollPosition { state.scrollPosition = scrollPosition } + if let selectedPost { state.selectedPost = selectedPost } else if resetSelectedPost { -- cgit v1.2.3