Files
andros 340770861e Apply theme background colors to NoteListView, NoteDetailView, NoteEditorView
Each view now uses themeManager.current.background and secondaryBackground explicitly, matching the pattern in SetupView. preferredColorScheme alone only toggles system dark/light — specific hex colors require explicit background modifiers.
2026-05-26 11:49:01 +02:00

139 lines
4.7 KiB
Swift

import SwiftUI
struct NoteListView: View {
@EnvironmentObject var client: WebDAVClient
@State private var searchText = ""
@State private var themeManager = ThemeManager.shared
let onSettings: () -> Void
private var filteredNotes: [DenoteNote] {
guard !searchText.isEmpty else { return client.notes }
let q = searchText.lowercased()
return client.notes.filter {
$0.title.lowercased().contains(q) ||
$0.keywords.joined(separator: " ").lowercased().contains(q) ||
($0.content?.lowercased().contains(q) == true)
}
}
var body: some View {
NavigationStack {
VStack(spacing: 0) {
searchBar
if client.isLoading {
Spacer()
ProgressView("Loading notes...")
Spacer()
} else if let error = client.errorMessage {
errorView(message: error)
} else {
notesList
}
}
.background(themeManager.current.background)
.toolbarBackground(themeManager.current.secondaryBackground, for: .navigationBar)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button { onSettings() } label: {
Image(systemName: "gearshape")
}
}
if client.isPrefetching {
ToolbarItem(placement: .navigationBarTrailing) {
HStack(spacing: 6) {
ProgressView().scaleEffect(0.75)
Text("Indexing").font(.caption).foregroundStyle(.secondary)
}
}
}
}
}
.task { await client.loadNotes() }
}
private var searchBar: some View {
HStack(spacing: 8) {
Image(systemName: "magnifyingglass")
.foregroundStyle(.secondary)
TextField("Search notes", text: $searchText)
.autocorrectionDisabled()
if !searchText.isEmpty {
Button { searchText = "" } label: {
Image(systemName: "xmark.circle.fill")
.foregroundStyle(.tertiary)
}
}
}
.padding(.horizontal, 12)
.padding(.vertical, 9)
.background(themeManager.current.secondaryBackground)
.clipShape(RoundedRectangle(cornerRadius: 10))
.padding(.horizontal, 16)
.padding(.vertical, 8)
.background(themeManager.current.background)
}
private var notesList: some View {
List(filteredNotes) { note in
NavigationLink(destination: NoteDetailView(note: note)) {
NoteRowView(note: note)
}
}
.listStyle(.plain)
.scrollContentBackground(.hidden)
.background(themeManager.current.background)
.refreshable { await client.loadNotes() }
.overlay {
if !searchText.isEmpty && filteredNotes.isEmpty {
ContentUnavailableView.search(text: searchText)
}
}
}
private func errorView(message: String) -> some View {
VStack(spacing: 16) {
Image(systemName: "wifi.exclamationmark")
.font(.system(size: 48))
.foregroundStyle(.secondary)
Text(message)
.multilineTextAlignment(.center)
.foregroundStyle(.secondary)
Button("Retry") { Task { await client.loadNotes() } }
.buttonStyle(.bordered)
}
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
struct NoteRowView: View {
let note: DenoteNote
var body: some View {
VStack(alignment: .leading, spacing: 6) {
Text(note.displayTitle)
.font(.headline)
.lineLimit(2)
if !note.keywords.isEmpty {
HStack(spacing: 4) {
ForEach(note.keywords, id: \.self) { keyword in
Text(keyword)
.font(.caption2)
.padding(.horizontal, 6)
.padding(.vertical, 2)
.background(Color.accentColor.opacity(0.15))
.foregroundStyle(Color.accentColor)
.clipShape(Capsule())
}
}
}
if !note.formattedDate.isEmpty {
Text(note.formattedDate)
.font(.caption)
.foregroundStyle(.secondary)
}
}
.padding(.vertical, 2)
}
}