Add appearance setting
This commit is contained in:
@@ -1,10 +1,15 @@
|
||||
import SwiftUI
|
||||
import UIComponents
|
||||
import AppSettings
|
||||
import Modifiers
|
||||
import Theme
|
||||
import UIComponents
|
||||
|
||||
public struct HomeView: View {
|
||||
@ObservedObject private var viewModel = HomeViewModel(selectedNetwork: .mixnet)
|
||||
public init() {}
|
||||
@ObservedObject private var viewModel: HomeViewModel
|
||||
|
||||
public init(viewModel: HomeViewModel) {
|
||||
self.viewModel = viewModel
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
HomeFlowCoordinator(state: viewModel, content: content)
|
||||
@@ -23,6 +28,7 @@ private extension HomeView {
|
||||
connectionSection()
|
||||
connectButton()
|
||||
}
|
||||
.appearanceUpdate()
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background {
|
||||
NymColor.background
|
||||
@@ -34,8 +40,7 @@ private extension HomeView {
|
||||
func navbar() -> some View {
|
||||
CustomNavBar(
|
||||
title: "NymVPN".localizedString,
|
||||
rightButton: CustomNavBarButton(type: .settings, action: { print("settings")
|
||||
viewModel.navigateToSettings() })
|
||||
rightButton: CustomNavBarButton(type: .settings, action: { viewModel.navigateToSettings() })
|
||||
)
|
||||
Spacer()
|
||||
.frame(height: 50)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
D99A147A2B357E9900F2728B /* NymVPNApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D99A14792B357E9900F2728B /* NymVPNApp.swift */; };
|
||||
D99A148A2B35A73F00F2728B /* Home in Frameworks */ = {isa = PBXBuildFile; productRef = D99A14892B35A73F00F2728B /* Home */; };
|
||||
D99A148C2B35A74200F2728B /* Theme in Frameworks */ = {isa = PBXBuildFile; productRef = D99A148B2B35A74200F2728B /* Theme */; };
|
||||
D9CD835D2B6C32D4008DA864 /* Modifiers in Frameworks */ = {isa = PBXBuildFile; productRef = D9CD835C2B6C32D4008DA864 /* Modifiers */; };
|
||||
D9F699E02B4C1862005576A9 /* Settings in Frameworks */ = {isa = PBXBuildFile; productRef = D9F699DF2B4C1862005576A9 /* Settings */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@@ -76,6 +77,7 @@
|
||||
D968BDC92B6AEA9D003F42E5 /* AppSettings in Frameworks */,
|
||||
D99A148C2B35A74200F2728B /* Theme in Frameworks */,
|
||||
D99A148A2B35A73F00F2728B /* Home in Frameworks */,
|
||||
D9CD835D2B6C32D4008DA864 /* Modifiers in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -181,6 +183,7 @@
|
||||
D96740222B56A67A001F6891 /* Tunnels */,
|
||||
D968BDC82B6AEA9D003F42E5 /* AppSettings */,
|
||||
D968BDCA2B6BC98E003F42E5 /* AppVersionProvider */,
|
||||
D9CD835C2B6C32D4008DA864 /* Modifiers */,
|
||||
);
|
||||
productName = NymVPN;
|
||||
productReference = D99A14762B357E9900F2728B /* NymVPN.app */;
|
||||
@@ -605,6 +608,10 @@
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Theme;
|
||||
};
|
||||
D9CD835C2B6C32D4008DA864 /* Modifiers */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Modifiers;
|
||||
};
|
||||
D9F699DF2B4C1862005576A9 /* Settings */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Settings;
|
||||
|
||||
@@ -6,6 +6,7 @@ import Tunnels
|
||||
|
||||
@main
|
||||
struct NymVPNApp: App {
|
||||
|
||||
init() {
|
||||
setup()
|
||||
}
|
||||
@@ -13,9 +14,8 @@ struct NymVPNApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
NavigationStack {
|
||||
HomeView()
|
||||
HomeView(viewModel: HomeViewModel(selectedNetwork: .mixnet))
|
||||
}
|
||||
.preferredColorScheme(AppSettings.shared.currentTheme.colorScheme)
|
||||
.environmentObject(AppSettings.shared)
|
||||
.environmentObject(TunnelsManager.shared)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,10 @@ let package = Package(
|
||||
name: "AppVersionProvider",
|
||||
targets: ["AppVersionProvider"]
|
||||
),
|
||||
.library(
|
||||
name: "Modifiers",
|
||||
targets: ["Modifiers"]
|
||||
),
|
||||
.library(
|
||||
name: "Tunnels",
|
||||
targets: ["Tunnels"]
|
||||
@@ -34,6 +38,13 @@ let package = Package(
|
||||
dependencies: [],
|
||||
path: "Sources/Services/AppVersionProvider"
|
||||
),
|
||||
.target(
|
||||
name: "Modifiers",
|
||||
dependencies: [
|
||||
"AppSettings"
|
||||
],
|
||||
path: "Sources/Services/Modifiers"
|
||||
),
|
||||
.target(
|
||||
name: "Tunnels",
|
||||
dependencies: [],
|
||||
|
||||
@@ -3,5 +3,5 @@ import SwiftUI
|
||||
public final class AppSettings: ObservableObject {
|
||||
public static let shared = AppSettings()
|
||||
|
||||
@AppStorage("currentTheme") public var currentTheme: Theme = .automatic
|
||||
@AppStorage("currentAppearance") public var currentTheme: AppSetting.Appearance = .automatic
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import SwiftUI
|
||||
|
||||
public struct AppSetting {
|
||||
public enum Appearance: Int, CaseIterable {
|
||||
case automatic
|
||||
case light
|
||||
case dark
|
||||
|
||||
public var colorScheme: ColorScheme? {
|
||||
switch self {
|
||||
case .light:
|
||||
return .light
|
||||
case .dark:
|
||||
return .dark
|
||||
case .automatic:
|
||||
return ColorScheme(.unspecified)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
public enum Theme: Int {
|
||||
case light
|
||||
case dark
|
||||
case automatic
|
||||
|
||||
public var colorScheme: ColorScheme? {
|
||||
switch self {
|
||||
case .light:
|
||||
return .light
|
||||
case .dark:
|
||||
return .dark
|
||||
case .automatic:
|
||||
return ColorScheme(.unspecified)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import SwiftUI
|
||||
import AppSettings
|
||||
|
||||
public struct AppearanceUpdate: ViewModifier {
|
||||
@EnvironmentObject private var appSettings: AppSettings
|
||||
|
||||
public func body(content: Content) -> some View {
|
||||
content
|
||||
.preferredColorScheme(appSettings.currentTheme.colorScheme)
|
||||
}
|
||||
}
|
||||
|
||||
public extension View {
|
||||
func appearanceUpdate() -> some View {
|
||||
modifier(AppearanceUpdate())
|
||||
}
|
||||
}
|
||||
@@ -16,16 +16,12 @@ let package = Package(
|
||||
)
|
||||
],
|
||||
dependencies: [
|
||||
// .package(name: "AppVersionProvider", path: "../Services"),
|
||||
// .package(name: "AppSettings", path: "../Services"),
|
||||
.package(path: "../UIComponents")
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "Settings",
|
||||
dependencies: [
|
||||
// "AppVersionProvider",
|
||||
// "AppSettings",
|
||||
"UIComponents"
|
||||
]
|
||||
),
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
import Theme
|
||||
import UIComponents
|
||||
|
||||
public struct SettingsConfig {
|
||||
public init() {}
|
||||
|
||||
var sections: [SettingsSection] {
|
||||
[
|
||||
connectionSection(),
|
||||
themeSection(),
|
||||
logsSection(),
|
||||
feedbackSection(),
|
||||
legalSection()
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
private extension SettingsConfig {
|
||||
func connectionSection() -> SettingsSection {
|
||||
.connection(
|
||||
viewModels: [
|
||||
SettingsListItemViewModel(
|
||||
accessory: .arrow,
|
||||
title: "autoConnectTitle".localizedString,
|
||||
subtitle: "autoConnectSubtitle".localizedString,
|
||||
imageName: "autoConnect",
|
||||
action: {}
|
||||
),
|
||||
SettingsListItemViewModel(
|
||||
accessory: .toggle,
|
||||
title: "entryLocationTitle".localizedString,
|
||||
subtitle: "entryLocationSubtitle".localizedString,
|
||||
imageName: "entryHop",
|
||||
action: {}
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func themeSection() -> SettingsSection {
|
||||
.theme(
|
||||
viewModels: [
|
||||
SettingsListItemViewModel(
|
||||
accessory: .arrow,
|
||||
title: "displayTheme".localizedString,
|
||||
imageName: "displayTheme",
|
||||
action: {
|
||||
|
||||
}
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func logsSection() -> SettingsSection {
|
||||
.logs(
|
||||
viewModels: [
|
||||
SettingsListItemViewModel(
|
||||
accessory: .arrow,
|
||||
title: "logs".localizedString,
|
||||
imageName: "logs",
|
||||
action: {}
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func feedbackSection() -> SettingsSection {
|
||||
.feedback(
|
||||
viewModels: [
|
||||
SettingsListItemViewModel(
|
||||
accessory: .arrow,
|
||||
title: "feedback".localizedString,
|
||||
imageName: "feedback",
|
||||
action: {}
|
||||
),
|
||||
SettingsListItemViewModel(
|
||||
accessory: .arrow,
|
||||
title: "support".localizedString,
|
||||
imageName: "support",
|
||||
action: {}
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func legalSection() -> SettingsSection {
|
||||
.legal(
|
||||
viewModels: [
|
||||
SettingsListItemViewModel(
|
||||
accessory: .arrow,
|
||||
title: "legal".localizedString,
|
||||
action: {}
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import SwiftUI
|
||||
import AppSettings
|
||||
|
||||
struct SettingsFlowCoordinator<Content: View>: View {
|
||||
@ObservedObject var state: SettingsFlowState
|
||||
@@ -12,7 +13,7 @@ struct SettingsFlowCoordinator<Content: View>: View {
|
||||
@ViewBuilder private func linkDestination(link: SettingsLink) -> some View {
|
||||
switch link {
|
||||
case .theme:
|
||||
SettingsThemeView()
|
||||
AppearanceView(viewModel: AppearanceViewModel(path: $state.path, appSettings: AppSettings.shared))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import SwiftUI
|
||||
import Theme
|
||||
import AppSettings
|
||||
import Modifiers
|
||||
import UIComponents
|
||||
import Theme
|
||||
|
||||
public struct SettingsView: View {
|
||||
@StateObject var viewModel: SettingsViewModel
|
||||
@StateObject private var viewModel: SettingsViewModel
|
||||
|
||||
public init(viewModel: SettingsViewModel) {
|
||||
_viewModel = StateObject(wrappedValue: viewModel)
|
||||
@@ -22,6 +24,7 @@ private extension SettingsView {
|
||||
settingsList()
|
||||
Spacer()
|
||||
}
|
||||
.appearanceUpdate()
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.ignoresSafeArea(edges: [.bottom])
|
||||
@@ -44,7 +47,7 @@ private extension SettingsView {
|
||||
SettingsList(
|
||||
viewModel:
|
||||
SettingsListViewModel(
|
||||
sections: viewModel.settingsConfig.sections,
|
||||
sections: viewModel.sections,
|
||||
appVersion: viewModel.appVersion()
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
import Foundation
|
||||
import AppVersionProvider
|
||||
import UIComponents
|
||||
|
||||
public class SettingsViewModel: SettingsFlowState {
|
||||
let settingsTitle = "settings".localizedString
|
||||
let settingsConfig = SettingsConfig()
|
||||
|
||||
var sections: [SettingsSection] {
|
||||
[
|
||||
connectionSection(),
|
||||
themeSection(),
|
||||
logsSection(),
|
||||
feedbackSection(),
|
||||
legalSection()
|
||||
]
|
||||
}
|
||||
|
||||
func navigateHome() {
|
||||
path = .init()
|
||||
@@ -19,3 +29,85 @@ private extension SettingsViewModel {
|
||||
path.append(SettingsLink.theme)
|
||||
}
|
||||
}
|
||||
|
||||
private extension SettingsViewModel {
|
||||
func connectionSection() -> SettingsSection {
|
||||
.connection(
|
||||
viewModels: [
|
||||
SettingsListItemViewModel(
|
||||
accessory: .arrow,
|
||||
title: "autoConnectTitle".localizedString,
|
||||
subtitle: "autoConnectSubtitle".localizedString,
|
||||
imageName: "autoConnect",
|
||||
action: {}
|
||||
),
|
||||
SettingsListItemViewModel(
|
||||
accessory: .toggle,
|
||||
title: "entryLocationTitle".localizedString,
|
||||
subtitle: "entryLocationSubtitle".localizedString,
|
||||
imageName: "entryHop",
|
||||
action: {}
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func themeSection() -> SettingsSection {
|
||||
.theme(
|
||||
viewModels: [
|
||||
SettingsListItemViewModel(
|
||||
accessory: .arrow,
|
||||
title: "displayTheme".localizedString,
|
||||
imageName: "displayTheme",
|
||||
action: { [weak self] in
|
||||
self?.navigateToTheme()
|
||||
}
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func logsSection() -> SettingsSection {
|
||||
.logs(
|
||||
viewModels: [
|
||||
SettingsListItemViewModel(
|
||||
accessory: .arrow,
|
||||
title: "logs".localizedString,
|
||||
imageName: "logs",
|
||||
action: {}
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func feedbackSection() -> SettingsSection {
|
||||
.feedback(
|
||||
viewModels: [
|
||||
SettingsListItemViewModel(
|
||||
accessory: .arrow,
|
||||
title: "feedback".localizedString,
|
||||
imageName: "feedback",
|
||||
action: {}
|
||||
),
|
||||
SettingsListItemViewModel(
|
||||
accessory: .arrow,
|
||||
title: "support".localizedString,
|
||||
imageName: "support",
|
||||
action: {}
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func legalSection() -> SettingsSection {
|
||||
.legal(
|
||||
viewModels: [
|
||||
SettingsListItemViewModel(
|
||||
accessory: .arrow,
|
||||
title: "legal".localizedString,
|
||||
action: {}
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import SwiftUI
|
||||
import AppSettings
|
||||
import Modifiers
|
||||
import Theme
|
||||
import UIComponents
|
||||
|
||||
public struct AppearanceView: View {
|
||||
private let viewModel: AppearanceViewModel
|
||||
|
||||
public init(viewModel: AppearanceViewModel) {
|
||||
self.viewModel = viewModel
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
VStack {
|
||||
navbar()
|
||||
themeOptions()
|
||||
Spacer()
|
||||
}
|
||||
.appearanceUpdate()
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.ignoresSafeArea(edges: [.bottom])
|
||||
.background {
|
||||
NymColor.background
|
||||
.ignoresSafeArea()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension AppearanceView {
|
||||
@ViewBuilder
|
||||
func navbar() -> some View {
|
||||
CustomNavBar(
|
||||
title: viewModel.title,
|
||||
leftButton: CustomNavBarButton(type: .back, action: { viewModel.navigateBack() })
|
||||
)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func themeOptions() -> some View {
|
||||
ForEach(viewModel.themes, id: \.self) { theme in
|
||||
SettingButton(
|
||||
viewModel:
|
||||
SettingButtonViewModel(
|
||||
title: viewModel.themeTitle(for: theme),
|
||||
subtitle: viewModel.themeSubtitle(for: theme),
|
||||
isSelected: viewModel.isSelected(for: theme)
|
||||
)
|
||||
)
|
||||
.onTapGesture {
|
||||
viewModel.setCurrentTheme(with: theme)
|
||||
}
|
||||
.padding(EdgeInsets(top: 24, leading: 16, bottom: 0, trailing: 16))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import SwiftUI
|
||||
import AppSettings
|
||||
import Theme
|
||||
|
||||
public struct AppearanceViewModel {
|
||||
private let appSettings: AppSettings
|
||||
|
||||
let title = "displayTheme".localizedString
|
||||
|
||||
@Binding var path: NavigationPath
|
||||
|
||||
var themes: [AppSetting.Appearance] {
|
||||
AppSetting.Appearance.allCases
|
||||
}
|
||||
|
||||
var currentTheme: AppSetting.Appearance {
|
||||
appSettings.currentTheme
|
||||
}
|
||||
|
||||
public init(path: Binding<NavigationPath>, appSettings: AppSettings) {
|
||||
_path = path
|
||||
self.appSettings = appSettings
|
||||
}
|
||||
|
||||
func setCurrentTheme(with theme: AppSetting.Appearance) {
|
||||
appSettings.currentTheme = theme
|
||||
}
|
||||
}
|
||||
|
||||
extension AppearanceViewModel {
|
||||
func themeTitle(for theme: AppSetting.Appearance) -> String {
|
||||
switch theme {
|
||||
case .light:
|
||||
return "lightThemeTitle".localizedString
|
||||
case .dark:
|
||||
return "darkThemeTitle".localizedString
|
||||
case .automatic:
|
||||
return "automaticThemeTitle".localizedString
|
||||
}
|
||||
}
|
||||
|
||||
func themeSubtitle(for theme: AppSetting.Appearance) -> String? {
|
||||
switch theme {
|
||||
case .light, .dark:
|
||||
return nil
|
||||
case .automatic:
|
||||
return "automaticThemeSubtitle".localizedString
|
||||
}
|
||||
}
|
||||
|
||||
func isSelected(for theme: AppSetting.Appearance) -> Bool {
|
||||
appSettings.currentTheme == theme
|
||||
}
|
||||
}
|
||||
|
||||
extension AppearanceViewModel {
|
||||
func navigateBack() {
|
||||
path.removeLast()
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
public struct SettingsThemeView: View {
|
||||
// private let viewModel: SettingsThemeViewModel
|
||||
//
|
||||
// public init(viewModel: SettingsThemeViewModel) {
|
||||
// self.viewModel = viewModel
|
||||
// }
|
||||
|
||||
public var body: some View {
|
||||
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
public struct SettingsThemeViewModel {
|
||||
// private let appSettings: AppSettings
|
||||
//
|
||||
// public init(appSettings: AppSettings) {
|
||||
// self.appSettings = appSettings
|
||||
// }
|
||||
}
|
||||
@@ -67,6 +67,28 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"automaticThemeSubtitle" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Use device theme"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"automaticThemeTitle" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Automatic"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"connect" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -111,6 +133,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"darkThemeTitle" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Dark theme"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"disconnected" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -243,6 +276,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"lightThemeTitle" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Light theme"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"logs" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
|
||||
-59
@@ -41,62 +41,3 @@ public struct NetworkButton: View {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public struct NetworkButtonViewModel {
|
||||
public enum ButtonType {
|
||||
case mixnet
|
||||
case wireguard
|
||||
|
||||
var imageName: String {
|
||||
switch self {
|
||||
case .mixnet:
|
||||
return "mixnetIcon"
|
||||
case .wireguard:
|
||||
return "wireguardIcon"
|
||||
}
|
||||
}
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .mixnet:
|
||||
"5hopMixnetTitle".localizedString
|
||||
case .wireguard:
|
||||
"2hopWireGuardTitle".localizedString
|
||||
}
|
||||
}
|
||||
|
||||
var subtitle: String {
|
||||
switch self {
|
||||
case .mixnet:
|
||||
"5hopMixnetSubtitle".localizedString
|
||||
case .wireguard:
|
||||
"2hopWireGuardSubtitle".localizedString
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let type: ButtonType
|
||||
|
||||
@Binding var selectedNetwork: ButtonType
|
||||
|
||||
public init(type: ButtonType, selectedNetwork: Binding<ButtonType>) {
|
||||
self.type = type
|
||||
self._selectedNetwork = selectedNetwork
|
||||
}
|
||||
|
||||
private var isSelected: Bool {
|
||||
type == selectedNetwork
|
||||
}
|
||||
|
||||
var selectionImageName: String {
|
||||
isSelected ? "networkSelectedCircle" : "networkCircle"
|
||||
}
|
||||
|
||||
var selectionImageColor: Color {
|
||||
isSelected ? NymColor.primaryOrange : NymColor.networkButtonCircle
|
||||
}
|
||||
|
||||
var selectionStrokeColor: Color {
|
||||
isSelected ? NymColor.primaryOrange : .clear
|
||||
}
|
||||
}
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
import SwiftUI
|
||||
import Theme
|
||||
|
||||
public struct NetworkButtonViewModel {
|
||||
public enum ButtonType {
|
||||
case mixnet
|
||||
case wireguard
|
||||
|
||||
var imageName: String {
|
||||
switch self {
|
||||
case .mixnet:
|
||||
return "mixnetIcon"
|
||||
case .wireguard:
|
||||
return "wireguardIcon"
|
||||
}
|
||||
}
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .mixnet:
|
||||
"5hopMixnetTitle".localizedString
|
||||
case .wireguard:
|
||||
"2hopWireGuardTitle".localizedString
|
||||
}
|
||||
}
|
||||
|
||||
var subtitle: String {
|
||||
switch self {
|
||||
case .mixnet:
|
||||
"5hopMixnetSubtitle".localizedString
|
||||
case .wireguard:
|
||||
"2hopWireGuardSubtitle".localizedString
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let type: ButtonType
|
||||
|
||||
@Binding var selectedNetwork: ButtonType
|
||||
|
||||
public init(type: ButtonType, selectedNetwork: Binding<ButtonType>) {
|
||||
self.type = type
|
||||
self._selectedNetwork = selectedNetwork
|
||||
}
|
||||
|
||||
private var isSelected: Bool {
|
||||
type == selectedNetwork
|
||||
}
|
||||
|
||||
var selectionImageName: String {
|
||||
isSelected ? "networkSelectedCircle" : "networkCircle"
|
||||
}
|
||||
|
||||
var selectionImageColor: Color {
|
||||
isSelected ? NymColor.primaryOrange : NymColor.networkButtonCircle
|
||||
}
|
||||
|
||||
var selectionStrokeColor: Color {
|
||||
isSelected ? NymColor.primaryOrange : .clear
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
import SwiftUI
|
||||
import Theme
|
||||
|
||||
public struct SettingButton: View {
|
||||
private let viewModel: SettingButtonViewModel
|
||||
|
||||
public init(viewModel: SettingButtonViewModel) {
|
||||
self.viewModel = viewModel
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
VStack {
|
||||
HStack {
|
||||
Image(viewModel.selectionImageName, bundle: .module)
|
||||
.foregroundStyle(viewModel.selectionImageColor)
|
||||
.padding(.leading, 16)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text(viewModel.title)
|
||||
.foregroundStyle(NymColor.sysOnSurface)
|
||||
.textStyle(.Body.Large.primary)
|
||||
if let subtitle = viewModel.subtitle {
|
||||
Text(subtitle)
|
||||
.foregroundStyle(NymColor.sysOutline)
|
||||
.textStyle(.Body.Medium.primary)
|
||||
}
|
||||
}
|
||||
.padding(.leading, 8)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, minHeight: 64, maxHeight: 64)
|
||||
.background(NymColor.navigationBarBackground)
|
||||
.cornerRadius(8)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.inset(by: 0.5)
|
||||
.stroke(viewModel.selectionStrokeColor)
|
||||
)
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import SwiftUI
|
||||
import Theme
|
||||
|
||||
public struct SettingButtonViewModel {
|
||||
let title: String
|
||||
let subtitle: String?
|
||||
let isSelected: Bool
|
||||
|
||||
public init(title: String, subtitle: String?, isSelected: Bool) {
|
||||
self.title = title
|
||||
self.subtitle = subtitle
|
||||
self.isSelected = isSelected
|
||||
}
|
||||
|
||||
var selectionStrokeColor: Color {
|
||||
isSelected ? NymColor.primaryOrange : .clear
|
||||
}
|
||||
|
||||
var selectionImageName: String {
|
||||
isSelected ? "networkSelectedCircle" : "networkCircle"
|
||||
}
|
||||
|
||||
var selectionImageColor: Color {
|
||||
isSelected ? NymColor.primaryOrange : NymColor.networkButtonCircle
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,9 @@ public struct SettingsListItem: View {
|
||||
)
|
||||
)
|
||||
.padding(.horizontal, 16)
|
||||
.onTapGesture {
|
||||
viewModel.action()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user