Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Override light/dark appearance #1198

Merged
merged 6 commits into from
Feb 17, 2025
Merged
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
4 changes: 2 additions & 2 deletions Packages/App/Sources/CommonLibrary/CommonLibrary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ private extension CommonLibrary {

func configureShared() {
UserDefaults.appGroup.register(defaults: [
AppPreference.logsPrivateData.key: false,
AppPreference.dnsFallsBack.key: true
AppPreference.dnsFallsBack.key: true,
AppPreference.logsPrivateData.key: false
])
}
}
43 changes: 43 additions & 0 deletions Packages/App/Sources/UILibrary/Domain/SystemAppearance.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// SystemAppearance.swift
// Passepartout
//
// Created by Davide De Rosa on 2/17/25.
// Copyright (c) 2025 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
import SwiftUI

public enum SystemAppearance: String, RawRepresentable {
case light

case dark
}

extension Optional where Wrapped == SystemAppearance {
public var colorScheme: ColorScheme? {
switch self {
case .none: return nil
case .light: return .light
case .dark: return .dark
}
}
}
2 changes: 2 additions & 0 deletions Packages/App/Sources/UILibrary/Domain/UIPreference.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ public enum UIPreference: String, PreferenceProtocol {

case profilesLayout

case systemAppearance

public var key: String {
"UI.\(rawValue)"
}
Expand Down
12 changes: 12 additions & 0 deletions Packages/App/Sources/UILibrary/L10n/SwiftGen+Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,16 @@ public enum Strings {
/// Inactive
public static let inactive = Strings.tr("Localizable", "entities.tunnel_status.inactive", fallback: "Inactive")
}
public enum Ui {
public enum SystemAppearance {
/// Dark
public static let dark = Strings.tr("Localizable", "entities.ui.system_appearance.dark", fallback: "Dark")
/// Light
public static let light = Strings.tr("Localizable", "entities.ui.system_appearance.light", fallback: "Light")
/// System
public static let system = Strings.tr("Localizable", "entities.ui.system_appearance.system", fallback: "System")
}
}
}
public enum Errors {
public enum App {
Expand Down Expand Up @@ -844,6 +854,8 @@ public enum Strings {
public static let locksInBackground = Strings.tr("Localizable", "views.preferences.locks_in_background", fallback: "Lock in background")
/// Pin active profile
public static let pinsActiveProfile = Strings.tr("Localizable", "views.preferences.pins_active_profile", fallback: "Pin active profile")
/// Appearance
public static let systemAppearance = Strings.tr("Localizable", "views.preferences.system_appearance", fallback: "Appearance")
public enum DnsFallsBack {
/// Fall back to CloudFlare servers when the VPN does not provide DNS settings.
public static let footer = Strings.tr("Localizable", "views.preferences.dns_falls_back.footer", fallback: "Fall back to CloudFlare servers when the VPN does not provide DNS settings.")
Expand Down
38 changes: 38 additions & 0 deletions Packages/App/Sources/UILibrary/L10n/UILibrary+L10n.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// UILibrary+L10n.swift
// Passepartout
//
// Created by Davide De Rosa on 2/17/25.
// Copyright (c) 2025 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 CommonUtils
import Foundation

extension Optional: LocalizableEntity where Wrapped == SystemAppearance {
public var localizedDescription: String {
let V = Strings.Entities.Ui.SystemAppearance.self
switch self {
case .none: return V.system
case .light: return V.light
case .dark: return V.dark
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
"views.paywall.alerts.restricted.message" = "Some features are unavailable in this build.";
"views.paywall.alerts.pending.message" = "The purchase is pending external confirmation. The feature will be credited upon approval.";

"views.preferences.system_appearance" = "Appearance";
"views.preferences.launches_on_login" = "Launch on login";
"views.preferences.launches_on_login.footer" = "Open the app in background after login.";
"views.preferences.keeps_in_menu" = "Keep in menu bar";
Expand Down Expand Up @@ -235,6 +236,12 @@
"entities.openvpn.otp_method.append" = "Append";
"entities.openvpn.otp_method.encode" = "Encode";

// MARK: Entities (Library)

"entities.ui.system_appearance.system" = "System";
"entities.ui.system_appearance.light" = "Light";
"entities.ui.system_appearance.dark" = "Dark";

// MARK: - Features

"features.appletv" = "%@";
Expand Down
31 changes: 31 additions & 0 deletions Packages/App/Sources/UILibrary/Theme/UI/Theme+Modifiers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ public enum ThemeModalSize: Hashable {
}

extension View {
public func themeAppearance(systemScheme: ColorScheme) -> some View {
modifier(ThemeAppearanceModifier(systemScheme: systemScheme))
}

public func themeModal<Content>(
isPresented: Binding<Bool>,
options: ThemeModalOptions? = nil,
Expand Down Expand Up @@ -335,6 +339,12 @@ struct ThemeBooleanModalModifier<Modal>: ViewModifier where Modal: View {
@EnvironmentObject
private var theme: Theme

@Environment(\.colorScheme)
private var colorScheme

@AppStorage(UIPreference.systemAppearance.key)
private var systemAppearance: SystemAppearance?

@Binding
var isPresented: Bool

Expand All @@ -358,6 +368,7 @@ struct ThemeBooleanModalModifier<Modal>: ViewModifier where Modal: View {
#endif
.interactiveDismissDisabled(!options.isInteractive)
.themeLockScreen()
.themeAppearance(systemScheme: colorScheme)
}
}
}
Expand All @@ -367,6 +378,12 @@ struct ThemeItemModalModifier<Modal, T>: ViewModifier where Modal: View, T: Iden
@EnvironmentObject
private var theme: Theme

@Environment(\.colorScheme)
private var colorScheme

@AppStorage(UIPreference.systemAppearance.key)
private var systemAppearance: SystemAppearance?

@Binding
var item: T?

Expand All @@ -390,6 +407,7 @@ struct ThemeItemModalModifier<Modal, T>: ViewModifier where Modal: View, T: Iden
#endif
.interactiveDismissDisabled(!options.isInteractive)
.themeLockScreen()
.themeAppearance(systemScheme: colorScheme)
}
}
}
Expand Down Expand Up @@ -454,6 +472,19 @@ struct ThemeNavigationStackModifier: ViewModifier {

// MARK: - Content modifiers

struct ThemeAppearanceModifier: ViewModifier {

@AppStorage(UIPreference.systemAppearance.key)
private var systemAppearance: SystemAppearance?

let systemScheme: ColorScheme

func body(content: Content) -> some View {
content
.preferredColorScheme(systemAppearance.colorScheme ?? systemScheme)
}
}

struct ThemeManualInputModifier: ViewModifier {
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ import SwiftUI

public struct PreferencesGroup: View {

@AppStorage(UIPreference.systemAppearance.key)
private var systemAppearance: SystemAppearance?

#if os(iOS)
@AppStorage(UIPreference.locksInBackground.key)
private var locksInBackground = false
Expand All @@ -56,6 +59,7 @@ public struct PreferencesGroup: View {
}

public var body: some View {
systemAppearancePicker
#if os(iOS)
lockInBackgroundToggle
#elseif os(macOS)
Expand All @@ -69,6 +73,20 @@ public struct PreferencesGroup: View {
}

private extension PreferencesGroup {
static let systemAppearances: [SystemAppearance?] = [
nil,
.light,
.dark
]

var systemAppearancePicker: some View {
Picker(Strings.Views.Preferences.systemAppearance, selection: $systemAppearance) {
ForEach(Self.systemAppearances, id: \.self) {
Text($0.localizedDescription)
}
}
}

#if os(iOS)
var lockInBackgroundToggle: some View {
Toggle(Strings.Views.Preferences.locksInBackground, isOn: $locksInBackground)
Expand Down
3 changes: 3 additions & 0 deletions Passepartout/App/PassepartoutApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ import SwiftUI
@main
struct PassepartoutApp: App {

@Environment(\.colorScheme)
var colorScheme

#if os(iOS) || os(tvOS)

@UIApplicationDelegateAdaptor
Expand Down
1 change: 1 addition & 0 deletions Passepartout/App/Platforms/App+iOS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ extension PassepartoutApp {
.withEnvironment(from: context, theme: theme)
.environment(\.isUITesting, AppCommandLine.contains(.uiTesting))
.tint(.accentColor)
.themeAppearance(systemScheme: colorScheme)
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions Passepartout/App/Platforms/App+macOS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ extension PassepartoutApp {
.withEnvironment(from: context, theme: theme)
.environment(\.isUITesting, AppCommandLine.contains(.uiTesting))
.frame(minWidth: 600, minHeight: 400)
.themeAppearance(systemScheme: colorScheme)
}
.defaultSize(width: 600, height: 400)

Expand All @@ -72,6 +73,7 @@ extension PassepartoutApp {
.withEnvironment(from: context, theme: theme)
.environmentObject(settings)
.environment(\.isUITesting, AppCommandLine.contains(.uiTesting))
.themeAppearance(systemScheme: colorScheme)
}
.defaultSize(width: 500, height: 400)

Expand Down
Loading