pageta-native/Sources/Features/FeedItemReaderView.swift
Till JS d4c0e11d5b Tabs umbenannt: Lese-Liste → Stapel, Markierungen → Marker
User-facing Strings konsistent umgestellt: Tab-Labels + Nav-Titel +
macOS-Sidebar, leere Zustände, Such-Prompt, Feed-Speichern-Aktionen
("Auf den Stapel"), Share-Toast ("Zum Stapel hinzugefügt"), App-Shortcut-
Beschreibung, Konto-Prosa, Marker-Bezüge im Reader (Notiz-Sheet, Zähler,
Löschen-Label, Kopieren). Verb "markieren" + Lese-Status bleiben.
Swift-Typnamen (LeseListeTabView/MarkierungenTabView) unverändert (interne
Identifier). Build grün.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 01:04:14 +02:00

142 lines
3.5 KiB
Swift

import ManaAuthUI
import ManaCore
import SwiftUI
/// Reader für Feed-Items (Pool-Artikel, noch nicht in der Lese-Liste).
///
/// FeedItems kommen schon mit Readability-extrahiertem `content` aus
/// dem Pool, also können wir sie direkt im Native-Reader anzeigen ohne
/// Save-Roundtrip. Toolbar-Action „In Lese-Liste" macht den Save dann
/// explizit.
struct FeedItemReaderView: View {
let item: FeedItem
@Environment(AuthClient.self) private var auth
private var api: PagetaAPI { PagetaAPI(auth: auth) }
@State private var saveState: SaveState = .idle
enum SaveState: Equatable {
case idle
case saving
case saved
case error(String)
}
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 20) {
header
ForEach(paragraphs, id: \.self) { paragraph in
Text(paragraph)
.font(.body)
.foregroundStyle(PagetaTheme.foreground)
.fixedSize(horizontal: false, vertical: true)
}
}
.padding(.horizontal, 20)
.padding(.vertical, 16)
.frame(maxWidth: 720, alignment: .leading)
.frame(maxWidth: .infinity, alignment: .leading)
}
.background(PagetaTheme.background)
.contentMargins(.bottom, 60, for: .scrollContent)
.navigationTitle("")
#if os(iOS)
.navigationBarTitleDisplayMode(.inline)
#endif
.toolbar { toolbarContent }
}
private var header: some View {
VStack(alignment: .leading, spacing: 8) {
HStack(spacing: 6) {
Text(item.topic.emoji)
Text(item.topic.label)
.font(.caption.weight(.medium))
.foregroundStyle(.secondary)
Text("•").foregroundStyle(.secondary)
Text(item.siteName)
.font(.caption)
.foregroundStyle(.secondary)
if let mins = item.readingTimeMinutes {
Text("•").foregroundStyle(.secondary)
Text("\(mins) min lesen")
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
}
Text(item.title)
.font(.system(.title2, design: .serif, weight: .semibold))
.foregroundStyle(PagetaTheme.foreground)
if let author = item.author, !author.isEmpty {
Text("von \(author)")
.font(.subheadline)
.foregroundStyle(.secondary)
}
}
}
@ToolbarContentBuilder
private var toolbarContent: some ToolbarContent {
ToolbarItem(placement: .primaryAction) {
Menu {
Button {
Task { await save() }
} label: {
Label(saveLabel, systemImage: saveIcon)
}
.disabled(saveState == .saving || saveState == .saved)
if let url = URL(string: item.originalUrl) {
Link(destination: url) {
Label("Im Browser öffnen", systemImage: "safari")
}
ShareLink(item: url) {
Label("Teilen", systemImage: "square.and.arrow.up")
}
}
} label: {
Image(systemName: "ellipsis")
}
}
}
private var saveLabel: String {
switch saveState {
case .idle: "Auf den Stapel speichern"
case .saving: "Speichere …"
case .saved: "Gespeichert"
case .error: "Erneut versuchen"
}
}
private var saveIcon: String {
switch saveState {
case .idle, .error: "books.vertical"
case .saving: "ellipsis"
case .saved: "checkmark"
}
}
private var paragraphs: [String] {
item.content
.components(separatedBy: "\n\n")
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { !$0.isEmpty }
}
private func save() async {
guard let url = URL(string: item.originalUrl) else {
saveState = .error("Ungültige URL")
return
}
saveState = .saving
do {
try await api.saveArticle(url: url)
saveState = .saved
} catch {
saveState = .error(error.localizedDescription)
}
}
}