Move theme-related logic to UI target

Fixes #719
This commit is contained in:
Davide 2024-10-28 21:38:26 +01:00
parent 6f64edc95b
commit c930225458
No known key found for this signature in database
GPG Key ID: A48836171C759F5E
10 changed files with 51 additions and 82 deletions

View File

@ -97,7 +97,7 @@ struct Issue: Identifiable {
.replacingOccurrences(of: "$osLine", with: osLine) .replacingOccurrences(of: "$osLine", with: osLine)
.replacingOccurrences(of: "$deviceLine", with: deviceLine ?? "unknown") .replacingOccurrences(of: "$deviceLine", with: deviceLine ?? "unknown")
.replacingOccurrences(of: "$providerName", with: providerName ?? "none") .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) .replacingOccurrences(of: "$purchasedProducts", with: purchasedProducts.map(\.rawValue).description)
} }
} }

View File

@ -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 { extension UUID {
var flatString: String { var flatString: String {
let str = uuidString.replacingOccurrences(of: "-", with: "") let str = uuidString.replacingOccurrences(of: "-", with: "")

View File

@ -42,7 +42,7 @@ extension AboutView {
Section { Section {
diagnosticsLink diagnosticsLink
Text(Strings.Global.version) Text(Strings.Global.version)
.withTrailingText(BundleConfiguration.mainVersionString) .themeTrailingValue(BundleConfiguration.mainVersionString)
} }
} }
.navigationTitle(Strings.Global.settings) .navigationTitle(Strings.Global.settings)

View File

@ -124,11 +124,11 @@ private extension IPView.RouteView {
VStack { VStack {
route.destination.map { route.destination.map {
Text("Destination") Text("Destination")
.withTrailingText($0.rawValue) .themeTrailingValue($0.rawValue)
} }
route.gateway.map { route.gateway.map {
Text("Gateway") Text("Gateway")
.withTrailingText($0.rawValue) .themeTrailingValue($0.rawValue)
} }
} }
} }

View File

@ -94,7 +94,7 @@ private extension View {
switch row { switch row {
case .text(let caption, let value): case .text(let caption, let value):
Text(caption) Text(caption)
.withTrailingText(value) .themeTrailingValue(value)
case .textList(let caption, let values): case .textList(let caption, let values):
if !values.isEmpty { if !values.isEmpty {
@ -109,7 +109,7 @@ private extension View {
} }
} else { } else {
Text(caption) Text(caption)
.withTrailingText(Strings.Global.empty) .themeTrailingValue(Strings.Global.empty)
} }
case .copiableText(let caption, let value, let multiline): case .copiableText(let caption, let value, let multiline):

View File

@ -166,7 +166,7 @@ private extension ProviderContentModifier {
guard let lastUpdate else { guard let lastUpdate else {
return providerManager.isLoading ? Strings.Providers.LastUpdated.loading : nil return providerManager.isLoading ? Strings.Providers.LastUpdated.loading : nil
} }
return Strings.Providers.lastUpdated(lastUpdate.timestamp) return Strings.Providers.lastUpdated(lastUpdate.localizedDescription(style: .timestamp))
} }
} }

View File

@ -242,6 +242,26 @@ struct ThemeAnimationModifier<T>: 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 { struct ThemeSectionWithHeaderFooterModifier: ViewModifier {
let header: String? let header: String?

View File

@ -183,6 +183,10 @@ extension View {
modifier(ThemeAnimationModifier(value: value, category: category)) 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 { public func themeSection(header: String? = nil, footer: String? = nil) -> some View {
modifier(ThemeSectionWithHeaderFooterModifier(header: header, footer: footer)) modifier(ThemeSectionWithHeaderFooterModifier(header: header, footer: footer))
} }

View File

@ -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 <http://www.gnu.org/licenses/>.
//
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)))
}
}

View File

@ -38,21 +38,6 @@ extension View {
self 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 { extension ViewModifier {