From 4dc7f05ac9bef6102892db5e118b62642ecdbd5f Mon Sep 17 00:00:00 2001 From: mojganii Date: Wed, 11 Dec 2024 15:13:09 +0100 Subject: [PATCH] Add FeaturesIndicatoresView --- .../WireGuardObfuscationSettings.swift | 4 + ios/MullvadVPN.xcodeproj/project.pbxproj | 36 ++++++++ .../FeatureIndicators/ChipFeatures.swift | 88 ++++++++++++++++++ .../ChipsView/ChipContainerView.swift | 89 +++++++++++++++++++ .../ChipsView/ChipModel.swift | 15 ++++ .../ChipsView/ChipView.swift | 39 ++++++++ .../ChipsView/ChipViewModelProtocol.swift | 24 +++++ .../FeatureIndicators/ConnectionView.swift | 1 + .../FeatureChipViewModel.swift | 50 +++++++++++ .../FeaturesIndicatoresView.swift | 38 ++++++++ 10 files changed, 384 insertions(+) create mode 100644 ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ChipFeatures.swift create mode 100644 ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ChipsView/ChipContainerView.swift create mode 100644 ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ChipsView/ChipModel.swift create mode 100644 ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ChipsView/ChipView.swift create mode 100644 ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ChipsView/ChipViewModelProtocol.swift create mode 100644 ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/FeatureChipViewModel.swift create mode 100644 ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/FeaturesIndicatoresView.swift diff --git a/ios/MullvadSettings/WireGuardObfuscationSettings.swift b/ios/MullvadSettings/WireGuardObfuscationSettings.swift index f067114cc634..bc08cfa7c9ca 100644 --- a/ios/MullvadSettings/WireGuardObfuscationSettings.swift +++ b/ios/MullvadSettings/WireGuardObfuscationSettings.swift @@ -46,6 +46,10 @@ public enum WireGuardObfuscationState: Codable { self = .off } } + + public var isEnabled: Bool { + self != .off + } } public enum WireGuardObfuscationUdpOverTcpPort: Codable, Equatable, CustomStringConvertible { diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 36ca68285350..362f5285a97d 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -990,7 +990,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 /* FeatureChipViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0ADF1D22D01B6B400299F09 /* FeatureChipViewModel.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 /* FeaturesIndicatoresView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0B4957B2D03154200CFEC2A /* FeaturesIndicatoresView.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 */; }; @@ -2222,7 +2229,14 @@ F0ADC3712CD3AD1600A1AD97 /* ChipCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChipCollectionView.swift; sourceTree = ""; }; F0ADC3732CD3C47400A1AD97 /* ChipFlowLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChipFlowLayout.swift; sourceTree = ""; }; F0ADF1CC2CFDFF3100299F09 /* StringConversionError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringConversionError.swift; sourceTree = ""; }; + F0ADF1D02D01B55C00299F09 /* ChipModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChipModel.swift; sourceTree = ""; }; + F0ADF1D22D01B6B400299F09 /* FeatureChipViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureChipViewModel.swift; sourceTree = ""; }; + F0ADF1D42D01DCFD00299F09 /* ChipView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChipView.swift; sourceTree = ""; }; F0B0E6962AFE6E7E001DC66B /* XCTest+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTest+Async.swift"; sourceTree = ""; }; + F0B495752D02025200CFEC2A /* ChipContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChipContainerView.swift; sourceTree = ""; }; + F0B495772D02038B00CFEC2A /* ChipViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChipViewModelProtocol.swift; sourceTree = ""; }; + F0B495792D02F41F00CFEC2A /* ChipFeatures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChipFeatures.swift; sourceTree = ""; }; + F0B4957B2D03154200CFEC2A /* FeaturesIndicatoresView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturesIndicatoresView.swift; sourceTree = ""; }; F0B894EE2BF751C500817A42 /* RelayWithLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayWithLocation.swift; sourceTree = ""; }; F0B894F02BF751E300817A42 /* RelayWithDistance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayWithDistance.swift; sourceTree = ""; }; F0B894F22BF7526700817A42 /* RelaySelector+Wireguard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RelaySelector+Wireguard.swift"; sourceTree = ""; }; @@ -4042,6 +4056,10 @@ 7AA130972CFF364F00640DF9 /* FeatureIndicators */ = { isa = PBXGroup; children = ( + F0B495792D02F41F00CFEC2A /* ChipFeatures.swift */, + F0ADF1CF2D01B50B00299F09 /* ChipsView */, + F0ADF1D22D01B6B400299F09 /* FeatureChipViewModel.swift */, + F0B4957B2D03154200CFEC2A /* FeaturesIndicatoresView.swift */, 7AA130982CFF365A00640DF9 /* ConnectionView.swift */, ); path = FeatureIndicators; @@ -4354,6 +4372,17 @@ path = MullvadTypes; sourceTree = ""; }; + F0ADF1CF2D01B50B00299F09 /* ChipsView */ = { + isa = PBXGroup; + children = ( + F0B495752D02025200CFEC2A /* ChipContainerView.swift */, + F0ADF1D02D01B55C00299F09 /* ChipModel.swift */, + F0ADF1D42D01DCFD00299F09 /* ChipView.swift */, + F0B495772D02038B00CFEC2A /* ChipViewModelProtocol.swift */, + ); + path = ChipsView; + sourceTree = ""; + }; F0DC779F2B2222D20087F09D /* Relay */ = { isa = PBXGroup; children = ( @@ -5874,6 +5903,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 */, @@ -5948,6 +5978,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 */, @@ -6055,6 +6086,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 */, @@ -6089,8 +6121,10 @@ 586C0D782B039CC000E7CDD7 /* AccessMethodProtocolPicker.swift in Sources */, 58677710290975E9006F721F /* SettingsInteractorFactory.swift in Sources */, 7A9CCCC02A96302800DD6A34 /* ProfileVoucherCoordinator.swift in Sources */, + F0B4957C2D03154200CFEC2A /* FeaturesIndicatoresView.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 */, @@ -6134,6 +6168,7 @@ 7A9CCCC22A96302800DD6A34 /* SafariCoordinator.swift in Sources */, 58CEB3082AFD484100E6E088 /* BasicCell.swift in Sources */, 7A5869C12B57D21A00640D27 /* IPOverrideStatusView.swift in Sources */, + F0ADF1D32D01B6B400299F09 /* FeatureChipViewModel.swift in Sources */, 58CEB2F52AFD0BB500E6E088 /* TextCellContentConfiguration.swift in Sources */, 58E20771274672CA00DE5D77 /* LaunchViewController.swift in Sources */, F0E8CC032A4C753B007ED3B4 /* WelcomeViewController.swift in Sources */, @@ -6153,6 +6188,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 */, diff --git a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ChipFeatures.swift b/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ChipFeatures.swift new file mode 100644 index 000000000000..dab3ed78d4c0 --- /dev/null +++ b/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ChipFeatures.swift @@ -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 } + func name() -> LocalizedStringKey +} + +struct DaitaFeature: ChipFeature { + let settings: LatestTunnelSettings + + var isEnabled: Bool { + settings.daita.daitaState.isEnabled + } + + func name() -> LocalizedStringKey { + LocalizedStringKey("DAITA") + } +} + +struct QuantumResistanceFeature: ChipFeature { + let settings: LatestTunnelSettings + var isEnabled: Bool { + settings.tunnelQuantumResistance.isEnabled + } + + func name() -> LocalizedStringKey { + LocalizedStringKey("Quantum resistance") + } +} + +struct MultihopFeature: ChipFeature { + let settings: LatestTunnelSettings + var isEnabled: Bool { + settings.tunnelMultihopState.isEnabled + } + + func name() -> LocalizedStringKey { + LocalizedStringKey("Multihop") + } +} + +struct ObfuscationFeature: ChipFeature { + let settings: LatestTunnelSettings + + var isEnabled: Bool { + settings.wireGuardObfuscation.state.isEnabled + } + + func name() -> LocalizedStringKey { + LocalizedStringKey("Obfuscation") + } +} + +struct DNSFeature: ChipFeature { + let settings: LatestTunnelSettings + + var isEnabled: Bool { + settings.dnsSettings.enableCustomDNS || !settings.dnsSettings.blockingOptions.isEmpty + } + + func name() -> LocalizedStringKey { + if !settings.dnsSettings.blockingOptions.isEmpty { + return LocalizedStringKey("DNS content blockers") + } + return LocalizedStringKey("Custom DNS") + } +} + +struct IPOverrideFeature: ChipFeature { + let repository: IPOverrideRepositoryProtocol + + var isEnabled: Bool { + !repository.fetchAll().isEmpty + } + + func name() -> LocalizedStringKey { + LocalizedStringKey("Server IP override") + } +} diff --git a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ChipsView/ChipContainerView.swift b/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ChipsView/ChipContainerView.swift new file mode 100644 index 000000000000..ba634bebbdf9 --- /dev/null +++ b/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ChipsView/ChipContainerView.swift @@ -0,0 +1,89 @@ +// +// ChipContainerView.swift +// MullvadVPN +// +// Created by Mojgan on 2024-12-05. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import SwiftUI +struct ChipContainerView: View where ViewModel: ChipViewModelProtocol { + @ObservedObject var viewModel: ViewModel + @State private var isExpanded = false + + init(viewModel: ViewModel) { + self.viewModel = viewModel + } + + var body: some View { + GeometryReader { geo in + let containerWidth = geo.size.width + let chipsPerLine = 2 + if isExpanded { + ZStack(alignment: .topLeading) { + createChipViews(chips: viewModel.chips, containerWidth: containerWidth) + } + } else { + HStack { + createChipViews(chips: Array(viewModel.chips.prefix(chipsPerLine)), containerWidth: containerWidth) + if viewModel.chips.count > chipsPerLine { + Button(action: { + withAnimation { + isExpanded.toggle() + } + }, label: { + Text(LocalizedStringKey("\(viewModel.chips.count - chipsPerLine) more...")) + .font(.body) + .lineLimit(1) + .multilineTextAlignment(.center) + .foregroundStyle(UIColor.primaryTextColor.color) + }) + } + Spacer() + } + } + } + } + + private func createChipViews(chips: [ChipModel], containerWidth: CGFloat) -> some View { + var width = CGFloat.zero + var height = CGFloat.zero + + return ForEach(chips) { data in + ChipView(item: data) + .padding(UIMetrics.padding4) + .alignmentGuide(.leading) { dimension in + if abs(width - dimension.width) > containerWidth { + width = 0 + height -= dimension.height + } + let result = width + if data.id == viewModel.chips.last!.id { + width = 0 + } else { + width -= dimension.width + } + return result + } + .alignmentGuide(.top) { _ in + let result = height + if data.id == viewModel.chips.last!.id { + height = 0 + } + return result + } + } + } +} + +#Preview("ChipContainerView") { + ChipContainerView(viewModel: MockChipViewModel()) +} + +private class MockChipViewModel: ChipViewModelProtocol { + @Published var chips: [ChipModel] = (5 ..< 20).map { index in + let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + return ChipModel(name: LocalizedStringKey(String((0 ..< index).map { _ in letters.randomElement()! }))) + } +} diff --git a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ChipsView/ChipModel.swift b/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ChipsView/ChipModel.swift new file mode 100644 index 000000000000..c1e990a1b1d9 --- /dev/null +++ b/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ChipsView/ChipModel.swift @@ -0,0 +1,15 @@ +// +// FeatureChipModel.swift +// MullvadVPN +// +// Created by Mojgan on 2024-12-05. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import SwiftUI + +struct ChipModel: Identifiable { + let id = UUID() + let name: LocalizedStringKey +} diff --git a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ChipsView/ChipView.swift b/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ChipsView/ChipView.swift new file mode 100644 index 000000000000..3d60a5c8f520 --- /dev/null +++ b/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ChipsView/ChipView.swift @@ -0,0 +1,39 @@ +// +// FeatureChipView.swift +// MullvadVPN +// +// Created by Mojgan on 2024-12-05. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import SwiftUI + +struct ChipView: View { + let item: ChipModel + var body: some View { + HStack(spacing: UIMetrics.padding4) { + Text(item.name) + .font(.body) + .lineLimit(1) + .multilineTextAlignment(.center) + .foregroundStyle(UIColor.primaryTextColor.color) + } + .padding(.horizontal, UIMetrics.padding8) + .padding(.vertical, UIMetrics.padding4) + .background( + RoundedRectangle(cornerRadius: 8.0) + .stroke( + UIColor.primaryColor.color, + lineWidth: 1 + ) + .background( + RoundedRectangle(cornerRadius: 8.0) + .fill(UIColor.secondaryColor.color) + ) + ) + } +} + +#Preview { + ChipView(item: ChipModel(name: LocalizedStringKey("Example"))) +} diff --git a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ChipsView/ChipViewModelProtocol.swift b/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ChipsView/ChipViewModelProtocol.swift new file mode 100644 index 000000000000..367fceaa4d5e --- /dev/null +++ b/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ChipsView/ChipViewModelProtocol.swift @@ -0,0 +1,24 @@ +// +// ChipViewModelProtocol.swift +// MullvadVPN +// +// Created by Mojgan on 2024-12-05. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import SwiftUI +protocol ChipViewModelProtocol: ObservableObject { + var chips: [ChipModel] { get } +} + +class FeaturesIndicatoresMockViewModel: ChipViewModelProtocol { + @Published var chips: [ChipModel] = [ + ChipModel(name: LocalizedStringKey("DAITA")), + ChipModel(name: LocalizedStringKey("Obfuscation")), + ChipModel(name: LocalizedStringKey("Quantum resistance")), + ChipModel(name: LocalizedStringKey("Multihop")), + ChipModel(name: LocalizedStringKey("DNS content blockers")), + ChipModel(name: LocalizedStringKey("Custom DNS")), + ChipModel(name: LocalizedStringKey("Server IP override")), + ] +} diff --git a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView.swift b/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView.swift index 3f3d4e473bde..d1fc0c2f822f 100644 --- a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView.swift +++ b/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView.swift @@ -17,6 +17,7 @@ struct ConnectionView: View { VStack(alignment: .leading, spacing: 16) { ConnectionPanel() + FeaturesIndicatoresView(viewModel: FeaturesIndicatoresMockViewModel()) ButtonPanel() } .padding(16) diff --git a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/FeatureChipViewModel.swift b/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/FeatureChipViewModel.swift new file mode 100644 index 000000000000..fb31553b561c --- /dev/null +++ b/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/FeatureChipViewModel.swift @@ -0,0 +1,50 @@ +// +// FeatureChipViewModel.swift +// MullvadVPN +// +// Created by Mojgan on 2024-12-05. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import MullvadSettings +import SwiftUI +class FeatureChipViewModel: ChipViewModelProtocol { + @Published var chips: [ChipModel] = [] + + let tunnelManager: TunnelManager + let ipOverrideRepository: IPOverrideRepositoryProtocol + var observer: TunnelObserver? + + init(tunnelManager: TunnelManager, ipOverrideRepository: IPOverrideRepositoryProtocol) { + self.tunnelManager = tunnelManager + self.ipOverrideRepository = ipOverrideRepository + let observer = TunnelBlockObserver( + didLoadConfiguration: { [weak self] tunnelManager in + guard let self else { return } + chips = createChips(tunnelManager.settings) + }, + didUpdateTunnelSettings: { [weak self] _, latestTunnelSettings in + guard let self else { return } + chips = createChips(latestTunnelSettings) + } + ) + self.observer = observer + tunnelManager.addObserver(observer) + } + + private func createChips(_ latestTunnelSettings: LatestTunnelSettings) -> [ChipModel] { + let features: [ChipFeature] = [ + DaitaFeature(settings: latestTunnelSettings), + QuantumResistanceFeature(settings: latestTunnelSettings), + MultihopFeature(settings: latestTunnelSettings), + ObfuscationFeature(settings: latestTunnelSettings), + DNSFeature(settings: latestTunnelSettings), + IPOverrideFeature(repository: ipOverrideRepository), + ] + + return features + .filter { $0.isEnabled } + .map { ChipModel(name: $0.name()) } + } +} diff --git a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/FeaturesIndicatoresView.swift b/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/FeaturesIndicatoresView.swift new file mode 100644 index 000000000000..3bdd7eda1595 --- /dev/null +++ b/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/FeaturesIndicatoresView.swift @@ -0,0 +1,38 @@ +// +// FeaturesIndicatoresView.swift +// MullvadVPN +// +// Created by Mojgan on 2024-12-06. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import SwiftUI +struct FeaturesIndicatoresView: View where ViewModel: ChipViewModelProtocol { + @ObservedObject var viewModel: ViewModel + + var body: some View { + ZStack { + VStack(spacing: UIMetrics.padding16) { + HStack(alignment: .top) { + Text(LocalizedStringKey("Active features")) + .lineLimit(1) + .font(.body.weight(.semibold)) + .foregroundStyle(.white.opacity(0.6)) + .padding(.leading, UIMetrics.padding8) + Spacer() + } + + ScrollView { + HStack { + ChipContainerView(viewModel: viewModel) + } + } + Spacer() + } + } + } +} + +#Preview("FeaturesIndicatoresView") { + FeaturesIndicatoresView(viewModel: FeaturesIndicatoresMockViewModel()) +}