Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
rablador committed Nov 13, 2024
1 parent c838835 commit 73edc2b
Show file tree
Hide file tree
Showing 44 changed files with 384 additions and 54 deletions.
34 changes: 34 additions & 0 deletions ios/MullvadVPN.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,11 @@
7A88DCF42A93471F00D2FF0E /* ApplicationRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5893C6FB29C311E9009090D1 /* ApplicationRouter.swift */; };
7A88DCF52A93471F00D2FF0E /* ApplicationRouterTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5802EBCA2A8E45DC00E5CE4C /* ApplicationRouterTypes.swift */; };
7A88DCF62A93471F00D2FF0E /* AppRouteProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5802EBC62A8E457A00E5CE4C /* AppRouteProtocol.swift */; };
7A8A18F92CE34EA8000BCB5B /* SettingsMultihopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A18F82CE34E9F000BCB5B /* SettingsMultihopView.swift */; };
7A8A18FB2CE4B678000BCB5B /* View+TapAreaSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A18FA2CE4B66C000BCB5B /* View+TapAreaSize.swift */; };
7A8A18FD2CE4BE8D000BCB5B /* CustomToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A18FC2CE4BE88000BCB5B /* CustomToggleStyle.swift */; };
7A8A19022CE4DD56000BCB5B /* ViewGeometry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A19012CE4DD4D000BCB5B /* ViewGeometry.swift */; };
7A8A19032CE4DD56000BCB5B /* ViewGeometry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A19012CE4DD4D000BCB5B /* ViewGeometry.swift */; };
7A9BE5A22B8F88C500E2A7D0 /* LocationNodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9BE5A12B8F88C500E2A7D0 /* LocationNodeTests.swift */; };
7A9BE5A32B8F89B900E2A7D0 /* LocationNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6389F72B864CDF008E77E1 /* LocationNode.swift */; };
7A9BE5A52B90760C00E2A7D0 /* CustomListsDataSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9BE5A42B90760C00E2A7D0 /* CustomListsDataSourceTests.swift */; };
Expand Down Expand Up @@ -1880,6 +1885,10 @@
7A88DCD02A8FABBE00D2FF0E /* Routing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Routing.h; sourceTree = "<group>"; };
7A88DCD72A8FABBE00D2FF0E /* RoutingTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RoutingTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
7A88DCDE2A8FABBF00D2FF0E /* RoutingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoutingTests.swift; sourceTree = "<group>"; };
7A8A18F82CE34E9F000BCB5B /* SettingsMultihopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsMultihopView.swift; sourceTree = "<group>"; };
7A8A18FA2CE4B66C000BCB5B /* View+TapAreaSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+TapAreaSize.swift"; sourceTree = "<group>"; };
7A8A18FC2CE4BE88000BCB5B /* CustomToggleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomToggleStyle.swift; sourceTree = "<group>"; };
7A8A19012CE4DD4D000BCB5B /* ViewGeometry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewGeometry.swift; sourceTree = "<group>"; };
7A9BE5A12B8F88C500E2A7D0 /* LocationNodeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationNodeTests.swift; sourceTree = "<group>"; };
7A9BE5A42B90760C00E2A7D0 /* CustomListsDataSourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomListsDataSourceTests.swift; sourceTree = "<group>"; };
7A9BE5A82B90806800E2A7D0 /* CustomListsRepositoryStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomListsRepositoryStub.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2920,6 +2929,7 @@
58ACF64E26567A7100ACE4B7 /* CustomSwitchContainer.swift */,
58293FB025124117005D0BB5 /* CustomTextField.swift */,
58293FB2251241B3005D0BB5 /* CustomTextView.swift */,
7A8A18FC2CE4BE88000BCB5B /* CustomToggleStyle.swift */,
5892A45D265FABFF00890742 /* EmptyTableViewHeaderFooterView.swift */,
58FD5BF32428C67600112C88 /* InAppPurchaseButton.swift */,
F03580242A13842C00E5DAFD /* IncreasedHitButton.swift */,
Expand Down Expand Up @@ -3003,6 +3013,7 @@
7A21DACE2A30AA3700A787A9 /* UITextField+Appearance.swift */,
5878F4FF29CDA742003D4BE2 /* UIView+AutoLayoutBuilder.swift */,
7A516C2D2B6D357500BBD33D /* URL+Scoping.swift */,
7A8A18FA2CE4B66C000BCB5B /* View+TapAreaSize.swift */,
);
path = Extensions;
sourceTree = "<group>";
Expand Down Expand Up @@ -3052,6 +3063,7 @@
582AE30F2440A6CA00E6733A /* InputTextFormatter.swift */,
7A7907322BC0280A00B61F81 /* InterceptibleNavigationController.swift */,
58DFF7D12B0256A300F864E0 /* MarkdownStylingOptions.swift */,
7A8A19012CE4DD4D000BCB5B /* ViewGeometry.swift */,
);
path = Classes;
sourceTree = "<group>";
Expand Down Expand Up @@ -3737,6 +3749,8 @@
children = (
58CEB2E72AFBB9F300E6E088 /* APIAccess */,
7A5869A92B55516700640D27 /* IPOverride */,
7A8A18F72CE34E8F000BCB5B /* Multihop */,
7A8A18FE2CE4C7FA000BCB5B /* Views */,
58EFC7702AFB45E500E9F4CB /* SettingsChildCoordinator.swift */,
7A9CCCAD2A96302800DD6A34 /* SettingsCoordinator.swift */,
7A6389EC2B7FADA1008E77E1 /* SettingsFieldValidationErrorConfiguration.swift */,
Expand Down Expand Up @@ -3891,6 +3905,21 @@
path = RoutingTests;
sourceTree = "<group>";
};
7A8A18F72CE34E8F000BCB5B /* Multihop */ = {
isa = PBXGroup;
children = (
7A8A18F82CE34E9F000BCB5B /* SettingsMultihopView.swift */,
);
path = Multihop;
sourceTree = "<group>";
};
7A8A18FE2CE4C7FA000BCB5B /* Views */ = {
isa = PBXGroup;
children = (
);
path = Views;
sourceTree = "<group>";
};
7A9BE5A02B8F881B00E2A7D0 /* SelectLocation */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -5430,6 +5459,7 @@
A9B6AC182ADE8F4300F7802A /* MigrationManagerTests.swift in Sources */,
7A9BE5AB2B909A1700E2A7D0 /* LocationDataSourceProtocol.swift in Sources */,
A9A5FA2A2ACB05160083449F /* CoordinatesTests.swift in Sources */,
7A8A19022CE4DD56000BCB5B /* ViewGeometry.swift in Sources */,
44DD7D242B6CFFD70005F67F /* StartTunnelOperationTests.swift in Sources */,
44BB5F982BE527F4002520EB /* TunnelState+UI.swift in Sources */,
A9A5FA2B2ACB05160083449F /* CustomDateComponentsFormattingTests.swift in Sources */,
Expand Down Expand Up @@ -5638,6 +5668,7 @@
7A9CCCB72A96302800DD6A34 /* RevokedCoordinator.swift in Sources */,
7A6389F82B864CDF008E77E1 /* LocationNode.swift in Sources */,
587D96742886D87C00CD8F1C /* DeviceManagementContentView.swift in Sources */,
7A8A18F92CE34EA8000BCB5B /* SettingsMultihopView.swift in Sources */,
44BB5F972BE527F4002520EB /* TunnelState+UI.swift in Sources */,
7A11DD0B2A9495D400098CD8 /* AppRoutes.swift in Sources */,
5827B0902B0CAA0500CCBBA1 /* EditAccessMethodCoordinator.swift in Sources */,
Expand Down Expand Up @@ -5856,6 +5887,7 @@
5827B0B02B0F4CCD00CCBBA1 /* ListAccessMethodViewControllerDelegate.swift in Sources */,
588D7EE02AF3A595005DF40A /* ListAccessMethodInteractor.swift in Sources */,
58607A4D2947287800BC467D /* AccountExpiryInAppNotificationProvider.swift in Sources */,
7A8A18FD2CE4BE8D000BCB5B /* CustomToggleStyle.swift in Sources */,
58C8191829FAA2C400DEB1B4 /* NotificationConfiguration.swift in Sources */,
58FF9FE82B07650A00E4C97D /* ButtonCellContentConfiguration.swift in Sources */,
7A6652B82BB44C3E0042D848 /* LocationDiffableDataSourceProtocol.swift in Sources */,
Expand Down Expand Up @@ -5936,6 +5968,7 @@
F0E8CC032A4C753B007ED3B4 /* WelcomeViewController.swift in Sources */,
584D26C4270C855B004EA533 /* VPNSettingsDataSource.swift in Sources */,
F0D8825B2B04F53600D3EF9A /* OutgoingConnectionData.swift in Sources */,
7A8A18FB2CE4B678000BCB5B /* View+TapAreaSize.swift in Sources */,
7A6F2FAF2AFE36E7006D0856 /* VPNSettingsInfoButtonItem.swift in Sources */,
7A6389DD2B7E3BD6008E77E1 /* CustomListDataSourceConfiguration.swift in Sources */,
5827B0BF2B14B37D00CCBBA1 /* Publisher+PreviousValue.swift in Sources */,
Expand All @@ -5946,6 +5979,7 @@
7A5869A22B502EA800640D27 /* MethodSettingsSectionIdentifier.swift in Sources */,
586C0D812B03CA8400E7CDD7 /* CurrentValueSubject+UIActionBindings.swift in Sources */,
581DFAEA2B176C51005D6D1C /* PersistentProxyConfiguration+ViewModel.swift in Sources */,
7A8A19032CE4DD56000BCB5B /* ViewGeometry.swift in Sources */,
A99E5EE02B7628150033F241 /* ProblemReportViewModel.swift in Sources */,
58FD5BF024238EB300112C88 /* SKProduct+Formatting.swift in Sources */,
58B43C1925F77DB60002C8C3 /* TunnelControlView.swift in Sources */,
Expand Down
1 change: 1 addition & 0 deletions ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ public enum AccessibilityIdentifier: String {
case customListLocationCell
case daitaConfirmAlertBackButton
case daitaConfirmAlertEnableButton
case multihopCell

// Labels
case accountPageDeviceNameLabel
Expand Down
33 changes: 33 additions & 0 deletions ios/MullvadVPN/Classes/ViewGeometry.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// ViewGeometry.swift
// MullvadVPN
//
// Created by Jon Petersson on 2024-11-13.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import SwiftUI

/// Struct for measuring view size. Typically used in a `.background()`,
/// eg. `.background(ViewGeometry { actualViewSize in [...] }).
struct ViewGeometry: View {
let onSizeChange: (CGSize) -> Void

var body: some View {
GeometryReader { geometry in
Color.clear
.preference(key: ViewSizeKey.self, value: geometry.size)
.onPreferenceChange(ViewSizeKey.self) {
onSizeChange($0)
}
}
}
}

private struct ViewSizeKey: PreferenceKey {
static var defaultValue: CGSize = .zero

static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
//
// SettingsMultihopView.swift
// MullvadVPN
//
// Created by Andrew Bulhak on 2024-09-23.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import SwiftUI

struct SettingsInfoViewModel {
var body: [String]
var images: [Image]
}

struct SettingsMultihopView: View {
@State private var enabled = true

var didToggleEnabled: ((Bool) -> Void)?

var body: some View {
ZStack {
VStack(spacing: 24) {
SettingsInfoView(viewModel: SettingsInfoViewModel())

Check failure on line 24 in ios/MullvadVPN/Coordinators/Settings/Multihop/SettingsMultihopView.swift

View workflow job for this annotation

GitHub Actions / Unit tests

missing arguments for parameters 'body', 'images' in call

SwitchRowView(
enabled: enabled,
text: NSLocalizedString(
"SETTINGS_SWITCH_MULTIHOP",
tableName: "Settings",
value: "Enable",
comment: ""
),
didToggle: { didToggleEnabled?($0) }
)

Spacer()
}
.padding(UIMetrics.contentInsets.toEdgeInsets)
}
.background(Color(.secondaryColor))
.foregroundColor(Color(.primaryTextColor))
}
}

#Preview {
SettingsMultihopView { enabled in
print("\(enabled)")
}
}

struct SwitchRowView: View {
@State private var enabled: Bool
private let text: String

let didToggle: ((Bool) -> Void)
var didTapAction: (() -> Void)?

init(enabled: Bool, text: String, didToggle: @escaping ((Bool) -> Void)) {
self.enabled = enabled
self.text = text
self.didToggle = didToggle
}

var body: some View {
HStack {
Toggle(isOn: $enabled, label: {
Text(text)
}).onChange(of: enabled, perform: { enabled in
didToggle(enabled)
})
.toggleStyle(CustomToggleStyle {
didTapAction?()
})
.font(.body.weight(.semibold))
.accessibilityIdentifier(AccessibilityIdentifier.multihopSwitch.rawValue)
}
.frame(height: UIMetrics.SettingsViewCell.height)
.padding(UIMetrics.SettingsViewCell.layoutMargins)
.background(Color(.primaryColor))
.foregroundColor(Color(.primaryTextColor))
.cornerRadius(UIMetrics.SettingsViewCell.cornerRadius)
}
}

#Preview {
SwitchRowView(enabled: true, text: "Enable") { enabled in
print("\(enabled)")
}
}

