Pendant zur Web-Route /rueckblick. Synthetisiert, was man WIRKLICH gelesen hat (status=finished oder readingProgress>=0.5 im Fenster). - Core/Domain/Digest.swift: reine, getestete Logik — Swift-Spiegel von pageta-domain/digest.ts (Auswahl nach Zeitfenster + Lese-Signal, Theme-Gruppierung nach Tags, Prompt-Builder). 8 XCTest-Fälle, grün. - Features/RueckblickTabView.swift: deterministisches Gerüst steht IMMER (gratis, offline) — Kennzahlen, Themencluster, eigene Markierungen wörtlich. Darüber optionale Synthese via ManaLLM.generate (lokal-first; liefert sie nichts, bleibt das Gerüst nutzbar). Tag/Woche/Gesamt-Picker, Markdown-ShareLink, Tap → ArticleDetailView. - RootView: neuer Tab „Rückblick" (iOS TabView + macOS Sidebar), Icon clock.arrow.circlepath, nach Markierungen. - Motivations-Leitplanke (Wert 8): ruhiger Spiegel, keine Streaks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
147 lines
4.7 KiB
Swift
147 lines
4.7 KiB
Swift
import SwiftUI
|
|
|
|
/// Top-Level-Container. Platform-specific:
|
|
/// - **iOS**: TabView am unteren Rand (iPhone-Standard, gut für Daumen).
|
|
/// - **macOS**: NavigationSplitView mit Sidebar + Detail (Desktop-
|
|
/// Standard, nutzt die größere Fläche).
|
|
struct RootView: View {
|
|
#if os(macOS)
|
|
@SceneStorage("pageta.sidebar.section") private var rawSection: String = SidebarSection.liste.rawValue
|
|
|
|
// Konto-Section ist auf macOS bewusst NICHT in der Sidebar —
|
|
// Konto/Darstellung/Benachrichtigungen leben in der Settings-
|
|
// Scene (⌘,), wie es macOS-Standard ist.
|
|
//
|
|
// `.feed` ist seit 2026-06-01 NICHT mehr gelistet (siehe
|
|
// SidebarSection.feed-Kommentar + ../pageta/docs/FEED.md).
|
|
private static let macSidebar: [SidebarSection] = [.liste, .markierungen, .rueckblick]
|
|
|
|
private var selection: Binding<SidebarSection> {
|
|
Binding(
|
|
get: {
|
|
let saved = SidebarSection(rawValue: rawSection) ?? .liste
|
|
return Self.macSidebar.contains(saved) ? saved : .liste
|
|
},
|
|
set: { rawSection = $0.rawValue }
|
|
)
|
|
}
|
|
|
|
var body: some View {
|
|
NavigationSplitView {
|
|
List(Self.macSidebar, id: \.self, selection: selection) { section in
|
|
Label(section.label, systemImage: section.icon)
|
|
.tag(section)
|
|
}
|
|
.listStyle(.sidebar)
|
|
.navigationTitle("Pageta")
|
|
.navigationSplitViewColumnWidth(min: 200, ideal: 240, max: 320)
|
|
} detail: {
|
|
detailView(for: selection.wrappedValue)
|
|
}
|
|
.navigationSplitViewStyle(.balanced)
|
|
}
|
|
|
|
@ViewBuilder
|
|
private func detailView(for section: SidebarSection) -> some View {
|
|
switch section {
|
|
case .liste: LeseListeTabView()
|
|
case .markierungen: MarkierungenTabView()
|
|
case .rueckblick: RueckblickTabView()
|
|
case .feed: FeedTabView()
|
|
case .konto: LeseListeTabView() // Fallback — Konto ist über ⌘, erreichbar.
|
|
}
|
|
}
|
|
#else
|
|
@SceneStorage("pageta.tab.section") private var rawSection: String = SidebarSection.liste.rawValue
|
|
|
|
// iOS-Tabs ohne `.feed` (dormant). Ein in SceneStorage
|
|
// persistierter `.feed`-Zustand (User war zuletzt im alten
|
|
// Feed-Tab) fällt auf `.liste` zurück — sonst landet die App
|
|
// auf einem nicht mehr existierenden Tab.
|
|
private static let iosTabs: [SidebarSection] = [.liste, .markierungen, .rueckblick, .konto]
|
|
|
|
private var selection: Binding<SidebarSection> {
|
|
Binding(
|
|
get: {
|
|
let saved = SidebarSection(rawValue: rawSection) ?? .liste
|
|
return Self.iosTabs.contains(saved) ? saved : .liste
|
|
},
|
|
set: { rawSection = $0.rawValue }
|
|
)
|
|
}
|
|
|
|
// Tab-Reihenfolge: Lese-Liste links (= default-Tab beim Cold-
|
|
// Launch), Markierungen, Konto rechts. Pageta ist primär eine
|
|
// Reader-App — die persönliche Liste ist die wichtigste Surface,
|
|
// also der erste Tab.
|
|
//
|
|
// Der Feed-Tab ist seit 2026-06-01 raus (Werte-Spannung zum
|
|
// Engagement-Feed-Verbot + commodity-Charakter). FeedTabView +
|
|
// PagetaAPI.fetchFeed bleiben dormant im Repo — Begründung und
|
|
// Revival-Bedingung in ../pageta/docs/FEED.md.
|
|
var body: some View {
|
|
TabView(selection: selection) {
|
|
LeseListeTabView()
|
|
.tag(SidebarSection.liste)
|
|
.tabItem { Label("Stapel", systemImage: "books.vertical") }
|
|
|
|
MarkierungenTabView()
|
|
.tag(SidebarSection.markierungen)
|
|
.tabItem { Label("Marker", systemImage: "highlighter") }
|
|
|
|
RueckblickTabView()
|
|
.tag(SidebarSection.rueckblick)
|
|
.tabItem { Label("Rückblick", systemImage: "clock.arrow.circlepath") }
|
|
|
|
AccountTabView()
|
|
.tag(SidebarSection.konto)
|
|
.tabItem { Label("Konto", systemImage: "person.crop.circle") }
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/// Top-Level-Surfaces der App. iOS rendert als TabView-Items,
|
|
/// macOS als Sidebar-Liste. `rawValue` ist persistent über
|
|
/// SceneStorage — User landet beim Re-Open im letzten Tab/Sidebar.
|
|
enum SidebarSection: String, CaseIterable, Identifiable, Hashable {
|
|
// Reihenfolge bestimmt sowohl macOS-Sidebar-Listing als auch
|
|
// impliziert die Tab-Reihenfolge auf iOS. `liste` ist bewusst
|
|
// zuerst — siehe Kommentar in RootView.body.
|
|
//
|
|
// `feed` ist seit 2026-06-01 dormant: der Case bleibt, damit
|
|
// FeedTabView weiter kompiliert und ein Wiedereinbau billig ist,
|
|
// wird aber weder von `iosTabs` noch von `macSidebar` gelistet.
|
|
// Siehe ../pageta/docs/FEED.md.
|
|
case liste
|
|
case markierungen
|
|
case rueckblick
|
|
case feed
|
|
case konto
|
|
|
|
var id: String { rawValue }
|
|
|
|
var label: String {
|
|
switch self {
|
|
case .liste: "Stapel"
|
|
case .markierungen: "Marker"
|
|
case .rueckblick: "Rückblick"
|
|
case .feed: "Feed"
|
|
case .konto: "Konto"
|
|
}
|
|
}
|
|
|
|
var icon: String {
|
|
switch self {
|
|
case .liste: "books.vertical"
|
|
case .markierungen: "highlighter"
|
|
case .rueckblick: "clock.arrow.circlepath"
|
|
case .feed: "newspaper"
|
|
case .konto: "person.crop.circle"
|
|
}
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
RootView()
|
|
}
|