78ba61034f
- Remove .toolbarBackground(.visible) from all NavigationStack views: this modifier suppresses large title rendering in iOS 26. - Discover: switch to always-present List + overlay pattern so SwiftUI never loses the scroll context during loading transitions. - Discover, Notifications, Groups: use .inline title mode; the tab bar already identifies these screens, large titles are redundant. - RootView: add .toolbarColorScheme propagation for nav bar foreground. - Settings sheet: apply theme background and toolbar color to the Form.
112 lines
4.4 KiB
Swift
112 lines
4.4 KiB
Swift
import SwiftUI
|
|
import OrgSocialKit
|
|
|
|
struct SearchView: View {
|
|
@State private var viewModel = SearchViewModel()
|
|
@AppStorage("useRelay") private var useRelay = true
|
|
@Environment(\.appTheme) private var theme
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
VStack(spacing: 0) {
|
|
searchBar
|
|
Divider()
|
|
content
|
|
}
|
|
.background(theme.background)
|
|
.navigationTitle("Search")
|
|
.navigationBarTitleDisplayMode(.large)
|
|
.toolbarBackground(theme.secondaryBackground, for: .navigationBar)
|
|
.navigationDestination(for: URL.self) { ProfileView(feedURL: $0) }
|
|
.navigationDestination(for: ThreadRoute.self) {
|
|
ThreadView(postURL: $0.postURL, relayURL: $0.relayURL)
|
|
}
|
|
}
|
|
}
|
|
|
|
private var searchBar: some View {
|
|
VStack(spacing: 8) {
|
|
HStack(spacing: 8) {
|
|
HStack(spacing: 6) {
|
|
Image(systemName: "magnifyingglass").foregroundStyle(.secondary)
|
|
TextField(viewModel.isTagSearch ? "Tag name…" : "Search posts…", text: $viewModel.query)
|
|
.autocorrectionDisabled()
|
|
.textInputAutocapitalization(.never)
|
|
.submitLabel(.search)
|
|
.onSubmit { if useRelay { Task { await viewModel.search() } } }
|
|
if !viewModel.query.isEmpty {
|
|
Button { viewModel.query = "" } label: {
|
|
Image(systemName: "xmark.circle.fill").foregroundStyle(.secondary)
|
|
}
|
|
.buttonStyle(.plain)
|
|
}
|
|
}
|
|
.padding(8)
|
|
.background(Color.secondary.opacity(0.1), in: RoundedRectangle(cornerRadius: 10))
|
|
|
|
Button(viewModel.isTagSearch ? "#Tag" : "Text") {
|
|
viewModel.isTagSearch.toggle()
|
|
}
|
|
.font(.caption.weight(.semibold))
|
|
.padding(.horizontal, 10).padding(.vertical, 8)
|
|
.background(Color.accentColor.opacity(0.1), in: RoundedRectangle(cornerRadius: 10))
|
|
.foregroundStyle(Color.accentColor)
|
|
}
|
|
|
|
Button {
|
|
Task { await viewModel.search() }
|
|
} label: {
|
|
Label("Search", systemImage: "magnifyingglass")
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
.buttonStyle(.borderedProminent)
|
|
.disabled(viewModel.query.trimmingCharacters(in: .whitespaces).isEmpty || viewModel.isLoading || !useRelay)
|
|
|
|
if !useRelay {
|
|
Text("Enable Use Relay in Settings to search.")
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
.padding()
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var content: some View {
|
|
if viewModel.isLoading {
|
|
ProgressView().frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
} else if let error = viewModel.errorMessage {
|
|
ContentUnavailableView {
|
|
Label("Search failed", systemImage: "exclamationmark.triangle")
|
|
} description: {
|
|
Text(error)
|
|
}
|
|
} else if viewModel.results.isEmpty && !viewModel.query.isEmpty {
|
|
ContentUnavailableView(
|
|
"No results",
|
|
systemImage: "magnifyingglass",
|
|
description: Text("No posts found for \"\(viewModel.query)\".")
|
|
)
|
|
} else if viewModel.results.isEmpty {
|
|
ContentUnavailableView(
|
|
"Search posts",
|
|
systemImage: "magnifyingglass",
|
|
description: Text("Enter a keyword or tag to search posts on the relay.")
|
|
)
|
|
} else {
|
|
List {
|
|
ForEach(viewModel.results, id: \.timestamp) { post in
|
|
PostRowView(post: post)
|
|
.listRowSeparator(.visible)
|
|
.listRowSeparatorTint(Color.secondary.opacity(0.2))
|
|
.listRowInsets(EdgeInsets(top: 12, leading: 16, bottom: 12, trailing: 16))
|
|
.listRowBackground(theme.background)
|
|
}
|
|
}
|
|
.listStyle(.plain)
|
|
.scrollContentBackground(.hidden)
|
|
.background(theme.background)
|
|
}
|
|
}
|
|
}
|