struct SettingsInfoView: View {
let viewModel: SettingsInfoViewModel

var body: some View {
VStack(alignment: .leading, spacing: 16) {
Image(.multihopIllustration)
.resizable()
.aspectRatio(contentMode: .fit)
Text(NSLocalizedString(
"SETTINGS_INFO_MULTIHOP",
tableName: "Settings",
value: """
Multihop routes your traffic into one WireGuard server and out another, making it \
harder to trace. This results in increased latency but increases anonymity online.
""",
comment: ""
))
.font(.body.weight(.semibold))
.opacity(0.6)
}
}
}
27 changes: 27 additions & 0 deletions ios/MullvadVPN/Coordinators/Settings/SettingsCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import MullvadLogging
import MullvadSettings
import Operations
import Routing
import SwiftUI
import UIKit

/// Settings navigation route.
Expand All @@ -28,6 +29,9 @@ enum SettingsNavigationRoute: Equatable {

/// API access route.
case apiAccess

/// Multihop route.
case multihop
}

/// Top-level settings coordinator.
Expand Down Expand Up @@ -267,6 +271,29 @@ final class SettingsCoordinator: Coordinator, Presentable, Presenting, SettingsV
case .faq:
// Handled separately and presented as a modal.
return .failed

case .multihop:
var view = SettingsMultihopView()

