From e00402830ab0373d5b2e4ac4d1e095fbdfcc4926 Mon Sep 17 00:00:00 2001 From: Jon Petersson Date: Thu, 9 Jan 2025 10:11:47 +0100 Subject: [PATCH] Fix UI tests and Make connection view releasable --- ios/MullvadVPN.xcodeproj/project.pbxproj | 38 +- .../Classes/AccessbilityIdentifier.swift | 1 + .../Coordinators/TunnelCoordinator.swift | 10 - .../ActivityIndicator.swift | 0 .../Tunnel/ConnectionPanelView.swift | 329 --------------- .../ConnectionView/ButtonPanel.swift | 0 .../ChipView/ChipContainerView.swift | 0 .../ConnectionView/ChipView/ChipFeature.swift | 0 .../ConnectionView/ChipView/ChipModel.swift | 0 .../ConnectionView/ChipView/ChipView.swift | 0 .../ChipView/ChipViewModelProtocol.swift | 0 .../ConnectionView/ConnectionView.swift | 0 .../ConnectionViewComponentPreview.swift | 0 .../ConnectionViewViewModel.swift | 10 +- .../ConnectionView/DetailsContainer.swift | 0 .../ConnectionView/DetailsView.swift | 28 +- .../FeatureIndicatorsView.swift | 0 .../FeatureIndicatorsViewModel.swift | 0 .../ConnectionView/HeaderView.swift | 3 +- .../Tunnel/DisconnectSplitButton.swift | 55 --- .../Tunnel/TranslucentButtonBlurView.swift | 84 ---- .../Tunnel/TunnelControlView.swift | 398 ------------------ .../Tunnel/TunnelControlViewModel.swift | 72 ---- .../Tunnel/TunnelViewController.swift | 121 +++--- ios/MullvadVPNUITests/ConnectivityTests.swift | 4 +- .../Pages/TunnelControlPage.swift | 79 ++-- ios/MullvadVPNUITests/RelayTests.swift | 96 ++--- .../Screenshots/ScreenshotTests.swift | 2 +- 28 files changed, 187 insertions(+), 1143 deletions(-) rename ios/MullvadVPN/View controllers/Tunnel/{FeatureIndicators => }/ActivityIndicator.swift (100%) delete mode 100644 ios/MullvadVPN/View controllers/Tunnel/ConnectionPanelView.swift rename ios/MullvadVPN/View controllers/Tunnel/{FeatureIndicators => }/ConnectionView/ButtonPanel.swift (100%) rename ios/MullvadVPN/View controllers/Tunnel/{FeatureIndicators => }/ConnectionView/ChipView/ChipContainerView.swift (100%) rename ios/MullvadVPN/View controllers/Tunnel/{FeatureIndicators => }/ConnectionView/ChipView/ChipFeature.swift (100%) rename ios/MullvadVPN/View controllers/Tunnel/{FeatureIndicators => }/ConnectionView/ChipView/ChipModel.swift (100%) rename ios/MullvadVPN/View controllers/Tunnel/{FeatureIndicators => }/ConnectionView/ChipView/ChipView.swift (100%) rename ios/MullvadVPN/View controllers/Tunnel/{FeatureIndicators => }/ConnectionView/ChipView/ChipViewModelProtocol.swift (100%) rename ios/MullvadVPN/View controllers/Tunnel/{FeatureIndicators => }/ConnectionView/ConnectionView.swift (100%) rename ios/MullvadVPN/View controllers/Tunnel/{FeatureIndicators => }/ConnectionView/ConnectionViewComponentPreview.swift (100%) rename ios/MullvadVPN/View controllers/Tunnel/{FeatureIndicators => }/ConnectionView/ConnectionViewViewModel.swift (96%) rename ios/MullvadVPN/View controllers/Tunnel/{FeatureIndicators => }/ConnectionView/DetailsContainer.swift (100%) rename ios/MullvadVPN/View controllers/Tunnel/{FeatureIndicators => }/ConnectionView/DetailsView.swift (63%) rename ios/MullvadVPN/View controllers/Tunnel/{ => ConnectionView}/FeatureIndicators/FeatureIndicatorsView.swift (100%) rename ios/MullvadVPN/View controllers/Tunnel/{ => ConnectionView}/FeatureIndicators/FeatureIndicatorsViewModel.swift (100%) rename ios/MullvadVPN/View controllers/Tunnel/{FeatureIndicators => }/ConnectionView/HeaderView.swift (91%) delete mode 100644 ios/MullvadVPN/View controllers/Tunnel/DisconnectSplitButton.swift delete mode 100644 ios/MullvadVPN/View controllers/Tunnel/TranslucentButtonBlurView.swift delete mode 100644 ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift delete mode 100644 ios/MullvadVPN/View controllers/Tunnel/TunnelControlViewModel.swift diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 664e6cbe937e..ec046d9f94ff 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -146,7 +146,6 @@ 585E820327F3285E00939F0E /* SendStoreReceiptOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585E820227F3285E00939F0E /* SendStoreReceiptOperation.swift */; }; 58607A4D2947287800BC467D /* AccountExpiryInAppNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58607A4C2947287800BC467D /* AccountExpiryInAppNotificationProvider.swift */; }; 586168692976F6BD00EF8598 /* DisplayError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 586168682976F6BD00EF8598 /* DisplayError.swift */; }; - 5862805422428EF100F5A6E1 /* TranslucentButtonBlurView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5862805322428EF100F5A6E1 /* TranslucentButtonBlurView.swift */; }; 5864AF0729C78843005B0CD9 /* SettingsCellFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5864AF0029C7879B005B0CD9 /* SettingsCellFactory.swift */; }; 5864AF0829C78849005B0CD9 /* CellFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5864AF0129C7879B005B0CD9 /* CellFactoryProtocol.swift */; }; 5864AF0929C78850005B0CD9 /* VPNSettingsCellFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5864AF0229C7879B005B0CD9 /* VPNSettingsCellFactory.swift */; }; @@ -221,7 +220,6 @@ 588D7EE02AF3A595005DF40A /* ListAccessMethodInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 588D7EDF2AF3A595005DF40A /* ListAccessMethodInteractor.swift */; }; 588E4EAE28FEEDD8008046E3 /* MullvadREST.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 06799ABC28F98E1D00ACD94E /* MullvadREST.framework */; }; 58906DE02445C7A5002F0673 /* NEProviderStopReason+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58906DDF2445C7A5002F0673 /* NEProviderStopReason+Debug.swift */; }; - 58907D9524D17B4E00CFC3F5 /* DisconnectSplitButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58907D9424D17B4E00CFC3F5 /* DisconnectSplitButton.swift */; }; 58915D682A25FA080066445B /* DeviceCheckRemoteService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58915D672A25FA080066445B /* DeviceCheckRemoteService.swift */; }; 58915D6E2A26037A0066445B /* WireGuardKitTypes in Frameworks */ = {isa = PBXBuildFile; productRef = 58915D6D2A26037A0066445B /* WireGuardKitTypes */; }; 5891BF1C25E3E3EB006D6FB0 /* Bundle+ProductVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5891BF1B25E3E3EB006D6FB0 /* Bundle+ProductVersion.swift */; }; @@ -235,7 +233,6 @@ 589A455D28E094BF00565204 /* OperationObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583E1E292848DF67004838B3 /* OperationObserverTests.swift */; }; 589A455F28E094BF00565204 /* OperationConditionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580CBFB72848D503007878F0 /* OperationConditionTests.swift */; }; 589E76C02A9378F100E502F3 /* RESTRequestExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 589E76BF2A9378F100E502F3 /* RESTRequestExecutor.swift */; }; - 58A1AA8C23F5584C009F7EA6 /* ConnectionPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A1AA8B23F5584B009F7EA6 /* ConnectionPanelView.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 */; }; @@ -270,7 +267,6 @@ 58B2FDEB2AA72049003EB5C6 /* WireGuardKitTypes in Frameworks */ = {isa = PBXBuildFile; productRef = 58B2FDEA2AA72049003EB5C6 /* WireGuardKitTypes */; }; 58B2FDEE2AA72098003EB5C6 /* ApplicationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BFA5CB22A7CE1F00A6173D /* ApplicationConfiguration.swift */; }; 58B2FDEF2AA720C4003EB5C6 /* ApplicationTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C76A072A33850E00100D75 /* ApplicationTarget.swift */; }; - 58B43C1925F77DB60002C8C3 /* TunnelControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B43C1825F77DB60002C8C3 /* TunnelControlView.swift */; }; 58B465702A98C53300467203 /* RequestExecutorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B4656F2A98C53300467203 /* RequestExecutorTests.swift */; }; 58B93A1326C3F13600A55733 /* TunnelState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B93A1226C3F13600A55733 /* TunnelState.swift */; }; 58B993B12608A34500BA7811 /* LoginContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B993B02608A34500BA7811 /* LoginContentView.swift */; }; @@ -311,7 +307,6 @@ 58C9B8CE2ABB252E00040B46 /* DeviceCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FE65922AB1CDE000E53CB5 /* DeviceCheck.swift */; }; 58CAFA002983FF0200BE19F7 /* LoginInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58CAF9FF2983FF0200BE19F7 /* LoginInteractor.swift */; }; 58CAFA032985367600BE19F7 /* Promise.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58CAFA01298530DC00BE19F7 /* Promise.swift */; }; - 58CCA010224249A1004F3011 /* TunnelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58CCA00F224249A1004F3011 /* TunnelViewController.swift */; }; 58CCA01222424D11004F3011 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58CCA01122424D11004F3011 /* SettingsViewController.swift */; }; 58CCA0162242560B004F3011 /* UIColor+Palette.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58CCA0152242560B004F3011 /* UIColor+Palette.swift */; }; 58CCA01822426713004F3011 /* AccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58CCA01722426713004F3011 /* AccountViewController.swift */; }; @@ -669,7 +664,7 @@ 7AF9BE952A40461100DBFEDB /* RelayFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF9BE942A40461100DBFEDB /* RelayFilterView.swift */; }; 7AF9BE972A41C71F00DBFEDB /* ChipViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF9BE962A41C71F00DBFEDB /* ChipViewCell.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 */; }; + 7AFBE3892D089163002335FC /* TunnelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE3882D08915D002335FC /* 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 */; }; @@ -736,7 +731,6 @@ A90C48672C36BC2600DCB94C /* EphemeralPeerReceiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90C48662C36BC2600DCB94C /* EphemeralPeerReceiver.swift */; }; A90C48692C36BF3900DCB94C /* TunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90C48682C36BF3900DCB94C /* TunnelProvider.swift */; }; A91614D12B108D1B00F416EB /* TransportLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91614D02B108D1B00F416EB /* TransportLayer.swift */; }; - A91614D62B10B26B00F416EB /* TunnelControlViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91614D52B10B26B00F416EB /* TunnelControlViewModel.swift */; }; A917352129FAAA5200D5DCFD /* TransportStrategyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A917352029FAAA5200D5DCFD /* TransportStrategyTests.swift */; }; A9173C322C36CCDD00F6A08C /* EphemeralPeerReceiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9A557F42B7E3E5C0017ADA8 /* EphemeralPeerReceiver.swift */; }; A9173C372C36CD2B00F6A08C /* MullvadTypes.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58D223D5294C8E5E0029F5F8 /* MullvadTypes.framework */; platformFilter = ios; }; @@ -1608,7 +1602,6 @@ 585E820227F3285E00939F0E /* SendStoreReceiptOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendStoreReceiptOperation.swift; sourceTree = ""; }; 58607A4C2947287800BC467D /* AccountExpiryInAppNotificationProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountExpiryInAppNotificationProvider.swift; sourceTree = ""; }; 586168682976F6BD00EF8598 /* DisplayError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayError.swift; sourceTree = ""; }; - 5862805322428EF100F5A6E1 /* TranslucentButtonBlurView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslucentButtonBlurView.swift; sourceTree = ""; }; 5864AF0029C7879B005B0CD9 /* SettingsCellFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsCellFactory.swift; sourceTree = ""; }; 5864AF0129C7879B005B0CD9 /* CellFactoryProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CellFactoryProtocol.swift; sourceTree = ""; }; 5864AF0229C7879B005B0CD9 /* VPNSettingsCellFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VPNSettingsCellFactory.swift; sourceTree = ""; }; @@ -1689,7 +1682,6 @@ 588D7EDF2AF3A595005DF40A /* ListAccessMethodInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListAccessMethodInteractor.swift; sourceTree = ""; }; 58900D0228BBDCC70094E4F0 /* FixedWidthInteger+Arithmetics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FixedWidthInteger+Arithmetics.swift"; sourceTree = ""; }; 58906DDF2445C7A5002F0673 /* NEProviderStopReason+Debug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEProviderStopReason+Debug.swift"; sourceTree = ""; }; - 58907D9424D17B4E00CFC3F5 /* DisconnectSplitButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisconnectSplitButton.swift; sourceTree = ""; }; 58915D622A25F8400066445B /* DeviceCheckOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceCheckOperationTests.swift; sourceTree = ""; }; 58915D672A25FA080066445B /* DeviceCheckRemoteService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceCheckRemoteService.swift; sourceTree = ""; }; 5891BF1B25E3E3EB006D6FB0 /* Bundle+ProductVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+ProductVersion.swift"; sourceTree = ""; }; @@ -1714,7 +1706,6 @@ 589D28812846306C00F9A7B3 /* GroupOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupOperation.swift; sourceTree = ""; }; 589E76BF2A9378F100E502F3 /* RESTRequestExecutor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTRequestExecutor.swift; sourceTree = ""; }; 58A1AA8623F43901009F7EA6 /* Location.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Location.swift; sourceTree = ""; }; - 58A1AA8B23F5584B009F7EA6 /* ConnectionPanelView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionPanelView.swift; sourceTree = ""; }; 58A3BDAF28A1821A00C8C2C6 /* WgStats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WgStats.swift; sourceTree = ""; }; 58A8EE592976BFBB009C0F8D /* SKError+Localized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SKError+Localized.swift"; sourceTree = ""; }; 58A8EE5D2976DB00009C0F8D /* StorePaymentManagerError+Display.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StorePaymentManagerError+Display.swift"; sourceTree = ""; }; @@ -1737,7 +1728,6 @@ 58B26E292943545A00D5980C /* NotificationManagerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManagerDelegate.swift; sourceTree = ""; }; 58B2FDD32AA71D2A003EB5C6 /* MullvadSettings.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MullvadSettings.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 58B2FDD52AA71D2A003EB5C6 /* MullvadSettings.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MullvadSettings.h; sourceTree = ""; }; - 58B43C1825F77DB60002C8C3 /* TunnelControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelControlView.swift; sourceTree = ""; }; 58B4656F2A98C53300467203 /* RequestExecutorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestExecutorTests.swift; sourceTree = ""; }; 58B93A1226C3F13600A55733 /* TunnelState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelState.swift; sourceTree = ""; }; 58B993B02608A34500BA7811 /* LoginContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginContentView.swift; sourceTree = ""; }; @@ -1766,7 +1756,6 @@ 58CAF9FF2983FF0200BE19F7 /* LoginInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginInteractor.swift; sourceTree = ""; }; 58CAFA01298530DC00BE19F7 /* Promise.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Promise.swift; sourceTree = ""; }; 58CC40EE24A601900019D96E /* ObserverList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObserverList.swift; sourceTree = ""; }; - 58CCA00F224249A1004F3011 /* TunnelViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelViewController.swift; sourceTree = ""; }; 58CCA01122424D11004F3011 /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; 58CCA0152242560B004F3011 /* UIColor+Palette.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Palette.swift"; sourceTree = ""; }; 58CCA01722426713004F3011 /* AccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountViewController.swift; sourceTree = ""; }; @@ -2054,7 +2043,7 @@ 7AF9BE942A40461100DBFEDB /* RelayFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayFilterView.swift; sourceTree = ""; }; 7AF9BE962A41C71F00DBFEDB /* ChipViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChipViewCell.swift; sourceTree = ""; }; 7AFBE3862D084C96002335FC /* ActivityIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicator.swift; sourceTree = ""; }; - 7AFBE3882D08915D002335FC /* FI_TunnelViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FI_TunnelViewController.swift; sourceTree = ""; }; + 7AFBE3882D08915D002335FC /* TunnelViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelViewController.swift; sourceTree = ""; }; 7AFBE38A2D09AAFF002335FC /* SinglehopPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SinglehopPicker.swift; sourceTree = ""; }; 7AFBE38C2D09AB2E002335FC /* MultihopPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultihopPicker.swift; sourceTree = ""; }; 85006A8E2B73EF67004AD8FB /* MullvadVPNUITestsSmoke.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = MullvadVPNUITestsSmoke.xctestplan; sourceTree = ""; }; @@ -2132,7 +2121,6 @@ A90C48662C36BC2600DCB94C /* EphemeralPeerReceiver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EphemeralPeerReceiver.swift; sourceTree = ""; }; A90C48682C36BF3900DCB94C /* TunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelProvider.swift; sourceTree = ""; }; A91614D02B108D1B00F416EB /* TransportLayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransportLayer.swift; sourceTree = ""; }; - A91614D52B10B26B00F416EB /* TunnelControlViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelControlViewModel.swift; sourceTree = ""; }; A917352029FAAA5200D5DCFD /* TransportStrategyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransportStrategyTests.swift; sourceTree = ""; }; A91EBED92C1337040004A84D /* RetryStrategyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RetryStrategyTests.swift; sourceTree = ""; }; A92962582B1F4FDB00DFB93B /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; @@ -2713,6 +2701,7 @@ isa = PBXGroup; children = ( F0ADF1CF2D01B50B00299F09 /* ChipView */, + 7AA130972CFF364F00640DF9 /* FeatureIndicators */, 449E9A6E2D283C7400F8574A /* ButtonPanel.swift */, 7AA130982CFF365A00640DF9 /* ConnectionView.swift */, 449E9A6C2D283A2500F8574A /* ConnectionViewComponentPreview.swift */, @@ -3063,17 +3052,13 @@ 583FE01E29C197D5006E85F9 /* Tunnel */ = { isa = PBXGroup; children = ( - 7AA130972CFF364F00640DF9 /* FeatureIndicators */, - 58A1AA8B23F5584B009F7EA6 /* ConnectionPanelView.swift */, + 4419AA862D28264D001B13C9 /* ConnectionView */, + 7AFBE3862D084C96002335FC /* ActivityIndicator.swift */, 5878A27A2909649A0096FC88 /* CustomOverlayRenderer.swift */, - 58907D9424D17B4E00CFC3F5 /* DisconnectSplitButton.swift */, 58C3F4F82964B08300D72515 /* MapViewController.swift */, F09D04BC2AEBB7C5003D4F89 /* OutgoingConnectionService.swift */, - 5862805322428EF100F5A6E1 /* TranslucentButtonBlurView.swift */, - 58B43C1825F77DB60002C8C3 /* TunnelControlView.swift */, - 58CCA00F224249A1004F3011 /* TunnelViewController.swift */, + 7AFBE3882D08915D002335FC /* TunnelViewController.swift */, 5878A278290954790096FC88 /* TunnelViewControllerInteractor.swift */, - A91614D52B10B26B00F416EB /* TunnelControlViewModel.swift */, ); path = Tunnel; sourceTree = ""; @@ -4116,11 +4101,8 @@ 7AA130972CFF364F00640DF9 /* FeatureIndicators */ = { isa = PBXGroup; children = ( - 4419AA862D28264D001B13C9 /* ConnectionView */, - 7AFBE3862D084C96002335FC /* ActivityIndicator.swift */, F0B4957B2D03154200CFEC2A /* FeatureIndicatorsView.swift */, F0ADF1D22D01B6B400299F09 /* FeatureIndicatorsViewModel.swift */, - 7AFBE3882D08915D002335FC /* FI_TunnelViewController.swift */, ); path = FeatureIndicators; sourceTree = ""; @@ -5928,7 +5910,7 @@ 7A8A19142CEF2548000BCB5B /* DAITATunnelSettingsViewModel.swift in Sources */, 7A8A18F92CE34EA8000BCB5B /* SettingsMultihopView.swift in Sources */, 44BB5F972BE527F4002520EB /* TunnelState+UI.swift in Sources */, - 7AFBE3892D089163002335FC /* FI_TunnelViewController.swift in Sources */, + 7AFBE3892D089163002335FC /* TunnelViewController.swift in Sources */, 7A11DD0B2A9495D400098CD8 /* AppRoutes.swift in Sources */, 5827B0902B0CAA0500CCBBA1 /* EditAccessMethodCoordinator.swift in Sources */, 5846227126E229F20035F7C2 /* StoreSubscription.swift in Sources */, @@ -5963,11 +5945,9 @@ 58B26E2A2943545A00D5980C /* NotificationManagerDelegate.swift in Sources */, 7A8A19072CE4E9D3000BCB5B /* SettingsInfoView.swift in Sources */, 7AA1309B2D0048D800640DF9 /* MainButton.swift in Sources */, - 58A1AA8C23F5584C009F7EA6 /* ConnectionPanelView.swift in Sources */, 5878A27B2909649A0096FC88 /* CustomOverlayRenderer.swift in Sources */, 7A0EAEA02D0333CE00D3EB8B /* Color+Helpers.swift in Sources */, 7A8A19052CE4E9A9000BCB5B /* SwitchRowView.swift in Sources */, - A91614D62B10B26B00F416EB /* TunnelControlViewModel.swift in Sources */, 7A5869972B32EA4500640D27 /* AppButton.swift in Sources */, 586C0D8F2B03D88100E7CDD7 /* ProxyProtocolConfigurationItemIdentifier.swift in Sources */, 7A27E3CF2CBD4A8C0088BCFF /* SelectableSettingsDetailsCell.swift in Sources */, @@ -5983,7 +5963,6 @@ 58EFC7752AFB4CEF00E9F4CB /* AboutViewController.swift in Sources */, 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 */, @@ -6051,7 +6030,6 @@ F0E8CC0C2A4EE672007ED3B4 /* SetupAccountCompletedController.swift in Sources */, 5846227726E22A7C0035F7C2 /* StorePaymentManagerDelegate.swift in Sources */, 58EF581125D69DB400AEBA94 /* StatusImageView.swift in Sources */, - 58907D9524D17B4E00CFC3F5 /* DisconnectSplitButton.swift in Sources */, 58EE2E3B272FF814003BFF93 /* SettingsDataSourceDelegate.swift in Sources */, 5823FA5426CE49F700283BF8 /* TunnelObserver.swift in Sources */, 5888AD87227B17950051EB06 /* LocationViewController.swift in Sources */, @@ -6130,7 +6108,6 @@ 7A6F2FA72AFBB9AE006D0856 /* AccountExpiry.swift in Sources */, 5819C2172729595500D6EC38 /* SettingsAddDNSEntryCell.swift in Sources */, 7A1A26452A29CEF700B978AA /* RelayFilterViewController.swift in Sources */, - 5862805422428EF100F5A6E1 /* TranslucentButtonBlurView.swift in Sources */, 587EB66A270EFACB00123C75 /* CharacterSet+IPAddress.swift in Sources */, 5888AD83227B11080051EB06 /* LocationCell.swift in Sources */, 5891BF1C25E3E3EB006D6FB0 /* Bundle+ProductVersion.swift in Sources */, @@ -6274,7 +6251,6 @@ 581DFAEA2B176C51005D6D1C /* PersistentProxyConfiguration+ViewModel.swift in Sources */, 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 */, diff --git a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift index 1feeed7331f1..728684ff95c3 100644 --- a/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift +++ b/ios/MullvadVPN/Classes/AccessbilityIdentifier.swift @@ -157,6 +157,7 @@ public enum AccessibilityIdentifier: Equatable { case connectionPanelInAddressRow case connectionPanelOutAddressRow case connectionPanelOutIpv6AddressRow + case connectionPanelServerLabel case customSwitch case customWireGuardPortTextField case dnsContentBlockersHeaderView diff --git a/ios/MullvadVPN/Coordinators/TunnelCoordinator.swift b/ios/MullvadVPN/Coordinators/TunnelCoordinator.swift index 0c55f7e1af46..4cfcb98b71c7 100644 --- a/ios/MullvadVPN/Coordinators/TunnelCoordinator.swift +++ b/ios/MullvadVPN/Coordinators/TunnelCoordinator.swift @@ -12,13 +12,7 @@ import UIKit class TunnelCoordinator: Coordinator, Presenting { private let tunnelManager: TunnelManager - - #if DEBUG - private let controller: FI_TunnelViewController - #else private let controller: TunnelViewController - #endif - private var tunnelObserver: TunnelObserver? var presentationContext: UIViewController { @@ -44,11 +38,7 @@ class TunnelCoordinator: Coordinator, Presenting { ipOverrideRepository: ipOverrideRepository ) - #if DEBUG - controller = FI_TunnelViewController(interactor: interactor) - #else controller = TunnelViewController(interactor: interactor) - #endif super.init() diff --git a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ActivityIndicator.swift b/ios/MullvadVPN/View controllers/Tunnel/ActivityIndicator.swift similarity index 100% rename from ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ActivityIndicator.swift rename to ios/MullvadVPN/View controllers/Tunnel/ActivityIndicator.swift diff --git a/ios/MullvadVPN/View controllers/Tunnel/ConnectionPanelView.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionPanelView.swift deleted file mode 100644 index ab61264800ae..000000000000 --- a/ios/MullvadVPN/View controllers/Tunnel/ConnectionPanelView.swift +++ /dev/null @@ -1,329 +0,0 @@ -// -// ConnectionPanelView.swift -// MullvadVPN -// -// Created by pronebird on 12/02/2020. -// Copyright © 2020 Mullvad VPN AB. All rights reserved. -// - -import Foundation -import UIKit - -struct ConnectionPanelData { - var inAddress: String - var outAddress: String? -} - -class ConnectionPanelView: UIView { - var dataSource: ConnectionPanelData? { - didSet { - didChangeDataSource() - } - } - - var showsConnectionInfo = false { - didSet { - updateConnectionInfoVisibility() - } - } - - var connectedRelayName = "" { - didSet { - collapseView.setAccessibilityIdentifier(.relayStatusCollapseButton) - collapseView.title.text = connectedRelayName - collapseView.accessibilityLabel = NSLocalizedString( - "RELAY_ACCESSIBILITY_LABEL", - tableName: "ConnectionPanel", - value: "Connected relay", - comment: "" - ) - collapseView.accessibilityAttributedValue = NSAttributedString( - string: connectedRelayName.replacingOccurrences( - of: "-wireguard", - with: " WireGuard" - ), - attributes: [.accessibilitySpeechLanguage: "en"] - ) - } - } - - private let collapseView: ConnectionPanelCollapseView = { - let collapseView = ConnectionPanelCollapseView() - collapseView.axis = .horizontal - collapseView.alignment = .top - collapseView.distribution = .fill - collapseView.translatesAutoresizingMaskIntoConstraints = false - collapseView.tintColor = .white - collapseView.isAccessibilityElement = false - return collapseView - }() - - private let inAddressRow = ConnectionPanelAddressRow() - private let outAddressRow = ConnectionPanelAddressRow() - - private lazy var stackView: UIStackView = { - let stackView = UIStackView(arrangedSubviews: [inAddressRow, outAddressRow]) - stackView.axis = .vertical - stackView.translatesAutoresizingMaskIntoConstraints = false - return stackView - }() - - private let textLabelLayoutGuide: UILayoutGuide = { - let layoutGuide = UILayoutGuide() - layoutGuide.identifier = "TextLabelLayoutGuide" - return layoutGuide - }() - - override init(frame: CGRect) { - super.init(frame: frame) - - inAddressRow.translatesAutoresizingMaskIntoConstraints = false - outAddressRow.translatesAutoresizingMaskIntoConstraints = false - - inAddressRow.setAccessibilityIdentifier(.connectionPanelInAddressRow) - outAddressRow.setAccessibilityIdentifier(.connectionPanelOutAddressRow) - - inAddressRow.title = NSLocalizedString( - "IN_ADDRESS_LABEL", - tableName: "ConnectionPanel", - value: "In", - comment: "" - ) - outAddressRow.title = NSLocalizedString( - "OUT_ADDRESS_LABEL", - tableName: "ConnectionPanel", - value: "Out", - comment: "" - ) - - addSubview(collapseView) - addSubview(stackView) - addLayoutGuide(textLabelLayoutGuide) - - NSLayoutConstraint.activate([ - collapseView.topAnchor.constraint(equalTo: topAnchor), - collapseView.leadingAnchor.constraint(equalTo: leadingAnchor), - collapseView.trailingAnchor.constraint(equalTo: trailingAnchor), - - stackView.topAnchor.constraint(equalTo: collapseView.bottomAnchor, constant: 4), - stackView.leadingAnchor.constraint(equalTo: leadingAnchor), - stackView.trailingAnchor.constraint(equalTo: trailingAnchor), - stackView.bottomAnchor.constraint(equalTo: bottomAnchor), - - inAddressRow.heightAnchor.constraint(equalToConstant: UIMetrics.ConnectionPanelView.inRowHeight), - outAddressRow.heightAnchor.constraint(equalToConstant: UIMetrics.ConnectionPanelView.outRowHeight), - - // Align all text labels with the guide, so that they maintain equal width - textLabelLayoutGuide.trailingAnchor - .constraint(equalTo: inAddressRow.textLabelLayoutGuide.trailingAnchor), - textLabelLayoutGuide.trailingAnchor - .constraint(equalTo: outAddressRow.textLabelLayoutGuide.trailingAnchor), - ]) - - updateConnectionInfoVisibility() - updateCollapseButtonAccessibilityHint() - - let longPressGestureRecognizer = UILongPressGestureRecognizer( - target: self, - action: #selector(toggleCollapse(_:)) - ) - longPressGestureRecognizer.minimumPressDuration = 0 - collapseView.addGestureRecognizer(longPressGestureRecognizer) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func didChangeDataSource() { - inAddressRow.value = dataSource?.inAddress - inAddressRow.alpha = dataSource?.inAddress == nil ? 0 : 1.0 - - outAddressRow.value = dataSource?.outAddress - outAddressRow.alpha = dataSource?.outAddress == nil ? 0 : 1.0 - } - - private func toggleConnectionInfoVisibility() { - showsConnectionInfo = !showsConnectionInfo - } - - @objc private func toggleCollapse(_ sender: UILongPressGestureRecognizer) { - switch sender.state { - case .began: - collapseView.title.textColor = .lightGray - collapseView.imageView.tintColor = .lightGray - case .ended: - collapseView.title.textColor = .white - collapseView.imageView.tintColor = .white - toggleConnectionInfoVisibility() - default: - break - } - } - - private func updateConnectionInfoVisibility() { - stackView.isHidden = !showsConnectionInfo - collapseView.style = showsConnectionInfo ? .up : .down - - if collapseView.accessibilityElementIsFocused(), showsConnectionInfo { - UIAccessibility.post( - notification: .layoutChanged, - argument: stackView.arrangedSubviews.first - ) - } - updateCollapseButtonAccessibilityHint() - } - - private func updateCollapseButtonAccessibilityHint() { - if showsConnectionInfo { - collapseView.accessibilityHint = NSLocalizedString( - "COLLAPSE_BUTTON_ACCESSIBILITY_HINT", - tableName: "ConnectionPanel", - value: "Double tap to collapse the connection info panel.", - comment: "" - ) - } else { - collapseView.accessibilityHint = NSLocalizedString( - "EXPAND_BUTTON_ACCESSIBILITY_HINT", - tableName: "ConnectionPanel", - value: "Double tap to expand the connection info panel.", - comment: "" - ) - } - } -} - -class ConnectionPanelAddressRow: UIView { - private let textLabel: UILabel = { - let textLabel = UILabel() - textLabel.font = .systemFont(ofSize: 17) - textLabel.textColor = .white - textLabel.translatesAutoresizingMaskIntoConstraints = false - textLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal) - return textLabel - }() - - private let detailTextLabel: UILabel = { - let detailTextLabel = UILabel() - detailTextLabel.setAccessibilityIdentifier(.connectionPanelDetailLabel) - detailTextLabel.font = .systemFont(ofSize: 17) - detailTextLabel.textColor = .white - detailTextLabel.translatesAutoresizingMaskIntoConstraints = false - detailTextLabel.numberOfLines = .zero - detailTextLabel.lineBreakMode = .byWordWrapping - return detailTextLabel - }() - - private lazy var stackView: UIStackView = { - let stackView = UIStackView(arrangedSubviews: [textLabel, detailTextLabel]) - stackView.spacing = UIStackView.spacingUseSystem - stackView.translatesAutoresizingMaskIntoConstraints = false - stackView.alignment = .top - return stackView - }() - - let textLabelLayoutGuide = UILayoutGuide() - - var title: String? { - get { - textLabel.text - } - set { - textLabel.text = newValue - accessibilityLabel = newValue - } - } - - var value: String? { - get { - detailTextLabel.text - } - set { - detailTextLabel.text = newValue - accessibilityValue = newValue - } - } - - override init(frame: CGRect) { - super.init(frame: frame) - - isAccessibilityElement = true - - addSubview(stackView) - addLayoutGuide(textLabelLayoutGuide) - - NSLayoutConstraint.activate([ - stackView.topAnchor.constraint(equalTo: topAnchor), - stackView.bottomAnchor.constraint(equalTo: bottomAnchor), - stackView.leadingAnchor.constraint(equalTo: leadingAnchor), - stackView.trailingAnchor.constraint(equalTo: trailingAnchor), - - textLabelLayoutGuide.leadingAnchor.constraint(equalTo: textLabel.leadingAnchor), - textLabelLayoutGuide.trailingAnchor.constraint(equalTo: textLabel.trailingAnchor), - textLabelLayoutGuide.topAnchor.constraint(equalTo: textLabel.topAnchor), - textLabelLayoutGuide.bottomAnchor.constraint(equalTo: textLabel.bottomAnchor), - ]) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -class ConnectionPanelCollapseView: UIStackView { - enum Style { - case up, down - - var image: UIImage? { - switch self { - case .up: - return UIImage(named: "IconChevronUp") - case .down: - return UIImage(named: "IconChevronDown") - } - } - } - - var style = Style.up { - didSet { - updateImage() - } - } - - private(set) var title: UILabel = { - let button = UILabel() - button.textColor = .white - button.numberOfLines = 0 - return button - }() - - private(set) var imageView: UIImageView = { - let imageView = UIImageView() - imageView.contentMode = .scaleAspectFit - return imageView - }() - - override init(frame: CGRect) { - super.init(frame: frame) - - addArrangedSubview(title) - addArrangedSubview(imageView) - - title.setContentHuggingPriority(.defaultLow, for: .horizontal) - title.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) - - imageView.setContentHuggingPriority(.defaultHigh, for: .horizontal) - imageView.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) - addArrangedSubview(UIView()) // Pushes content left. - - updateImage() - } - - required init(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func updateImage() { - imageView.image = style.image - } -} diff --git a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/ButtonPanel.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ButtonPanel.swift similarity index 100% rename from ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/ButtonPanel.swift rename to ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ButtonPanel.swift diff --git a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/ChipView/ChipContainerView.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipContainerView.swift similarity index 100% rename from ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/ChipView/ChipContainerView.swift rename to ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipContainerView.swift diff --git a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/ChipView/ChipFeature.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipFeature.swift similarity index 100% rename from ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/ChipView/ChipFeature.swift rename to ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipFeature.swift diff --git a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/ChipView/ChipModel.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipModel.swift similarity index 100% rename from ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/ChipView/ChipModel.swift rename to ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipModel.swift diff --git a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/ChipView/ChipView.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipView.swift similarity index 100% rename from ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/ChipView/ChipView.swift rename to ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipView.swift diff --git a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/ChipView/ChipViewModelProtocol.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipViewModelProtocol.swift similarity index 100% rename from ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/ChipView/ChipViewModelProtocol.swift rename to ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ChipView/ChipViewModelProtocol.swift diff --git a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/ConnectionView.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionView.swift similarity index 100% rename from ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/ConnectionView.swift rename to ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionView.swift diff --git a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/ConnectionViewComponentPreview.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionViewComponentPreview.swift similarity index 100% rename from ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/ConnectionViewComponentPreview.swift rename to ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionViewComponentPreview.swift diff --git a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/ConnectionViewViewModel.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionViewViewModel.swift similarity index 96% rename from ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/ConnectionViewViewModel.swift rename to ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionViewViewModel.swift index 962eaa0d63fd..96a47d5ab40a 100644 --- a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/ConnectionViewViewModel.swift +++ b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionViewViewModel.swift @@ -24,7 +24,7 @@ class ConnectionViewViewModel: ObservableObject { case selectLocation } - @Published var tunnelStatus: TunnelStatus + @Published private(set) var tunnelStatus: TunnelStatus @Published var outgoingConnectionInfo: OutgoingConnectionInfo? @Published var showsActivityIndicator = false @@ -46,6 +46,14 @@ class ConnectionViewViewModel: ObservableObject { init(tunnelStatus: TunnelStatus) { self.tunnelStatus = tunnelStatus } + + func update(tunnelStatus: TunnelStatus) { + self.tunnelStatus = tunnelStatus + + if !tunnelIsConnected { + outgoingConnectionInfo = nil + } + } } extension ConnectionViewViewModel { diff --git a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/DetailsContainer.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/DetailsContainer.swift similarity index 100% rename from ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/DetailsContainer.swift rename to ios/MullvadVPN/View controllers/Tunnel/ConnectionView/DetailsContainer.swift diff --git a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/DetailsView.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/DetailsView.swift similarity index 63% rename from ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/DetailsView.swift rename to ios/MullvadVPN/View controllers/Tunnel/ConnectionView/DetailsView.swift index ff07dc94b582..87daea5046be 100644 --- a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/DetailsView.swift +++ b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/DetailsView.swift @@ -24,17 +24,26 @@ extension ConnectionView { VStack(alignment: .leading, spacing: 0) { if let inAddress = viewModel.inAddress { - connectionDetailRow(title: LocalizedStringKey("In"), value: inAddress) - .accessibilityIdentifier(AccessibilityIdentifier.connectionPanelInAddressRow.asString) + connectionDetailRow( + title: LocalizedStringKey("In"), + value: inAddress, + accessibilityId: .connectionPanelInAddressRow + ) } if viewModel.tunnelIsConnected { if let outAddressIpv4 = viewModel.outAddressIpv4 { - connectionDetailRow(title: LocalizedStringKey("Out IPv4"), value: outAddressIpv4) - .accessibilityIdentifier(AccessibilityIdentifier.connectionPanelOutAddressRow.asString) + connectionDetailRow( + title: LocalizedStringKey("Out IPv4"), + value: outAddressIpv4, + accessibilityId: .connectionPanelOutAddressRow + ) } if let outAddressIpv6 = viewModel.outAddressIpv6 { - connectionDetailRow(title: LocalizedStringKey("Out IPv6"), value: outAddressIpv6) - .accessibilityIdentifier(AccessibilityIdentifier.connectionPanelOutAddressRow.asString) + connectionDetailRow( + title: LocalizedStringKey("Out IPv6"), + value: outAddressIpv6, + accessibilityId: .connectionPanelOutAddressRow + ) } } } @@ -42,7 +51,11 @@ extension ConnectionView { } @ViewBuilder - private func connectionDetailRow(title: LocalizedStringKey, value: String) -> some View { + private func connectionDetailRow( + title: LocalizedStringKey, + value: String, + accessibilityId: AccessibilityIdentifier + ) -> some View { HStack(alignment: .top, spacing: 8) { Text(title) .font(.subheadline) @@ -52,6 +65,7 @@ extension ConnectionView { Text(value) .font(.subheadline) .foregroundStyle(UIColor.primaryTextColor.color) + .accessibilityIdentifier(accessibilityId.asString) } } } diff --git a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/FeatureIndicatorsView.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/FeatureIndicators/FeatureIndicatorsView.swift similarity index 100% rename from ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/FeatureIndicatorsView.swift rename to ios/MullvadVPN/View controllers/Tunnel/ConnectionView/FeatureIndicators/FeatureIndicatorsView.swift diff --git a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/FeatureIndicatorsViewModel.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/FeatureIndicators/FeatureIndicatorsViewModel.swift similarity index 100% rename from ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/FeatureIndicatorsViewModel.swift rename to ios/MullvadVPN/View controllers/Tunnel/ConnectionView/FeatureIndicators/FeatureIndicatorsViewModel.swift diff --git a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/HeaderView.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/HeaderView.swift similarity index 91% rename from ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/HeaderView.swift rename to ios/MullvadVPN/View controllers/Tunnel/ConnectionView/HeaderView.swift index fa1112a80e1c..388754f5968e 100644 --- a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView/HeaderView.swift +++ b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/HeaderView.swift @@ -21,6 +21,7 @@ extension ConnectionView { .font(.title3.weight(.semibold)) .foregroundStyle(viewModel.textColorForSecureLabel.color) .accessibilityIdentifier(viewModel.accessibilityIdForSecureLabel.asString) + .accessibilityLabel(viewModel.localizedAccessibilityLabelForSecureLabel) if let countryAndCity = viewModel.titleForCountryAndCity { Text(countryAndCity) @@ -34,9 +35,9 @@ extension ConnectionView { .font(.body) .foregroundStyle(UIColor.primaryTextColor.color.opacity(0.6)) .padding(.top, 2) + .accessibilityIdentifier(AccessibilityIdentifier.connectionPanelServerLabel.asString) } } - .accessibilityLabel(viewModel.localizedAccessibilityLabelForSecureLabel) Group { Spacer() diff --git a/ios/MullvadVPN/View controllers/Tunnel/DisconnectSplitButton.swift b/ios/MullvadVPN/View controllers/Tunnel/DisconnectSplitButton.swift deleted file mode 100644 index 640d799de396..000000000000 --- a/ios/MullvadVPN/View controllers/Tunnel/DisconnectSplitButton.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// DisconnectSplitButton.swift -// MullvadVPN -// -// Created by pronebird on 29/07/2020. -// Copyright © 2020 Mullvad VPN AB. All rights reserved. -// - -import Foundation -import UIKit - -class DisconnectSplitButton: UIView { - let primaryButton = AppButton(style: .translucentDangerSplitLeft) - let secondaryButton = AppButton(style: .translucentDangerSplitRight) - - override init(frame: CGRect) { - super.init(frame: .zero) - commonInit() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func commonInit() { - let primaryButtonBlurView = TranslucentButtonBlurView(button: primaryButton) - let secondaryButtonBlurView = TranslucentButtonBlurView(button: secondaryButton) - - let stackView = UIStackView(arrangedSubviews: [primaryButtonBlurView, secondaryButtonBlurView]) - stackView.translatesAutoresizingMaskIntoConstraints = false - stackView.axis = .horizontal - stackView.distribution = .fill - stackView.alignment = .fill - stackView.spacing = 1 - - let secondaryButtonSize = UIMetrics.DisconnectSplitButton.secondaryButton - - addConstrainedSubviews([stackView]) { - stackView.pinEdgesToSuperview() - - secondaryButton.widthAnchor.constraint(equalToConstant: secondaryButtonSize.width) - secondaryButton.heightAnchor.constraint(equalToConstant: secondaryButtonSize.height) - } - - primaryButton.configuration?.contentInsets.leading += secondaryButtonSize.width - - // Ideally, we shouldn't need to manually resize the image ourselves. - // However, since UIButton.Configuration doesn't provide a direct property - // for controlling image scaling (like imageScaling or contentMode in other contexts), - // manual resizing has been one approach to ensure the image fits within bounds. - secondaryButton.configuration?.image = UIImage(resource: .iconReload) - .resizeImage(targetSize: secondaryButtonSize.deducting(insets: secondaryButton.defaultContentInsets)) - .imageFlippedForRightToLeftLayoutDirection() - } -} diff --git a/ios/MullvadVPN/View controllers/Tunnel/TranslucentButtonBlurView.swift b/ios/MullvadVPN/View controllers/Tunnel/TranslucentButtonBlurView.swift deleted file mode 100644 index 5f27e8be5a82..000000000000 --- a/ios/MullvadVPN/View controllers/Tunnel/TranslucentButtonBlurView.swift +++ /dev/null @@ -1,84 +0,0 @@ -// -// TranslucentButtonBlurView.swift -// MullvadVPN -// -// Created by pronebird on 20/03/2019. -// Copyright © 2019 Mullvad VPN AB. All rights reserved. -// - -import UIKit - -class TranslucentButtonBlurView: UIVisualEffectView { - let appButton: AppButton - - var isEnabled: Bool { - didSet { - appButton.isEnabled = isEnabled - effect = appButton.blurEffect(isEnabled: isEnabled) - } - } - - init(button: AppButton) { - appButton = button - isEnabled = button.isEnabled - - let effect = appButton.blurEffect(isEnabled: isEnabled) - super.init(effect: effect) - - contentView.addConstrainedSubviews([button]) { - button.pinEdgesToSuperview() - } - - layer.cornerRadius = UIMetrics.controlCornerRadius - layer.maskedCorners = button.style.cornerMask(effectiveUserInterfaceLayoutDirection) - layer.masksToBounds = true - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -private extension AppButton { - func blurEffect(isEnabled: Bool) -> UIBlurEffect { - let style = isEnabled ? style.blurEffectStyle : style.disabledStateBlurEffectStyle - return UIBlurEffect(style: style) - } -} - -private extension AppButton.Style { - func cornerMask(_ userInterfaceLayoutDirection: UIUserInterfaceLayoutDirection) - -> CACornerMask { - switch (self, userInterfaceLayoutDirection) { - case (.translucentDangerSplitLeft, .leftToRight), - (.translucentDangerSplitRight, .rightToLeft): - return [.layerMinXMinYCorner, .layerMinXMaxYCorner] - case (.translucentDangerSplitRight, .leftToRight), - (.translucentDangerSplitLeft, .rightToLeft): - return [.layerMaxXMinYCorner, .layerMaxXMaxYCorner] - default: - return [ - .layerMinXMinYCorner, .layerMinXMaxYCorner, - .layerMaxXMinYCorner, .layerMaxXMaxYCorner, - ] - } - } - - var blurEffectStyle: UIBlurEffect.Style { - switch self { - case .translucentDangerSplitLeft, .translucentDangerSplitRight, .translucentDanger: - return .systemUltraThinMaterialDark - default: - return .light - } - } - - var disabledStateBlurEffectStyle: UIBlurEffect.Style { - switch self { - case .success, .translucentNeutral: - return .systemThinMaterialDark - default: - return blurEffectStyle - } - } -} diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift deleted file mode 100644 index cff522297669..000000000000 --- a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift +++ /dev/null @@ -1,398 +0,0 @@ -// -// TunnelControlView.swift -// MullvadVPN -// -// Created by pronebird on 09/03/2021. -// Copyright © 2021 Mullvad VPN AB. All rights reserved. -// - -import MapKit -import MullvadREST -import MullvadTypes -import PacketTunnelCore -import UIKit - -enum TunnelControlAction { - case connect - case disconnect - case cancel - case reconnect - case selectLocation -} - -final class TunnelControlView: UIView { - private let secureLabel = makeBoldTextLabel(ofSize: 20, numberOfLines: 0) - private let cityLabel = makeBoldTextLabel(ofSize: 34) - private let countryLabel = makeBoldTextLabel(ofSize: 34) - - private let activityIndicator: SpinnerActivityIndicatorView = { - let activityIndicator = SpinnerActivityIndicatorView(style: .large) - activityIndicator.translatesAutoresizingMaskIntoConstraints = false - activityIndicator.tintColor = .white - activityIndicator.setContentHuggingPriority(.defaultHigh, for: .horizontal) - activityIndicator.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) - return activityIndicator - }() - - private let locationContainerView: UIStackView = { - let view = UIStackView() - view.translatesAutoresizingMaskIntoConstraints = false - view.isAccessibilityElement = false - view.accessibilityTraits = .summaryElement - view.axis = .vertical - view.spacing = 8 - return view - }() - - private let connectionPanel: ConnectionPanelView = { - let view = ConnectionPanelView() - view.translatesAutoresizingMaskIntoConstraints = false - return view - }() - - private let buttonsStackView: UIStackView = { - let stackView = UIStackView() - stackView.spacing = UIMetrics.interButtonSpacing - stackView.axis = .vertical - stackView.translatesAutoresizingMaskIntoConstraints = false - return stackView - }() - - private let connectButton: AppButton = { - let button = AppButton(style: .success) - button.setAccessibilityIdentifier(.connectButton) - button.translatesAutoresizingMaskIntoConstraints = false - return button - }() - - private let cancelButton: AppButton = { - let button = AppButton(style: .translucentDanger) - button.setAccessibilityIdentifier(.cancelButton) - button.translatesAutoresizingMaskIntoConstraints = false - return button - }() - - private let selectLocationButton: AppButton = { - let button = AppButton(style: .translucentNeutral) - button.setAccessibilityIdentifier(.selectLocationButton) - button.translatesAutoresizingMaskIntoConstraints = false - return button - }() - - private let selectLocationButtonBlurView: TranslucentButtonBlurView - private let connectButtonBlurView: TranslucentButtonBlurView - private let cancelButtonBlurView: TranslucentButtonBlurView - - private let splitDisconnectButton: DisconnectSplitButton = { - let button = DisconnectSplitButton() - button.primaryButton.setAccessibilityIdentifier(.disconnectButton) - button.translatesAutoresizingMaskIntoConstraints = false - return button - }() - - private let containerView: UIView = { - let view = UIView() - view.translatesAutoresizingMaskIntoConstraints = false - return view - }() - - private var traitConstraints = [NSLayoutConstraint]() - private var viewModel: TunnelControlViewModel? - - var actionHandler: ((TunnelControlAction) -> Void)? - - var mapCenterAlignmentView: UIView { - activityIndicator - } - - override init(frame: CGRect) { - selectLocationButtonBlurView = TranslucentButtonBlurView(button: selectLocationButton) - connectButtonBlurView = TranslucentButtonBlurView(button: connectButton) - cancelButtonBlurView = TranslucentButtonBlurView(button: cancelButton) - - super.init(frame: frame) - - backgroundColor = .clear - directionalLayoutMargins = UIMetrics.contentLayoutMargins - accessibilityContainerType = .semanticGroup - setAccessibilityIdentifier(.connectionView) - - addSubviews() - addButtonHandlers() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func update(with model: TunnelControlViewModel) { - viewModel = model - let tunnelState = model.tunnelStatus.state - secureLabel.text = model.secureLabelText - secureLabel.textColor = tunnelState.textColorForSecureLabel - selectLocationButtonBlurView.isEnabled = model.enableButtons - connectButtonBlurView.isEnabled = model.enableButtons - cityLabel.attributedText = attributedStringForLocation(string: model.city) - countryLabel.attributedText = attributedStringForLocation(string: model.country) - connectionPanel.connectedRelayName = model.connectedRelaysName - connectionPanel.dataSource = model.connectionPanel - - updateSecureLabel(tunnelState: tunnelState) - updateActionButtons(tunnelState: tunnelState) - updateTunnelRelays(tunnelStatus: model.tunnelStatus) - } - - func setAnimatingActivity(_ isAnimating: Bool) { - if isAnimating { - activityIndicator.startAnimating() - } else { - activityIndicator.stopAnimating() - } - } - - private func updateActionButtons(tunnelState: TunnelState) { - let view = view(forActionButton: tunnelState.actionButton) - - updateButtonTitles(tunnelState: tunnelState) - updateButtonEnabledStates(shouldEnableButtons: tunnelState.shouldEnableButtons) - setArrangedButtons([selectLocationButtonBlurView, view]) - } - - private func updateSecureLabel(tunnelState: TunnelState) { - secureLabel.text = tunnelState.localizedTitleForSecureLabel.uppercased() - secureLabel.textColor = tunnelState.textColorForSecureLabel - - switch tunnelState { - case .connected: - secureLabel.setAccessibilityIdentifier(.connectionStatusConnectedLabel) - case .connecting: - secureLabel.setAccessibilityIdentifier(.connectionStatusConnectingLabel) - default: - secureLabel.setAccessibilityIdentifier(.connectionStatusNotConnectedLabel) - } - } - - private func updateButtonTitles(tunnelState: TunnelState) { - connectButton.setTitle( - NSLocalizedString( - "CONNECT_BUTTON_TITLE", - tableName: "Main", - value: "Secure connection", - comment: "" - ), for: .normal - ) - selectLocationButton.setTitle( - tunnelState.localizedTitleForSelectLocationButton, - for: .normal - ) - cancelButton.setTitle( - NSLocalizedString( - "CANCEL_BUTTON_TITLE", - tableName: "Main", - value: tunnelState == .waitingForConnectivity(.noConnection) ? "Disconnect" : "Cancel", - comment: "" - ), for: .normal - ) - splitDisconnectButton.primaryButton.setTitle( - NSLocalizedString( - "DISCONNECT_BUTTON_TITLE", - tableName: "Main", - value: "Disconnect", - comment: "" - ), for: .normal - ) - splitDisconnectButton.secondaryButton.accessibilityLabel = NSLocalizedString( - "RECONNECT_BUTTON_ACCESSIBILITY_LABEL", - tableName: "Main", - value: "Reconnect", - comment: "" - ) - } - - private func updateButtonEnabledStates(shouldEnableButtons: Bool) { - selectLocationButtonBlurView.isEnabled = shouldEnableButtons - connectButtonBlurView.isEnabled = shouldEnableButtons - } - - private func updateTunnelRelays(tunnelStatus: TunnelStatus) { - let tunnelState = tunnelStatus.state - let observedState = tunnelStatus.observedState - - if tunnelState.isSecured, let tunnelRelays = tunnelState.relays { - cityLabel.attributedText = attributedStringForLocation( - string: tunnelRelays.exit.location.city - ) - countryLabel.attributedText = attributedStringForLocation( - string: tunnelRelays.exit.location.country - ) - - let exitName = tunnelRelays.exit.hostname - let entryName = tunnelRelays.entry?.hostname - let usingDaita = observedState.connectionState?.isDaitaEnabled == true - - let connectedRelayName = if let entryName { - String(format: NSLocalizedString( - "CONNECT_PANEL_TITLE", - tableName: "Main", - value: "%@ via %@\(usingDaita ? " using DAITA" : "")", - comment: "" - ), exitName, entryName) - } else { - String(format: NSLocalizedString( - "CONNECT_PANEL_TITLE", - tableName: "Main", - value: "%@\(usingDaita ? " using DAITA" : "")", - comment: "" - ), exitName) - } - - connectionPanel.isHidden = false - connectionPanel.connectedRelayName = NSLocalizedString( - "CONNECT_PANEL_TITLE", - tableName: "Main", - value: connectedRelayName, - comment: "" - ) - } else { - countryLabel.attributedText = attributedStringForLocation(string: " ") - cityLabel.attributedText = attributedStringForLocation(string: " ") - connectionPanel.dataSource = nil - connectionPanel.isHidden = true - } - - locationContainerView.accessibilityLabel = viewModel?.tunnelStatus.state.localizedAccessibilityLabel - } - - // MARK: - Private - - private func addSubviews() { - for subview in [secureLabel, countryLabel, cityLabel, connectionPanel] { - locationContainerView.addArrangedSubview(subview) - } - - for subview in [activityIndicator, buttonsStackView, locationContainerView] { - containerView.addSubview(subview) - } - - addSubview(containerView) - - NSLayoutConstraint.activate([ - containerView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor), - containerView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), - containerView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor), - containerView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), - - locationContainerView.topAnchor.constraint(greaterThanOrEqualTo: containerView.topAnchor), - locationContainerView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), - locationContainerView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor), - - activityIndicator.centerXAnchor.constraint(equalTo: containerView.centerXAnchor), - locationContainerView.topAnchor.constraint( - equalTo: activityIndicator.bottomAnchor, - constant: 22 - ), - - buttonsStackView.topAnchor.constraint( - equalTo: locationContainerView.bottomAnchor, - constant: 24 - ), - buttonsStackView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), - buttonsStackView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor), - buttonsStackView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor), - ]) - } - - private func addButtonHandlers() { - connectButton.addTarget( - self, - action: #selector(handleConnect), - for: .touchUpInside - ) - cancelButton.addTarget( - self, - action: #selector(handleCancel), - for: .touchUpInside - ) - splitDisconnectButton.primaryButton.addTarget( - self, - action: #selector(handleDisconnect), - for: .touchUpInside - ) - splitDisconnectButton.secondaryButton.addTarget( - self, - action: #selector(handleReconnect), - for: .touchUpInside - ) - selectLocationButton.addTarget( - self, - action: #selector(handleSelectLocation), - for: .touchUpInside - ) - } - - private func setArrangedButtons(_ newButtons: [UIView]) { - buttonsStackView.arrangedSubviews.forEach { button in - if !newButtons.contains(button) { - buttonsStackView.removeArrangedSubview(button) - button.removeFromSuperview() - } - } - - newButtons.forEach { button in - buttonsStackView.addArrangedSubview(button) - } - } - - private func view(forActionButton actionButton: TunnelState.TunnelControlActionButton) -> UIView { - switch actionButton { - case .connect: - return connectButton - case .disconnect: - return splitDisconnectButton - case .cancel: - return cancelButtonBlurView - } - } - - private func attributedStringForLocation(string: String) -> NSAttributedString { - let paragraphStyle = NSMutableParagraphStyle() - paragraphStyle.lineSpacing = 0 - paragraphStyle.lineHeightMultiple = 0.8 - - return NSAttributedString( - string: string, - attributes: [.paragraphStyle: paragraphStyle] - ) - } - - private static func makeBoldTextLabel(ofSize fontSize: CGFloat, numberOfLines: Int = 1) -> UILabel { - let textLabel = UILabel() - textLabel.translatesAutoresizingMaskIntoConstraints = false - textLabel.font = UIFont.boldSystemFont(ofSize: fontSize) - textLabel.textColor = .white - textLabel.numberOfLines = numberOfLines - return textLabel - } - - // MARK: - Actions - - @objc private func handleConnect() { - actionHandler?(.connect) - } - - @objc private func handleCancel() { - actionHandler?(.cancel) - } - - @objc private func handleDisconnect() { - actionHandler?(.disconnect) - } - - @objc private func handleReconnect() { - actionHandler?(.reconnect) - } - - @objc private func handleSelectLocation() { - actionHandler?(.selectLocation) - } -} diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlViewModel.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelControlViewModel.swift deleted file mode 100644 index d1ce7b3a7a93..000000000000 --- a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlViewModel.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// TunnelControlViewModel.swift -// MullvadVPN -// -// Created by Marco Nikic on 2023-11-24. -// Copyright © 2023 Mullvad VPN AB. All rights reserved. -// - -import Foundation - -struct TunnelControlViewModel { - let tunnelStatus: TunnelStatus - let secureLabelText: String - let enableButtons: Bool - let city: String - let country: String - let connectedRelaysName: String - let outgoingConnectionInfo: OutgoingConnectionInfo? - - var connectionPanel: ConnectionPanelData? { - guard let tunnelRelays = tunnelStatus.state.relays else { - return nil - } - - var portAndTransport = "" - if let inPort = tunnelStatus.observedState.connectionState?.remotePort { - let protocolLayer = tunnelStatus.observedState.connectionState?.transportLayer == .tcp ? "TCP" : "UDP" - portAndTransport = ":\(inPort) \(protocolLayer)" - } - - return ConnectionPanelData( - inAddress: "\(tunnelRelays.entry?.endpoint.ipv4Relay.ip ?? tunnelRelays.exit.endpoint.ipv4Relay.ip)\(portAndTransport)", - outAddress: outgoingConnectionInfo?.outAddress - ) - } - - static var empty: Self { - TunnelControlViewModel( - tunnelStatus: TunnelStatus(), - secureLabelText: "", - enableButtons: true, - city: "", - country: "", - connectedRelaysName: "", - outgoingConnectionInfo: nil - ) - } - - func update(status: TunnelStatus) -> TunnelControlViewModel { - TunnelControlViewModel( - tunnelStatus: status, - secureLabelText: secureLabelText, - enableButtons: enableButtons, - city: city, - country: country, - connectedRelaysName: connectedRelaysName, - outgoingConnectionInfo: nil - ) - } - - func update(outgoingConnectionInfo: OutgoingConnectionInfo) -> TunnelControlViewModel { - TunnelControlViewModel( - tunnelStatus: tunnelStatus, - secureLabelText: secureLabelText, - enableButtons: enableButtons, - city: city, - country: country, - connectedRelaysName: connectedRelaysName, - outgoingConnectionInfo: outgoingConnectionInfo - ) - } -} diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift index 78bd6c27b0e9..a486014e7241 100644 --- a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift +++ b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift @@ -2,21 +2,25 @@ // TunnelViewController.swift // MullvadVPN // -// Created by pronebird on 20/03/2019. -// Copyright © 2019 Mullvad VPN AB. All rights reserved. +// Created by Jon Petersson on 2024-12-10. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. // +import Combine import MapKit import MullvadLogging +import MullvadSettings import MullvadTypes -import UIKit +import SwiftUI class TunnelViewController: UIViewController, RootContainment { private let logger = Logger(label: "TunnelViewController") private let interactor: TunnelViewControllerInteractor - private let contentView = TunnelControlView(frame: CGRect(x: 0, y: 0, width: 320, height: 480)) private var tunnelState: TunnelState = .disconnected - private var viewModel = TunnelControlViewModel.empty + private var connectionViewViewModel: ConnectionViewViewModel + private var indicatorsViewViewModel: FeatureIndicatorsViewModel + private var connectionView: ConnectionView + private var connectionController: UIHostingController? var shouldShowSelectLocationPicker: (() -> Void)? var shouldShowCancelTunnelAlert: (() -> Void)? @@ -46,7 +50,26 @@ class TunnelViewController: UIViewController, RootContainment { init(interactor: TunnelViewControllerInteractor) { self.interactor = interactor + tunnelState = interactor.tunnelStatus.state + connectionViewViewModel = ConnectionViewViewModel(tunnelStatus: interactor.tunnelStatus) + indicatorsViewViewModel = FeatureIndicatorsViewModel( + tunnelSettings: interactor.tunnelSettings, + ipOverrides: interactor.ipOverrides + ) + + connectionView = ConnectionView( + connectionViewModel: self.connectionViewViewModel, + indicatorsViewModel: self.indicatorsViewViewModel + ) + super.init(nibName: nil, bundle: nil) + + // When content size is updated in SwiftUI we need to explicitly tell UIKit to + // update its view size. This is not necessary on iOS 16 where we can set + // hostingController.sizingOptions instead. + connectionView.onContentUpdate = { [weak self] in + self?.connectionController?.view.setNeedsUpdateConstraints() + } } required init?(coder: NSCoder) { @@ -61,15 +84,24 @@ class TunnelViewController: UIViewController, RootContainment { } interactor.didUpdateTunnelStatus = { [weak self] tunnelStatus in + self?.connectionViewViewModel.update(tunnelStatus: tunnelStatus) self?.setTunnelState(tunnelStatus.state, animated: true) - self?.updateViewModel(tunnelStatus: tunnelStatus) + self?.view.setNeedsLayout() } interactor.didGetOutgoingAddress = { [weak self] outgoingConnectionInfo in - self?.updateViewModel(outgoingConnectionInfo: outgoingConnectionInfo) + self?.connectionViewViewModel.outgoingConnectionInfo = outgoingConnectionInfo + } + + interactor.didUpdateTunnelSettings = { [weak self] tunnelSettings in + self?.indicatorsViewViewModel.tunnelSettings = tunnelSettings + } + + interactor.didUpdateIpOverrides = { [weak self] overrides in + self?.indicatorsViewViewModel.ipOverrides = overrides } - contentView.actionHandler = { [weak self] action in + connectionView.action = { [weak self] action in switch action { case .connect: self?.interactor.startTunnel() @@ -94,37 +126,12 @@ class TunnelViewController: UIViewController, RootContainment { addMapController() addContentView() - - tunnelState = interactor.tunnelStatus.state updateMap(animated: false) - updateViewModel(tunnelStatus: interactor.tunnelStatus) - } - - func updateViewModel( - tunnelStatus: TunnelStatus? = nil, - outgoingConnectionInfo: OutgoingConnectionInfo? = nil - ) { - if let tunnelStatus { - viewModel = viewModel.update(status: tunnelStatus) - } - if let outgoingConnectionInfo { - viewModel = viewModel.update(outgoingConnectionInfo: outgoingConnectionInfo) - } - contentView.update(with: viewModel) - } - - override func viewWillTransition( - to size: CGSize, - with coordinator: UIViewControllerTransitionCoordinator - ) { - super.viewWillTransition(to: size, with: coordinator) - - contentView.update(with: viewModel) } func setMainContentHidden(_ isHidden: Bool, animated: Bool) { let actions = { - self.contentView.alpha = isHidden ? 0 : 1 + _ = self.connectionView.opacity(isHidden ? 0 : 1) } if animated { @@ -138,6 +145,7 @@ class TunnelViewController: UIViewController, RootContainment { private func setTunnelState(_ tunnelState: TunnelState, animated: Bool) { self.tunnelState = tunnelState + setNeedsHeaderBarStyleAppearanceUpdate() guard isViewLoaded else { return } @@ -149,18 +157,18 @@ class TunnelViewController: UIViewController, RootContainment { switch tunnelState { case let .connecting(tunnelRelays, _, _): mapViewController.removeLocationMarker() - contentView.setAnimatingActivity(true) mapViewController.setCenter(tunnelRelays?.exit.location.geoCoordinate, animated: animated) + connectionViewViewModel.showsActivityIndicator = true case let .reconnecting(tunnelRelays, _, _), let .negotiatingEphemeralPeer(tunnelRelays, _, _, _): mapViewController.removeLocationMarker() - contentView.setAnimatingActivity(true) mapViewController.setCenter(tunnelRelays.exit.location.geoCoordinate, animated: animated) + connectionViewViewModel.showsActivityIndicator = true case let .connected(tunnelRelays, _, _): let center = tunnelRelays.exit.location.geoCoordinate mapViewController.setCenter(center, animated: animated) { - self.contentView.setAnimatingActivity(false) + self.connectionViewViewModel.showsActivityIndicator = false // Connection can change during animation, so make sure we're still connected before adding marker. if case .connected = self.tunnelState { @@ -170,45 +178,42 @@ class TunnelViewController: UIViewController, RootContainment { case .pendingReconnect: mapViewController.removeLocationMarker() - contentView.setAnimatingActivity(true) + connectionViewViewModel.showsActivityIndicator = true case .waitingForConnectivity, .error: mapViewController.removeLocationMarker() - contentView.setAnimatingActivity(false) + connectionViewViewModel.showsActivityIndicator = false case .disconnected, .disconnecting: mapViewController.removeLocationMarker() - contentView.setAnimatingActivity(false) mapViewController.setCenter(nil, animated: animated) + connectionViewViewModel.showsActivityIndicator = false } } private func addMapController() { let mapView = mapViewController.view! - mapView.translatesAutoresizingMaskIntoConstraints = false - mapViewController.alignmentView = contentView.mapCenterAlignmentView addChild(mapViewController) - view.addSubview(mapView) mapViewController.didMove(toParent: self) - NSLayoutConstraint.activate([ - mapView.topAnchor.constraint(equalTo: view.topAnchor), - mapView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - mapView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - mapView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - ]) + view.addConstrainedSubviews([mapView]) { + mapView.pinEdgesToSuperview() + } } private func addContentView() { - contentView.translatesAutoresizingMaskIntoConstraints = false - 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), - ]) + let connectionController = UIHostingController(rootView: connectionView) + self.connectionController = connectionController + + let connectionViewProxy = connectionController.view! + connectionViewProxy.backgroundColor = .clear + + addChild(connectionController) + connectionController.didMove(toParent: self) + + view.addConstrainedSubviews([connectionViewProxy]) { + connectionViewProxy.pinEdgesToSuperview(.all()) + } } } diff --git a/ios/MullvadVPNUITests/ConnectivityTests.swift b/ios/MullvadVPNUITests/ConnectivityTests.swift index 909cdce159da..791c5c969782 100644 --- a/ios/MullvadVPNUITests/ConnectivityTests.swift +++ b/ios/MullvadVPNUITests/ConnectivityTests.swift @@ -205,12 +205,12 @@ class ConnectivityTests: LoggedOutUITestCase { // Actual test. Make sure it is possible to connect to a relay TunnelControlPage(app) - .tapSecureConnectionButton() + .tapConnectButton() allowAddVPNConfigurationsIfAsked() TunnelControlPage(app) - .waitForSecureConnectionLabel() + .waitForConnectedLabel() HeaderBar(app) .tapAccountButton() diff --git a/ios/MullvadVPNUITests/Pages/TunnelControlPage.swift b/ios/MullvadVPNUITests/Pages/TunnelControlPage.swift index da47a2c33404..d1a3b3dceae7 100644 --- a/ios/MullvadVPNUITests/Pages/TunnelControlPage.swift +++ b/ios/MullvadVPNUITests/Pages/TunnelControlPage.swift @@ -30,7 +30,7 @@ class TunnelControlPage: Page { let startTime = Date() let pollingInterval = TimeInterval(0.5) // How often to check for changes - let inAddressRow = app.otherElements[AccessibilityIdentifier.connectionPanelInAddressRow] + let inAddressRow = app.staticTexts[AccessibilityIdentifier.connectionPanelInAddressRow] while Date().timeIntervalSince(startTime) < timeout { let expectation = XCTestExpectation(description: "Wait for connection attempts") @@ -41,29 +41,29 @@ class TunnelControlPage: Page { _ = XCTWaiter.wait(for: [expectation], timeout: pollingInterval + 0.5) - if let currentText = inAddressRow.value as? String { - // Skip initial label value with IP address only - no port or protocol - guard currentText.contains(" ") == true else { - continue - } + let currentText = inAddressRow.label + + // Skip initial label value with IP address only - no port or protocol + guard currentText.contains(" ") == true else { + continue + } - let addressPortComponent = currentText.components(separatedBy: " ")[0] - let ipAddress = addressPortComponent.components(separatedBy: ":")[0] - let port = addressPortComponent.components(separatedBy: ":")[1] - let protocolName = currentText.components(separatedBy: " ")[1] - let connectionAttempt = ConnectionAttempt( - ipAddress: ipAddress, - port: port, - protocolName: protocolName - ) - - if connectionAttempt != lastConnectionAttempt { - connectionAttempts.append(connectionAttempt) - lastConnectionAttempt = connectionAttempt - - if connectionAttempts.count == attemptsCount { - break - } + let addressPortComponent = currentText.components(separatedBy: " ")[0] + let ipAddress = addressPortComponent.components(separatedBy: ":")[0] + let port = addressPortComponent.components(separatedBy: ":")[1] + let protocolName = currentText.components(separatedBy: " ")[1] + let connectionAttempt = ConnectionAttempt( + ipAddress: ipAddress, + port: port, + protocolName: protocolName + ) + + if connectionAttempt != lastConnectionAttempt { + connectionAttempts.append(connectionAttempt) + lastConnectionAttempt = connectionAttempt + + if connectionAttempts.count == attemptsCount { + break } } } @@ -83,7 +83,7 @@ class TunnelControlPage: Page { return self } - @discardableResult func tapSecureConnectionButton() -> Self { + @discardableResult func tapConnectButton() -> Self { app.buttons[AccessibilityIdentifier.connectButton].tap() return self } @@ -112,7 +112,7 @@ class TunnelControlPage: Page { return self } - @discardableResult func waitForSecureConnectionLabel() -> Self { + @discardableResult func waitForConnectedLabel() -> Self { let labelFound = app.staticTexts[.connectionStatusConnectedLabel] .waitForExistence(timeout: BaseUITestCase.extremelyLongTimeout) XCTAssertTrue(labelFound, "Secure connection label presented") @@ -121,7 +121,7 @@ class TunnelControlPage: Page { } @discardableResult func tapRelayStatusExpandCollapseButton() -> Self { - app.otherElements[AccessibilityIdentifier.relayStatusCollapseButton].press(forDuration: .leastNonzeroMagnitude) + app.images[AccessibilityIdentifier.relayStatusCollapseButton].press(forDuration: .leastNonzeroMagnitude) return self } @@ -194,38 +194,23 @@ class TunnelControlPage: Page { /// Verify that the app attempts to connect over Multihop. @discardableResult func verifyConnectingOverMultihop() -> Self { - let relayName = getCurrentRelayName().lowercased() - XCTAssertTrue(relayName.contains("via")) + XCTAssertTrue(app.staticTexts["Multihop"].exists) return self } /// Verify that the app attempts to connect using DAITA. @discardableResult func verifyConnectingUsingDAITA() -> Self { - let relayName = getCurrentRelayName().lowercased() - XCTAssertTrue(relayName.contains("using daita")) + XCTAssertTrue(app.staticTexts["DAITA"].exists) return self } func getInIPAddressFromConnectionStatus() -> String { - let inAddressRow = app.otherElements[AccessibilityIdentifier.connectionPanelInAddressRow] - - if let textValue = inAddressRow.value as? String { - let ipAddress = textValue.components(separatedBy: ":")[0] - return ipAddress - } else { - XCTFail("Failed to read relay IP address from status label") - return String() - } + let inAddressRow = app.staticTexts[.connectionPanelInAddressRow] + return inAddressRow.label.components(separatedBy: ":")[0] } func getCurrentRelayName() -> String { - let relayExpandButton = app.otherElements[.relayStatusCollapseButton] - - guard let relayName = relayExpandButton.value as? String else { - XCTFail("Failed to read relay name from tunnel control page") - return String() - } - - return relayName + let server = app.staticTexts[.connectionPanelServerLabel] + return server.label } } diff --git a/ios/MullvadVPNUITests/RelayTests.swift b/ios/MullvadVPNUITests/RelayTests.swift index 9bf9c790421f..f92feee29ec7 100644 --- a/ios/MullvadVPNUITests/RelayTests.swift +++ b/ios/MullvadVPNUITests/RelayTests.swift @@ -75,12 +75,12 @@ class RelayTests: LoggedInWithTimeUITestCase { .swipeDownToDismissModal() TunnelControlPage(app) - .tapSecureConnectionButton() + .tapConnectButton() allowAddVPNConfigurationsIfAsked() // Allow adding VPN configurations iOS permission TunnelControlPage(app) - .waitForSecureConnectionLabel() + .waitForConnectedLabel() try Networking.verifyCannotReachAdServingDomain() @@ -90,12 +90,12 @@ class RelayTests: LoggedInWithTimeUITestCase { func testAppConnection() throws { TunnelControlPage(app) - .tapSecureConnectionButton() + .tapConnectButton() allowAddVPNConfigurationsIfAsked() TunnelControlPage(app) - .waitForSecureConnectionLabel() + .waitForConnectedLabel() try Networking.verifyCanAccessInternet() try Networking.verifyConnectedThroughMullvad() @@ -158,12 +158,12 @@ class RelayTests: LoggedInWithTimeUITestCase { .tapDoneButton() TunnelControlPage(app) - .tapSecureConnectionButton() + .tapConnectButton() allowAddVPNConfigurationsIfAsked() TunnelControlPage(app) - .waitForSecureConnectionLabel() + .waitForConnectedLabel() try Networking.verifyCanAccessInternet() @@ -199,12 +199,12 @@ class RelayTests: LoggedInWithTimeUITestCase { .tapDoneButton() TunnelControlPage(app) - .tapSecureConnectionButton() + .tapConnectButton() allowAddVPNConfigurationsIfAsked() TunnelControlPage(app) - .waitForSecureConnectionLabel() + .waitForConnectedLabel() try Networking.verifyCanAccessInternet() @@ -253,7 +253,7 @@ class RelayTests: LoggedInWithTimeUITestCase { // Should be two UDP connection attempts but sometimes only one is shown in the UI TunnelControlPage(app) .verifyConnectingOverTCPAfterUDPAttempts() - .waitForSecureConnectionLabel() + .waitForConnectedLabel() .tapDisconnectButton() } @@ -275,14 +275,14 @@ class RelayTests: LoggedInWithTimeUITestCase { .swipeDownToDismissModal() TunnelControlPage(app) - .tapSecureConnectionButton() + .tapConnectButton() allowAddVPNConfigurationsIfAsked() TunnelControlPage(app) .tapRelayStatusExpandCollapseButton() .verifyConnectingToPort("4001") - .waitForSecureConnectionLabel() + .waitForConnectedLabel() .tapDisconnectButton() } @@ -318,12 +318,12 @@ class RelayTests: LoggedInWithTimeUITestCase { .tapDoneButton() TunnelControlPage(app) - .tapSecureConnectionButton() + .tapConnectButton() allowAddVPNConfigurationsIfAsked() TunnelControlPage(app) - .waitForSecureConnectionLabel() + .waitForConnectedLabel() .verifyConnectingUsingDAITA() .tapDisconnectButton() } @@ -358,57 +358,27 @@ class RelayTests: LoggedInWithTimeUITestCase { .tapDoneButton() TunnelControlPage(app) - .tapSecureConnectionButton() + .tapConnectButton() allowAddVPNConfigurationsIfAsked() TunnelControlPage(app) - .waitForSecureConnectionLabel() + .waitForConnectedLabel() .verifyConnectingOverMultihop() .tapDisconnectButton() } - /// Connect to a relay in the default country and city, get name and IP address of the relay the app successfully connects to. Assumes user is logged on and at tunnel control page. - private func getDefaultRelayInfo() -> RelayInfo { - TunnelControlPage(app) - .tapSelectLocationButton() - - if SelectLocationPage(app).locationCellIsExpanded(BaseUITestCase.testsDefaultCountryName) { - // Already expanded - just make sure the correct city cell is selected - SelectLocationPage(app) - .tapLocationCell(withName: BaseUITestCase.testsDefaultCityName) - } else { - SelectLocationPage(app) - .tapLocationCellExpandButton(withName: BaseUITestCase.testsDefaultCountryName) - .tapLocationCell(withName: BaseUITestCase.testsDefaultCityName) - } - - allowAddVPNConfigurationsIfAsked() - - let relayIPAddress = TunnelControlPage(app) - .waitForSecureConnectionLabel() - .tapRelayStatusExpandCollapseButton() - .getInIPAddressFromConnectionStatus() - - let relayName = TunnelControlPage(app).getCurrentRelayName() - - TunnelControlPage(app) - .tapDisconnectButton() - - return RelayInfo(name: relayName, ipAddress: relayIPAddress) - } - func testCustomDNS() throws { let dnsServerIPAddress = "8.8.8.8" let dnsServerProviderName = "GOOGLE" TunnelControlPage(app) - .tapSecureConnectionButton() + .tapConnectButton() allowAddVPNConfigurationsIfAsked() TunnelControlPage(app) - .waitForSecureConnectionLabel() + .waitForConnectedLabel() try Networking.verifyCanAccessInternet() @@ -432,4 +402,36 @@ class RelayTests: LoggedInWithTimeUITestCase { try Networking.verifyDNSServerProvider(dnsServerProviderName, isMullvad: false) } +} + +extension RelayTests { + /// Connect to a relay in the default country and city, get name and IP address of the relay the app successfully connects to. Assumes user is logged on and at tunnel control page. + private func getDefaultRelayInfo() -> RelayInfo { + TunnelControlPage(app) + .tapSelectLocationButton() + + if SelectLocationPage(app).locationCellIsExpanded(BaseUITestCase.testsDefaultCountryName) { + // Already expanded - just make sure the correct city cell is selected + SelectLocationPage(app) + .tapLocationCell(withName: BaseUITestCase.testsDefaultCityName) + } else { + SelectLocationPage(app) + .tapLocationCellExpandButton(withName: BaseUITestCase.testsDefaultCountryName) + .tapLocationCell(withName: BaseUITestCase.testsDefaultCityName) + } + + allowAddVPNConfigurationsIfAsked() + + let relayIPAddress = TunnelControlPage(app) + .waitForConnectedLabel() + .tapRelayStatusExpandCollapseButton() + .getInIPAddressFromConnectionStatus() + + let relayName = TunnelControlPage(app).getCurrentRelayName() + + TunnelControlPage(app) + .tapDisconnectButton() + + return RelayInfo(name: relayName, ipAddress: relayIPAddress) + } } // swiftlint:disable:this file_length diff --git a/ios/MullvadVPNUITests/Screenshots/ScreenshotTests.swift b/ios/MullvadVPNUITests/Screenshots/ScreenshotTests.swift index 326a5c56a189..dc53791101f5 100644 --- a/ios/MullvadVPNUITests/Screenshots/ScreenshotTests.swift +++ b/ios/MullvadVPNUITests/Screenshots/ScreenshotTests.swift @@ -50,7 +50,7 @@ class ScreenshotTests: LoggedInWithTimeUITestCase { .tapLocationCell(withName: "Sweden") TunnelControlPage(app) - .waitForSecureConnectionLabel() + .waitForConnectedLabel() snapshot("QuantumConnectionSecured") }