This commit is contained in:
2026-03-21 09:49:01 +01:00
parent 677b927edf
commit 57d0d1092e
29 changed files with 1298 additions and 404 deletions
+143
View File
@@ -6,12 +6,20 @@ struct SettingsView: View {
@AppStorage("syncWiFiOnly") private var syncWiFiOnly = true
@AppStorage("showComments") private var showComments = true
@AppStorage("appTheme") private var appTheme = "system"
@AppStorage("accentTheme") private var accentThemeRaw = AccentTheme.ocean.rawValue
@AppStorage("loggingEnabled") private var loggingEnabled = false
private var selectedTheme: AccentTheme {
AccentTheme(rawValue: accentThemeRaw) ?? .ocean
}
@State private var serverURL = UserDefaults.standard.string(forKey: "serverURL") ?? ""
@State private var showSignOutAlert = false
@State private var isSyncing = false
@State private var lastSynced = UserDefaults.standard.object(forKey: "lastSynced") as? Date
@State private var showSafari: URL? = nil
@State private var selectedLanguage: LanguageManager.Language = LanguageManager.shared.current
@State private var showLogViewer = false
@State private var shareItems: [Any]? = nil
private let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0"
private let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "1"
@@ -50,6 +58,39 @@ struct SettingsView: View {
Text(L("settings.appearance.theme.dark")).tag("dark")
}
.pickerStyle(.segmented)
// Accent colour swatches
VStack(alignment: .leading, spacing: 10) {
Text(L("settings.appearance.accent"))
.font(.subheadline)
.foregroundStyle(.secondary)
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 10), count: 8), spacing: 10) {
ForEach(AccentTheme.allCases) { theme in
Button {
accentThemeRaw = theme.rawValue
} label: {
ZStack {
Circle()
.fill(theme.shelfColor)
.frame(width: 32, height: 32)
if selectedTheme == theme {
Circle()
.strokeBorder(.white, lineWidth: 2)
.frame(width: 32, height: 32)
Image(systemName: "checkmark")
.font(.system(size: 11, weight: .bold))
.foregroundStyle(.white)
}
}
}
.buttonStyle(.plain)
.accessibilityLabel(theme.displayName)
.accessibilityAddTraits(selectedTheme == theme ? .isSelected : [])
}
}
}
.padding(.vertical, 4)
}
// Account section
@@ -87,6 +128,35 @@ struct SettingsView: View {
Toggle(L("settings.reader.showcomments"), isOn: $showComments)
}
// Logging section
Section(L("settings.log")) {
Toggle(L("settings.log.enabled"), isOn: $loggingEnabled)
.onChange(of: loggingEnabled) { _, newValue in
LogManager.shared.isEnabled = newValue
}
if loggingEnabled {
Button {
showLogViewer = true
} label: {
Label(L("settings.log.viewer.title"), systemImage: "list.bullet.rectangle")
}
Button {
let text = LogManager.shared.exportText()
shareItems = [text]
} label: {
Label(L("settings.log.share"), systemImage: "square.and.arrow.up")
}
Button(role: .destructive) {
LogManager.shared.clear()
} label: {
Label(L("settings.log.clear"), systemImage: "trash")
}
}
}
// Sync section
Section(L("settings.sync")) {
Toggle(L("settings.sync.wifionly"), isOn: $syncWiFiOnly)
@@ -134,6 +204,9 @@ struct SettingsView: View {
}
}
.navigationTitle(L("settings.title"))
.onAppear {
loggingEnabled = LogManager.shared.isEnabled
}
.alert(L("settings.signout.alert.title"), isPresented: $showSignOutAlert) {
Button(L("settings.signout.alert.confirm"), role: .destructive) { signOut() }
Button(L("settings.signout.alert.cancel"), role: .cancel) {}
@@ -144,6 +217,17 @@ struct SettingsView: View {
SafariView(url: url)
.ignoresSafeArea()
}
.sheet(isPresented: $showLogViewer) {
LogViewerView()
}
.sheet(isPresented: Binding(
get: { shareItems != nil },
set: { if !$0 { shareItems = nil } }
)) {
if let items = shareItems {
ShareSheet(items: items)
}
}
}
}
@@ -186,6 +270,65 @@ extension URL: @retroactive Identifiable {
public var id: String { absoluteString }
}
// MARK: - Log Viewer
struct LogViewerView: View {
@Environment(\.dismiss) private var dismiss
@State private var logManager = LogManager.shared
var body: some View {
NavigationStack {
Group {
if logManager.entries.isEmpty {
ContentUnavailableView(
L("settings.log.viewer.title"),
systemImage: "list.bullet.rectangle",
description: Text("No log entries yet.")
)
} else {
List(logManager.entries.reversed()) { entry in
VStack(alignment: .leading, spacing: 2) {
Text(entry.formatted)
.font(.system(.caption, design: .monospaced))
.foregroundStyle(colorFor(entry.level))
}
.listRowInsets(.init(top: 4, leading: 12, bottom: 4, trailing: 12))
}
.listStyle(.plain)
}
}
.navigationTitle(L("settings.log.viewer.title"))
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button(L("common.ok")) { dismiss() }
}
}
}
}
private func colorFor(_ level: LogEntry.Level) -> Color {
switch level {
case .debug: return .secondary
case .info: return .primary
case .warning: return .orange
case .error: return .red
}
}
}
// MARK: - Share Sheet
struct ShareSheet: UIViewControllerRepresentable {
let items: [Any]
func makeUIViewController(context: Context) -> UIActivityViewController {
UIActivityViewController(activityItems: items, applicationActivities: nil)
}
func updateUIViewController(_ vc: UIActivityViewController, context: Context) {}
}
#Preview {
SettingsView()
}