view.didToggleEnabled = { [weak self] in
print($0)
}

let host = UIHostingController(rootView: view)
host.title = NSLocalizedString(
"NAVIGATION_TITLE_MULTIHOP",
tableName: "Settings",
value: "Multihop",
comment: ""
)

return .viewController(host)

// return .childCoordinator(ListAccessMethodCoordinator(
// navigationController: navigationController,
// accessMethodRepository: accessMethodRepository,
// proxyConfigurationTester: proxyConfigurationTester
// ))
}
}

Expand Down
35 changes: 35 additions & 0 deletions ios/MullvadVPN/Extensions/View+TapAreaSize.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// View+TapAreaSize.swift
// MullvadVPN
//
// Created by Jon Petersson on 2024-11-13.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import SwiftUI

extension View {
/// Adjusts tappable area to at least minimum (default) size without changing
/// actual view size.
func adjustingTapAreaSize() -> some View {
modifier(TappablePadding())
}
}

private struct TappablePadding: ViewModifier {
@State var actualViewSize: CGSize = .zero
let tappableViewSize = UIMetrics.Button.minimumTappableAreaSize

func body(content: Content) -> some View {
content
.background(ViewGeometry {
actualViewSize = $0
})
.frame(
width: max(actualViewSize.width, tappableViewSize.width),
height: max(actualViewSize.height, tappableViewSize.height)
)
.contentShape(Rectangle())
.frame(width: actualViewSize.width, height: actualViewSize.height)
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "MultihopIllustration.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
Binary file not shown.
7 changes: 6 additions & 1 deletion ios/MullvadVPN/UI appearance/UIEdgeInsets+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// Copyright © 2023 Mullvad VPN AB. All rights reserved.
//

import Foundation
import SwiftUI
import UIKit

extension UIEdgeInsets {
Expand All @@ -19,4 +19,9 @@ extension UIEdgeInsets {
trailing: right
)
}

/// Returns edge insets.
var toEdgeInsets: EdgeInsets {
EdgeInsets(toDirectionalInsets)
}
}
Loading

0 comments on commit 73edc2b

Please sign in to comment.