From c9302254586a5202c636fd937aeba83dafd74989 Mon Sep 17 00:00:00 2001 From: Davide Date: Mon, 28 Oct 2024 21:38:26 +0100 Subject: [PATCH] Move theme-related logic to UI target Fixes #719 --- .../Library/Sources/AppUI/Domain/Issue.swift | 2 +- .../Sources/AppUI/L10n/Foundation+L10n.swift | 20 +++++++ .../AppUI/Views/About/iOS/AboutView+iOS.swift | 2 +- .../AppUI/Views/Modules/IPView+Route.swift | 4 +- .../Views/ProfileEditor/ModuleSection.swift | 4 +- .../Provider/ProviderContentModifier.swift | 2 +- .../Sources/AppUI/Views/Theme/Theme+UI.swift | 20 +++++++ .../Sources/AppUI/Views/Theme/Theme.swift | 4 ++ .../Extensions/Dates+Extensions.swift | 60 ------------------- .../UtilsLibrary/Views/View+Extensions.swift | 15 ----- 10 files changed, 51 insertions(+), 82 deletions(-) delete mode 100644 Passepartout/Library/Sources/UtilsLibrary/Extensions/Dates+Extensions.swift diff --git a/Passepartout/Library/Sources/AppUI/Domain/Issue.swift b/Passepartout/Library/Sources/AppUI/Domain/Issue.swift index 0f1bd9f7..a2c8cb0e 100644 --- a/Passepartout/Library/Sources/AppUI/Domain/Issue.swift +++ b/Passepartout/Library/Sources/AppUI/Domain/Issue.swift @@ -97,7 +97,7 @@ struct Issue: Identifiable { .replacingOccurrences(of: "$osLine", with: osLine) .replacingOccurrences(of: "$deviceLine", with: deviceLine ?? "unknown") .replacingOccurrences(of: "$providerName", with: providerName ?? "none") - .replacingOccurrences(of: "$providerLastUpdate", with: providerLastUpdate?.timestamp ?? "unknown") + .replacingOccurrences(of: "$providerLastUpdate", with: providerLastUpdate?.localizedDescription(style: .timestamp) ?? "unknown") .replacingOccurrences(of: "$purchasedProducts", with: purchasedProducts.map(\.rawValue).description) } } diff --git a/Passepartout/Library/Sources/AppUI/L10n/Foundation+L10n.swift b/Passepartout/Library/Sources/AppUI/L10n/Foundation+L10n.swift index 87a5cd61..36aeec0c 100644 --- a/Passepartout/Library/Sources/AppUI/L10n/Foundation+L10n.swift +++ b/Passepartout/Library/Sources/AppUI/L10n/Foundation+L10n.swift @@ -43,6 +43,26 @@ extension TimeInterval: StyledLocalizableEntity { } } +extension Date: StyledLocalizableEntity { + public enum Style { + case timestamp + } + + public func localizedDescription(style: Style) -> String { + switch style { + case .timestamp: + return Self.timestampFormatter.string(from: self) + } + } + + private static let timestampFormatter: DateFormatter = { + let fmt = DateFormatter() + fmt.dateStyle = .medium + fmt.timeStyle = .medium + return fmt + }() +} + extension UUID { var flatString: String { let str = uuidString.replacingOccurrences(of: "-", with: "") diff --git a/Passepartout/Library/Sources/AppUI/Views/About/iOS/AboutView+iOS.swift b/Passepartout/Library/Sources/AppUI/Views/About/iOS/AboutView+iOS.swift index c2ace1de..5377b114 100644 --- a/Passepartout/Library/Sources/AppUI/Views/About/iOS/AboutView+iOS.swift +++ b/Passepartout/Library/Sources/AppUI/Views/About/iOS/AboutView+iOS.swift @@ -42,7 +42,7 @@ extension AboutView { Section { diagnosticsLink Text(Strings.Global.version) - .withTrailingText(BundleConfiguration.mainVersionString) + .themeTrailingValue(BundleConfiguration.mainVersionString) } } .navigationTitle(Strings.Global.settings) diff --git a/Passepartout/Library/Sources/AppUI/Views/Modules/IPView+Route.swift b/Passepartout/Library/Sources/AppUI/Views/Modules/IPView+Route.swift index 30747f14..33534d43 100644 --- a/Passepartout/Library/Sources/AppUI/Views/Modules/IPView+Route.swift +++ b/Passepartout/Library/Sources/AppUI/Views/Modules/IPView+Route.swift @@ -124,11 +124,11 @@ private extension IPView.RouteView { VStack { route.destination.map { Text("Destination") - .withTrailingText($0.rawValue) + .themeTrailingValue($0.rawValue) } route.gateway.map { Text("Gateway") - .withTrailingText($0.rawValue) + .themeTrailingValue($0.rawValue) } } } diff --git a/Passepartout/Library/Sources/AppUI/Views/ProfileEditor/ModuleSection.swift b/Passepartout/Library/Sources/AppUI/Views/ProfileEditor/ModuleSection.swift index 8c8a4ed2..ef908f61 100644 --- a/Passepartout/Library/Sources/AppUI/Views/ProfileEditor/ModuleSection.swift +++ b/Passepartout/Library/Sources/AppUI/Views/ProfileEditor/ModuleSection.swift @@ -94,7 +94,7 @@ private extension View { switch row { case .text(let caption, let value): Text(caption) - .withTrailingText(value) + .themeTrailingValue(value) case .textList(let caption, let values): if !values.isEmpty { @@ -109,7 +109,7 @@ private extension View { } } else { Text(caption) - .withTrailingText(Strings.Global.empty) + .themeTrailingValue(Strings.Global.empty) } case .copiableText(let caption, let value, let multiline): diff --git a/Passepartout/Library/Sources/AppUI/Views/Provider/ProviderContentModifier.swift b/Passepartout/Library/Sources/AppUI/Views/Provider/ProviderContentModifier.swift index 46ad2cdf..04a3e73b 100644 --- a/Passepartout/Library/Sources/AppUI/Views/Provider/ProviderContentModifier.swift +++ b/Passepartout/Library/Sources/AppUI/Views/Provider/ProviderContentModifier.swift @@ -166,7 +166,7 @@ private extension ProviderContentModifier { guard let lastUpdate else { return providerManager.isLoading ? Strings.Providers.LastUpdated.loading : nil } - return Strings.Providers.lastUpdated(lastUpdate.timestamp) + return Strings.Providers.lastUpdated(lastUpdate.localizedDescription(style: .timestamp)) } } diff --git a/Passepartout/Library/Sources/AppUI/Views/Theme/Theme+UI.swift b/Passepartout/Library/Sources/AppUI/Views/Theme/Theme+UI.swift index 8c17e3bb..e05082fa 100644 --- a/Passepartout/Library/Sources/AppUI/Views/Theme/Theme+UI.swift +++ b/Passepartout/Library/Sources/AppUI/Views/Theme/Theme+UI.swift @@ -242,6 +242,26 @@ struct ThemeAnimationModifier: ViewModifier where T: Equatable { } } +struct ThemeTrailingValueModifier: ViewModifier { + let value: CustomStringConvertible? + + let truncationMode: Text.TruncationMode + + func body(content: Content) -> some View { + LabeledContent { + if let value { + Spacer() + Text(value.description) + .foregroundStyle(.secondary) + .lineLimit(1) + .truncationMode(truncationMode) + } + } label: { + content + } + } +} + struct ThemeSectionWithHeaderFooterModifier: ViewModifier { let header: String? diff --git a/Passepartout/Library/Sources/AppUI/Views/Theme/Theme.swift b/Passepartout/Library/Sources/AppUI/Views/Theme/Theme.swift index e5b3b66c..2d20ecf2 100644 --- a/Passepartout/Library/Sources/AppUI/Views/Theme/Theme.swift +++ b/Passepartout/Library/Sources/AppUI/Views/Theme/Theme.swift @@ -183,6 +183,10 @@ extension View { modifier(ThemeAnimationModifier(value: value, category: category)) } + public func themeTrailingValue(_ value: CustomStringConvertible?, truncationMode: Text.TruncationMode = .tail) -> some View { + modifier(ThemeTrailingValueModifier(value: value, truncationMode: truncationMode)) + } + public func themeSection(header: String? = nil, footer: String? = nil) -> some View { modifier(ThemeSectionWithHeaderFooterModifier(header: header, footer: footer)) } diff --git a/Passepartout/Library/Sources/UtilsLibrary/Extensions/Dates+Extensions.swift b/Passepartout/Library/Sources/UtilsLibrary/Extensions/Dates+Extensions.swift deleted file mode 100644 index e664080e..00000000 --- a/Passepartout/Library/Sources/UtilsLibrary/Extensions/Dates+Extensions.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// Dates+Extensions.swift -// Passepartout -// -// Created by Davide De Rosa on 8/18/24. -// Copyright (c) 2024 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation - -private let timestampFormatter: DateFormatter = { - let fmt = DateFormatter() - fmt.dateStyle = .medium - fmt.timeStyle = .medium - return fmt -}() - -private let componentsFormatter: DateComponentsFormatter = { - let fmt = DateComponentsFormatter() - fmt.unitsStyle = .full - return fmt -}() - -extension Date { - public var timestamp: String { - timestampFormatter.string(from: self) - } -} - -extension TimeInterval { - public var localizedDescription: String { - guard let str = componentsFormatter.string(from: self) else { - fatalError("Could not format a TimeInterval?") - } - return str - } -} - -extension TimeInterval { - public var dispatchTimeInterval: DispatchTimeInterval { - .nanoseconds(Int(self * Double(NSEC_PER_SEC))) - } -} diff --git a/Passepartout/Library/Sources/UtilsLibrary/Views/View+Extensions.swift b/Passepartout/Library/Sources/UtilsLibrary/Views/View+Extensions.swift index 08dd4a28..0666bff6 100644 --- a/Passepartout/Library/Sources/UtilsLibrary/Views/View+Extensions.swift +++ b/Passepartout/Library/Sources/UtilsLibrary/Views/View+Extensions.swift @@ -38,21 +38,6 @@ extension View { self } } - - public func withTrailingText(_ text: String?, truncationMode: Text.TruncationMode = .tail) -> some View { - LabeledContent { - if let text { - Spacer() - let trailing = Text(text) - .foregroundStyle(.secondary) - .lineLimit(1) - .truncationMode(truncationMode) - trailing - } - } label: { - self - } - } } extension ViewModifier {