Skip to content

Commit

Permalink
Add FeaturesIndicatoresView
Browse files Browse the repository at this point in the history
  • Loading branch information
mojganii authored and rablador committed Dec 16, 2024
1 parent 37e90ed commit f6e8d4f
Show file tree
Hide file tree
Showing 18 changed files with 1,655 additions and 45 deletions.
10 changes: 9 additions & 1 deletion ios/MullvadSettings/IPOverrideRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,23 @@
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import Foundation
import Combine
import MullvadLogging

public protocol IPOverrideRepositoryProtocol {
var overridesPublisher: AnyPublisher<[IPOverride], Never> { get }
func add(_ overrides: [IPOverride])
func fetchAll() -> [IPOverride]
func deleteAll()
func parse(data: Data) throws -> [IPOverride]
}

public class IPOverrideRepository: IPOverrideRepositoryProtocol {
private let overridesSubject: CurrentValueSubject<[IPOverride], Never> = .init([])
public var overridesPublisher: AnyPublisher<[IPOverride], Never> {
overridesSubject.eraseToAnyPublisher()
}

private let logger = Logger(label: "IPOverrideRepository")
private let readWriteLock = NSLock()

Expand Down Expand Up @@ -58,6 +64,7 @@ public class IPOverrideRepository: IPOverrideRepositoryProtocol {
do {
try readWriteLock.withLock {
try SettingsManager.store.delete(key: .ipOverrides)
overridesSubject.send([])
}
} catch {
logger.error("Could not delete all overrides. \nError: \(error)")
Expand Down Expand Up @@ -85,6 +92,7 @@ public class IPOverrideRepository: IPOverrideRepositoryProtocol {

try readWriteLock.withLock {
try SettingsManager.store.write(data, for: .ipOverrides)
overridesSubject.send(overrides)
}
}

Expand Down
4 changes: 4 additions & 0 deletions ios/MullvadSettings/WireGuardObfuscationSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ public enum WireGuardObfuscationState: Codable {
self = .off
}
}

public var isEnabled: Bool {
[.udpOverTcp, .shadowsocks].contains(self)
}
}

public enum WireGuardObfuscationUdpOverTcpPort: Codable, Equatable, CustomStringConvertible {
Expand Down
53 changes: 40 additions & 13 deletions ios/MullvadVPN.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -659,10 +659,10 @@
7AF9BE902A39F26000DBFEDB /* Collection+Sorting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF9BE8F2A39F26000DBFEDB /* Collection+Sorting.swift */; };
7AF9BE952A40461100DBFEDB /* RelayFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF9BE942A40461100DBFEDB /* RelayFilterView.swift */; };
7AF9BE972A41C71F00DBFEDB /* ChipViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF9BE962A41C71F00DBFEDB /* ChipViewCell.swift */; };
7AFBE38B2D09AAFF002335FC /* SinglehopPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE38A2D09AAFF002335FC /* SinglehopPicker.swift */; };
7AFBE38D2D09AB2E002335FC /* MultihopPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE38C2D09AB2E002335FC /* MultihopPicker.swift */; };
7AFBE3872D084C9D002335FC /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE3862D084C96002335FC /* ActivityIndicator.swift */; };
7AFBE3892D089163002335FC /* FI_TunnelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE3882D08915D002335FC /* FI_TunnelViewController.swift */; };
7AFBE38B2D09AAFF002335FC /* SinglehopPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE38A2D09AAFF002335FC /* SinglehopPicker.swift */; };
7AFBE38D2D09AB2E002335FC /* MultihopPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE38C2D09AB2E002335FC /* MultihopPicker.swift */; };
850201DB2B503D7700EF8C96 /* RelayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850201DA2B503D7700EF8C96 /* RelayTests.swift */; };
850201DD2B503D8C00EF8C96 /* SelectLocationPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850201DC2B503D8C00EF8C96 /* SelectLocationPage.swift */; };
850201DF2B5040A500EF8C96 /* TunnelControlPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850201DE2B5040A500EF8C96 /* TunnelControlPage.swift */; };
Expand Down Expand Up @@ -997,7 +997,14 @@
F0ADC3722CD3AD1600A1AD97 /* ChipCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0ADC3712CD3AD1600A1AD97 /* ChipCollectionView.swift */; };
F0ADC3742CD3C47400A1AD97 /* ChipFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0ADC3732CD3C47400A1AD97 /* ChipFlowLayout.swift */; };
F0ADF1CD2CFDFF3100299F09 /* StringConversionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0ADF1CC2CFDFF3100299F09 /* StringConversionError.swift */; };
F0ADF1D12D01B55C00299F09 /* ChipModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0ADF1D02D01B55C00299F09 /* ChipModel.swift */; };
F0ADF1D32D01B6B400299F09 /* FeatureIndicatorsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0ADF1D22D01B6B400299F09 /* FeatureIndicatorsViewModel.swift */; };
F0ADF1D52D01DCFD00299F09 /* ChipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0ADF1D42D01DCFD00299F09 /* ChipView.swift */; };
F0B0E6972AFE6E7E001DC66B /* XCTest+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0B0E6962AFE6E7E001DC66B /* XCTest+Async.swift */; };
F0B495762D02025200CFEC2A /* ChipContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0B495752D02025200CFEC2A /* ChipContainerView.swift */; };
F0B495782D02038B00CFEC2A /* ChipViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0B495772D02038B00CFEC2A /* ChipViewModelProtocol.swift */; };
F0B4957A2D02F49200CFEC2A /* ChipFeatures.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0B495792D02F41F00CFEC2A /* ChipFeatures.swift */; };
F0B4957C2D03154200CFEC2A /* FeatureIndicatorsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0B4957B2D03154200CFEC2A /* FeatureIndicatorsView.swift */; };
F0B894EF2BF751C500817A42 /* RelayWithLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0B894EE2BF751C500817A42 /* RelayWithLocation.swift */; };
F0B894F12BF751E300817A42 /* RelayWithDistance.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0B894F02BF751E300817A42 /* RelayWithDistance.swift */; };
F0B894F32BF7526700817A42 /* RelaySelector+Wireguard.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0B894F22BF7526700817A42 /* RelaySelector+Wireguard.swift */; };
Expand Down Expand Up @@ -2020,10 +2027,10 @@
7AF9BE8F2A39F26000DBFEDB /* Collection+Sorting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+Sorting.swift"; sourceTree = "<group>"; };
7AF9BE942A40461100DBFEDB /* RelayFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayFilterView.swift; sourceTree = "<group>"; };
7AF9BE962A41C71F00DBFEDB /* ChipViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChipViewCell.swift; sourceTree = "<group>"; };
7AFBE38A2D09AAFF002335FC /* SinglehopPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SinglehopPicker.swift; sourceTree = "<group>"; };
7AFBE38C2D09AB2E002335FC /* MultihopPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultihopPicker.swift; sourceTree = "<group>"; };
7AFBE3862D084C96002335FC /* ActivityIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicator.swift; sourceTree = "<group>"; };
7AFBE3882D08915D002335FC /* FI_TunnelViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FI_TunnelViewController.swift; sourceTree = "<group>"; };
7AFBE38A2D09AAFF002335FC /* SinglehopPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SinglehopPicker.swift; sourceTree = "<group>"; };
7AFBE38C2D09AB2E002335FC /* MultihopPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultihopPicker.swift; sourceTree = "<group>"; };
85006A8E2B73EF67004AD8FB /* MullvadVPNUITestsSmoke.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = MullvadVPNUITestsSmoke.xctestplan; sourceTree = "<group>"; };
850201DA2B503D7700EF8C96 /* RelayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayTests.swift; sourceTree = "<group>"; };
850201DC2B503D8C00EF8C96 /* SelectLocationPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectLocationPage.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2236,7 +2243,14 @@
F0ADC3712CD3AD1600A1AD97 /* ChipCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChipCollectionView.swift; sourceTree = "<group>"; };
F0ADC3732CD3C47400A1AD97 /* ChipFlowLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChipFlowLayout.swift; sourceTree = "<group>"; };
F0ADF1CC2CFDFF3100299F09 /* StringConversionError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringConversionError.swift; sourceTree = "<group>"; };
F0ADF1D02D01B55C00299F09 /* ChipModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChipModel.swift; sourceTree = "<group>"; };
F0ADF1D22D01B6B400299F09 /* FeatureIndicatorsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureIndicatorsViewModel.swift; sourceTree = "<group>"; };
F0ADF1D42D01DCFD00299F09 /* ChipView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChipView.swift; sourceTree = "<group>"; };
F0B0E6962AFE6E7E001DC66B /* XCTest+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTest+Async.swift"; sourceTree = "<group>"; };
F0B495752D02025200CFEC2A /* ChipContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChipContainerView.swift; sourceTree = "<group>"; };
F0B495772D02038B00CFEC2A /* ChipViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChipViewModelProtocol.swift; sourceTree = "<group>"; };
F0B495792D02F41F00CFEC2A /* ChipFeatures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChipFeatures.swift; sourceTree = "<group>"; };
F0B4957B2D03154200CFEC2A /* FeatureIndicatorsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureIndicatorsView.swift; sourceTree = "<group>"; };
F0B894EE2BF751C500817A42 /* RelayWithLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayWithLocation.swift; sourceTree = "<group>"; };
F0B894F02BF751E300817A42 /* RelayWithDistance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayWithDistance.swift; sourceTree = "<group>"; };
F0B894F22BF7526700817A42 /* RelaySelector+Wireguard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RelaySelector+Wireguard.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3624,7 +3638,6 @@
A9D9A4C12C36D53C004088DD /* MullvadRustRuntimeTests */,
58CE5E61224146200008646E /* Products */,
584F991F2902CBDD001F858D /* Frameworks */,
7A0EAE982D01B29E00D3EB8B /* Recovered References */,
);
sourceTree = "<group>";
};
Expand Down Expand Up @@ -3925,14 +3938,6 @@
path = Edit;
sourceTree = "<group>";
};
7A0EAE982D01B29E00D3EB8B /* Recovered References */ = {
isa = PBXGroup;
children = (
7AA1309C2D0072F900640DF9 /* View+Size.swift */,
);
name = "Recovered References";
sourceTree = "<group>";
};
7A2960F72A964A3500389B82 /* Alert */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -4067,9 +4072,13 @@
7AA130972CFF364F00640DF9 /* FeatureIndicators */ = {
isa = PBXGroup;
children = (
F0ADF1CF2D01B50B00299F09 /* ChipView */,
7AFBE3862D084C96002335FC /* ActivityIndicator.swift */,
F0B495792D02F41F00CFEC2A /* ChipFeatures.swift */,
7AA130982CFF365A00640DF9 /* ConnectionView.swift */,
7A0EAEA32D06DF8200D3EB8B /* ConnectionViewViewModel.swift */,
F0B4957B2D03154200CFEC2A /* FeatureIndicatorsView.swift */,
F0ADF1D22D01B6B400299F09 /* FeatureIndicatorsViewModel.swift */,
7AFBE3882D08915D002335FC /* FI_TunnelViewController.swift */,
);
path = FeatureIndicators;
Expand Down Expand Up @@ -4392,6 +4401,17 @@
path = MullvadTypes;
sourceTree = "<group>";
};
F0ADF1CF2D01B50B00299F09 /* ChipView */ = {
isa = PBXGroup;
children = (
F0B495752D02025200CFEC2A /* ChipContainerView.swift */,
F0ADF1D02D01B55C00299F09 /* ChipModel.swift */,
F0ADF1D42D01DCFD00299F09 /* ChipView.swift */,
F0B495772D02038B00CFEC2A /* ChipViewModelProtocol.swift */,
);
path = ChipView;
sourceTree = "<group>";
};
F0DC779F2B2222D20087F09D /* Relay */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -5917,6 +5937,7 @@
5878A27129091CF20096FC88 /* AccountInteractor.swift in Sources */,
7AF9BE882A30C62100DBFEDB /* SelectableSettingsCell.swift in Sources */,
58CCA010224249A1004F3011 /* TunnelViewController.swift in Sources */,
F0B495782D02038B00CFEC2A /* ChipViewModelProtocol.swift in Sources */,
58CEB30A2AFD584700E6E088 /* CustomCellDisclosureHandling.swift in Sources */,
58B26E22294351EA00D5980C /* InAppNotificationProvider.swift in Sources */,
5893716A28817A45004EE76C /* DeviceManagementViewController.swift in Sources */,
Expand Down Expand Up @@ -5991,6 +6012,7 @@
58293FB3251241B4005D0BB5 /* CustomTextView.swift in Sources */,
586A950E290125F3007BAF2B /* ProductsRequestOperation.swift in Sources */,
7AF9BE902A39F26000DBFEDB /* Collection+Sorting.swift in Sources */,
F0B495762D02025200CFEC2A /* ChipContainerView.swift in Sources */,
58F19E35228C15BA00C7710B /* SpinnerActivityIndicatorView.swift in Sources */,
7A0EAE9A2D01B41500D3EB8B /* MainButtonStyle.swift in Sources */,
58CEB3022AFD365600E6E088 /* SwitchCellContentConfiguration.swift in Sources */,
Expand Down Expand Up @@ -6100,6 +6122,7 @@
588D7EDE2AF3A585005DF40A /* ListAccessMethodItem.swift in Sources */,
5827B0B02B0F4CCD00CCBBA1 /* ListAccessMethodViewControllerDelegate.swift in Sources */,
588D7EE02AF3A595005DF40A /* ListAccessMethodInteractor.swift in Sources */,
F0B4957A2D02F49200CFEC2A /* ChipFeatures.swift in Sources */,
58607A4D2947287800BC467D /* AccountExpiryInAppNotificationProvider.swift in Sources */,
7A8A18FD2CE4BE8D000BCB5B /* CustomToggleStyle.swift in Sources */,
58C8191829FAA2C400DEB1B4 /* NotificationConfiguration.swift in Sources */,
Expand Down Expand Up @@ -6134,8 +6157,10 @@
586C0D782B039CC000E7CDD7 /* AccessMethodProtocolPicker.swift in Sources */,
58677710290975E9006F721F /* SettingsInteractorFactory.swift in Sources */,
7A9CCCC02A96302800DD6A34 /* ProfileVoucherCoordinator.swift in Sources */,
F0B4957C2D03154200CFEC2A /* FeatureIndicatorsView.swift in Sources */,
7A9CCCBC2A96302800DD6A34 /* ChangeLogCoordinator.swift in Sources */,
58B26E282943527300D5980C /* SystemNotificationProvider.swift in Sources */,
F0ADF1D52D01DCFD00299F09 /* ChipView.swift in Sources */,
586C0D932B03D90700E7CDD7 /* ShadowsocksItemIdentifier.swift in Sources */,
58EFC7712AFB45E500E9F4CB /* SettingsChildCoordinator.swift in Sources */,
7A8A19102CEE391B000BCB5B /* RowSeparator.swift in Sources */,
Expand Down Expand Up @@ -6179,6 +6204,7 @@
7A9CCCC22A96302800DD6A34 /* SafariCoordinator.swift in Sources */,
58CEB3082AFD484100E6E088 /* BasicCell.swift in Sources */,
7A5869C12B57D21A00640D27 /* IPOverrideStatusView.swift in Sources */,
F0ADF1D32D01B6B400299F09 /* FeatureIndicatorsViewModel.swift in Sources */,
58CEB2F52AFD0BB500E6E088 /* TextCellContentConfiguration.swift in Sources */,
58E20771274672CA00DE5D77 /* LaunchViewController.swift in Sources */,
F0E8CC032A4C753B007ED3B4 /* WelcomeViewController.swift in Sources */,
Expand All @@ -6198,6 +6224,7 @@
A99E5EE02B7628150033F241 /* ProblemReportViewModel.swift in Sources */,
58FD5BF024238EB300112C88 /* SKProduct+Formatting.swift in Sources */,
58B43C1925F77DB60002C8C3 /* TunnelControlView.swift in Sources */,
F0ADF1D12D01B55C00299F09 /* ChipModel.swift in Sources */,
F09A297B2A9F8A9B00EA3B6F /* LogoutDialogueView.swift in Sources */,
58CEB2FB2AFD13E600E6E088 /* UIListContentConfiguration+Extensions.swift in Sources */,
5811DE50239014550011EB53 /* NEVPNStatus+Debug.swift in Sources */,
Expand Down
3 changes: 2 additions & 1 deletion ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,8 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
private func makeTunnelCoordinator() -> TunnelCoordinator {
let tunnelCoordinator = TunnelCoordinator(
tunnelManager: tunnelManager,
outgoingConnectionService: outgoingConnectionService
outgoingConnectionService: outgoingConnectionService,
ipOverrideRepository: ipOverrideRepository
)

tunnelCoordinator.showSelectLocationPicker = { [weak self] in
Expand Down
7 changes: 5 additions & 2 deletions ios/MullvadVPN/Coordinators/TunnelCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// Copyright © 2023 Mullvad VPN AB. All rights reserved.
//

import MullvadSettings
import Routing
import UIKit

Expand All @@ -32,13 +33,15 @@ class TunnelCoordinator: Coordinator, Presenting {

init(
tunnelManager: TunnelManager,
outgoingConnectionService: OutgoingConnectionServiceHandling
outgoingConnectionService: OutgoingConnectionServiceHandling,
ipOverrideRepository: IPOverrideRepositoryProtocol
) {
self.tunnelManager = tunnelManager

let interactor = TunnelViewControllerInteractor(
tunnelManager: tunnelManager,
outgoingConnectionService: outgoingConnectionService
outgoingConnectionService: outgoingConnectionService,
ipOverrideRepository: ipOverrideRepository
)

#if DEBUG
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//
// ChipFeatures.swift
// MullvadVPN
//
// Created by Mojgan on 2024-12-06.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//
import Foundation
import MullvadSettings
import SwiftUI

protocol ChipFeature {
var isEnabled: Bool { get }
var name: LocalizedStringKey { get }
}

struct DaitaFeature: ChipFeature {
let settings: LatestTunnelSettings

var isEnabled: Bool {
settings.daita.daitaState.isEnabled
}

var name: LocalizedStringKey {
LocalizedStringKey("DAITA")
}
}

struct QuantumResistanceFeature: ChipFeature {
let settings: LatestTunnelSettings
var isEnabled: Bool {
settings.tunnelQuantumResistance.isEnabled
}

var name: LocalizedStringKey {
LocalizedStringKey("Quantum resistance")
}
}

struct MultihopFeature: ChipFeature {
let settings: LatestTunnelSettings
var isEnabled: Bool {
settings.tunnelMultihopState.isEnabled
}

var name: LocalizedStringKey {
LocalizedStringKey("Multihop")
}
}

struct ObfuscationFeature: ChipFeature {
let settings: LatestTunnelSettings

var isEnabled: Bool {
settings.wireGuardObfuscation.state.isEnabled
}

var name: LocalizedStringKey {
LocalizedStringKey("Obfuscation")
}
}

struct DNSFeature: ChipFeature {
let settings: LatestTunnelSettings

var isEnabled: Bool {
settings.dnsSettings.enableCustomDNS || !settings.dnsSettings.blockingOptions.isEmpty
}

var name: LocalizedStringKey {
if !settings.dnsSettings.blockingOptions.isEmpty {
return LocalizedStringKey("DNS content blockers")
}
return LocalizedStringKey("Custom DNS")
}
}

struct IPOverrideFeature: ChipFeature {
let overrides: [IPOverride]

var isEnabled: Bool {
!overrides.isEmpty
}

var name: LocalizedStringKey {
LocalizedStringKey("Server IP override")
}
}
Loading

0 comments on commit f6e8d4f

Please sign in to comment.