Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions CommonUI/Sources/CommonUI/LogsViewer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public struct LogsViewer: View {
"(subsystem BEGINSWITH $PREFIX)")

@State private var text = String(localized: "Loading…")
@State private var exportURL: URL?

let myFont = Font
.system(size: 10)
Expand All @@ -31,8 +32,20 @@ public struct LogsViewer: View {
.font(myFont)
.padding()
}
.navigationTitle("Logs")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
if let exportURL {
ShareLink(item: exportURL) {
Image(systemName: "square.and.arrow.up")
}
.accessibilityLabel("Share Logs")
}
}
}
.task {
text = await fetchLogs()
exportURL = makeExportURL(for: text)
}
}

Expand Down Expand Up @@ -63,6 +76,21 @@ public struct LogsViewer: View {
return error.localizedDescription
}
}

private func makeExportURL(for text: String) -> URL? {
let fileName = "openhab-logs-\(Date.now.formatted(.iso8601.year().month().day().time(includingFractionalSeconds: false)))"
.replacingOccurrences(of: ":", with: "-")
let url = FileManager.default.temporaryDirectory
.appendingPathComponent(fileName)
.appendingPathExtension("log")

do {
try text.write(to: url, atomically: true, encoding: .utf8)
return url
} catch {
return nil
}
}
}

