diff --git a/OpenHABCore/Sources/OpenHABCore/Util/Logger.swift b/OpenHABCore/Sources/OpenHABCore/Util/Logger.swift new file mode 100644 index 00000000..c178f315 --- /dev/null +++ b/OpenHABCore/Sources/OpenHABCore/Util/Logger.swift @@ -0,0 +1,115 @@ +// Copyright (c) 2010-2024 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import CoreTransferable +import OSLog + +// Thanks to https://useyourloaf.com/blog/fetching-oslog-messages-in-swift/ + +// swiftlint:disable:next file_types_order +private extension OSLogEntryLog.Level { + var description: String { + switch self { + case .undefined: "undefined" + case .debug: "debug" + case .info: "info" + case .notice: "notice" + case .error: "error" + case .fault: "fault" + @unknown default: "default" + } + } +} + +public extension Logger { + static func fetch(since date: Date, + predicateFormat: String) async throws -> [String] { + let store = try OSLogStore(scope: .currentProcessIdentifier) + let position = store.position(date: date) + let predicate = NSPredicate(format: predicateFormat) + + let entries = try store + .getEntries( + at: position, + matching: predicate + ) + + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + + var logs: [String] = [] + for entry in entries { + try Task.checkCancellation() + if let log = entry as? OSLogEntryLog { + var attributedMessage = AttributedString(dateFormatter.string(from: entry.date)) + attributedMessage.font = .headline + + logs.append(""" + \(entry.date.formatted(.iso8601)): \ + \(log.category):\(log.level.description): \ + \(entry.composedMessage)\n + """) + } else { + logs.append("\(entry.date): \(entry.composedMessage)\n") + } + } + + if logs.isEmpty { logs = ["Nothing found"] } + return logs + } +} + +public protocol LogServiceProtocol { + func fetchLogs(with template: NSPredicate) async -> String +} + +public struct LogService { + public init() {} +} + +extension LogService: LogServiceProtocol { + public func fetchLogs(with template: NSPredicate) async -> String { + let calendar = Calendar.current + guard let hourAgo = calendar.date( + byAdding: .hour, + value: -1, + to: Date.now + ) else { + return "Invalid calendar" + } + + do { + let predicate = template.withSubstitutionVariables( + [ + "PREFIX": "org.openhab" + ]) + + let logs = try await Logger.fetch( + since: hourAgo, + predicateFormat: predicate.predicateFormat + ) + return logs.joined() + } catch { + return error.localizedDescription + } + } +} + +// extension LogService: Transferable { +// +// static var containerUrl = FileManager.default.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("Documents", isDirectory: true) +// +// public static var transferRepresentation: some TransferRepresentation { +// FileRepresentation(exportedContentType: .commaSeparatedText) { csvFile in +// SentTransferredFile(csvFile.url) +// } +// } +// } diff --git a/openHAB.xcodeproj/project.pbxproj b/openHAB.xcodeproj/project.pbxproj index 3fc9f5af..6f1dcbed 100644 --- a/openHAB.xcodeproj/project.pbxproj +++ b/openHAB.xcodeproj/project.pbxproj @@ -80,6 +80,7 @@ DA0F37D023D4ACC7007EAB48 /* SliderRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA0F37CF23D4ACC7007EAB48 /* SliderRow.swift */; }; DA15BFBD23C6726400BD8ADA /* ObservableOpenHABDataObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA15BFBC23C6726400BD8ADA /* ObservableOpenHABDataObject.swift */; }; DA162BEC2CD3B53E0040DAE5 /* LogsViewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA162BEB2CD3B53E0040DAE5 /* LogsViewer.swift */; }; + DA162BF02CD4CC730040DAE5 /* LogsViewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA162BEF2CD4CC730040DAE5 /* LogsViewer.swift */; }; DA19E25B22FD801D002F8F2F /* OpenHABGeneralTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA19E25A22FD801D002F8F2F /* OpenHABGeneralTests.swift */; }; DA21EAE22339621C001AB415 /* Throttler.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA21EAE12339621C001AB415 /* Throttler.swift */; }; DA242C622C83588600AFB10D /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA242C612C83588600AFB10D /* SettingsView.swift */; }; @@ -110,13 +111,14 @@ DA7E1E4B2233986E002AEFD8 /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA7E1E47222EB00B002AEFD8 /* PlayerView.swift */; }; DA817E7A234BF39B00C91824 /* CHANGELOG.md in Resources */ = {isa = PBXBuildFile; fileRef = DA817E79234BF39B00C91824 /* CHANGELOG.md */; }; DA88F8C622EC377200B408E5 /* ReleaseNotes.md in Resources */ = {isa = PBXBuildFile; fileRef = DA88F8C522EC377100B408E5 /* ReleaseNotes.md */; }; + DA8F986B2CDA4CAA001F5E8A /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA8F986A2CDA4CAA001F5E8A /* ContentView.swift */; }; DA9721C324E29A8F0092CCFD /* UserDefaultsBacked.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9721C224E29A8F0092CCFD /* UserDefaultsBacked.swift */; }; DA9F81872C85020F00B47B72 /* RTFTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9F81862C85020F00B47B72 /* RTFTextView.swift */; }; DAA070932B5181210060BB0E /* OpenHABImageDownloaderOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAA070922B5181210060BB0E /* OpenHABImageDownloaderOperation.swift */; }; DAA42BA821DC97E000244B2A /* NotificationTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAA42BA721DC97DF00244B2A /* NotificationTableViewCell.swift */; }; DAA42BAA21DC983B00244B2A /* VideoUITableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAA42BA921DC983B00244B2A /* VideoUITableViewCell.swift */; }; DAA42BAC21DC984A00244B2A /* WebUITableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAA42BAB21DC984A00244B2A /* WebUITableViewCell.swift */; }; - DAAC30872CBBF0420041927F /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA0775262346705F0086C685 /* ContentView.swift */; }; + DAAC30872CBBF0420041927F /* SitemapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA0775262346705F0086C685 /* SitemapView.swift */; }; DAC65FC7236EDF3900F4501E /* SpinnerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC65FC6236EDF3900F4501E /* SpinnerViewController.swift */; }; DAC6608D236F771600F4501E /* PreferencesSwiftUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC6608C236F771600F4501E /* PreferencesSwiftUIView.swift */; }; DAC9395522B00E7600C5F423 /* XCTestCaseExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC9395422B00E7600C5F423 /* XCTestCaseExtension.swift */; }; @@ -336,7 +338,7 @@ DA0775152346705D0086C685 /* openHABWatch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = openHABWatch.app; sourceTree = BUILT_PRODUCTS_DIR; }; DA07751A2346705F0086C685 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; DA07751C2346705F0086C685 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - DA0775262346705F0086C685 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + DA0775262346705F0086C685 /* SitemapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SitemapView.swift; sourceTree = ""; }; DA07752A2346705F0086C685 /* OpenHABWatchAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenHABWatchAppDelegate.swift; sourceTree = ""; }; DA07752C2346705F0086C685 /* NotificationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationController.swift; sourceTree = ""; }; DA07752E2346705F0086C685 /* NotificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationView.swift; sourceTree = ""; }; @@ -348,6 +350,7 @@ DA0F37CF23D4ACC7007EAB48 /* SliderRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SliderRow.swift; sourceTree = ""; }; DA15BFBC23C6726400BD8ADA /* ObservableOpenHABDataObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservableOpenHABDataObject.swift; sourceTree = ""; }; DA162BEB2CD3B53E0040DAE5 /* LogsViewer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogsViewer.swift; sourceTree = ""; }; + DA162BEF2CD4CC730040DAE5 /* LogsViewer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogsViewer.swift; sourceTree = ""; }; DA19E25A22FD801D002F8F2F /* OpenHABGeneralTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenHABGeneralTests.swift; sourceTree = ""; }; DA1C2E4B230DC28F00FACFB0 /* Appfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Appfile; sourceTree = ""; }; DA1C2E4C230DC28F00FACFB0 /* SnapshotHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnapshotHelper.swift; sourceTree = ""; }; @@ -408,6 +411,7 @@ DA7E1E47222EB00B002AEFD8 /* PlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerView.swift; sourceTree = ""; }; DA817E79234BF39B00C91824 /* CHANGELOG.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; DA88F8C522EC377100B408E5 /* ReleaseNotes.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = ReleaseNotes.md; sourceTree = ""; }; + DA8F986A2CDA4CAA001F5E8A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; DA9721C224E29A8F0092CCFD /* UserDefaultsBacked.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsBacked.swift; sourceTree = ""; }; DA9F81862C85020F00B47B72 /* RTFTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RTFTextView.swift; sourceTree = ""; }; DAA070922B5181210060BB0E /* OpenHABImageDownloaderOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenHABImageDownloaderOperation.swift; sourceTree = ""; }; @@ -666,6 +670,7 @@ isa = PBXGroup; children = ( DAD0855F2AE47824001D36BE /* OpenHABWatch.swift */, + DA8F986A2CDA4CAA001F5E8A /* ContentView.swift */, DA0775252346705F0086C685 /* Extension */, 1224F7C9228A8ED100750965 /* External */, 1224F7C8228A8EC600750965 /* Domain */, @@ -776,7 +781,7 @@ DA658720236F841F007E2E7F /* Views */ = { isa = PBXGroup; children = ( - DA0775262346705F0086C685 /* ContentView.swift */, + DA0775262346705F0086C685 /* SitemapView.swift */, DA162BEB2CD3B53E0040DAE5 /* LogsViewer.swift */, DAC6608C236F771600F4501E /* PreferencesSwiftUIView.swift */, DAF457A323DB7A820018B495 /* Rows */, @@ -871,6 +876,7 @@ 653B54BF285C0AC700298ECD /* OpenHABRootViewController.swift */, 65570A7C2476D16A00D524EA /* OpenHABWebViewController.swift */, DFB2624318830A3600D3244D /* OpenHABSitemapViewController.swift */, + DA162BEF2CD4CC730040DAE5 /* LogsViewer.swift */, DAC65FC6236EDF3900F4501E /* SpinnerViewController.swift */, DA6B2EF62C8B92E800DF77CF /* SelectionView.swift */, DA242C612C83588600AFB10D /* SettingsView.swift */, @@ -1093,6 +1099,7 @@ 39C91164B60A5677322E8DE2 /* Frameworks */, DA07751D2346705F0086C685 /* Sources */, 93F38C61238034AE001B1451 /* Embed Frameworks */, + DA162BEE2CD3EDFE0040DAE5 /* ShellScript */, ); buildRules = ( ); @@ -1397,6 +1404,23 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + DA162BEE2CD3EDFE0040DAE5 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "cd BuildTools\nSDKROOT=(xcrun --sdk macosx --show-sdk-path)\n\nswift package plugin --allow-writing-to-package-directory --allow-writing-to-directory \"$SRCROOT\" swiftformat \"$SRCROOT\" --config ./.swiftformat --cache /private/tmp/\nswift package plugin --allow-writing-to-package-directory --allow-writing-to-directory ../ swiftlint --cache-path /private/tmp/\n# Type a script or drag a script file from your workspace to insert its path.\n"; + }; DAF0A2902C56FE9F00A14A6A /* Run swiftformat & swiftlint */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -1480,8 +1504,9 @@ 9350F17A23814FAC00054BA8 /* ObservableOpenHABSitemapPage.swift in Sources */, DAF457A023DA3E1C0018B495 /* SegmentRow.swift in Sources */, DAF4578923D79AA50018B495 /* DetailTextLabelView.swift in Sources */, - DAAC30872CBBF0420041927F /* ContentView.swift in Sources */, + DAAC30872CBBF0420041927F /* SitemapView.swift in Sources */, DA15BFBD23C6726400BD8ADA /* ObservableOpenHABDataObject.swift in Sources */, + DA8F986B2CDA4CAA001F5E8A /* ContentView.swift in Sources */, DAC9AF4924F966FA006DAE93 /* LazyView.swift in Sources */, DA0776F0234788010086C685 /* UserData.swift in Sources */, DAC6608D236F771600F4501E /* PreferencesSwiftUIView.swift in Sources */, @@ -1540,6 +1565,7 @@ 1224F78F228A89FD00750965 /* WatchMessageService.swift in Sources */, DAA42BAC21DC984A00244B2A /* WebUITableViewCell.swift in Sources */, DF4B84131886DAC400F34902 /* FrameUITableViewCell.swift in Sources */, + DA162BF02CD4CC730040DAE5 /* LogsViewer.swift in Sources */, DF4B84161886EACA00F34902 /* GenericUITableViewCell.swift in Sources */, 935B484625342B8E00E44CF0 /* URL+Static.swift in Sources */, B7D5ECE121499E55001B0EC6 /* MapViewTableViewCell.swift in Sources */, diff --git a/openHAB/LogsViewer.swift b/openHAB/LogsViewer.swift new file mode 100644 index 00000000..04ba49c1 --- /dev/null +++ b/openHAB/LogsViewer.swift @@ -0,0 +1,87 @@ +// Copyright (c) 2010-2024 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +// import SwiftUI +// +// struct LogView: View { +// let logs: [OSLogEntryLog] +// +// var body: some View { +// List(logs, id: \.self) { log in +// VStack(alignment: .leading) { +// Text(log.composedMessage) +// HStack { +// Text(log.subsystem) +// Text(log.date, format: .dateTime) +// }.bold() +// } +// } +// } +// } +// +// #Preview { +// LogView(logs: .init([])) +// } + +import Foundation +import OpenHABCore +import OSLog +import SwiftUI + +// swiftlint:disable:next file_types_order +struct LogsViewer: View { + let template = NSPredicate( + format: "(subsystem BEGINSWITH $PREFIX)" + ) + let myFont = Font + .system(size: 10) + .monospaced() + + var logService: LogServiceProtocol + + var body: some View { + List { + Text(text) + .font(myFont) + } + .toolbar { + ToolbarItem(placement: .primaryAction) { + ShareLink( + item: text, + preview: SharePreview("Logs", image: Image(.openHABIcon)) + ) { + Label("Share Logs", systemSymbol: .squareAndArrowUp) + } + } + } + .navigationTitle("Logs") + .task { + text = await logService.fetchLogs(with: template) + } + } + + @State private var text = "Loading..." +} + +#if DEBUG +struct MockLogService: LogServiceProtocol { + func fetchLogs(with template: NSPredicate) async -> String { + """ + Mocked Data + Test data + """ + } +} +#endif + +#Preview { + LogsViewer(logService: MockLogService()) +} diff --git a/openHAB/SettingsView.swift b/openHAB/SettingsView.swift index cd728014..5adf27cb 100644 --- a/openHAB/SettingsView.swift +++ b/openHAB/SettingsView.swift @@ -43,6 +43,8 @@ struct SettingsView: View { @State private var sitemaps: [OpenHABSitemap] = [] + @State private var exportShown = false + @Environment(\.dismiss) private var dismiss var appData: OpenHABDataObject? { @@ -287,6 +289,14 @@ struct SettingsView: View { } } + Section(header: Text(LocalizedStringKey("debug"))) { + NavigationLink { + LogsViewer(logService: LogService()) + } label: { + Text("Logs") + } + } + Section(header: Text(LocalizedStringKey("about_settings"))) { LabeledContent("App Version", value: appVersion) diff --git a/openHABWatch/ContentView.swift b/openHABWatch/ContentView.swift new file mode 100644 index 00000000..ffb693f0 --- /dev/null +++ b/openHABWatch/ContentView.swift @@ -0,0 +1,48 @@ +// Copyright (c) 2010-2024 Contributors to the openHAB project +// +// See the NOTICE file(s) distributed with this work for additional +// information. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 +// +// SPDX-License-Identifier: EPL-2.0 + +import OpenHABCore +import SwiftUI + +struct ContentView: View { + @ObservedObject var viewModel: UserData + @EnvironmentObject var settings: ObservableOpenHABDataObject + @State var title = "openHAB" + + var body: some View { + TabView { + NavigationStack { + SitemapView(viewModel: viewModel) + } + .tabItem { + Label("Sitemap", systemSymbol: .circleFill) + } + NavigationStack { + PreferencesSwiftUIView() + } + .tabItem { + Label("Preferences", systemSymbol: .circleFill) + } + NavigationStack { + LogsViewer(logService: LogService()) + } + .tabItem { + Label("Debug", systemSymbol: .circleFill) + } + } + .tabViewStyle(.page) + } +} + +#Preview { + ContentView(viewModel: .init()) + .environmentObject(ObservableOpenHABDataObject()) +} diff --git a/openHABWatch/OpenHABWatch.swift b/openHABWatch/OpenHABWatch.swift index 0aaf26ed..8fb89e2d 100644 --- a/openHABWatch/OpenHABWatch.swift +++ b/openHABWatch/OpenHABWatch.swift @@ -9,6 +9,7 @@ // // SPDX-License-Identifier: EPL-2.0 +import OpenHABCore import SDWebImage import SDWebImageSVGCoder import SwiftUI @@ -23,28 +24,14 @@ struct OpenHABWatch: App { var body: some Scene { WindowGroup { - TabView { - ContentView(viewModel: userData) - .tabItem { - Label("Sitemap", systemSymbol: .circleFill) - } - PreferencesSwiftUIView() - .tabItem { - Label("Preferences", systemSymbol: .circleFill) - } - LogsViewer() - .tabItem { - Label("Debug", systemSymbol: .circleFill) - } - } - .tabViewStyle(.page) - .environmentObject(settings) - .task { - let center = UNUserNotificationCenter.current() - _ = try? await center.requestAuthorization( - options: [.alert, .sound, .badge] - ) - } + ContentView(viewModel: userData) + .environmentObject(settings) + .task { + let center = UNUserNotificationCenter.current() + _ = try? await center.requestAuthorization( + options: [.alert, .sound, .badge] + ) + } } WKNotificationScene(controller: NotificationController.self, category: "openHABNotification") } diff --git a/openHABWatch/Views/LogsViewer.swift b/openHABWatch/Views/LogsViewer.swift index d4ac8cbc..36b5bed5 100644 --- a/openHABWatch/Views/LogsViewer.swift +++ b/openHABWatch/Views/LogsViewer.swift @@ -1,111 +1,62 @@ +// Copyright (c) 2010-2024 Contributors to the openHAB project // -// LogView.swift -// openHABWatch +// See the NOTICE file(s) distributed with this work for additional +// information. // -// Created by Tim Müller-Seydlitz on 31.10.24. -// Copyright © 2024 openHAB e.V. All rights reserved. +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0 // +// SPDX-License-Identifier: EPL-2.0 import Foundation +import OpenHABCore import OSLog import SwiftUI // Thanks to https://useyourloaf.com/blog/fetching-oslog-messages-in-swift/ -extension OSLogEntryLog.Level { - fileprivate var description: String { - switch self { - case .undefined: "undefined" - case .debug: "debug" - case .info: "info" - case .notice: "notice" - case .error: "error" - case .fault: "fault" - @unknown default: "default" - } - } -} - -extension Logger { - static public func fetch(since date: Date, - predicateFormat: String) async throws -> [String] { - let store = try OSLogStore(scope: .currentProcessIdentifier) - let position = store.position(date: date) - let predicate = NSPredicate(format: predicateFormat) - let entries = try store.getEntries( - at: position, - matching: predicate - ) - - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" - - var logs: [String] = [] - for entry in entries { - try Task.checkCancellation() - if let log = entry as? OSLogEntryLog { - var attributedMessage = AttributedString(dateFormatter.string(from: entry.date)) - attributedMessage.font = .headline - - logs.append(""" - \(dateFormatter.string(from: entry.date)): \ - \(log.category):\(log.level.description): \ - \(entry.composedMessage)\n - """) - } else { - logs.append("\(entry.date): \(entry.composedMessage)\n") - } - } - - if logs.isEmpty { logs = ["Nothing found"] } - return logs - } -} - +// swiftlint:disable:next file_types_order struct LogsViewer: View { - @State private var text = "Loading..." - - static private let template = NSPredicate(format: - "(subsystem BEGINSWITH $PREFIX)") - + let template = NSPredicate( + format: "(subsystem BEGINSWITH $PREFIX)" + ) let myFont = Font - .system(size: 10) - .monospaced() - - private func fetchLogs() async -> String { - let calendar = Calendar.current - guard let dayAgo = calendar.date(byAdding: .day, - value: -1, to: Date.now) else { - return "Invalid calendar" - } - - do { - let predicate = Self.template.withSubstitutionVariables( - [ - "PREFIX": "org.openhab" - ]) + .system(size: 10) + .monospaced() - let logs = try await Logger.fetch(since: dayAgo, - predicateFormat: predicate.predicateFormat) - return logs.joined() - } catch { - return error.localizedDescription - } - } + var logService: LogServiceProtocol var body: some View { - - ScrollView { - Text(text) - .font(myFont) - .padding() + List { + Text(text) + .font(myFont) } + .toolbar { + ToolbarItem(placement: .primaryAction) { + ShareLink( + item: text, + preview: SharePreview("Logs") + ) { + Label("Share Logs", systemSymbol: .squareAndArrowUp) + } + } + } + .navigationTitle("Logs") .task { - text = await fetchLogs() + text = await logService.fetchLogs(with: template) } } + + @State private var text = "Loading..." +} + +struct MockLogService: LogServiceProtocol { + func fetchLogs(with template: NSPredicate) async -> String { + "Mocked Data" + } } #Preview { - LogsViewer() + LogsViewer(logService: MockLogService()) } diff --git a/openHABWatch/Views/PreferencesSwiftUIView.swift b/openHABWatch/Views/PreferencesSwiftUIView.swift index 2629d9e9..9cd020a3 100644 --- a/openHABWatch/Views/PreferencesSwiftUIView.swift +++ b/openHABWatch/Views/PreferencesSwiftUIView.swift @@ -32,11 +32,14 @@ struct PreferencesSwiftUIView: View { LabeledContent(LocalizedStringKey("username"), value: settings.openHABUsername) LabeledContent(LocalizedStringKey("version"), value: applicationVersionNumber) } - - Button { AppMessageService.singleton.requestApplicationContext() - } label: { Label("sync_prefs", systemSymbol: .arrowTriangle2Circlepath) + .toolbar { + ToolbarItem(placement: .primaryAction) { + Button { AppMessageService.singleton.requestApplicationContext() + } label: { Label("sync_prefs", systemSymbol: .arrowTriangle2Circlepath) + } + } } - .buttonStyle(.borderedProminent) + .navigationTitle("Preferences") } } diff --git a/openHABWatch/Views/ContentView.swift b/openHABWatch/Views/SitemapView.swift similarity index 86% rename from openHABWatch/Views/ContentView.swift rename to openHABWatch/Views/SitemapView.swift index 96b4565f..e6899794 100644 --- a/openHABWatch/Views/ContentView.swift +++ b/openHABWatch/Views/SitemapView.swift @@ -13,7 +13,7 @@ import OpenHABCore import os.log import SwiftUI -struct ContentView: View { +struct SitemapView: View { @ObservedObject var viewModel: UserData @EnvironmentObject var settings: ObservableOpenHABDataObject @State var title = "openHAB" @@ -21,17 +21,16 @@ struct ContentView: View { var body: some View { ZStack { ScrollView { - HStack { - Text(viewModel.openHABSitemapPage?.title ?? "Sitemap without title") - .font(.body) - .lineLimit(1) - Spacer() - } ForEach(viewModel.widgets) { widget in rowWidget(widget: widget) } + + if viewModel.widgets.isEmpty { + Text("No sitemap to show") + .foregroundStyle(.gray) + } } - .navigationBarTitle(Text(title)) + .navigationTitle(Text(title)) .actionSheet(isPresented: $viewModel.showCertificateAlert) { ActionSheet( title: Text(NSLocalizedString("warning", comment: "")), @@ -111,15 +110,18 @@ struct ContentView: View { } #Preview { - Group { - ContentView(viewModel: UserData()) + SitemapView(viewModel: UserData()) - .environmentObject({ () -> UserData in - let envObj = UserData() - return envObj - }()) + .environmentObject({ () -> UserData in + let envObj = UserData() + return envObj + }()) - ContentView(viewModel: UserData()) - } - .environmentObject(ObservableOpenHABDataObject()) + .environmentObject(ObservableOpenHABDataObject()) +} + +#Preview { + SitemapView(viewModel: UserData()) + + .environmentObject(ObservableOpenHABDataObject()) }