From b275dbd7c02b2bcb2b6fff97a7c22c928cb7188f Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Tue, 21 Apr 2026 03:31:11 +0200 Subject: [PATCH] Polish Log Viewer for macOS Convert detail and filter sheets to shared helpers, add inline Filter / Export / Clear buttons next to the search bar (toolbar items weren't surfacing in the settings detail pane), inline the Reset Filters button at the bottom of the filter sheet, use a 'Close' text button, and trim the macOS Share Sheet to just the scrollable log with a Copy button. --- Yattee/Views/Components/ShareSheet.swift | 11 ++-- Yattee/Views/Settings/LogViewerView.swift | 80 +++++++++++++++++++---- 2 files changed, 71 insertions(+), 20 deletions(-) diff --git a/Yattee/Views/Components/ShareSheet.swift b/Yattee/Views/Components/ShareSheet.swift index 67470c1a..eae76102 100644 --- a/Yattee/Views/Components/ShareSheet.swift +++ b/Yattee/Views/Components/ShareSheet.swift @@ -22,16 +22,14 @@ struct ShareSheet: View { let items: [Any] var body: some View { - VStack { - Text(String(localized: "settings.advanced.logs.export.instructions")) - .padding() - + VStack(spacing: 12) { if let text = items.first as? String { ScrollView { Text(text) .font(.caption) .fontDesign(.monospaced) .textSelection(.enabled) + .frame(maxWidth: .infinity, alignment: .leading) .padding() } .frame(maxHeight: 400) @@ -43,9 +41,10 @@ struct ShareSheet: View { NSPasteboard.general.setString(text, forType: .string) } } - .padding() + .keyboardShortcut(.defaultAction) } - .frame(minWidth: 400, minHeight: 300) + .padding() + .frame(minWidth: 420, minHeight: 320) } } #endif diff --git a/Yattee/Views/Settings/LogViewerView.swift b/Yattee/Views/Settings/LogViewerView.swift index 34ed6709..2e72ed74 100644 --- a/Yattee/Views/Settings/LogViewerView.swift +++ b/Yattee/Views/Settings/LogViewerView.swift @@ -19,8 +19,38 @@ struct LogViewerView: View { var body: some View { VStack(spacing: 0) { - // Search bar + #if os(macOS) + HStack(spacing: 8) { + searchBar + + Button { + showingFilters = true + } label: { + Label(String(localized: "settings.advanced.logs.filter"), systemImage: "line.3.horizontal.decrease.circle") + .labelStyle(.iconOnly) + } + .help(String(localized: "settings.advanced.logs.filter")) + + Button { + showingExportSheet = true + } label: { + Label(String(localized: "settings.advanced.logs.export"), systemImage: "square.and.arrow.up") + .labelStyle(.iconOnly) + } + .help(String(localized: "settings.advanced.logs.export")) + + Button(role: .destructive) { + loggingService.clearLogs() + } label: { + Label(String(localized: "settings.advanced.logs.clear"), systemImage: "trash") + .labelStyle(.iconOnly) + } + .help(String(localized: "settings.advanced.logs.clear")) + } + .padding(.trailing) + #else searchBar + #endif // Log list logList @@ -192,13 +222,18 @@ private struct LogEntryDetailView: View { var body: some View { NavigationStack { - List { - Section(String(localized: "settings.advanced.logs.detail.info")) { - LabeledContent(String(localized: "settings.advanced.logs.detail.timestamp")) { + SettingsFormContainer { + SettingsFormSection("settings.advanced.logs.detail.info") { + HStack { + Text(String(localized: "settings.advanced.logs.detail.timestamp")) + Spacer() Text(entry.timestamp.formatted(date: .abbreviated, time: .standard)) + .foregroundStyle(.secondary) } - LabeledContent(String(localized: "settings.advanced.logs.detail.level")) { + HStack { + Text(String(localized: "settings.advanced.logs.detail.level")) + Spacer() HStack { Image(systemName: entry.level.icon) Text(entry.level.rawValue.capitalized) @@ -206,27 +241,32 @@ private struct LogEntryDetailView: View { .foregroundStyle(levelColor) } - LabeledContent(String(localized: "settings.advanced.logs.detail.category")) { + HStack { + Text(String(localized: "settings.advanced.logs.detail.category")) + Spacer() HStack { Image(systemName: entry.category.icon) Text(entry.category.rawValue) } + .foregroundStyle(.secondary) } } - Section(String(localized: "settings.advanced.logs.detail.message")) { + SettingsFormSection("settings.advanced.logs.detail.message") { Text(entry.message) .font(.body) + .frame(maxWidth: .infinity, alignment: .leading) #if !os(tvOS) .textSelection(.enabled) #endif } if let details = entry.details { - Section(String(localized: "settings.advanced.logs.detail.details")) { + SettingsFormSection("settings.advanced.logs.detail.details") { Text(details) .font(.caption) .fontDesign(.monospaced) + .frame(maxWidth: .infinity, alignment: .leading) #if !os(tvOS) .textSelection(.enabled) #endif @@ -249,6 +289,9 @@ private struct LogEntryDetailView: View { } } .presentationDetents([.medium, .large]) + #if os(macOS) + .frame(minWidth: 500, minHeight: 400) + #endif } private var levelColor: Color { @@ -269,28 +312,38 @@ private struct LogFiltersSheet: View { var body: some View { NavigationStack { - List { - Section(String(localized: "settings.advanced.logs.filter.categories")) { + SettingsFormContainer { + SettingsFormSection("settings.advanced.logs.filter.categories") { ForEach(LogCategory.allCases, id: \.self) { category in Toggle(isOn: binding(for: category)) { Label(category.rawValue, systemImage: category.icon) } } } + #if os(macOS) + .labelStyle(FixedIconWidthLabelStyle()) + #endif - Section(String(localized: "settings.advanced.logs.filter.levels")) { + SettingsFormSection("settings.advanced.logs.filter.levels") { ForEach(LogLevel.allCases, id: \.self) { level in Toggle(isOn: binding(for: level)) { Label(level.rawValue.capitalized, systemImage: level.icon) } } } + #if os(macOS) + .labelStyle(FixedIconWidthLabelStyle()) + #endif - Section { + HStack { Button(String(localized: "settings.advanced.logs.filter.reset")) { loggingService.resetFilters() } + Spacer() } + .padding(.horizontal, 16) + .padding(.top, 4) + .padding(.bottom, 16) } .navigationTitle(String(localized: "settings.advanced.logs.filter.title")) #if os(iOS) @@ -301,8 +354,7 @@ private struct LogFiltersSheet: View { Button(role: .cancel) { dismiss() } label: { - Label(String(localized: "common.close"), systemImage: "xmark") - .labelStyle(.iconOnly) + Text(String(localized: "common.close")) } } }