Tabs/Sidebar, ArticleStatus.label, LeseListe-navTitle + Kontextmenue, Marker-navTitle, Suche ziehen aus mana/i18n (generiert via scripts/i18n/generate.mjs). Cross-Platform-identisch mit pageta-android. PagetaStrings.generated.swift kompiliert in den PagetaNative-Target (XcodeGen-Glob, kein import noetig). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
85 lines
2.8 KiB
Swift
85 lines
2.8 KiB
Swift
import ManaAuthUI
|
|
import ManaComponentsUI
|
|
import ManaCore
|
|
import SwiftUI
|
|
|
|
/// Inhalt des iOS-26-Such-Tabs (`Tab(role: .search)` in ``RootView``). Sucht
|
|
/// die gespeicherten Artikel **lokal** über Titel/Quelle/Anriss und nutzt die
|
|
/// geteilte ``ManaSearchResultsView``. Eigener `NavigationStack` +
|
|
/// `navigationDestination` → Treffer öffnen das Lese-Detail im Such-Tab. Auf
|
|
/// iOS 26 rendert `.searchable` als unten schwebendes Liquid-Glass-Suchfeld.
|
|
///
|
|
/// Die Suche lag bis 2026-06-06 inline (`.searchable`) in ``LeseListeTabView``;
|
|
/// sie ist mit dem flottenweiten Such-Tab-Rollout hierher gewandert (siehe
|
|
/// ../mana/docs/playbooks/SEARCH_TAB_ROLLOUT.md).
|
|
struct PagetaSearchView: View {
|
|
var accountProfile: ProfileInfo?
|
|
var onAccountTap: () -> Void = {}
|
|
|
|
@Environment(AuthClient.self) private var auth
|
|
private var api: PagetaAPI {
|
|
PagetaAPI(auth: auth)
|
|
}
|
|
|
|
@State private var articles: [Article] = []
|
|
@State private var query = ""
|
|
@State private var searchPresented = false
|
|
|
|
/// Volltext-Match über Titel/Quelle/Anriss. Leerer Query matcht nichts —
|
|
/// im Such-Tab zeigen wir bei leerer Eingabe den Prompt, nicht alle Artikel.
|
|
private var hits: [Article] {
|
|
let trimmed = query.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
|
guard !trimmed.isEmpty else { return [] }
|
|
return articles.filter { article in
|
|
if article.title.lowercased().contains(trimmed) { return true }
|
|
if let site = article.siteName?.lowercased(), site.contains(trimmed) { return true }
|
|
if let excerpt = article.excerpt?.lowercased(),
|
|
excerpt.contains(trimmed) { return true }
|
|
return false
|
|
}
|
|
}
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
ManaSearchResultsView(
|
|
query: query,
|
|
hasResults: !hits.isEmpty,
|
|
prompt: PagetaStrings.searchPlaceholder
|
|
) {
|
|
List {
|
|
ForEach(hits) { article in
|
|
NavigationLink(value: article.id) {
|
|
ArticleRow(article: article)
|
|
}
|
|
.listRowInsets(EdgeInsets(top: 6, leading: 16, bottom: 6, trailing: 16))
|
|
.listRowSeparator(.hidden)
|
|
}
|
|
}
|
|
.listStyle(.plain)
|
|
.macReadingWidth()
|
|
}
|
|
.navigationTitle(PagetaStrings.actionSearch)
|
|
#if os(iOS)
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.manaAccountToolbar(auth: auth, profile: accountProfile, onTap: onAccountTap)
|
|
#endif
|
|
.navigationDestination(for: String.self) { articleId in
|
|
ArticleDetailView(articleId: articleId)
|
|
}
|
|
.paperBackground()
|
|
}
|
|
.searchable(text: $query, isPresented: $searchPresented, prompt: PagetaStrings.searchPlaceholder)
|
|
.onAppear { searchPresented = true }
|
|
.task {
|
|
if articles.isEmpty {
|
|
await loadArticles()
|
|
}
|
|
}
|
|
}
|
|
|
|
private func loadArticles() async {
|
|
// Alle nicht-archivierten Artikel — dieselbe Quelle wie die Lese-Liste.
|
|
guard let all = try? await api.fetchArticles(limit: 200) else { return }
|
|
articles = all.filter { $0.status != .archived }
|
|
}
|
|
}
|