diff options
| author | Bug Magnet <marco.nikic@mullvad.net> | 2025-06-12 15:54:47 +0200 |
|---|---|---|
| committer | Bug Magnet <marco.nikic@mullvad.net> | 2025-06-12 15:54:47 +0200 |
| commit | 9bdbcbaec9a99004cc7ac0bfa4eef32f21b962a7 (patch) | |
| tree | 7fde0052d524b36baa52f3dbae3b4a2375ca6c9c | |
| parent | 7ef26ac21de0bc64aa1043e693725fe255c14487 (diff) | |
| parent | 95727b1887d935cf72ef1b8125f2f0a4e867d36c (diff) | |
| download | mullvadvpn-9bdbcbaec9a99004cc7ac0bfa4eef32f21b962a7.tar.xz mullvadvpn-9bdbcbaec9a99004cc7ac0bfa4eef32f21b962a7.zip | |
Merge branch 'rewrite-tos-page-swift-ui'
12 files changed, 143 insertions, 274 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index eb12f8006a..7f27111634 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -156,7 +156,6 @@ 583FE02429C1ACB3006E85F9 /* RESTCreateApplePaymentResponse+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06FAE67828F83CA50033DD93 /* RESTCreateApplePaymentResponse+Localization.swift */; }; 58421030282D8A3C00F24E46 /* UpdateAccountDataOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5842102F282D8A3C00F24E46 /* UpdateAccountDataOperation.swift */; }; 58421032282E42B000F24E46 /* UpdateDeviceDataOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58421031282E42B000F24E46 /* UpdateDeviceDataOperation.swift */; }; - 584592612639B4A200EF967F /* TermsOfServiceContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584592602639B4A200EF967F /* TermsOfServiceContentView.swift */; }; 5846227126E229F20035F7C2 /* StoreSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846227026E229F20035F7C2 /* StoreSubscription.swift */; }; 5846227326E22A160035F7C2 /* StorePaymentObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846227226E22A160035F7C2 /* StorePaymentObserver.swift */; }; 5846227726E22A7C0035F7C2 /* StorePaymentManagerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846227626E22A7C0035F7C2 /* StorePaymentManagerDelegate.swift */; }; @@ -255,7 +254,6 @@ 589E76C02A9378F100E502F3 /* RESTRequestExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 589E76BF2A9378F100E502F3 /* RESTRequestExecutor.swift */; }; 58A8EE5A2976BFBB009C0F8D /* SKError+Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A8EE592976BFBB009C0F8D /* SKError+Localized.swift */; }; 58A8EE5E2976DB00009C0F8D /* StorePaymentManagerError+Display.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A8EE5D2976DB00009C0F8D /* StorePaymentManagerError+Display.swift */; }; - 58A99ED3240014A0006599E9 /* TermsOfServiceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A99ED2240014A0006599E9 /* TermsOfServiceViewController.swift */; }; 58ACF6492655365700ACE4B7 /* VPNSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58ACF6482655365700ACE4B7 /* VPNSettingsViewController.swift */; }; 58ACF64B26553C3F00ACE4B7 /* SettingsSwitchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58ACF64A26553C3F00ACE4B7 /* SettingsSwitchCell.swift */; }; 58ACF64D26567A5000ACE4B7 /* CustomSwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58ACF64C26567A4F00ACE4B7 /* CustomSwitch.swift */; }; @@ -908,6 +906,7 @@ A9A5FA412ACB05D90083449F /* DeviceStateAccessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583D86472A2678DC0060D63B /* DeviceStateAccessor.swift */; }; A9A5FA422ACB05D90083449F /* DeviceStateAccessorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580810E42A30E13A00B74552 /* DeviceStateAccessorProtocol.swift */; }; A9A5FA432ACB05F20083449F /* UIColor+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587CBFE222807F530028DED3 /* UIColor+Helpers.swift */; }; + A9A60ED42DF6E5AC00CD9C3D /* UIHostingRootController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9A60ED32DF6E5AC00CD9C3D /* UIHostingRootController.swift */; }; A9A8A8EB2A262AB30086D569 /* FileCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9A8A8EA2A262AB30086D569 /* FileCache.swift */; }; A9B6AC182ADE8F4300F7802A /* MigrationManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9B6AC172ADE8F4300F7802A /* MigrationManagerTests.swift */; }; A9B6AC1A2ADE8FBB00F7802A /* InMemorySettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9B6AC192ADE8FBB00F7802A /* InMemorySettingsStore.swift */; }; @@ -937,6 +936,8 @@ A9E0317A2ACB0AE70095D843 /* UIApplication+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E031792ACB0AE70095D843 /* UIApplication+Stubs.swift */; }; A9E0317F2ACC331C0095D843 /* TunnelStatusBlockObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E0317D2ACC32920095D843 /* TunnelStatusBlockObserver.swift */; }; A9E034642ABB302000E59A5A /* UIEdgeInsets+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E034632ABB302000E59A5A /* UIEdgeInsets+Extensions.swift */; }; + A9EE855F2DF0893E00F2D769 /* Color+Mullvad.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9394EEB2DBF56AA009595EA /* Color+Mullvad.swift */; }; + A9EE85612DF1BE2900F2D769 /* TermsOfServiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EE85602DF1BE2900F2D769 /* TermsOfServiceView.swift */; }; E1187ABC289BBB850024E748 /* OutOfTimeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1187ABA289BBB850024E748 /* OutOfTimeViewController.swift */; }; E1187ABD289BBB850024E748 /* OutOfTimeContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1187ABB289BBB850024E748 /* OutOfTimeContentView.swift */; }; E158B360285381C60002F069 /* String+AccountFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = E158B35F285381C60002F069 /* String+AccountFormatting.swift */; }; @@ -1119,7 +1120,6 @@ F9394EEC2DBF56B6009595EA /* Color+Mullvad.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9394EEB2DBF56AA009595EA /* Color+Mullvad.swift */; }; F9394EF02DC0B58D009595EA /* MullvadListNavigationItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9394EEF2DC0B58D009595EA /* MullvadListNavigationItemView.swift */; }; F9394EF32DC21D8C009595EA /* MullvadList.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9394EF22DC21D8C009595EA /* MullvadList.swift */; }; - F97C38C82DE48AAE006DCB08 /* Color+Mullvad.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9394EEB2DBF56AA009595EA /* Color+Mullvad.swift */; }; F97C38CA2DE49869006DCB08 /* MultihopSettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97C38C92DE49869006DCB08 /* MultihopSettingsCoordinator.swift */; }; F97C38D92DE5930F006DCB08 /* CustomDNSCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97C38D82DE59307006DCB08 /* CustomDNSCoordinator.swift */; }; F998EFF82D359C4600D88D01 /* SKProduct+Formatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FD5BEF24238EB300112C88 /* SKProduct+Formatting.swift */; }; @@ -1767,7 +1767,6 @@ 5842102D282D3FC200F24E46 /* ResultBlockOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultBlockOperation.swift; sourceTree = "<group>"; }; 5842102F282D8A3C00F24E46 /* UpdateAccountDataOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateAccountDataOperation.swift; sourceTree = "<group>"; }; 58421031282E42B000F24E46 /* UpdateDeviceDataOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateDeviceDataOperation.swift; sourceTree = "<group>"; }; - 584592602639B4A200EF967F /* TermsOfServiceContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsOfServiceContentView.swift; sourceTree = "<group>"; }; 5846226426E0D9630035F7C2 /* ProductsRequestOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductsRequestOperation.swift; sourceTree = "<group>"; }; 5846227026E229F20035F7C2 /* StoreSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreSubscription.swift; sourceTree = "<group>"; }; 5846227226E22A160035F7C2 /* StorePaymentObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorePaymentObserver.swift; sourceTree = "<group>"; }; @@ -1895,7 +1894,6 @@ 58A8EE592976BFBB009C0F8D /* SKError+Localized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SKError+Localized.swift"; sourceTree = "<group>"; }; 58A8EE5D2976DB00009C0F8D /* StorePaymentManagerError+Display.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StorePaymentManagerError+Display.swift"; sourceTree = "<group>"; }; 58A94AE326CFD945001CB97C /* TunnelStatusNotificationProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelStatusNotificationProvider.swift; sourceTree = "<group>"; }; - 58A99ED2240014A0006599E9 /* TermsOfServiceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsOfServiceViewController.swift; sourceTree = "<group>"; }; 58ACF6482655365700ACE4B7 /* VPNSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNSettingsViewController.swift; sourceTree = "<group>"; }; 58ACF64A26553C3F00ACE4B7 /* SettingsSwitchCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSwitchCell.swift; sourceTree = "<group>"; }; 58ACF64C26567A4F00ACE4B7 /* CustomSwitch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSwitch.swift; sourceTree = "<group>"; }; @@ -2374,6 +2372,7 @@ A9A1DE782AD5708E0073F689 /* TransportStrategy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransportStrategy.swift; sourceTree = "<group>"; }; A9A557F42B7E3E5C0017ADA8 /* EphemeralPeerReceiver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EphemeralPeerReceiver.swift; sourceTree = "<group>"; }; A9A5F9A12ACB003D0083449F /* TunnelManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelManagerTests.swift; sourceTree = "<group>"; }; + A9A60ED32DF6E5AC00CD9C3D /* UIHostingRootController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIHostingRootController.swift; sourceTree = "<group>"; }; A9A8A8EA2A262AB30086D569 /* FileCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileCache.swift; sourceTree = "<group>"; }; A9B6AC172ADE8F4300F7802A /* MigrationManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationManagerTests.swift; sourceTree = "<group>"; }; A9B6AC192ADE8FBB00F7802A /* InMemorySettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InMemorySettingsStore.swift; sourceTree = "<group>"; }; @@ -2392,6 +2391,7 @@ A9E034632ABB302000E59A5A /* UIEdgeInsets+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIEdgeInsets+Extensions.swift"; sourceTree = "<group>"; }; A9EB4F9C2B7FAB21002A2D7A /* EphemeralPeerNegotiator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EphemeralPeerNegotiator.swift; sourceTree = "<group>"; }; A9EC20E72A5D3A8C0040D56E /* CoordinatesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoordinatesTests.swift; sourceTree = "<group>"; }; + A9EE85602DF1BE2900F2D769 /* TermsOfServiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsOfServiceView.swift; sourceTree = "<group>"; }; A9F360332AAB626300F53531 /* VPNConnectionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNConnectionProtocol.swift; sourceTree = "<group>"; }; E1187ABA289BBB850024E748 /* OutOfTimeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutOfTimeViewController.swift; sourceTree = "<group>"; }; E1187ABB289BBB850024E748 /* OutOfTimeContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutOfTimeContentView.swift; sourceTree = "<group>"; }; @@ -3382,8 +3382,7 @@ 583FE02229C1AC68006E85F9 /* TermsOfService */ = { isa = PBXGroup; children = ( - 584592602639B4A200EF967F /* TermsOfServiceContentView.swift */, - 58A99ED2240014A0006599E9 /* TermsOfServiceViewController.swift */, + A9EE85602DF1BE2900F2D769 /* TermsOfServiceView.swift */, ); path = TermsOfService; sourceTree = "<group>"; @@ -4043,6 +4042,7 @@ 58F3C0A3249CB069003E76BE /* HeaderBarView.swift */, 7A818F1E29F0305800C7F0F4 /* RootConfiguration.swift */, 587425C02299833500CA2045 /* RootContainerViewController.swift */, + A9A60ED32DF6E5AC00CD9C3D /* UIHostingRootController.swift */, ); path = Root; sourceTree = "<group>"; @@ -5948,7 +5948,7 @@ 7A5869C32B5820CE00640D27 /* IPOverrideRepositoryTests.swift in Sources */, A9A5FA392ACB05910083449F /* UIColor+Palette.swift in Sources */, 7A5468AD2C6B5E4B00590086 /* LocationRelays.swift in Sources */, - F97C38C82DE48AAE006DCB08 /* Color+Mullvad.swift in Sources */, + A9EE855F2DF0893E00F2D769 /* Color+Mullvad.swift in Sources */, A9A5FA3A2ACB05910083449F /* UIEdgeInsets+Extensions.swift in Sources */, A9A5FA3B2ACB05910083449F /* UIMetrics.swift in Sources */, 58B07C182AEFDD6C00A09625 /* StoreTransactionLog.swift in Sources */, @@ -6410,6 +6410,7 @@ 58138E61294871C600684F0C /* DeviceDataThrottling.swift in Sources */, 7A6389ED2B7FADA1008E77E1 /* SettingsFieldValidationErrorConfiguration.swift in Sources */, 5878A279290954790096FC88 /* TunnelViewControllerInteractor.swift in Sources */, + A9A60ED42DF6E5AC00CD9C3D /* UIHostingRootController.swift in Sources */, 7A818F1F29F0305800C7F0F4 /* RootConfiguration.swift in Sources */, 7A9CCCBF2A96302800DD6A34 /* SettingsCoordinator.swift in Sources */, 58F70FE52AEA707800E6890E /* StoreTransactionLog.swift in Sources */, @@ -6435,7 +6436,6 @@ 7AA1309F2D007B2500640DF9 /* VisualEffectView.swift in Sources */, 7A0C0F632A979C4A0058EFCE /* Coordinator+Router.swift in Sources */, 7A6F2FAB2AFD3097006D0856 /* CustomDNSCellFactory.swift in Sources */, - 58A99ED3240014A0006599E9 /* TermsOfServiceViewController.swift in Sources */, 7A6000FE2B628E9F001CF0D9 /* ListCellContentView.swift in Sources */, 4419AA8B2D2826E5001B13C9 /* DetailsView.swift in Sources */, 58CCA0162242560B004F3011 /* UIColor+Palette.swift in Sources */, @@ -6556,6 +6556,7 @@ 58CE5E64224146200008646E /* AppDelegate.swift in Sources */, F9394EEC2DBF56B6009595EA /* Color+Mullvad.swift in Sources */, F0DA87492A9CBA9F006044F1 /* AccountDeviceRow.swift in Sources */, + A9EE85612DF1BE2900F2D769 /* TermsOfServiceView.swift in Sources */, 58FF9FE42B075BDD00E4C97D /* EditAccessMethodItemIdentifier.swift in Sources */, 449E9A6F2D283C7400F8574A /* ButtonPanel.swift in Sources */, 4419AA8E2D2828A4001B13C9 /* HeaderView.swift in Sources */, @@ -6610,7 +6611,6 @@ 58F2E144276A13F300A79513 /* StartTunnelOperation.swift in Sources */, 58CCA01E2242787B004F3011 /* AccountTextField.swift in Sources */, 586E54FB27A2DF6D0029B88B /* SendTunnelProviderMessageOperation.swift in Sources */, - 584592612639B4A200EF967F /* TermsOfServiceContentView.swift in Sources */, 5875960A26F371FC00BF6711 /* Tunnel+Messaging.swift in Sources */, 586C0D912B03D8A400E7CDD7 /* AccessMethodHeaderFooterReuseIdentifier.swift in Sources */, F0B583D42D6DCE12007F5AE4 /* FilterDescriptor.swift in Sources */, diff --git a/ios/MullvadVPN/Containers/Root/HeaderBarView.swift b/ios/MullvadVPN/Containers/Root/HeaderBarView.swift index 3ea6fbd041..977296c532 100644 --- a/ios/MullvadVPN/Containers/Root/HeaderBarView.swift +++ b/ios/MullvadVPN/Containers/Root/HeaderBarView.swift @@ -30,7 +30,7 @@ class HeaderBarView: UIView { private lazy var deviceNameLabel: UILabel = { let label = UILabel() - label.font = UIFont.systemFont(ofSize: 14) + label.font = .mullvadMiniSemiBold label.textColor = UIColor(white: 1.0, alpha: 0.8) label.setContentHuggingPriority(.defaultHigh, for: .horizontal) label.setAccessibilityIdentifier(.headerDeviceNameLabel) @@ -39,7 +39,7 @@ class HeaderBarView: UIView { private lazy var timeLeftLabel: UILabel = { let label = UILabel() - label.font = UIFont.systemFont(ofSize: 14) + label.font = .mullvadMiniSemiBold label.textColor = UIColor(white: 1.0, alpha: 0.8) label.setContentHuggingPriority(.defaultLow, for: .horizontal) return label @@ -159,9 +159,9 @@ class HeaderBarView: UIView { super.init(frame: frame) directionalLayoutMargins = NSDirectionalEdgeInsets( top: 0, - leading: UIMetrics.contentLayoutMargins.leading, + leading: 16, bottom: 0, - trailing: UIMetrics.contentLayoutMargins.trailing + trailing: 16 ) accessibilityContainerType = .semanticGroup diff --git a/ios/MullvadVPN/Containers/Root/UIHostingRootController.swift b/ios/MullvadVPN/Containers/Root/UIHostingRootController.swift new file mode 100644 index 0000000000..15e98c66bc --- /dev/null +++ b/ios/MullvadVPN/Containers/Root/UIHostingRootController.swift @@ -0,0 +1,34 @@ +// +// UIHostingRootController.swift +// MullvadVPN +// +// Created by Marco Nikic on 2025-06-09. +// Copyright © 2025 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import SwiftUI + +@MainActor +public final class UIHostingRootController<Content: View>: UIHostingController<Content>, RootContainment { + let preferredHeaderBarPresentation: HeaderBarPresentation + let prefersHeaderBarHidden: Bool + let prefersDeviceInfoBarHidden: Bool + + init( + preferredHeaderBarPresentation: HeaderBarPresentation = + HeaderBarPresentation(style: .default, showsDivider: false), + prefersHeaderBarHidden: Bool = false, + prefersDeviceInfoBarHidden: Bool = true, + rootView: Content + ) { + self.preferredHeaderBarPresentation = preferredHeaderBarPresentation + self.prefersHeaderBarHidden = prefersHeaderBarHidden + self.prefersDeviceInfoBarHidden = prefersDeviceInfoBarHidden + super.init(rootView: rootView) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift b/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift index b429c9053e..bd67bbb072 100644 --- a/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift +++ b/ios/MullvadVPN/Coordinators/ApplicationCoordinator.swift @@ -333,7 +333,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, @preconcurrency Roo private func presentTOS(animated: Bool, completion: @escaping (Coordinator) -> Void) { let coordinator = TermsOfServiceCoordinator(navigationController: navigationContainer) - coordinator.didFinish = { [weak self] _ in + coordinator.didAgreeToTermsOfService = { [weak self] in self?.appPreferences.isAgreedToTermsOfService = true self?.continueFlow(animated: true) } diff --git a/ios/MullvadVPN/Coordinators/TermsOfServiceCoordinator.swift b/ios/MullvadVPN/Coordinators/TermsOfServiceCoordinator.swift index b359187ca6..042e21b7c6 100644 --- a/ios/MullvadVPN/Coordinators/TermsOfServiceCoordinator.swift +++ b/ios/MullvadVPN/Coordinators/TermsOfServiceCoordinator.swift @@ -7,6 +7,7 @@ // import Routing +import SwiftUI import UIKit class TermsOfServiceCoordinator: Coordinator, Presenting { @@ -16,24 +17,16 @@ class TermsOfServiceCoordinator: Coordinator, Presenting { navigationController } - var didFinish: ((TermsOfServiceCoordinator) -> Void)? + var didAgreeToTermsOfService: (() -> Void)? init(navigationController: RootContainerViewController) { self.navigationController = navigationController } func start() { - let controller = TermsOfServiceViewController() - - controller.showPrivacyPolicy = { [weak self] in - self?.presentChild(SafariCoordinator(url: ApplicationConfiguration.privacyPolicyURL), animated: true) - } - - controller.completionHandler = { [weak self] in - guard let self else { return } - didFinish?(self) - } - - navigationController.pushViewController(controller, animated: false) + let termsOfService = TermsOfServiceView(agreeToTermsAndServices: didAgreeToTermsOfService) + let hostingController = UIHostingRootController(rootView: termsOfService) + hostingController.view.setAccessibilityIdentifier(.termsOfServiceView) + navigationController.pushViewController(hostingController, animated: false) } } diff --git a/ios/MullvadVPN/Extensions/UIImage+Assets.swift b/ios/MullvadVPN/Extensions/UIImage+Assets.swift index ac0571b6da..60026ca90b 100644 --- a/ios/MullvadVPN/Extensions/UIImage+Assets.swift +++ b/ios/MullvadVPN/Extensions/UIImage+Assets.swift @@ -103,6 +103,10 @@ extension UIImage { UIImage(named: "IconTickSml")! } + static var iconExtLink: UIImage { + UIImage(named: "IconExtlink")! + } + static var checkboxSelected: UIImage { UIImage(named: "CheckboxSelected")! } diff --git a/ios/MullvadVPN/Notifications/UI/NotificationBannerView.swift b/ios/MullvadVPN/Notifications/UI/NotificationBannerView.swift index 1eff1a5d7e..54b2695b3e 100644 --- a/ios/MullvadVPN/Notifications/UI/NotificationBannerView.swift +++ b/ios/MullvadVPN/Notifications/UI/NotificationBannerView.swift @@ -13,7 +13,7 @@ final class NotificationBannerView: UIView { private let titleLabel: UILabel = { let textLabel = UILabel() - textLabel.font = UIFont.systemFont(ofSize: 17, weight: .bold) + textLabel.font = .mullvadTinySemiBold textLabel.textColor = UIColor.InAppNotificationBanner.titleColor textLabel.numberOfLines = 0 textLabel.lineBreakMode = .byWordWrapping @@ -23,7 +23,7 @@ final class NotificationBannerView: UIView { private let bodyLabel: UILabel = { let textLabel = UILabel() - textLabel.font = UIFont.systemFont(ofSize: 17) + textLabel.font = .mullvadTiny textLabel.textColor = UIColor.InAppNotificationBanner.bodyColor textLabel.numberOfLines = 0 textLabel.lineBreakMode = .byWordWrapping diff --git a/ios/MullvadVPN/UI appearance/Color+Mullvad.swift b/ios/MullvadVPN/UI appearance/Color+Mullvad.swift index 6fad2f47b2..ab3987c722 100644 --- a/ios/MullvadVPN/UI appearance/Color+Mullvad.swift +++ b/ios/MullvadVPN/UI appearance/Color+Mullvad.swift @@ -12,6 +12,7 @@ extension Color { static let mullvadTextPrimaryDisabled: Color = .mullvadTextPrimary.opacity( 0.2 ) + static let secondaryTextColor: Color = UIColor.secondaryTextColor.color enum MullvadButton { static let primary: Color = .mullvadPrimaryColor diff --git a/ios/MullvadVPN/View controllers/TermsOfService/TermsOfServiceContentView.swift b/ios/MullvadVPN/View controllers/TermsOfService/TermsOfServiceContentView.swift deleted file mode 100644 index a72f1ff3f0..0000000000 --- a/ios/MullvadVPN/View controllers/TermsOfService/TermsOfServiceContentView.swift +++ /dev/null @@ -1,178 +0,0 @@ -// -// TermsOfServiceContentView.swift -// MullvadVPN -// -// Created by pronebird on 28/04/2021. -// Copyright © 2025 Mullvad VPN AB. All rights reserved. -// - -import UIKit - -class TermsOfServiceContentView: UIView { - let titleLabel: UILabel = { - let titleLabel = UILabel() - titleLabel.translatesAutoresizingMaskIntoConstraints = false - titleLabel.font = UIFont.systemFont(ofSize: 24, weight: .bold) - titleLabel.numberOfLines = 0 - titleLabel.textColor = .white - titleLabel.allowsDefaultTighteningForTruncation = true - titleLabel.text = NSLocalizedString( - "PRIVACY_NOTICE_HEADING", - tableName: "TermsOfService", - value: "Do you agree to remaining anonymous?", - comment: "" - ) - titleLabel.lineBreakMode = .byWordWrapping - titleLabel.lineBreakStrategy = [] - return titleLabel - }() - - let bodyLabel: UILabel = { - let bodyLabel = UILabel() - - let message = NSMutableAttributedString(string: NSLocalizedString( - "PRIVACY_NOTICE_BODY", - tableName: "TermsOfService", - value: """ - You have a right to privacy. That’s why we never store activity logs, don’t ask for personal \ - information, and encourage anonymous payments. - In some situations, as outlined in our privacy policy, we might process personal data that you \ - choose to send, for example if you email us. - We strongly believe in retaining as little data as possible because we want you to remain anonymous. - """, - comment: "" - )) - message.apply(paragraphStyle: .alert) - - bodyLabel.attributedText = message - bodyLabel.translatesAutoresizingMaskIntoConstraints = false - bodyLabel.font = UIFont.systemFont(ofSize: 18) - bodyLabel.textColor = .white - bodyLabel.numberOfLines = 0 - - return bodyLabel - }() - - let privacyPolicyLink: LinkButton = { - let button = LinkButton() - button.translatesAutoresizingMaskIntoConstraints = false - button.titleString = NSLocalizedString( - "PRIVACY_POLICY_LINK_TITLE", - tableName: "TermsOfService", - value: "Privacy policy", - comment: "" - ) - button.setImage(UIImage(named: "IconExtlink"), for: .normal) - return button - }() - - let agreeButton: AppButton = { - let button = AppButton(style: .default) - button.translatesAutoresizingMaskIntoConstraints = false - button.setAccessibilityIdentifier(.agreeButton) - button.setTitle(NSLocalizedString( - "CONTINUE_BUTTON_TITLE", - tableName: "TermsOfService", - value: "Agree and continue", - comment: "" - ), for: .normal) - return button - }() - - let scrollView: UIScrollView = { - let scrollView = UIScrollView() - scrollView.translatesAutoresizingMaskIntoConstraints = false - return scrollView - }() - - let scrollContentContainer: UIView = { - let contentView = UIView() - contentView.translatesAutoresizingMaskIntoConstraints = false - contentView.directionalLayoutMargins = UIMetrics.contentLayoutMargins - return contentView - }() - - let footerContainer: UIView = { - let container = UIView() - container.translatesAutoresizingMaskIntoConstraints = false - container.directionalLayoutMargins = UIMetrics.contentLayoutMargins - container.backgroundColor = .secondaryColor - return container - }() - - override init(frame: CGRect) { - super.init(frame: frame) - - self.setAccessibilityIdentifier(.termsOfServiceView) - - addSubviews() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Private - - private func addSubviews() { - addSubview(scrollView) - addSubview(footerContainer) - - scrollView.addSubview(scrollContentContainer) - [titleLabel, bodyLabel, privacyPolicyLink].forEach { scrollContentContainer.addSubview($0) } - footerContainer.addSubview(agreeButton) - - scrollView.setContentCompressionResistancePriority(.defaultLow, for: .vertical) - footerContainer.setContentCompressionResistancePriority(.defaultHigh, for: .vertical) - - NSLayoutConstraint.activate([ - scrollView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor), - scrollView.leadingAnchor.constraint(equalTo: leadingAnchor), - scrollView.trailingAnchor.constraint(equalTo: trailingAnchor), - - scrollContentContainer.widthAnchor.constraint(equalTo: scrollView.widthAnchor), - scrollContentContainer.topAnchor.constraint(equalTo: scrollView.topAnchor), - scrollContentContainer.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor), - scrollContentContainer.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor), - scrollContentContainer.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor), - - footerContainer.topAnchor.constraint(equalTo: scrollView.bottomAnchor), - footerContainer.leadingAnchor.constraint(equalTo: leadingAnchor), - footerContainer.trailingAnchor.constraint(equalTo: trailingAnchor), - footerContainer.bottomAnchor.constraint(equalTo: bottomAnchor), - - agreeButton.topAnchor.constraint(equalTo: footerContainer.layoutMarginsGuide.topAnchor), - agreeButton.leadingAnchor - .constraint(equalTo: footerContainer.layoutMarginsGuide.leadingAnchor), - agreeButton.trailingAnchor - .constraint(equalTo: footerContainer.layoutMarginsGuide.trailingAnchor), - agreeButton.bottomAnchor - .constraint(equalTo: footerContainer.layoutMarginsGuide.bottomAnchor), - - titleLabel.topAnchor - .constraint(equalTo: scrollContentContainer.layoutMarginsGuide.topAnchor), - titleLabel.leadingAnchor - .constraint(equalTo: scrollContentContainer.layoutMarginsGuide.leadingAnchor), - titleLabel.trailingAnchor - .constraint(equalTo: scrollContentContainer.layoutMarginsGuide.trailingAnchor), - - bodyLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 24), - bodyLabel.leadingAnchor - .constraint(equalTo: scrollContentContainer.layoutMarginsGuide.leadingAnchor), - bodyLabel.trailingAnchor - .constraint(equalTo: scrollContentContainer.layoutMarginsGuide.trailingAnchor), - - privacyPolicyLink.topAnchor.constraint(equalTo: bodyLabel.bottomAnchor, constant: 24), - privacyPolicyLink.leadingAnchor - .constraint(equalTo: scrollContentContainer.layoutMarginsGuide.leadingAnchor), - privacyPolicyLink.trailingAnchor - .constraint( - lessThanOrEqualTo: scrollContentContainer.layoutMarginsGuide - .trailingAnchor - ), - privacyPolicyLink.bottomAnchor - .constraint(equalTo: scrollContentContainer.layoutMarginsGuide.bottomAnchor), - - ]) - } -} diff --git a/ios/MullvadVPN/View controllers/TermsOfService/TermsOfServiceView.swift b/ios/MullvadVPN/View controllers/TermsOfService/TermsOfServiceView.swift new file mode 100644 index 0000000000..b95b9cb717 --- /dev/null +++ b/ios/MullvadVPN/View controllers/TermsOfService/TermsOfServiceView.swift @@ -0,0 +1,80 @@ +// +// TermsOfServiceView.swift +// MullvadVPN +// +// Created by Marco Nikic on 2025-06-05. +// Copyright © 2025 Mullvad VPN AB. All rights reserved. +// + +import SwiftUI + +struct TermsOfServiceView: View { + public var agreeToTermsAndServices: (() -> Void)? + let padding = EdgeInsets(top: 24, leading: 16, bottom: 24, trailing: 16) + @ScaledMetric(relativeTo: .footnote) + var imageHeight = 20 + + let termsOfService = LocalizedStringKey(""" + You have a right to privacy. That’s why we never store activity logs, don’t ask for personal \ + information, and encourage anonymous payments. + + In some situations, as outlined in our privacy policy, we might process personal data that you \ + choose to send, for example if you email us. + + We strongly believe in retaining as little data as possible because we want you to remain anonymous. + """) + + let privacyPolicyLink = + LocalizedStringKey(stringLiteral: "[Privacy Policy](\(ApplicationConfiguration.privacyPolicyLink))") + var scrollableContent: some View { + ScrollView { + Text(LocalizedStringKey("Do you agree to remaining anonymous?")) + .font(.mullvadLarge) + .foregroundStyle(.white) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.bottom, 16) + Text(termsOfService) + .font(.mullvadSmall) + .foregroundStyle(Color.secondaryTextColor) + } + .padding(padding) + } + + var body: some View { + VStack(alignment: .leading) { + // Disable scrolling if the contents do not overflow + if #available(iOS 16.4, *) { + scrollableContent.scrollBounceBehavior(.basedOnSize) + } else { + scrollableContent + } + HStack { + Text(privacyPolicyLink) + .font(.mullvadSmall) + .underline(true, color: .white) + .foregroundStyle(.white) + .tint(.white) + Image(uiImage: UIImage.iconExtLink) + .resizable() + .scaledToFit() + .frame(height: imageHeight) + .foregroundStyle(.white) + } + .padding(padding) + MainButton( + text: LocalizedStringKey("Agree and continue"), + style: .default, + action: agreeToTermsAndServices ?? {} + ) + .accessibilityIdentifier(AccessibilityIdentifier.agreeButton.asString) + .padding(padding) + .background(Color(UIColor.secondaryColor)) + } + .background(Color(UIColor.primaryColor)) + } +} + +#Preview { + TermsOfServiceView() + .frame(maxWidth: .infinity, maxHeight: .infinity) +} diff --git a/ios/MullvadVPN/View controllers/TermsOfService/TermsOfServiceViewController.swift b/ios/MullvadVPN/View controllers/TermsOfService/TermsOfServiceViewController.swift deleted file mode 100644 index 29cb646826..0000000000 --- a/ios/MullvadVPN/View controllers/TermsOfService/TermsOfServiceViewController.swift +++ /dev/null @@ -1,65 +0,0 @@ -// -// TermsOfServiceViewController.swift -// MullvadVPN -// -// Created by pronebird on 21/02/2020. -// Copyright © 2025 Mullvad VPN AB. All rights reserved. -// - -import UIKit - -class TermsOfServiceViewController: UIViewController, RootContainment { - var showPrivacyPolicy: (() -> Void)? - var completionHandler: (() -> Void)? - - override var preferredStatusBarStyle: UIStatusBarStyle { - .lightContent - } - - var preferredHeaderBarPresentation: HeaderBarPresentation { - HeaderBarPresentation(style: .default, showsDivider: false) - } - - var prefersHeaderBarHidden: Bool { - false - } - - // MARK: - View lifecycle - - override func viewDidLoad() { - super.viewDidLoad() - - let contentView = TermsOfServiceContentView() - contentView.translatesAutoresizingMaskIntoConstraints = false - contentView.agreeButton.addTarget( - self, - action: #selector(handleAgreeButton(_:)), - for: .touchUpInside - ) - contentView.privacyPolicyLink.addTarget( - self, - action: #selector(handlePrivacyPolicyButton(_:)), - for: .touchUpInside - ) - - view.backgroundColor = .primaryColor - view.addSubview(contentView) - - NSLayoutConstraint.activate([ - contentView.topAnchor.constraint(equalTo: view.topAnchor), - contentView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - contentView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - contentView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) - } - - // MARK: - Actions - - @objc private func handlePrivacyPolicyButton(_ sender: Any) { - showPrivacyPolicy?() - } - - @objc private func handleAgreeButton(_ sender: Any) { - completionHandler?() - } -} diff --git a/ios/Shared/ApplicationConfiguration.swift b/ios/Shared/ApplicationConfiguration.swift index 16240b9bea..7235425b23 100644 --- a/ios/Shared/ApplicationConfiguration.swift +++ b/ios/Shared/ApplicationConfiguration.swift @@ -64,7 +64,7 @@ enum ApplicationConfiguration { static let logMaximumFileSize: UInt64 = 131_072 // 128 kB. /// Privacy policy URL. - static let privacyPolicyURL = URL(string: "https://\(Self.hostName)/help/privacy-policy/")! + static let privacyPolicyLink = "https://\(Self.hostName)/help/privacy-policy/" /// Make a start regarding policy URL. static let privacyGuidesURL = URL(string: "https://\(Self.hostName)/help/first-steps-towards-online-privacy/")! |
