pageta-native/Sources/Features/PagetaSearchView.swift
Till JS 291fa018b6 i18n: UI-Strings ueber generiertes PagetaStrings (Phase 3)
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>
2026-06-09 15:08:36 +02:00

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 }
}
}