diff options
| author | Mojgan <mojgan.jelodar@mullvad.net> | 2026-04-23 16:47:40 +0200 |
|---|---|---|
| committer | Mojgan <mojgan.jelodar@mullvad.net> | 2026-04-23 16:47:40 +0200 |
| commit | 7944e9b1982feb3deba8871ab49e05d65886a235 (patch) | |
| tree | 7b9c2482cf13631a4442b9f1d5a97f77b7eb78eb | |
| parent | 8ccdcafd4ce312f75ffabafc4ae93f8ef5bad736 (diff) | |
| download | mullvadvpn-speed-connetcion-hackday.tar.xz mullvadvpn-speed-connetcion-hackday.zip | |
Speed connection testspeed-connetcion-hackday
| -rw-r--r-- | ios/Assets/Localizable.xcstrings | 6 | ||||
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/project.pbxproj | 104 | ||||
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 2 | ||||
| -rw-r--r-- | ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionView.swift | 13 | ||||
| -rw-r--r-- | ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift | 3 | ||||
| -rw-r--r-- | ios/MullvadVPNTests/MullvadVPN/Networking/NetworkSpeedMonitorTests.swift | 20 | ||||
| -rw-r--r-- | ios/SpeedConnection/MockNetworkSpeedMonitor.swift | 41 | ||||
| -rw-r--r-- | ios/SpeedConnection/NetworkSpeedMonitor.swift | 119 | ||||
| -rw-r--r-- | ios/SpeedConnection/SpeedConnectionView.swift | 66 | ||||
| -rw-r--r-- | ios/SpeedConnection/SpeedConnectionViewModel.swift | 41 | ||||
| -rw-r--r-- | ios/SpeedConnection/TrafficData.swift | 26 | ||||
| -rw-r--r-- | ios/SpeedConnection/TrafficPackage.swift | 22 | ||||
| -rw-r--r-- | ios/SpeedConnection/TrafficSpeed.swift | 34 | ||||
| -rw-r--r-- | ios/SpeedConnection/TrafficStatus.swift | 17 | ||||
| -rw-r--r-- | ios/SpeedConnection/TrafficSummery.swift | 58 | ||||
| -rw-r--r-- | ios/SpeedConnection/Untitled.swift | 8 |
16 files changed, 559 insertions, 21 deletions
diff --git a/ios/Assets/Localizable.xcstrings b/ios/Assets/Localizable.xcstrings index bd526a0ae6..fd7b4ac168 100644 --- a/ios/Assets/Localizable.xcstrings +++ b/ios/Assets/Localizable.xcstrings @@ -21697,6 +21697,9 @@ } } }, + "Download: %lf KB" : { + + }, "Dublin" : { "localizations" : { "da" : { @@ -68167,6 +68170,9 @@ } } }, + "Upload: %lf KB" : { + + }, "USA" : { "localizations" : { "da" : { diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 425342cd1d..436aa58b52 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -7,12 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - E10A0001000000000000AB01 /* GotaTunActor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10A0001000000000000AA01 /* GotaTunActor.swift */; }; - E10A0002000000000000AB01 /* PacketTunnelDebugSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10A0002000000000000AA01 /* PacketTunnelDebugSettings.swift */; }; - E10A0002000000000000AB02 /* PacketTunnelDebugSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10A0002000000000000AA01 /* PacketTunnelDebugSettings.swift */; }; - E10A0002000000000000AB03 /* PacketTunnelDebugSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10A0002000000000000AA01 /* PacketTunnelDebugSettings.swift */; }; - E10A0002000000000000AB04 /* PacketTunnelDebugSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10A0002000000000000AA01 /* PacketTunnelDebugSettings.swift */; }; - E10A0002000000000000AB05 /* PacketTunnelDebugSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10A0002000000000000AA01 /* PacketTunnelDebugSettings.swift */; }; 0107F40B2F5B02580012451B /* WireGuardKitTypes in Frameworks */ = {isa = PBXBuildFile; productRef = 0107F40A2F5B02580012451B /* WireGuardKitTypes */; }; 0107F40D2F5B02840012451B /* MullvadTypes.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58D223D5294C8E5E0029F5F8 /* MullvadTypes.framework */; }; 0107F4142F5B02D70012451B /* MullvadLogging.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58D223F3294C8FF00029F5F8 /* MullvadLogging.framework */; }; @@ -943,6 +937,12 @@ A9E0317F2ACC331C0095D843 /* TunnelStatusBlockObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E0317D2ACC32920095D843 /* TunnelStatusBlockObserver.swift */; }; A9E034642ABB302000E59A5A /* UIEdgeInsets+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E034632ABB302000E59A5A /* UIEdgeInsets+Extensions.swift */; }; A9EE85612DF1BE2900F2D769 /* TermsOfServiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EE85602DF1BE2900F2D769 /* TermsOfServiceView.swift */; }; + E10A0001000000000000AB01 /* GotaTunActor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10A0001000000000000AA01 /* GotaTunActor.swift */; }; + E10A0002000000000000AB01 /* PacketTunnelDebugSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10A0002000000000000AA01 /* PacketTunnelDebugSettings.swift */; }; + E10A0002000000000000AB02 /* PacketTunnelDebugSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10A0002000000000000AA01 /* PacketTunnelDebugSettings.swift */; }; + E10A0002000000000000AB03 /* PacketTunnelDebugSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10A0002000000000000AA01 /* PacketTunnelDebugSettings.swift */; }; + E10A0002000000000000AB04 /* PacketTunnelDebugSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10A0002000000000000AA01 /* PacketTunnelDebugSettings.swift */; }; + E10A0002000000000000AB05 /* PacketTunnelDebugSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10A0002000000000000AA01 /* PacketTunnelDebugSettings.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 */; }; @@ -965,6 +965,17 @@ F02F41A02B9723AF00625A4F /* AddLocationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F02F419A2B9723AE00625A4F /* AddLocationsViewController.swift */; }; F02F41A12B9723AF00625A4F /* AddLocationsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = F02F419B2B9723AE00625A4F /* AddLocationsDataSource.swift */; }; F02F41A22B9723AF00625A4F /* AddLocationsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F02F419C2B9723AF00625A4F /* AddLocationsCoordinator.swift */; }; + F0341E472EB4D1220054C429 /* NetworkSpeedMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0341E442EB4D00B0054C429 /* NetworkSpeedMonitorTests.swift */; }; + F0341E482EB4D1A00054C429 /* NetworkSpeedMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0F2666B2EB4ABB300AAD7DA /* NetworkSpeedMonitor.swift */; }; + F0341E4A2EB4D20F0054C429 /* TrafficData.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0F2666D2EB4BBFF00AAD7DA /* TrafficData.swift */; }; + F0341E4C2EB50B850054C429 /* TrafficStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0341E4B2EB50B830054C429 /* TrafficStatus.swift */; }; + F0341E4D2EB50B850054C429 /* TrafficStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0341E4B2EB50B830054C429 /* TrafficStatus.swift */; }; + F0341E4F2EB50D280054C429 /* TrafficSummery.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0341E4E2EB50D1E0054C429 /* TrafficSummery.swift */; }; + F0341E502EB50D280054C429 /* TrafficSummery.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0341E4E2EB50D1E0054C429 /* TrafficSummery.swift */; }; + F0341E522EB50F190054C429 /* TrafficPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0341E512EB50F050054C429 /* TrafficPackage.swift */; }; + F0341E532EB50F190054C429 /* TrafficPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0341E512EB50F050054C429 /* TrafficPackage.swift */; }; + F0341E552EB50F350054C429 /* TrafficSpeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0341E542EB50F330054C429 /* TrafficSpeed.swift */; }; + F0341E562EB50F350054C429 /* TrafficSpeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0341E542EB50F330054C429 /* TrafficSpeed.swift */; }; F03580252A13842C00E5DAFD /* IncreasedHitButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03580242A13842C00E5DAFD /* IncreasedHitButton.swift */; }; F03A69F72C2AD2D6000E2E7E /* TimeInterval+Timeout.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03A69F62C2AD2D5000E2E7E /* TimeInterval+Timeout.swift */; }; F03A69F92C2AD414000E2E7E /* FormsheetPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03A69F82C2AD413000E2E7E /* FormsheetPresentationController.swift */; }; @@ -1043,6 +1054,10 @@ F0A89CB32D9D6C2100580C27 /* MullvadDeviceProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A89CB22D9D6C1400580C27 /* MullvadDeviceProxy.swift */; }; F0A89CB52D9D864B00580C27 /* RustProblemReportRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0EEFB9E2D8D60E1007FE4B3 /* RustProblemReportRequest.swift */; }; F0A89CB72D9D923300580C27 /* String+UnsafePointer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A89CB62D9D922300580C27 /* String+UnsafePointer.swift */; }; + F0A8B6DE2F9A521D008F29D9 /* SpeedConnectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A8B6DD2F9A521D008F29D9 /* SpeedConnectionViewModel.swift */; }; + F0A8B6DF2F9A521D008F29D9 /* SpeedConnectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A8B6DD2F9A521D008F29D9 /* SpeedConnectionViewModel.swift */; }; + F0A8B6E12F9A619C008F29D9 /* MockNetworkSpeedMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A8B6E02F9A619A008F29D9 /* MockNetworkSpeedMonitor.swift */; }; + F0A8B6E22F9A619C008F29D9 /* MockNetworkSpeedMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A8B6E02F9A619A008F29D9 /* MockNetworkSpeedMonitor.swift */; }; F0AC643A2F3B4FD40024427C /* NotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0AC64392F3B4FD40024427C /* NotificationSettings.swift */; }; F0AC643C2F3B50860024427C /* NotificationSettingsObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0AC643B2F3B50860024427C /* NotificationSettingsObserver.swift */; }; F0AC643D2F3B50860024427C /* NotificationSettingsObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0AC643B2F3B50860024427C /* NotificationSettingsObserver.swift */; }; @@ -1119,6 +1134,9 @@ F0EF50D52A949F8E0031E8DF /* ChangeLogViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0EF50D42A949F8E0031E8DF /* ChangeLogViewModel.swift */; }; F0F146942D9462E100BF78E7 /* RustProblemReportRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0F146912D94491200BF78E7 /* RustProblemReportRequestTests.swift */; }; F0F266692EB3B71A00AAD7DA /* RelayLocationList.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0F266682EB3B71A00AAD7DA /* RelayLocationList.swift */; }; + F0F2666C2EB4AECD00AAD7DA /* NetworkSpeedMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0F2666B2EB4ABB300AAD7DA /* NetworkSpeedMonitor.swift */; }; + F0F2666E2EB4BC0100AAD7DA /* TrafficData.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0F2666D2EB4BBFF00AAD7DA /* TrafficData.swift */; }; + F0F266702EB4BC3F00AAD7DA /* SpeedConnectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0F2666F2EB4BC3300AAD7DA /* SpeedConnectionView.swift */; }; F0F313A72E85321D00D55C43 /* RecentConnectionsRepositoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0F313A62E85320D00D55C43 /* RecentConnectionsRepositoryTests.swift */; }; F0F316192BF3572B0078DBCF /* RelaySelectorResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0F316182BF3572B0078DBCF /* RelaySelectorResult.swift */; }; F0F3161B2BF358590078DBCF /* NoRelaysSatisfyingConstraintsError.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0F3161A2BF358590078DBCF /* NoRelaysSatisfyingConstraintsError.swift */; }; @@ -1717,8 +1735,6 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - E10A0001000000000000AA01 /* GotaTunActor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GotaTunActor.swift; sourceTree = "<group>"; }; - E10A0002000000000000AA01 /* PacketTunnelDebugSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelDebugSettings.swift; sourceTree = "<group>"; }; 0107F3F82F56E3ED0012451B /* RelayCacheTrackerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayCacheTrackerTests.swift; sourceTree = "<group>"; }; 0107F4212F5B97F30012451B /* RelayListCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayListCacheTests.swift; sourceTree = "<group>"; }; 014E8C2F2F294FB000837D0A /* relays-test-data.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "relays-test-data.json"; sourceTree = "<group>"; }; @@ -2506,6 +2522,8 @@ 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>"; }; + E10A0001000000000000AA01 /* GotaTunActor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GotaTunActor.swift; sourceTree = "<group>"; }; + E10A0002000000000000AA01 /* PacketTunnelDebugSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelDebugSettings.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>"; }; E158B35F285381C60002F069 /* String+AccountFormatting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+AccountFormatting.swift"; sourceTree = "<group>"; }; @@ -2534,6 +2552,11 @@ F02F419A2B9723AE00625A4F /* AddLocationsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddLocationsViewController.swift; sourceTree = "<group>"; }; F02F419B2B9723AE00625A4F /* AddLocationsDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddLocationsDataSource.swift; sourceTree = "<group>"; }; F02F419C2B9723AF00625A4F /* AddLocationsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddLocationsCoordinator.swift; sourceTree = "<group>"; }; + F0341E442EB4D00B0054C429 /* NetworkSpeedMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkSpeedMonitorTests.swift; sourceTree = "<group>"; }; + F0341E4B2EB50B830054C429 /* TrafficStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrafficStatus.swift; sourceTree = "<group>"; }; + F0341E4E2EB50D1E0054C429 /* TrafficSummery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrafficSummery.swift; sourceTree = "<group>"; }; + F0341E512EB50F050054C429 /* TrafficPackage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrafficPackage.swift; sourceTree = "<group>"; }; + F0341E542EB50F330054C429 /* TrafficSpeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrafficSpeed.swift; sourceTree = "<group>"; }; F03580242A13842C00E5DAFD /* IncreasedHitButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncreasedHitButton.swift; sourceTree = "<group>"; }; F03A69F62C2AD2D5000E2E7E /* TimeInterval+Timeout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TimeInterval+Timeout.swift"; sourceTree = "<group>"; }; F03A69F82C2AD413000E2E7E /* FormsheetPresentationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormsheetPresentationController.swift; sourceTree = "<group>"; }; @@ -2604,6 +2627,8 @@ F0A7EBB12CEF6C79005BB671 /* ConsolidatedApplicationLogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsolidatedApplicationLogTests.swift; sourceTree = "<group>"; }; F0A89CB22D9D6C1400580C27 /* MullvadDeviceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MullvadDeviceProxy.swift; sourceTree = "<group>"; }; F0A89CB62D9D922300580C27 /* String+UnsafePointer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+UnsafePointer.swift"; sourceTree = "<group>"; }; + F0A8B6DD2F9A521D008F29D9 /* SpeedConnectionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpeedConnectionViewModel.swift; sourceTree = "<group>"; }; + F0A8B6E02F9A619A008F29D9 /* MockNetworkSpeedMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockNetworkSpeedMonitor.swift; sourceTree = "<group>"; }; F0AC64392F3B4FD40024427C /* NotificationSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettings.swift; sourceTree = "<group>"; }; F0AC643B2F3B50860024427C /* NotificationSettingsObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsObserver.swift; sourceTree = "<group>"; }; F0AC643E2F3B51830024427C /* UNUserNotificationCenter+Permission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNUserNotificationCenter+Permission.swift"; sourceTree = "<group>"; }; @@ -2673,6 +2698,9 @@ F0F146912D94491200BF78E7 /* RustProblemReportRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RustProblemReportRequestTests.swift; sourceTree = "<group>"; }; F0F1EF8C2BE8FF0A00CED01D /* LaunchArguments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchArguments.swift; sourceTree = "<group>"; }; F0F266682EB3B71A00AAD7DA /* RelayLocationList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayLocationList.swift; sourceTree = "<group>"; }; + F0F2666B2EB4ABB300AAD7DA /* NetworkSpeedMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkSpeedMonitor.swift; sourceTree = "<group>"; }; + F0F2666D2EB4BBFF00AAD7DA /* TrafficData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrafficData.swift; sourceTree = "<group>"; }; + F0F2666F2EB4BC3300AAD7DA /* SpeedConnectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpeedConnectionView.swift; sourceTree = "<group>"; }; F0F313A62E85320D00D55C43 /* RecentConnectionsRepositoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentConnectionsRepositoryTests.swift; sourceTree = "<group>"; }; F0F316182BF3572B0078DBCF /* RelaySelectorResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaySelectorResult.swift; sourceTree = "<group>"; }; F0F3161A2BF358590078DBCF /* NoRelaysSatisfyingConstraintsError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoRelaysSatisfyingConstraintsError.swift; sourceTree = "<group>"; }; @@ -4071,6 +4099,7 @@ 58CE5E57224146200008646E = { isa = PBXGroup; children = ( + F0F2666A2EB4AB9E00AAD7DA /* SpeedConnection */, 58F3C0A824A50C0E003E76BE /* Assets */, 58ECD29023F178FD004298B6 /* Configurations */, 584F991F2902CBDD001F858D /* Frameworks */, @@ -4096,6 +4125,7 @@ 589A454A28DDF59B00565204 /* Shared */, 7A83C3FC2A55B39500DFB83A /* TestPlans */, 58695A9E2A4ADA9200328DB3 /* TunnelObfuscationTests */, + F0A8B6DC2F9A05CE008F29D9 /* Recovered References */, ); sourceTree = "<group>"; }; @@ -4156,14 +4186,6 @@ path = MullvadVPN; sourceTree = "<group>"; }; - E10A0001000000000000AC01 /* GotaTunAdapter */ = { - isa = PBXGroup; - children = ( - E10A0001000000000000AA01 /* GotaTunActor.swift */, - ); - path = GotaTunAdapter; - sourceTree = "<group>"; - }; 58CE5E7A224146470008646E /* PacketTunnel */ = { isa = PBXGroup; children = ( @@ -4819,6 +4841,14 @@ path = MullvadRustRuntimeTests; sourceTree = "<group>"; }; + E10A0001000000000000AC01 /* GotaTunAdapter */ = { + isa = PBXGroup; + children = ( + E10A0001000000000000AA01 /* GotaTunActor.swift */, + ); + path = GotaTunAdapter; + sourceTree = "<group>"; + }; F028A5472A336E1900C0CAA3 /* RedeemVoucher */ = { isa = PBXGroup; children = ( @@ -4892,6 +4922,14 @@ path = Log; sourceTree = "<group>"; }; + F0A8B6DC2F9A05CE008F29D9 /* Recovered References */ = { + isa = PBXGroup; + children = ( + F0341E442EB4D00B0054C429 /* NetworkSpeedMonitorTests.swift */, + ); + name = "Recovered References"; + sourceTree = "<group>"; + }; F0AC64482F3B52380024427C /* Notification */ = { isa = PBXGroup; children = ( @@ -5079,6 +5117,22 @@ path = MullvadApi; sourceTree = "<group>"; }; + F0F2666A2EB4AB9E00AAD7DA /* SpeedConnection */ = { + isa = PBXGroup; + children = ( + F0A8B6E02F9A619A008F29D9 /* MockNetworkSpeedMonitor.swift */, + F0A8B6DD2F9A521D008F29D9 /* SpeedConnectionViewModel.swift */, + F0F2666B2EB4ABB300AAD7DA /* NetworkSpeedMonitor.swift */, + F0F2666F2EB4BC3300AAD7DA /* SpeedConnectionView.swift */, + F0F2666D2EB4BBFF00AAD7DA /* TrafficData.swift */, + F0341E512EB50F050054C429 /* TrafficPackage.swift */, + F0341E542EB50F330054C429 /* TrafficSpeed.swift */, + F0341E4B2EB50B830054C429 /* TrafficStatus.swift */, + F0341E4E2EB50D1E0054C429 /* TrafficSummery.swift */, + ); + path = SpeedConnection; + sourceTree = "<group>"; + }; F0FA16072D7F03F8007E2546 /* Filter */ = { isa = PBXGroup; children = ( @@ -6179,6 +6233,7 @@ A9A5FA422ACB05D90083449F /* DeviceStateAccessorProtocol.swift in Sources */, 7A5869C32B5820CE00640D27 /* IPOverrideRepositoryTests.swift in Sources */, A9A5FA392ACB05910083449F /* UIColor+Palette.swift in Sources */, + F0341E482EB4D1A00054C429 /* NetworkSpeedMonitor.swift in Sources */, 7A5468AD2C6B5E4B00590086 /* LocationRelays.swift in Sources */, F97C38DF2DEEDB0F006DCB08 /* Color+Mullvad.swift in Sources */, F0AC644D2F3B52F60024427C /* NotificationSettings.swift in Sources */, @@ -6246,6 +6301,7 @@ A9A5FA022ACB05160083449F /* RelayCacheTracker.swift in Sources */, A9A5FA032ACB05160083449F /* SimulatorTunnelInfo.swift in Sources */, A9A5FA042ACB05160083449F /* SimulatorTunnelProvider.swift in Sources */, + F0341E472EB4D1220054C429 /* NetworkSpeedMonitorTests.swift in Sources */, A9A5FA052ACB05160083449F /* SimulatorTunnelProviderHost.swift in Sources */, A9E0317A2ACB0AE70095D843 /* UIApplication+Stubs.swift in Sources */, A9A5FA062ACB05160083449F /* SimulatorTunnelProviderManager.swift in Sources */, @@ -6257,6 +6313,7 @@ A9A5FA0B2ACB05160083449F /* StorePaymentManager.swift in Sources */, 7A9ED2A92F433D1B005BC0D9 /* NotificationProviderIdentifier.swift in Sources */, F0FA16092D7F0425007E2546 /* FilterDescriptorTests.swift in Sources */, + F0341E4A2EB4D20F0054C429 /* TrafficData.swift in Sources */, A9A5FA0E2ACB05160083449F /* StorePaymentObserver.swift in Sources */, 7A6811542DC8EC6E009CB61A /* UIFont+Weight.swift in Sources */, A9A5FA102ACB05160083449F /* PacketTunnelAPITransport.swift in Sources */, @@ -6267,6 +6324,7 @@ 44DD7D292B7113CA0005F67F /* MockTunnel.swift in Sources */, A9A5FA152ACB05160083449F /* RedeemVoucherOperation.swift in Sources */, A9A5FA162ACB05160083449F /* RotateKeyOperation.swift in Sources */, + F0341E552EB50F350054C429 /* TrafficSpeed.swift in Sources */, 7AD63A3D2CD9065D00445268 /* RelayObfuscatorTests.swift in Sources */, F072D3CF2C07122400906F64 /* SettingsUpdaterTests.swift in Sources */, 7ACE19132C1C352100260BB6 /* RelayPickingTests.swift in Sources */, @@ -6280,6 +6338,7 @@ 7A2F410A2EC38FD20013D3C5 /* StoreSubscription.swift in Sources */, 7A4080AE2F5B1F7F00C02173 /* Breadcrumb.swift in Sources */, A9A5FA1A2ACB05160083449F /* StopTunnelOperation.swift in Sources */, + F0341E4F2EB50D280054C429 /* TrafficSummery.swift in Sources */, 7A9BE5A52B90760C00E2A7D0 /* CustomListsDataSourceTests.swift in Sources */, A9A5FA1B2ACB05160083449F /* Tunnel.swift in Sources */, A9A5FA1C2ACB05160083449F /* Tunnel+Messaging.swift in Sources */, @@ -6288,6 +6347,7 @@ A9A5FA1D2ACB05160083449F /* TunnelBlockObserver.swift in Sources */, A9A5FA1E2ACB05160083449F /* TunnelConfiguration.swift in Sources */, A9A5FA1F2ACB05160083449F /* TunnelInteractor.swift in Sources */, + F0A8B6E22F9A619C008F29D9 /* MockNetworkSpeedMonitor.swift in Sources */, A9A5FA202ACB05160083449F /* TunnelManager.swift in Sources */, A9A5FA212ACB05160083449F /* TunnelManagerErrors.swift in Sources */, A9C342C32ACC3EE90045F00E /* RelayCacheTracker+Stubs.swift in Sources */, @@ -6316,6 +6376,8 @@ A9A5FA2B2ACB05160083449F /* CustomDateComponentsFormattingTests.swift in Sources */, 440870842D809C980038972F /* UIImage+Helpers.swift in Sources */, A9A5FA2C2ACB05160083449F /* DeviceCheckOperationTests.swift in Sources */, + F0341E4D2EB50B850054C429 /* TrafficStatus.swift in Sources */, + F0341E522EB50F190054C429 /* TrafficPackage.swift in Sources */, A9A5FA2E2ACB05160083449F /* FileCacheTests.swift in Sources */, F0FA16112D7F2FE8007E2546 /* RelayFilterViewModel.swift in Sources */, F0D5591F2D38051C0072B63F /* LatestChangesNotificationProvider.swift in Sources */, @@ -6333,6 +6395,7 @@ 7ADCB2DA2B6A730400C88F89 /* IPOverrideRepositoryStub.swift in Sources */, A9A5FA312ACB05160083449F /* MockFileCache.swift in Sources */, 44E1F75A2D3FDCCA003A60FF /* DestinationDescriberTests.swift in Sources */, + F0A8B6DE2F9A521D008F29D9 /* SpeedConnectionViewModel.swift in Sources */, A9A5FA322ACB05160083449F /* RelayCacheTests.swift in Sources */, A9A5FA332ACB05160083449F /* RelaySelectorTests.swift in Sources */, F073FCB32C6617D70062EA1D /* TunnelStore+Stubs.swift in Sources */, @@ -6497,6 +6560,7 @@ files = ( 44075DFB2CDA4F7400F61139 /* UDPOverTCPObfuscationSettingsViewModel.swift in Sources */, F9E3BCF72DD35B78009986C3 /* ListAccessViewModelBridge.swift in Sources */, + F0F2666E2EB4BC0100AAD7DA /* TrafficData.swift in Sources */, 7A6389DC2B7E3BD6008E77E1 /* CustomListViewModel.swift in Sources */, 4422C0712CCFF6790001A385 /* UDPOverTCPObfuscationSettingsView.swift in Sources */, 7A95B67D2D5F7C5B00687524 /* DAITASettingsCoordinator.swift in Sources */, @@ -6534,6 +6598,7 @@ 7A8A18F92CE34EA8000BCB5B /* SettingsMultihopView.swift in Sources */, 7AFBE3892D089163002335FC /* TunnelViewController.swift in Sources */, 7A11DD0B2A9495D400098CD8 /* AppRoutes.swift in Sources */, + F0A8B6E12F9A619C008F29D9 /* MockNetworkSpeedMonitor.swift in Sources */, 5827B0902B0CAA0500CCBBA1 /* EditAccessMethodCoordinator.swift in Sources */, F97C38E52DEEDFD6006DCB08 /* Image+Assets.swift in Sources */, 58421030282D8A3C00F24E46 /* UpdateAccountDataOperation.swift in Sources */, @@ -6547,6 +6612,7 @@ 586A950C290125EE007BAF2B /* AlertPresenter.swift in Sources */, 7A9FA1422A2E3306000B728D /* CheckboxView.swift in Sources */, F062000A2CB7EB42002E6DB9 /* CGSize+Helpers.swift in Sources */, + F0341E562EB50F350054C429 /* TrafficSpeed.swift in Sources */, 586C0D892B03D5E000E7CDD7 /* TextCellContentConfiguration+Extensions.swift in Sources */, 58C3F4F92964B08300D72515 /* MapViewController.swift in Sources */, 584D26C6270C8741004EA533 /* SettingsDNSTextCell.swift in Sources */, @@ -6601,6 +6667,7 @@ 7AF9BE882A30C62100DBFEDB /* SelectableSettingsCell.swift in Sources */, F0B495782D02038B00CFEC2A /* ChipViewModelProtocol.swift in Sources */, F92C658C2E7A924E00B8E107 /* MockSelectLocationViewModel.swift in Sources */, + F0341E502EB50D280054C429 /* TrafficSummery.swift in Sources */, 58CEB30A2AFD584700E6E088 /* CustomCellDisclosureHandling.swift in Sources */, 58B26E22294351EA00D5980C /* InAppNotificationProvider.swift in Sources */, 7A9CCCB82A96302800DD6A34 /* SetupAccountCompletedCoordinator.swift in Sources */, @@ -6677,6 +6744,7 @@ F9394EF02DC0B58D009595EA /* MullvadListNavigationItemView.swift in Sources */, 582AE3102440A6CA00E6733A /* InputTextFormatter.swift in Sources */, 7A6F2FAD2AFD3DA7006D0856 /* CustomDNSViewController.swift in Sources */, + F0341E532EB50F190054C429 /* TrafficPackage.swift in Sources */, F0E8CC0C2A4EE672007ED3B4 /* SetupAccountCompletedController.swift in Sources */, 7AD03E1D2E8E910E00270EAE /* RevokedDeviceView.swift in Sources */, 58EF581125D69DB400AEBA94 /* StatusImageView.swift in Sources */, @@ -6743,6 +6811,7 @@ 7AF9BE8E2A331C7B00DBFEDB /* RelayFilterViewModel.swift in Sources */, 7AF822C72DF0664700BA4255 /* DAITAMultihopNotice.swift in Sources */, 7A8A191E2CEF5CF2000BCB5B /* TunnelSettingsObservable.swift in Sources */, + F0F266702EB4BC3F00AAD7DA /* SpeedConnectionView.swift in Sources */, 58F3C0A4249CB069003E76BE /* HeaderBarView.swift in Sources */, 5864AF0829C78849005B0CD9 /* CellFactoryProtocol.swift in Sources */, 7A6389E22B7E3BD6008E77E1 /* CustomListInteractor.swift in Sources */, @@ -6773,6 +6842,7 @@ 7A3353912AAA014400F0A71C /* SimulatorVPNConnection.swift in Sources */, F02F41A22B9723AF00625A4F /* AddLocationsCoordinator.swift in Sources */, F90052562E6EEB290085C80E /* SelectLocationViewModel.swift in Sources */, + F0341E4C2EB50B850054C429 /* TrafficStatus.swift in Sources */, F028A56A2A34D4E700C0CAA3 /* RedeemVoucherViewController.swift in Sources */, 447F3D8A2CDE1853006E3462 /* ShadowsocksObfuscationSettingsViewModel.swift in Sources */, 7A5869C52B5A899C00640D27 /* MethodSettingsCellConfiguration.swift in Sources */, @@ -6829,6 +6899,7 @@ 586C0D832B03D2FF00E7CDD7 /* ShadowsocksSectionHandler.swift in Sources */, 58B26E262943522400D5980C /* NotificationProvider.swift in Sources */, 58CE5E64224146200008646E /* AppDelegate.swift in Sources */, + F0A8B6DF2F9A521D008F29D9 /* SpeedConnectionViewModel.swift in Sources */, F9394EEC2DBF56B6009595EA /* Color+Mullvad.swift in Sources */, F0DA87492A9CBA9F006044F1 /* AccountDeviceRow.swift in Sources */, A9EE85612DF1BE2900F2D769 /* TermsOfServiceView.swift in Sources */, @@ -6931,6 +7002,7 @@ F0ADF1D12D01B55C00299F09 /* ChipModel.swift in Sources */, F09A297B2A9F8A9B00EA3B6F /* LogoutDialogueView.swift in Sources */, 5811DE50239014550011EB53 /* NEVPNStatus+Debug.swift in Sources */, + F0F2666C2EB4AECD00AAD7DA /* NetworkSpeedMonitor.swift in Sources */, 7A21DACF2A30AA3700A787A9 /* UITextField+Appearance.swift in Sources */, 585B1FF02AB09F97008AD470 /* VPNConnectionProtocol.swift in Sources */, 7A8A19122CEF1E68000BCB5B /* SettingsInfoContainerView.swift in Sources */, diff --git a/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d80aca3e5f..6bf0838040 100644 --- a/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "004c7b9e036775265a3ba321f2643b2be8953ebef355de33e8f9ba4a40a4d960", + "originHash" : "fa4c8f47e5b779cddf875f03a820b5084922b27bfbc9ee905e571dadaf4e1e4b", "pins" : [ { "identity" : "swift-log", diff --git a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionView.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionView.swift index b6b3d34bd5..e9dde36f83 100644 --- a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionView.swift +++ b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionView.swift @@ -11,6 +11,7 @@ import SwiftUI struct ConnectionView: View { @ObservedObject var connectionViewModel: ConnectionViewViewModel @ObservedObject var indicatorsViewModel: FeatureIndicatorsViewModel + @ObservedObject var speedConnectionViewModel: SpeedConnectionViewModel @State private(set) var isExpanded = false @@ -27,7 +28,7 @@ struct ConnectionView: View { VStack(alignment: .leading, spacing: 0) { HeaderView(viewModel: connectionViewModel, isExpanded: $isExpanded) .padding(.bottom, 4) - + SpeedConnectionView(viewModel: speedConnectionViewModel) Divider() .background(UIColor.secondaryTextColor.color) .padding(.top, 4) @@ -111,12 +112,18 @@ struct ConnectionView: View { #Preview("ConnectionView (Indicators)") { ConnectionViewComponentPreview(showIndicators: true) { indicatorModel, viewModel, _ in - ConnectionView(connectionViewModel: viewModel, indicatorsViewModel: indicatorModel) + ConnectionView( + connectionViewModel: viewModel, + indicatorsViewModel: indicatorModel, + speedConnectionViewModel: SpeedConnectionViewModel(networkSpeedMonitor: MockNetworkSpeedMonitor())) } } #Preview("ConnectionView (No indicators)") { ConnectionViewComponentPreview(showIndicators: false) { indicatorModel, viewModel, _ in - ConnectionView(connectionViewModel: viewModel, indicatorsViewModel: indicatorModel) + ConnectionView( + connectionViewModel: viewModel, + indicatorsViewModel: indicatorModel, + speedConnectionViewModel: SpeedConnectionViewModel(networkSpeedMonitor: MockNetworkSpeedMonitor())) } } diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift index 0fc671e992..d1ca6aac2e 100644 --- a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift +++ b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift @@ -79,7 +79,8 @@ class TunnelViewController: UIViewController, RootContainment { connectionView = ConnectionView( connectionViewModel: connectionViewViewModel, - indicatorsViewModel: indicatorsViewViewModel + indicatorsViewModel: indicatorsViewViewModel, + speedConnectionViewModel: SpeedConnectionViewModel(networkSpeedMonitor: NetworkSpeedMonitor()) ) super.init(nibName: nil, bundle: nil) diff --git a/ios/MullvadVPNTests/MullvadVPN/Networking/NetworkSpeedMonitorTests.swift b/ios/MullvadVPNTests/MullvadVPN/Networking/NetworkSpeedMonitorTests.swift new file mode 100644 index 0000000000..4cd8489992 --- /dev/null +++ b/ios/MullvadVPNTests/MullvadVPN/Networking/NetworkSpeedMonitorTests.swift @@ -0,0 +1,20 @@ +// +// NetworkSpeedMonitorTests.swift +// MullvadVPN +// +// Created by Mojgan on 2025-10-31. +// Copyright © 2025 Mullvad VPN AB. All rights reserved. +// + +import Testing + +@Suite("NetworkSpeedMonitorTests") +struct NetworkSpeedMonitorTests { + + @Test + func start() { + let monitor = NetworkSpeedMonitor() + monitor.start() + + } +} diff --git a/ios/SpeedConnection/MockNetworkSpeedMonitor.swift b/ios/SpeedConnection/MockNetworkSpeedMonitor.swift new file mode 100644 index 0000000000..941dec19fe --- /dev/null +++ b/ios/SpeedConnection/MockNetworkSpeedMonitor.swift @@ -0,0 +1,41 @@ +// +// MockNetworkSpeedMonitor.swift +// MullvadVPN +// +// Created by Mojgan on 2026-04-23. +// Copyright © 2026 Mullvad VPN AB. All rights reserved. +// +import Foundation + +final class MockNetworkSpeedMonitor: NetworkSpeedMonitorProtocol, @unchecked Sendable { + + var onUpdateTrafficSummery: (@Sendable (TrafficSummery) -> Void)? + + private var timer: Timer? + + func start(timeInterval: TimeInterval) { + timer?.invalidate() + + timer = Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: true) { [weak self] _ in + guard let self else { return } + + let sent = UInt64.random(in: 10_000...100_000) + let received = UInt64.random(in: 10_000...100_000) + + let new = TrafficPackage(wifi: TrafficData(sent: sent, received: received)) + + self.onUpdateTrafficSummery?( + TrafficSummery.make(self.previousTraffic, new: new, interval: timeInterval) + ) + + self.previousTraffic = new + } + } + + func stop() { + timer?.invalidate() + timer = nil + } + + private var previousTraffic = TrafficPackage() +} diff --git a/ios/SpeedConnection/NetworkSpeedMonitor.swift b/ios/SpeedConnection/NetworkSpeedMonitor.swift new file mode 100644 index 0000000000..8986bae361 --- /dev/null +++ b/ios/SpeedConnection/NetworkSpeedMonitor.swift @@ -0,0 +1,119 @@ +// +// NetworkSpeedMonitor.swift +// MullvadVPN +// +// Created by Mojgan on 2025-10-31. +// Copyright © 2025 Mullvad VPN AB. All rights reserved. +// +import Foundation +import SystemConfiguration + +protocol NetworkSpeedMonitorProtocol { + var onUpdateTrafficSummery: (@Sendable (TrafficSummery) -> Void)? { get set } + func start(timeInterval: TimeInterval) + func stop() +} + +final class NetworkSpeedMonitor : NetworkSpeedMonitorProtocol { + private var timer: DispatchSourceTimer? = nil + private var previousTrafficPackage = TrafficPackage() + private var lock = NSLock() + private let timerQueue = DispatchQueue(label: "NetworkSpeedMonitorTimerQueue") + + var onUpdateTrafficSummery: (@Sendable (TrafficSummery) -> Void)? + + func start(timeInterval: TimeInterval = 1.0) { + timer?.cancel() + timer = DispatchSource.makeTimerSource(queue: timerQueue) + timer?.setEventHandler { [weak self] in + guard let self = self else { return } + self.measureSpeed() + } + timer?.schedule(wallDeadline: .now(), repeating: timeInterval) + timer?.resume() + } + + func stop() { + timer?.cancel() + timer = nil + } + + private func measureSpeed() { + lock.lock() + defer { + lock.unlock() + } + let newTrafficPacket = getTrafficPackage() + onUpdateTrafficSummery?(TrafficSummery.make(previousTrafficPackage, new: newTrafficPacket, interval: 1)) + previousTrafficPackage = newTrafficPacket + } + + private func getTrafficPackage() -> TrafficPackage { + var result = TrafficPackage() + var address: UnsafeMutablePointer<ifaddrs>? = nil + + guard getifaddrs(&address) == 0, let first = address else { + return result + } + + defer { + freeifaddrs(first) + } + + var pointer: UnsafeMutablePointer<ifaddrs>? = first + + while let current = pointer?.pointee { + + defer { + pointer = current.ifa_next + } + + let interfaceName = String(cString: current.ifa_name) + let interface = mapInterface(interfaceName) + + guard + let dataPtr = current.ifa_data + else { + continue + } + + let data = dataPtr.assumingMemoryBound(to: if_data.self).pointee + + let sent = UInt64(data.ifi_obytes) + let received = UInt64(data.ifi_ibytes) + + switch interface { + case .cellular: + result.cellular.sent += sent + result.cellular.received += received + + case .wifi: + result.wifi.sent += sent + result.wifi.received += received + + case .vpn: + result.vpn.sent += sent + result.vpn.received += received + + default: + break + } + } + + return result + } + + func mapInterface(_ name: String) -> InterfaceType { + if name.hasPrefix("en") { + return .wifi + } else if name.hasPrefix("pdp_ip") { + return .cellular + } else if name.hasPrefix("utun") || name.hasPrefix("ppp") { + return .vpn + } else if name == "lo0" { + return .loopback + } else { + return .other + } + } +} diff --git a/ios/SpeedConnection/SpeedConnectionView.swift b/ios/SpeedConnection/SpeedConnectionView.swift new file mode 100644 index 0000000000..0071f9cbc1 --- /dev/null +++ b/ios/SpeedConnection/SpeedConnectionView.swift @@ -0,0 +1,66 @@ +// +// SpeedConnectionView.swift +// MullvadVPN +// +// Created by Mojgan on 2025-10-31. +// Copyright © 2025 Mullvad VPN AB. All rights reserved. +// + +import SwiftUI + +struct SpeedConnectionView<ViewModel: SpeedConnectionViewModelProtocol>: View { + @State private var uploadValue: Double = 0.0 + @State private var downloadValue: Double = 0.0 + + var viewModel: ViewModel + + init(viewModel: ViewModel) { + self.viewModel = viewModel + } + + var body: some View { + VStack { + Text("Download: \(uploadValue / 1024.0) KB") + .font(.mullvadTiny) + Text("Upload: \(downloadValue / 1024.0) KB") + .font(.mullvadTiny) + } + .padding(8.0) + .onAppear { + viewModel.startMonitoring() + } + .onDisappear { + viewModel.stopMonitoring() + } + } +} + +#Preview { + SpeedConnectionView(viewModel: MockSpeedConnectionViewModel()) +} + +final class MockSpeedConnectionViewModel: SpeedConnectionViewModelProtocol,@unchecked Sendable { + + var uploadValue: Double = 0.0 + var downloadValue: Double = 0.0 + + private var timer: Timer? + + func startMonitoring() { + timer?.invalidate() + + timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in + let upload = Double.random(in: 10_000...100_000) + let download = Double.random(in: 10_000...100_000) + Task { @MainActor in + self.uploadValue = upload + self.downloadValue = download + } + } + } + + func stopMonitoring() { + timer?.invalidate() + timer = nil + } +} diff --git a/ios/SpeedConnection/SpeedConnectionViewModel.swift b/ios/SpeedConnection/SpeedConnectionViewModel.swift new file mode 100644 index 0000000000..a06e857cc4 --- /dev/null +++ b/ios/SpeedConnection/SpeedConnectionViewModel.swift @@ -0,0 +1,41 @@ +// +// SpeedConnectionViewModel.swift +// MullvadVPN +// +// Created by Mojgan on 2026-04-23. +// Copyright © 2026 Mullvad VPN AB. All rights reserved. +// + +import Combine + +protocol SpeedConnectionViewModelProtocol: ObservableObject { + var uploadValue: Double { get } + var downloadValue: Double { get } + + func startMonitoring() + func stopMonitoring() +} + + +class SpeedConnectionViewModel: SpeedConnectionViewModelProtocol,@unchecked Sendable { + @Published var uploadValue: Double = 0.0 + @Published var downloadValue: Double = 0.0 + var networkSpeedMonitor : NetworkSpeedMonitorProtocol + + init(networkSpeedMonitor : NetworkSpeedMonitorProtocol) { + self.networkSpeedMonitor = networkSpeedMonitor + self.networkSpeedMonitor.onUpdateTrafficSummery = { [weak self] trafficSummery in + Task { @MainActor in + self?.uploadValue = trafficSummery.speed.sent + self?.downloadValue = trafficSummery.speed.received + } + } + } + func startMonitoring() { + self.networkSpeedMonitor.start(timeInterval: 1.0) + } + + func stopMonitoring() { + self.networkSpeedMonitor.stop() + } +} diff --git a/ios/SpeedConnection/TrafficData.swift b/ios/SpeedConnection/TrafficData.swift new file mode 100644 index 0000000000..ce855a833a --- /dev/null +++ b/ios/SpeedConnection/TrafficData.swift @@ -0,0 +1,26 @@ +// +// TrafficData.swift +// MullvadVPN +// +// Created by Mojgan on 2025-10-31. +// Copyright © 2025 Mullvad VPN AB. All rights reserved. +// + + +struct TrafficData { + var sent: UInt64 = 0// in bytes + var received: UInt64 = 0 // in bytes +} + +extension TrafficData { + static var zero: TrafficData { + TrafficData() + } +} + +func +(lhs: TrafficData, rhs: TrafficData) -> TrafficData { + var result = lhs + result.received += rhs.received + result.sent += rhs.sent + return result +} diff --git a/ios/SpeedConnection/TrafficPackage.swift b/ios/SpeedConnection/TrafficPackage.swift new file mode 100644 index 0000000000..62ac1d2d62 --- /dev/null +++ b/ios/SpeedConnection/TrafficPackage.swift @@ -0,0 +1,22 @@ +// +// TrafficPackage.swift +// MullvadVPN +// +// Created by Mojgan on 2025-10-31. +// Copyright © 2025 Mullvad VPN AB. All rights reserved. +// +import Foundation + +enum InterfaceType { + case wifi + case cellular + case vpn + case loopback + case other +} + +struct TrafficPackage { + var wifi = TrafficData() + var cellular = TrafficData() + var vpn = TrafficData() +} diff --git a/ios/SpeedConnection/TrafficSpeed.swift b/ios/SpeedConnection/TrafficSpeed.swift new file mode 100644 index 0000000000..1ba81f9ed0 --- /dev/null +++ b/ios/SpeedConnection/TrafficSpeed.swift @@ -0,0 +1,34 @@ +// +// TrafficSpeed.swift +// MullvadVPN +// +// Created by Mojgan on 2025-10-31. +// Copyright © 2025 Mullvad VPN AB. All rights reserved. +// + +struct TrafficSpeed { + var received: Double + var sent: Double + + private init(received: Double, sent: Double) { + self.received = received + self.sent = sent + } + + public init(old: TrafficData, new: TrafficData, interval: Double) { + self.received = Double((new.received - old.received)) / interval + self.sent = Double((new.sent - old.sent)) / interval + } +} +extension TrafficSpeed { + static var zero: TrafficSpeed { + .init(received: 0, sent: 0) + } +} + +func +(lhs: TrafficSpeed, rhs: TrafficSpeed) -> TrafficSpeed { + var result = lhs + result.received += rhs.received + result.sent += rhs.sent + return result +} diff --git a/ios/SpeedConnection/TrafficStatus.swift b/ios/SpeedConnection/TrafficStatus.swift new file mode 100644 index 0000000000..ebd3776989 --- /dev/null +++ b/ios/SpeedConnection/TrafficStatus.swift @@ -0,0 +1,17 @@ +// +// TrafficStatus.swift +// MullvadVPN +// +// Created by Mojgan on 2025-10-31. +// Copyright © 2025 Mullvad VPN AB. All rights reserved. +// + +struct TrafficStatus { + var speed: TrafficSpeed + var data: TrafficData + + init(speed: TrafficSpeed = .zero, data: TrafficData = .zero) { + self.speed = speed + self.data = data + } +} diff --git a/ios/SpeedConnection/TrafficSummery.swift b/ios/SpeedConnection/TrafficSummery.swift new file mode 100644 index 0000000000..7ef20ab29b --- /dev/null +++ b/ios/SpeedConnection/TrafficSummery.swift @@ -0,0 +1,58 @@ +// +// TrafficSummery.swift +// MullvadVPN +// +// Created by Mojgan on 2025-10-31. +// Copyright © 2025 Mullvad VPN AB. All rights reserved. +// +import Foundation + +struct TrafficSummery { + let wifi: TrafficStatus + let cellular: TrafficStatus + let vpn: TrafficStatus? + + init( + wifi: TrafficStatus = TrafficStatus(), + cellular: TrafficStatus = TrafficStatus(), + vpn: TrafficStatus? = nil + ) { + self.wifi = wifi + self.cellular = cellular + self.vpn = vpn + } + + var speed: TrafficSpeed { + wifi.speed + cellular.speed + (vpn?.speed ?? .zero) + } + + private var data: TrafficData { + if let vpn = vpn { + return vpn.data + } + return wifi.data + cellular.data + } + + static func make(_ old: TrafficPackage, new: TrafficPackage, interval: TimeInterval) -> Self { + TrafficSummery( + wifi: TrafficStatus( + speed: TrafficSpeed( + old: old.wifi, + new: new.wifi, + interval: interval), + data: new.wifi), + cellular: TrafficStatus( + speed: TrafficSpeed( + old: old.cellular, + new: new.cellular, + interval: interval), + data: new.cellular), + vpn: TrafficStatus( + speed: TrafficSpeed( + old: old.vpn, + new: new.vpn, + interval: interval), + data: new.vpn)) + } + +} diff --git a/ios/SpeedConnection/Untitled.swift b/ios/SpeedConnection/Untitled.swift new file mode 100644 index 0000000000..23d31d97a8 --- /dev/null +++ b/ios/SpeedConnection/Untitled.swift @@ -0,0 +1,8 @@ +// +// Untitled.swift +// MullvadVPN +// +// Created by Mojgan on 2025-10-31. +// Copyright © 2025 Mullvad VPN AB. All rights reserved. +// + |