private extension OSLogEntryLog.Level {
Expand Down
26 changes: 23 additions & 3 deletions OpenHABCore/Sources/OpenHABCore/Util/Preferences.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,26 @@ public struct HomePreferences: Codable, Equatable {
@MainActor
public struct ApplicationPreferences: Codable, Equatable {
public var showSearchField = true
public var sitemapDiagnosticsLogging = false

public init(
showSearchField: Bool = true,
sitemapDiagnosticsLogging: Bool = false
) {
self.showSearchField = showSearchField
self.sitemapDiagnosticsLogging = sitemapDiagnosticsLogging
}

enum CodingKeys: String, CodingKey {
case showSearchField
case sitemapDiagnosticsLogging
}

nonisolated public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
showSearchField = try container.decodeIfPresent(Bool.self, forKey: .showSearchField) ?? true
sitemapDiagnosticsLogging = try container.decodeIfPresent(Bool.self, forKey: .sitemapDiagnosticsLogging) ?? false
}
}

// MARK: Retrieving preference from user defaults, reacting to preference change
Expand Down Expand Up @@ -146,15 +166,15 @@ private enum PreferencesAccess {

@MainActor fileprivate static func preferenceChanged<T>(newValue: T, key: String, isHomeProperty: Bool, subject: CurrentValueSubject<T, Never>, sanitize: (T) -> (T?) = { $0 }, converter: (T) -> (some Sendable)?) {
guard let sanitized = sanitize(newValue) else {
Logger.preferences.debug("Preference \(key) new value \(String(describing: newValue), privacy: .private) could not be sanitized, will be ignored")
Logger.preferences.debug("Preference \(key, privacy: .public) value of type \(String(describing: T.self), privacy: .public) could not be sanitized, will be ignored")
return
}
let convertedValue = converter(sanitized)
guard convertedValue != nil else {
Logger.preferences.debug("Preference \(key) conversion of new value \(String(describing: sanitized), privacy: .private) failed, do not store.")
Logger.preferences.debug("Preference \(key, privacy: .public) conversion failed for value type \(String(describing: T.self), privacy: .public)")
return
}
Logger.preferences.debug("Preference \(key) will be changed to value \(String(describing: newValue), privacy: .private)")
Logger.preferences.debug("Preference \(key, privacy: .public) will be changed for value type \(String(describing: T.self), privacy: .public)")
sharedDefaults.set(convertedValue, forKey: key)

subject.send(sanitized)
Expand Down
177 changes: 0 additions & 177 deletions openHAB/Models/SitemapPageViewModel+SupportTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,180 +26,3 @@ struct QueuedCommand {
let version: Int
}

// swiftlint:disable:next file_types_order
struct WidgetRenderKey: Equatable {
let label: String
let icon: String
let state: String
let iconColor: String
let labelColor: String
let valueColor: String
let url: String
let period: String
let service: String
let legend: Bool?
let refresh: Int
let height: Double?
let forceAsItem: Bool?
let visibility: Bool
let staticIcon: Bool?
let switchSupport: Bool
let minValue: Double
let maxValue: Double
let step: Double
let pattern: String?
let unit: String?
let type: OpenHABWidget.WidgetType
let linkedPageLink: String?
let linkedPageTitle: String?
let mappings: [WidgetMappingKey]
let item: WidgetItemKey?
let childWidgetIDs: [String]

static func from(widget: OpenHABWidget) -> WidgetRenderKey {
WidgetRenderKey(
label: widget.label,
icon: widget.icon,
state: widget.state,
iconColor: widget.iconColor,
labelColor: widget.labelcolor,
valueColor: widget.valuecolor,
url: widget.url,
period: widget.period,
service: widget.service,
legend: widget.legend,
refresh: widget.refresh,
height: widget.height,
forceAsItem: widget.forceAsItem,
visibility: widget.visibility,
staticIcon: widget.staticIcon,
switchSupport: widget.switchSupport,
minValue: widget.minValue,
maxValue: widget.maxValue,
step: widget.step,
pattern: widget.pattern,
unit: widget.unit,
type: widget.type,
linkedPageLink: widget.linkedPage?.link,
linkedPageTitle: widget.linkedPage?.title,
mappings: widget.mappings.map(WidgetMappingKey.init),
item: WidgetItemKey.from(item: widget.item),
childWidgetIDs: widget.widgets.map(\.widgetId)
)
}

// Could be synthesized automatically by compiler. But this takes too long
static func == (lhs: WidgetRenderKey, rhs: WidgetRenderKey) -> Bool {
lhs.label == rhs.label &&
lhs.icon == rhs.icon &&
lhs.state == rhs.state &&
lhs.iconColor == rhs.iconColor &&
lhs.labelColor == rhs.labelColor &&
lhs.valueColor == rhs.valueColor &&
lhs.url == rhs.url &&
lhs.period == rhs.period &&
lhs.service == rhs.service &&
lhs.legend == rhs.legend &&
lhs.refresh == rhs.refresh &&
lhs.height == rhs.height &&
lhs.forceAsItem == rhs.forceAsItem &&
lhs.visibility == rhs.visibility &&
lhs.staticIcon == rhs.staticIcon &&
lhs.switchSupport == rhs.switchSupport &&
lhs.minValue == rhs.minValue &&
lhs.maxValue == rhs.maxValue &&
lhs.step == rhs.step &&
lhs.pattern == rhs.pattern &&
lhs.unit == rhs.unit &&
lhs.type == rhs.type &&
lhs.linkedPageLink == rhs.linkedPageLink &&
lhs.linkedPageTitle == rhs.linkedPageTitle &&
lhs.mappings == rhs.mappings &&
lhs.item == rhs.item &&
lhs.childWidgetIDs == rhs.childWidgetIDs
}
}

struct WidgetMappingKey: Equatable {
let command: String
let label: String
let row: Int?
let column: Int?
let icon: String?
let releaseCommand: String?

init(_ mapping: OpenHABWidgetMapping) {
command = mapping.command
label = mapping.label
row = mapping.row
column = mapping.column
icon = mapping.icon
releaseCommand = mapping.releaseCommand
}
}

struct WidgetItemKey: Equatable {
let name: String
let state: String?
let link: String
let label: String
let type: OpenHABItem.ItemType?
let groupType: OpenHABItem.ItemType?
let stateDescription: WidgetStateDescriptionKey?
let commandOptions: [WidgetCommandOptionKey]

static func from(item: OpenHABItem?) -> WidgetItemKey? {
guard let item else { return nil }
return WidgetItemKey(
name: item.name,
state: item.state,
link: item.link,
label: item.label,
type: item.type,
groupType: item.groupType,
stateDescription: WidgetStateDescriptionKey.from(stateDescription: item.stateDescription),
commandOptions: item.commandDescription?.commandOptions.map(WidgetCommandOptionKey.init) ?? []
)
}
}

struct WidgetStateDescriptionKey: Equatable {
let minimum: Double
let maximum: Double
let step: Double
let readOnly: Bool
let numberPattern: String?
let options: [WidgetOptionKey]

static func from(stateDescription: OpenHABStateDescription?) -> WidgetStateDescriptionKey? {
guard let stateDescription else { return nil }
return WidgetStateDescriptionKey(
minimum: stateDescription.minimum,
maximum: stateDescription.maximum,
step: stateDescription.step,
readOnly: stateDescription.readOnly,
numberPattern: stateDescription.numberPattern,
options: stateDescription.options.map(WidgetOptionKey.init)
)
}
}

struct WidgetOptionKey: Equatable {
let value: String
let label: String

init(_ option: OpenHABOptions) {
value = option.value
label = option.label
}
}

struct WidgetCommandOptionKey: Equatable {
let command: String
let label: String?

init(_ option: OpenHABCommandOptions) {
command = option.command
label = option.label
}
}
Loading
Loading