diff options
| author | Bug Magnet <marco.nikic@mullvad.net> | 2025-11-10 16:00:57 +0100 |
|---|---|---|
| committer | Bug Magnet <marco.nikic@mullvad.net> | 2025-11-10 16:00:57 +0100 |
| commit | 869b59e08c224f0d7e6300b9c0715a76df5ad3d9 (patch) | |
| tree | d10a27825d4604c2f2dcf61f9019c07651ca5216 | |
| parent | 206775e7e641cf5098d29241e672689010f449ea (diff) | |
| parent | b0a04e382faa3b1b63b6c503bb66485ac9b9424c (diff) | |
| download | mullvadvpn-confirm-ipv6-external-source-for-flakiness.tar.xz mullvadvpn-confirm-ipv6-external-source-for-flakiness.zip | |
Merge branch 'bump-min-deployment-target-ios-1374'confirm-ipv6-external-source-for-flakiness
26 files changed, 173 insertions, 457 deletions
diff --git a/ios/CHANGELOG.md b/ios/CHANGELOG.md index 09e7fae1e3..9b983220ba 100644 --- a/ios/CHANGELOG.md +++ b/ios/CHANGELOG.md @@ -24,6 +24,7 @@ Line wrap the file at 100 chars. Th ## UNRELEASED ### Changed +- Bump minimum version to iOS 17.0 - Quantum-resistant tunnel setting is now on by default through the "Automatic" setting. - Add a checkbox that lets users include their account token in problem reports. diff --git a/ios/MullvadREST/RetryStrategy/ExponentialBackoff.swift b/ios/MullvadREST/RetryStrategy/ExponentialBackoff.swift index 8505e092b2..349716e0ea 100644 --- a/ios/MullvadREST/RetryStrategy/ExponentialBackoff.swift +++ b/ios/MullvadREST/RetryStrategy/ExponentialBackoff.swift @@ -23,12 +23,19 @@ struct ExponentialBackoff: IteratorProtocol { mutating func next() -> Duration? { let next = _next - if next > maxDelay { + if next >= maxDelay { return maxDelay } - _next = next * Int(multiplier) + let nextMilliseconds = next.milliseconds + let maxMilliseconds = maxDelay.milliseconds + let (value, overflow) = nextMilliseconds.multipliedReportingOverflow(by: Int(multiplier)) + if overflow { + _next = .milliseconds(maxMilliseconds) + } else { + _next = .milliseconds(value) + } return next } } diff --git a/ios/MullvadRESTTests/ExponentialBackoffTests.swift b/ios/MullvadRESTTests/ExponentialBackoffTests.swift index 668d33ddc5..5b8cd88290 100644 --- a/ios/MullvadRESTTests/ExponentialBackoffTests.swift +++ b/ios/MullvadRESTTests/ExponentialBackoffTests.swift @@ -23,9 +23,9 @@ final class ExponentialBackoffTests: XCTestCase { func testAtMaximumValue() { var backoff = ExponentialBackoff(initial: .milliseconds(.max - 1), multiplier: 2, maxDelay: .seconds(.max - 1)) - XCTAssertEqual(backoff.next(), .milliseconds(.max - 1)) - XCTAssertEqual(backoff.next(), .milliseconds(.max)) - XCTAssertEqual(backoff.next(), .milliseconds(.max)) + XCTAssertEqual(backoff.next(), .milliseconds(Int.max - 1)) + XCTAssertEqual(backoff.next(), .milliseconds(Int.max)) + XCTAssertEqual(backoff.next(), .milliseconds(Int.max)) } func testMaximumBound() { diff --git a/ios/MullvadTypes/Duration+Extensions.swift b/ios/MullvadTypes/Duration+Extensions.swift index e01fa4f883..38c70943a2 100644 --- a/ios/MullvadTypes/Duration+Extensions.swift +++ b/ios/MullvadTypes/Duration+Extensions.swift @@ -77,4 +77,21 @@ extension Duration { public static func > (lhs: TimeInterval, rhs: Duration) -> Bool { return lhs > rhs.timeInterval } + + public func logFormat() -> String { + let timeInterval = timeInterval + + guard timeInterval >= 1 else { + return "\(milliseconds)ms" + } + + let trailingZeroesSuffix = ".00" + var string = String(format: "%.2f", timeInterval) + + if string.hasSuffix(trailingZeroesSuffix) { + string.removeLast(trailingZeroesSuffix.count) + } + + return "\(string)s" + } } diff --git a/ios/MullvadTypes/Duration.swift b/ios/MullvadTypes/Duration.swift deleted file mode 100644 index 8beeeaa321..0000000000 --- a/ios/MullvadTypes/Duration.swift +++ /dev/null @@ -1,93 +0,0 @@ -// -// Duration.swift -// MullvadTypes -// -// Created by Jon Petersson on 2023-08-16. -// Copyright © 2025 Mullvad VPN AB. All rights reserved. -// - -import Foundation - -/// Custom implementation of iOS native `Duration` (available from iOS16). Meant as a -/// drop-in replacement until the app supports iOS16. Ideally this whole file can -/// then be deleted without affecting the rest of the code base. -@available(iOS, introduced: 15.0, obsoleted: 16.0, message: "Replace with native Duration type.") -public struct Duration { - private(set) var components: (seconds: Int64, attoseconds: Int64) - - public init(secondsComponent: Int64, attosecondsComponent: Int64 = 0) { - components = ( - seconds: Int64(secondsComponent), - attoseconds: Int64(attosecondsComponent) - ) - } - - public static func milliseconds(_ milliseconds: Int) -> Duration { - let subSeconds = milliseconds % 1000 - let seconds = (milliseconds - subSeconds) / 1000 - - return Duration( - secondsComponent: Int64(seconds), - attosecondsComponent: Int64(subSeconds) * Int64(1e15) - ) - } - - public static func seconds(_ seconds: Int) -> Duration { - return Duration(secondsComponent: Int64(seconds)) - } - - public func logFormat() -> String { - let timeInterval = timeInterval - - guard timeInterval >= 1 else { - return "\(milliseconds)ms" - } - - let trailingZeroesSuffix = ".00" - var string = String(format: "%.2f", timeInterval) - - if string.hasSuffix(trailingZeroesSuffix) { - string.removeLast(trailingZeroesSuffix.count) - } - - return "\(string)s" - } -} - -extension Duration: DurationProtocol { - public static var zero: Duration { - return .seconds(0) - } - - public static func / (lhs: Duration, rhs: Int) -> Duration { - return .milliseconds(lhs.milliseconds / max(rhs, 1)) - } - - public static func * (lhs: Duration, rhs: Int) -> Duration { - return .milliseconds(lhs.milliseconds.saturatingMultiplication(rhs)) - } - - public static func / (lhs: Duration, rhs: Duration) -> Double { - guard rhs != .zero else { - return lhs.timeInterval - } - - return lhs.timeInterval / rhs.timeInterval - } - - public static func + (lhs: Duration, rhs: Duration) -> Duration { - return .milliseconds(lhs.milliseconds.saturatingAddition(rhs.milliseconds)) - } - - public static func - (lhs: Duration, rhs: Duration) -> Duration { - return .milliseconds(lhs.milliseconds.saturatingSubtraction(rhs.milliseconds)) - } - - public static func < (lhs: Duration, rhs: Duration) -> Bool { - return lhs.timeInterval < rhs.timeInterval - } - - public static func == (lhs: Duration, rhs: Duration) -> Bool { - return lhs.timeInterval == rhs.timeInterval - } -} diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index dbcb0d37b3..94d4071706 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -142,7 +142,6 @@ 58342C042AAB61FB003BA12D /* State+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58342C032AAB61FB003BA12D /* State+Extensions.swift */; }; 5835B7CC233B76CB0096D79F /* TunnelManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5835B7CB233B76CB0096D79F /* TunnelManager.swift */; }; 5838321B2AC1B18400EA2071 /* PacketTunnelActor+Mocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5838321A2AC1B18400EA2071 /* PacketTunnelActor+Mocks.swift */; }; - 5838321D2AC1C54600EA2071 /* TaskSleepTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5838321C2AC1C54600EA2071 /* TaskSleepTests.swift */; }; 5838321F2AC3160A00EA2071 /* PacketTunnelActor+KeyPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5838321E2AC3160A00EA2071 /* PacketTunnelActor+KeyPolicy.swift */; }; 583832232AC3181400EA2071 /* PacketTunnelActor+ErrorState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583832222AC3181400EA2071 /* PacketTunnelActor+ErrorState.swift */; }; 583832252AC318A100EA2071 /* PacketTunnelActor+ConnectionMonitoring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583832242AC318A100EA2071 /* PacketTunnelActor+ConnectionMonitoring.swift */; }; @@ -194,7 +193,6 @@ 586C14582AC463BB00245C01 /* EventChannelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 586C14572AC463BB00245C01 /* EventChannelTests.swift */; }; 586C145A2AC4735F00245C01 /* PacketTunnelActor+Public.swift in Sources */ = {isa = PBXBuildFile; fileRef = 586C14592AC4735F00245C01 /* PacketTunnelActor+Public.swift */; }; 586E54FB27A2DF6D0029B88B /* SendTunnelProviderMessageOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 586E54FA27A2DF6D0029B88B /* SendTunnelProviderMessageOperation.swift */; }; - 586E8DB82AAF4AC4007BF3DA /* Task+Duration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 586E8DB72AAF4AC4007BF3DA /* Task+Duration.swift */; }; 5871167F2910035700D41AAC /* VPNSettingsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5871167E2910035700D41AAC /* VPNSettingsInteractor.swift */; }; 5871FB96254ADE4E0051A0A4 /* ConsolidatedApplicationLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5871FB95254ADE4E0051A0A4 /* ConsolidatedApplicationLog.swift */; }; 5871FBA0254C26C00051A0A4 /* NSRegularExpression+IPAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5871FB9F254C26BF0051A0A4 /* NSRegularExpression+IPAddress.swift */; }; @@ -494,7 +492,6 @@ 7A2E7B722D6C9FE5009EF2C3 /* APIRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A2E7B6C2D6C9E53009EF2C3 /* APIRequest.swift */; }; 7A2E7B732D6C9FEB009EF2C3 /* APIRequestProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A95B6742D5DF86400687524 /* APIRequestProxy.swift */; }; 7A2E7B752D6CA0B1009EF2C3 /* APITransportProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A2E7B742D6CA0AC009EF2C3 /* APITransportProvider.swift */; }; - 7A307AD92A8CD8DA0017618B /* Duration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A307AD82A8CD8DA0017618B /* Duration.swift */; }; 7A307ADB2A8F56DF0017618B /* Duration+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A307ADA2A8F56DF0017618B /* Duration+Extensions.swift */; }; 7A3215722D3934E6005DF395 /* MullvadApiCompletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3215702D392F0B005DF395 /* MullvadApiCompletion.swift */; }; 7A33538F2AA9FF1600F0A71C /* SimulatorTunnelProviderManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A33538E2AA9FF1600F0A71C /* SimulatorTunnelProviderManager.swift */; }; @@ -885,7 +882,6 @@ A9A5FA2A2ACB05160083449F /* CoordinatesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EC20E72A5D3A8C0040D56E /* CoordinatesTests.swift */; }; A9A5FA2B2ACB05160083449F /* CustomDateComponentsFormattingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5896AE85246D6AD8005B36CB /* CustomDateComponentsFormattingTests.swift */; }; A9A5FA2C2ACB05160083449F /* DeviceCheckOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58915D622A25F8400066445B /* DeviceCheckOperationTests.swift */; }; - A9A5FA2D2ACB05160083449F /* DurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FBFBF0291630700020E046 /* DurationTests.swift */; }; A9A5FA2E2ACB05160083449F /* FileCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C3FA672A385C89006A450A /* FileCacheTests.swift */; }; A9A5FA2F2ACB05160083449F /* FixedWidthIntegerArithmeticsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582A8A3928BCE19B00D0F9FB /* FixedWidthIntegerArithmeticsTests.swift */; }; A9A5FA302ACB05160083449F /* InputTextFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F07BF2572A26112D00042943 /* InputTextFormatterTests.swift */; }; @@ -1761,7 +1757,6 @@ 58342C032AAB61FB003BA12D /* State+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "State+Extensions.swift"; sourceTree = "<group>"; }; 5835B7CB233B76CB0096D79F /* TunnelManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelManager.swift; sourceTree = "<group>"; }; 5838321A2AC1B18400EA2071 /* PacketTunnelActor+Mocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PacketTunnelActor+Mocks.swift"; sourceTree = "<group>"; }; - 5838321C2AC1C54600EA2071 /* TaskSleepTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskSleepTests.swift; sourceTree = "<group>"; }; 5838321E2AC3160A00EA2071 /* PacketTunnelActor+KeyPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PacketTunnelActor+KeyPolicy.swift"; sourceTree = "<group>"; }; 583832222AC3181400EA2071 /* PacketTunnelActor+ErrorState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PacketTunnelActor+ErrorState.swift"; sourceTree = "<group>"; }; 583832242AC318A100EA2071 /* PacketTunnelActor+ConnectionMonitoring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PacketTunnelActor+ConnectionMonitoring.swift"; sourceTree = "<group>"; }; @@ -1832,7 +1827,6 @@ 586C14592AC4735F00245C01 /* PacketTunnelActor+Public.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PacketTunnelActor+Public.swift"; sourceTree = "<group>"; }; 586E54FA27A2DF6D0029B88B /* SendTunnelProviderMessageOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendTunnelProviderMessageOperation.swift; sourceTree = "<group>"; }; 586E7A2C2A987689006DAB1B /* SettingsReaderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsReaderProtocol.swift; sourceTree = "<group>"; }; - 586E8DB72AAF4AC4007BF3DA /* Task+Duration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Task+Duration.swift"; sourceTree = "<group>"; }; 5871167E2910035700D41AAC /* VPNSettingsInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNSettingsInteractor.swift; sourceTree = "<group>"; }; 5871FB95254ADE4E0051A0A4 /* ConsolidatedApplicationLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsolidatedApplicationLog.swift; sourceTree = "<group>"; }; 5871FB9F254C26BF0051A0A4 /* NSRegularExpression+IPAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSRegularExpression+IPAddress.swift"; sourceTree = "<group>"; }; @@ -2033,7 +2027,6 @@ 58FB865926EA214400F188BC /* RelayCacheTrackerObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayCacheTrackerObserver.swift; sourceTree = "<group>"; }; 58FBFBE6291622580020E046 /* MullvadRESTTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MullvadRESTTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 58FBFBE8291622580020E046 /* ExponentialBackoffTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExponentialBackoffTests.swift; sourceTree = "<group>"; }; - 58FBFBF0291630700020E046 /* DurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DurationTests.swift; sourceTree = "<group>"; }; 58FC040927B3EE03001C21F0 /* TunnelMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelMonitor.swift; sourceTree = "<group>"; }; 58FD5BEF24238EB300112C88 /* SKProduct+Formatting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SKProduct+Formatting.swift"; sourceTree = "<group>"; }; 58FD5BF32428C67600112C88 /* InAppPurchaseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchaseButton.swift; sourceTree = "<group>"; }; @@ -2083,7 +2076,6 @@ 7A2E7B6C2D6C9E53009EF2C3 /* APIRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIRequest.swift; sourceTree = "<group>"; }; 7A2E7B6E2D6C9ED9009EF2C3 /* APIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIError.swift; sourceTree = "<group>"; }; 7A2E7B742D6CA0AC009EF2C3 /* APITransportProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APITransportProvider.swift; sourceTree = "<group>"; }; - 7A307AD82A8CD8DA0017618B /* Duration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Duration.swift; sourceTree = "<group>"; }; 7A307ADA2A8F56DF0017618B /* Duration+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Duration+Extensions.swift"; sourceTree = "<group>"; }; 7A3215702D392F0B005DF395 /* MullvadApiCompletion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MullvadApiCompletion.swift; sourceTree = "<group>"; }; 7A33538E2AA9FF1600F0A71C /* SimulatorTunnelProviderManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorTunnelProviderManager.swift; sourceTree = "<group>"; }; @@ -2908,7 +2900,6 @@ isa = PBXGroup; children = ( 7AF36A992CA2964000E1D497 /* AnyIPAddressTests.swift */, - 58FBFBF0291630700020E046 /* DurationTests.swift */, 58C3FA672A385C89006A450A /* FileCacheTests.swift */, 582A8A3928BCE19B00D0F9FB /* FixedWidthIntegerArithmeticsTests.swift */, 58C3FA652A38549D006A450A /* MockFileCache.swift */, @@ -3084,7 +3075,6 @@ 06AC113628F83FD70037AF9A /* Cancellable.swift */, 58E511E328DDDE8900B0BCDE /* CustomErrorDescriptionProtocol.swift */, 586168682976F6BD00EF8598 /* DisplayError.swift */, - 7A307AD82A8CD8DA0017618B /* Duration.swift */, 7A307ADA2A8F56DF0017618B /* Duration+Extensions.swift */, 58E511EA28DDE18400B0BCDE /* Error+Chain.swift */, A9A8A8EA2A262AB30086D569 /* FileCache.swift */, @@ -3620,7 +3610,6 @@ 58ED3A132A7C199C0085CE65 /* StartOptions.swift */, 5824030C2A811B0000163DE8 /* State.swift */, 58342C032AAB61FB003BA12D /* State+Extensions.swift */, - 586E8DB72AAF4AC4007BF3DA /* Task+Duration.swift */, 58DDA18E2ABC32380039C360 /* Timings.swift */, F04DD3D72C130DF600E03E28 /* TunnelSettingsManager.swift */, ); @@ -3891,7 +3880,6 @@ 58FE25D32AA729B5003D1918 /* PacketTunnelActorTests.swift */, A97D25B12B0CB02D00946B2D /* ProtocolObfuscatorTests.swift */, F0A163882C47B46300592300 /* SingleHopEphemeralPeerExchangerTests.swift */, - 5838321C2AC1C54600EA2071 /* TaskSleepTests.swift */, 58092E532A8B832E00C3CC72 /* TunnelMonitorTests.swift */, F062B94C2C16E09700B6D47A /* TunnelSettingsManagerTests.swift */, ); @@ -6201,7 +6189,6 @@ A9A5FA2B2ACB05160083449F /* CustomDateComponentsFormattingTests.swift in Sources */, 440870842D809C980038972F /* UIImage+Helpers.swift in Sources */, A9A5FA2C2ACB05160083449F /* DeviceCheckOperationTests.swift in Sources */, - A9A5FA2D2ACB05160083449F /* DurationTests.swift in Sources */, A9A5FA2E2ACB05160083449F /* FileCacheTests.swift in Sources */, F0FA16112D7F2FE8007E2546 /* RelayFilterViewModel.swift in Sources */, F0D5591F2D38051C0072B63F /* LatestChangesNotificationProvider.swift in Sources */, @@ -6322,7 +6309,6 @@ 580D6B8A2AB31AB400B2D6E0 /* NetworkPath+NetworkReachability.swift in Sources */, 7AD0AA1D2AD6A86700119E10 /* PacketTunnelActorProtocol.swift in Sources */, 5826B6CB2ABD83E200B1CA13 /* PacketTunnelOptions.swift in Sources */, - 586E8DB82AAF4AC4007BF3DA /* Task+Duration.swift in Sources */, 5838322B2AC3EF9600EA2071 /* EventChannel.swift in Sources */, 586C145A2AC4735F00245C01 /* PacketTunnelActor+Public.swift in Sources */, F0DAC8AD2C16EFE400F80144 /* TunnelSettingsManager.swift in Sources */, @@ -6353,7 +6339,6 @@ 581F23AD2A8CF92100788AB6 /* DefaultPathObserverFake.swift in Sources */, F07751582C50F149006E6A12 /* MultiHopEphemeralPeerExchangerTests.swift in Sources */, 5838321B2AC1B18400EA2071 /* PacketTunnelActor+Mocks.swift in Sources */, - 5838321D2AC1C54600EA2071 /* TaskSleepTests.swift in Sources */, 58092E542A8B832E00C3CC72 /* TunnelMonitorTests.swift in Sources */, 7AD0AA212AD6CB0000119E10 /* URLRequestProxyStub.swift in Sources */, 581F23AF2A8CF94D00788AB6 /* PingerMock.swift in Sources */, @@ -6875,7 +6860,6 @@ A97FF5502A0D2FFC00900996 /* NSFileCoordinator+Extensions.swift in Sources */, 58D22408294C90210029F5F8 /* AnyIPEndpoint.swift in Sources */, 58D22409294C90210029F5F8 /* AnyIPAddress.swift in Sources */, - 7A307AD92A8CD8DA0017618B /* Duration.swift in Sources */, 58D2240A294C90210029F5F8 /* IPAddress+Codable.swift in Sources */, 58E45A5729F12C5100281ECF /* Result+Extensions.swift in Sources */, F924C5A42DA65F28001F4660 /* Storekit2.swift in Sources */, @@ -7396,6 +7380,7 @@ INFOPLIST_FILE = MullvadREST/Info.plist; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -7434,6 +7419,7 @@ INFOPLIST_FILE = MullvadREST/Info.plist; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -7465,6 +7451,7 @@ DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = CKG9MXH72F; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN.OperationsTests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -7486,6 +7473,7 @@ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = CKG9MXH72F; "DEVELOPMENT_TEAM[sdk=macosx*]" = CKG9MXH72F; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN.OperationsTests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -7502,6 +7490,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; INFOPLIST_FILE = MullvadVPNTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -7522,6 +7511,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; INFOPLIST_FILE = MullvadVPNTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -7552,6 +7542,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -7589,6 +7580,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -7627,6 +7619,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -7667,6 +7660,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -7698,6 +7692,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = CKG9MXH72F; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN.PacketTunnelCoreTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -7719,6 +7714,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = CKG9MXH72F; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN.PacketTunnelCoreTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -7783,7 +7779,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -7846,7 +7842,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; @@ -7877,6 +7873,7 @@ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = CKG9MXH72F; ENABLE_BITCODE = NO; INFOPLIST_FILE = "MullvadVPN/Supporting Files/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -7905,6 +7902,7 @@ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = CKG9MXH72F; ENABLE_BITCODE = NO; INFOPLIST_FILE = "MullvadVPN/Supporting Files/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -7925,6 +7923,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnel/PacketTunnel.entitlements; ENABLE_BITCODE = NO; INFOPLIST_FILE = PacketTunnel/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -7946,6 +7945,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnel/PacketTunnel.entitlements; ENABLE_BITCODE = NO; INFOPLIST_FILE = PacketTunnel/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -7974,6 +7974,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8009,6 +8010,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8043,6 +8045,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8079,6 +8082,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8114,6 +8118,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8150,6 +8155,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8209,6 +8215,7 @@ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = CKG9MXH72F; "DEVELOPMENT_TEAM[sdk=macosx*]" = CKG9MXH72F; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN.MullvadRESTTests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -8228,6 +8235,7 @@ CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN.MullvadRESTTests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -8260,6 +8268,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8300,6 +8309,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8333,7 +8343,7 @@ DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = CKG9MXH72F; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN.RoutingTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -8358,7 +8368,7 @@ DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = CKG9MXH72F; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN.RoutingTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -8394,7 +8404,7 @@ GENERATE_INFOPLIST_FILE = YES; HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/mullvad-api/include"; INFOPLIST_FILE = MullvadVPNUITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; "LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64]" = "$(PROJECT_DIR)/../target/aarch64-apple-ios/debug"; "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=arm64]" = "$(PROJECT_DIR)/../target/aarch64-apple-ios-sim/debug"; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; @@ -8428,7 +8438,7 @@ GENERATE_INFOPLIST_FILE = YES; HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/mullvad-api/include"; INFOPLIST_FILE = MullvadVPNUITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; "LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64]" = "$(PROJECT_DIR)/../target/aarch64-apple-ios/release"; "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=arm64]" = "$(PROJECT_DIR)/../target/aarch64-apple-ios-sim/release"; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; @@ -8499,7 +8509,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -8533,6 +8543,7 @@ "DEBUG=1", ); INFOPLIST_FILE = "MullvadVPN/Supporting Files/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8572,6 +8583,7 @@ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = CKG9MXH72F; ENABLE_BITCODE = NO; INFOPLIST_FILE = PacketTunnel/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8592,6 +8604,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; INFOPLIST_FILE = MullvadVPNTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8622,6 +8635,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8650,6 +8664,7 @@ DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = CKG9MXH72F; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN.OperationsTests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -8677,6 +8692,7 @@ INFOPLIST_FILE = MullvadREST/Info.plist; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8708,6 +8724,7 @@ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = CKG9MXH72F; "DEVELOPMENT_TEAM[sdk=macosx*]" = CKG9MXH72F; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN.MullvadRESTTests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -8734,6 +8751,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8769,6 +8787,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8805,6 +8824,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8836,6 +8856,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = CKG9MXH72F; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN.PacketTunnelCoreTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -8864,6 +8885,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8907,6 +8929,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -8940,7 +8963,7 @@ DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = CKG9MXH72F; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN.RoutingTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -8965,7 +8988,7 @@ GENERATE_INFOPLIST_FILE = YES; HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/../mullvad-api/include"; INFOPLIST_FILE = MullvadVPNUITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; "LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64]" = "$(PROJECT_DIR)/../target/aarch64-apple-ios/debug"; "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=arm64]" = "$(PROJECT_DIR)/../target/aarch64-apple-ios-sim/debug"; PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER).MullvadVPNUITests"; @@ -8991,7 +9014,7 @@ GENERATE_INFOPLIST_FILE = YES; HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/../mullvad-api/include"; INFOPLIST_FILE = MullvadVPNUITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; "LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64]" = "$(PROJECT_DIR)/../target/aarch64-apple-ios/release"; "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=arm64]" = "$(PROJECT_DIR)/../target/aarch64-apple-ios-sim/release"; PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER).MullvadVPNUITests"; @@ -9030,7 +9053,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9082,7 +9105,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9134,7 +9157,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9184,7 +9207,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9223,7 +9246,7 @@ ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadRustRuntimeTests; @@ -9249,7 +9272,7 @@ ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadRustRuntimeTests; @@ -9274,7 +9297,7 @@ ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadRustRuntimeTests; @@ -9300,7 +9323,7 @@ ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadRustRuntimeTests; @@ -9361,7 +9384,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; @@ -9391,6 +9414,7 @@ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = CKG9MXH72F; ENABLE_BITCODE = NO; INFOPLIST_FILE = "MullvadVPN/Supporting Files/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9426,6 +9450,7 @@ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = CKG9MXH72F; ENABLE_BITCODE = NO; INFOPLIST_FILE = PacketTunnel/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9445,6 +9470,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; INFOPLIST_FILE = MullvadVPNTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9475,6 +9501,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9503,6 +9530,7 @@ DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = CKG9MXH72F; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN.OperationsTests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -9530,6 +9558,7 @@ INFOPLIST_FILE = MullvadREST/Info.plist; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9561,6 +9590,7 @@ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = CKG9MXH72F; "DEVELOPMENT_TEAM[sdk=macosx*]" = CKG9MXH72F; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN.MullvadRESTTests; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -9587,6 +9617,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9622,6 +9653,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9658,6 +9690,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9689,6 +9722,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = CKG9MXH72F; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN.PacketTunnelCoreTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -9717,6 +9751,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9757,6 +9792,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9790,7 +9826,7 @@ DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = CKG9MXH72F; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = net.mullvad.MullvadVPN.RoutingTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -9825,7 +9861,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9873,7 +9909,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9921,7 +9957,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -9970,7 +10006,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Mullvad VPN AB. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/ios/MullvadVPN/Classes/AutomaticKeyboardResponder.swift b/ios/MullvadVPN/Classes/AutomaticKeyboardResponder.swift index 00b83d3ed5..9de1a7827d 100644 --- a/ios/MullvadVPN/Classes/AutomaticKeyboardResponder.swift +++ b/ios/MullvadVPN/Classes/AutomaticKeyboardResponder.swift @@ -49,34 +49,23 @@ class AutomaticKeyboardResponder { guard let userInfo = notification.userInfo, let targetView else { return } - // In iOS 16.1 and later, the keyboard notification object is the screen the keyboard appears on. - if #available(iOS 16.1, *) { - guard let screen = notification.object as? UIScreen, - // Get the keyboard’s frame at the end of its animation. - let keyboardFrameEnd = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect - else { return } - - // Use that screen to get the coordinate space to convert from. - let fromCoordinateSpace = screen.coordinateSpace + guard let screen = notification.object as? UIScreen, + // Get the keyboard’s frame at the end of its animation. + let keyboardFrameEnd = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect + else { return } - // Get your view's coordinate space. - let toCoordinateSpace: UICoordinateSpace = targetView + // Use that screen to get the coordinate space to convert from. + let fromCoordinateSpace = screen.coordinateSpace - // Convert the keyboard's frame from the screen's coordinate space to your view's coordinate space. - let convertedKeyboardFrameEnd = fromCoordinateSpace.convert(keyboardFrameEnd, to: toCoordinateSpace) + // Get your view's coordinate space. + let toCoordinateSpace: UICoordinateSpace = targetView - lastKeyboardRect = convertedKeyboardFrameEnd + // Convert the keyboard's frame from the screen's coordinate space to your view's coordinate space. + let convertedKeyboardFrameEnd = fromCoordinateSpace.convert(keyboardFrameEnd, to: toCoordinateSpace) - adjustContentInsets(convertedKeyboardFrameEnd: convertedKeyboardFrameEnd) - } else { - guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue - else { return } - let keyboardFrameEnd = keyboardValue.cgRectValue - let convertedKeyboardFrameEnd = targetView.convert(keyboardFrameEnd, from: targetView.window) - lastKeyboardRect = convertedKeyboardFrameEnd + lastKeyboardRect = convertedKeyboardFrameEnd - adjustContentInsets(convertedKeyboardFrameEnd: convertedKeyboardFrameEnd) - } + adjustContentInsets(convertedKeyboardFrameEnd: convertedKeyboardFrameEnd) } private func adjustContentInsets(convertedKeyboardFrameEnd: CGRect) { diff --git a/ios/MullvadVPN/Containers/Root/RootContainerViewController.swift b/ios/MullvadVPN/Containers/Root/RootContainerViewController.swift index 9f9f050797..8458521da0 100644 --- a/ios/MullvadVPN/Containers/Root/RootContainerViewController.swift +++ b/ios/MullvadVPN/Containers/Root/RootContainerViewController.swift @@ -753,7 +753,7 @@ class RootContainerViewController: UIViewController { // Tell UIKit to update the interface orientation if attemptRotateToDeviceOrientation { - Self.attemptRotationToDeviceOrientation() + setNeedsUpdateOfSupportedInterfaceOrientations() } } } diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodView.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodView.swift index b6c305e338..8eef58257e 100644 --- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodView.swift +++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodView.swift @@ -86,13 +86,7 @@ struct ListAccessMethodView<ViewModel>: View where ViewModel: ListAccessViewMode .accessibilityIdentifier( AccessibilityIdentifier.apiAccessListView.asString ) - .apply { - if #available(iOS 16.4, *) { - $0.scrollBounceBehavior(.basedOnSize) - } else { - $0 - } - } + .scrollBounceBehavior(.basedOnSize) Spacer() } .background(Color.mullvadBackground) diff --git a/ios/MullvadVPN/Extensions/View+Modifier.swift b/ios/MullvadVPN/Extensions/View+Modifier.swift index 7cb201f01f..73f59e1e7c 100644 --- a/ios/MullvadVPN/Extensions/View+Modifier.swift +++ b/ios/MullvadVPN/Extensions/View+Modifier.swift @@ -11,16 +11,6 @@ import SwiftUI extension View { /** A view modifier that can be used to conditionally apply other view modifiers. - # Example # - ``` - .apply { - if #available(iOS 16.4, *) { - $0.scrollBounceBehavior(.basedOnSize) - } else { - $0 - } - } - ``` */ func apply<V: View>(@ViewBuilder _ block: (Self) -> V) -> V { block(self) } diff --git a/ios/MullvadVPN/GeneralAPIs/OutgoingConnectionProxy.swift b/ios/MullvadVPN/GeneralAPIs/OutgoingConnectionProxy.swift index ed54e4743b..0c2e281ba1 100644 --- a/ios/MullvadVPN/GeneralAPIs/OutgoingConnectionProxy.swift +++ b/ios/MullvadVPN/GeneralAPIs/OutgoingConnectionProxy.swift @@ -45,18 +45,20 @@ final class OutgoingConnectionProxy: OutgoingConnectionHandling { private func perform<T: Decodable>(retryStrategy: REST.RetryStrategy, version: ExitIPVersion) async throws -> T { let delayIterator = retryStrategy.makeDelayIterator() for _ in 0..<retryStrategy.maxRetryCount { - do { - return try await perform(host: version.host(hostname: hostname)) - } catch { - // ignore if request is cancelled - if case URLError.cancelled = error { - throw error - } else { - // retry with the delay - guard let delay = delayIterator.next() else { throw error } - let mills = UInt64(max(0, delay.milliseconds)) - let nanos = mills.saturatingMultiplication(1_000_000) - try await Task.sleep(nanoseconds: nanos) + if !Task.isCancelled { + do { + return try await perform(host: version.host(hostname: hostname)) + } catch { + // ignore if request is cancelled + if case URLError.cancelled = error { + throw error + } else { + // retry with the delay + guard let delay = delayIterator.next() else { throw error } + let mills = UInt64(max(0, delay.milliseconds)) + let nanos = mills.saturatingMultiplication(1_000_000) + try await Task.sleep(nanoseconds: nanos) + } } } } diff --git a/ios/MullvadVPN/Presentation controllers/FormsheetPresentationController.swift b/ios/MullvadVPN/Presentation controllers/FormsheetPresentationController.swift index 06bfcd60fd..cced1e8c8c 100644 --- a/ios/MullvadVPN/Presentation controllers/FormsheetPresentationController.swift +++ b/ios/MullvadVPN/Presentation controllers/FormsheetPresentationController.swift @@ -75,6 +75,12 @@ class FormSheetPresentationController: UIPresentationController { self.options = options super.init(presentedViewController: presentedViewController, presenting: presentingViewController) addKeyboardResponderIfNeeded() + + registerForTraitChanges( + [UITraitUserInterfaceStyle.self], + handler: { (self: Self, previousTraitCollection: UITraitCollection) in + self.postFullscreenPresentationWillChangeIfNeeded() + }) } override var frameOfPresentedViewInContainerView: CGRect { @@ -152,12 +158,6 @@ class FormSheetPresentationController: UIPresentationController { } } - override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { - super.traitCollectionDidChange(previousTraitCollection) - - postFullscreenPresentationWillChangeIfNeeded() - } - private func postFullscreenPresentationWillChangeIfNeeded() { let currentIsInFullScreen = isInFullScreenPresentation diff --git a/ios/MullvadVPN/View controllers/RelayFilter/ChipFlowLayout.swift b/ios/MullvadVPN/View controllers/RelayFilter/ChipFlowLayout.swift index 7bcd500a7a..46ac7354a6 100644 --- a/ios/MullvadVPN/View controllers/RelayFilter/ChipFlowLayout.swift +++ b/ios/MullvadVPN/View controllers/RelayFilter/ChipFlowLayout.swift @@ -30,8 +30,8 @@ class ChipFlowLayout: UICollectionViewFlowLayout { let attributes = originalAttributes.compactMap { $0.copy() as? UICollectionViewLayoutAttributes } // Detect RTL - let languageCode = Locale.current.languageCode ?? "en" - let isRTL = Locale.characterDirection(forLanguage: languageCode) == .rightToLeft + let languageCode = Locale.current.language.languageCode?.identifier ?? "en" + let isRTL = Locale.Language(identifier: languageCode).characterDirection == .rightToLeft var currentLineY: CGFloat = -1 var currentLineAttributes: [UICollectionViewLayoutAttributes] = [] diff --git a/ios/MullvadVPN/View controllers/RevokedDevice/RevokedDeviceView.swift b/ios/MullvadVPN/View controllers/RevokedDevice/RevokedDeviceView.swift index 5f7723fdd8..1cf55562d4 100644 --- a/ios/MullvadVPN/View controllers/RevokedDevice/RevokedDeviceView.swift +++ b/ios/MullvadVPN/View controllers/RevokedDevice/RevokedDeviceView.swift @@ -40,14 +40,7 @@ struct RevokedDeviceView: View { } .padding(.top, 24) } - .apply { - if #available(iOS 16.4, *) { - $0.scrollBounceBehavior(.automatic) - } else { - $0 - } - } - + .scrollBounceBehavior(.automatic) MainButton(text: "Go to login", style: viewModel.tunnelState.isSecured ? .danger : .default) { onLogout?() } diff --git a/ios/MullvadVPN/View controllers/Settings/SwiftUI components/SingleChoiceList.swift b/ios/MullvadVPN/View controllers/Settings/SwiftUI components/SingleChoiceList.swift index 7204fa4e03..d2560893ff 100644 --- a/ios/MullvadVPN/View controllers/Settings/SwiftUI components/SingleChoiceList.swift +++ b/ios/MullvadVPN/View controllers/Settings/SwiftUI components/SingleChoiceList.swift @@ -275,7 +275,7 @@ struct SingleChoiceList<Value>: View where Value: Equatable { ) ) .focused($customValueIsFocused) - .onChange(of: customValueInput) { _ in + .onChange(of: customValueInput) { if let maxInputLength { if customValueInput.count > maxInputLength { customValueInput = String(customValueInput.prefix(maxInputLength)) diff --git a/ios/MullvadVPN/View controllers/TermsOfService/TermsOfServiceView.swift b/ios/MullvadVPN/View controllers/TermsOfService/TermsOfServiceView.swift index 1f71ba4303..cd52037fc5 100644 --- a/ios/MullvadVPN/View controllers/TermsOfService/TermsOfServiceView.swift +++ b/ios/MullvadVPN/View controllers/TermsOfService/TermsOfServiceView.swift @@ -43,12 +43,7 @@ struct TermsOfServiceView: View { var body: some View { VStack(alignment: .leading) { - // Disable scrolling if the contents do not overflow - if #available(iOS 16.4, *) { - scrollableContent.scrollBounceBehavior(.basedOnSize) - } else { - scrollableContent - } + scrollableContent.scrollBounceBehavior(.basedOnSize) HStack { Text(privacyPolicyLink) .font(.mullvadSmall) diff --git a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionView.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionView.swift index d00eb5e49e..adaa600810 100644 --- a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionView.swift +++ b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionView.swift @@ -83,13 +83,7 @@ struct ConnectionView: View { } } .frame(maxHeight: scrollViewHeight) - .apply { - if #available(iOS 16.4, *) { - $0.scrollBounceBehavior(.basedOnSize) - } else { - $0 - } - } + .scrollBounceBehavior(.basedOnSize) } .transformEffect(.identity) .animation(.default, value: hasFeatureIndicators) @@ -99,8 +93,8 @@ struct ConnectionView: View { .background(BlurView(style: .dark)) .cornerRadius(12) .padding(EdgeInsets(top: 16, leading: 16, bottom: 24, trailing: 16)) - .onChange(of: connectionViewModel.showsConnectionDetails) { showsConnectionDetails in - if !showsConnectionDetails { + .onChange(of: connectionViewModel.showsConnectionDetails) { + if !connectionViewModel.showsConnectionDetails { withAnimation { isExpanded = false } diff --git a/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSViewController.swift b/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSViewController.swift index 29bab0ec63..e651ee53c5 100644 --- a/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSViewController.swift +++ b/ios/MullvadVPN/View controllers/VPNSettings/CustomDNSViewController.swift @@ -70,11 +70,7 @@ class CustomDNSViewController: UITableViewController { navigationItem.setHidesBackButton(editing, animated: animated) if navigationItem.rightBarButtonItem != editButtonItem { - if #available(iOS 16.0, *) { - navigationItem.rightBarButtonItem?.isHidden = editing - } else { - navigationItem.rightBarButtonItem?.isEnabled = !editing - } + navigationItem.rightBarButtonItem?.isHidden = editing } // Disable swipe to dismiss when editing diff --git a/ios/MullvadVPN/Views/List/MullvadListNavigationItemView.swift b/ios/MullvadVPN/Views/List/MullvadListNavigationItemView.swift index 8f494786cf..0509ecd906 100644 --- a/ios/MullvadVPN/Views/List/MullvadListNavigationItemView.swift +++ b/ios/MullvadVPN/Views/List/MullvadListNavigationItemView.swift @@ -91,9 +91,11 @@ private struct MullvadListButtonStyle: ButtonStyle { let onButtonPressedChange: (Bool) -> Void func makeBody(configuration: Configuration) -> some View { configuration.label - .onChange(of: configuration.isPressed) { newValue in - onButtonPressedChange(newValue) - } + .onChange( + of: configuration.isPressed, + { + onButtonPressedChange(configuration.isPressed) + }) } } diff --git a/ios/MullvadVPNTests/MullvadTypes/DurationTests.swift b/ios/MullvadVPNTests/MullvadTypes/DurationTests.swift deleted file mode 100644 index efeaa0645f..0000000000 --- a/ios/MullvadVPNTests/MullvadTypes/DurationTests.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// DurationTests.swift -// MullvadRESTTests -// -// Created by pronebird on 05/11/2022. -// Copyright © 2025 Mullvad VPN AB. All rights reserved. -// - -import MullvadTypes -import XCTest - -@testable import MullvadREST - -final class DurationTests: XCTestCase { - func testComparable() throws { - XCTAssertEqual(Duration.milliseconds(1000), .seconds(1)) - XCTAssertEqual(Duration.seconds(60), .minutes(1)) - XCTAssertEqual(Duration.minutes(60), .hours(1)) - XCTAssertEqual(Duration.hours(24), .days(1)) - - XCTAssertTrue(Duration.days(1) == 86400.0) - XCTAssertTrue(Duration.milliseconds(1234) == 1.234) - - XCTAssertTrue(Duration.milliseconds(.max) == 9.223372036854775e+15) - XCTAssertEqual(Duration.milliseconds(.max).milliseconds, 9_223_372_036_854_775_807) - XCTAssertEqual(Duration.seconds(.max).milliseconds, 9_223_372_036_854_775_807) - - XCTAssertLessThan(Duration.milliseconds(999), .seconds(1)) - XCTAssertGreaterThan(Duration.milliseconds(1001), .seconds(1)) - XCTAssertTrue(1.1 > Duration.milliseconds(1001)) - XCTAssertTrue(1.0 < Duration.milliseconds(1001)) - } - - func testAddition() throws { - XCTAssertEqual(Duration.seconds(4) + .seconds(116), .minutes(2)) - XCTAssertEqual(Duration.minutes(4) + .seconds(.max), .milliseconds(.max)) - } - - func testSubtraction() throws { - XCTAssertEqual(Duration.minutes(4) - .minutes(1), .seconds(180)) - XCTAssertEqual(Duration.seconds(4) - .seconds(64), .minutes(-1)) - } - - func testMultiplication() throws { - XCTAssertEqual(Duration.seconds(4) * 2.0, .seconds(8)) - XCTAssertEqual(Duration.seconds(4) * 4, .seconds(16)) - XCTAssertEqual(Duration.milliseconds(20000) * 3, .minutes(1)) - XCTAssertEqual(Duration.milliseconds(.max - 1) * 2, .milliseconds(.max)) - XCTAssertEqual( - Duration.milliseconds(.max) * (Double(Int.max) + 1.0), - .milliseconds(Int(Double(Int.max).nextDown)) - ) - } - - func testDivision() throws { - XCTAssertEqual(Duration.seconds(4) / 4, .seconds(1)) - XCTAssertEqual(Duration.milliseconds(21000) / 3, .seconds(7)) - XCTAssertEqual(Duration.minutes(3) / 3, .seconds(60)) - } - - func testLogFormat() throws { - XCTAssertEqual(Duration.milliseconds(999).logFormat(), "999ms") - XCTAssertEqual(Duration.milliseconds(1000).logFormat(), "1s") - XCTAssertEqual(Duration.milliseconds(1200).logFormat(), "1.20s") - } -} - -private extension Duration { - static func == (lhs: Duration, rhs: Double) -> Bool { - return lhs.timeInterval == rhs - } -} diff --git a/ios/MullvadVPNUITests/Screenshots/SnapshotHelper.swift b/ios/MullvadVPNUITests/Screenshots/SnapshotHelper.swift index c8d7fd2bf8..efd432f755 100644 --- a/ios/MullvadVPNUITests/Screenshots/SnapshotHelper.swift +++ b/ios/MullvadVPNUITests/Screenshots/SnapshotHelper.swift @@ -208,15 +208,11 @@ open class Snapshot: NSObject { #if os(watchOS) return image #else - if #available(iOS 10.0, *) { - let format = UIGraphicsImageRendererFormat() - format.scale = image.scale - let renderer = UIGraphicsImageRenderer(size: image.size, format: format) - return renderer.image { _ in - image.draw(in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)) - } - } else { - return image + let format = UIGraphicsImageRendererFormat() + format.scale = image.scale + let renderer = UIGraphicsImageRenderer(size: image.size, format: format) + return renderer.image { _ in + image.draw(in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)) } #endif } diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor+ErrorState.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+ErrorState.swift index 683cc9c596..dfead154f6 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActor+ErrorState.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor+ErrorState.swift @@ -152,7 +152,9 @@ extension PacketTunnelActor { while !Task.isCancelled { guard let self else { return } - try await Task.sleepUsingContinuousClock(for: timings.bootRecoveryPeriodicity) + try await Task.sleep(for: timings.bootRecoveryPeriodicity) + + guard !Task.isCancelled else { return } // Schedule task to reconnect. eventChannel.send(.reconnect(.random)) diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor+KeyPolicy.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+KeyPolicy.swift index 8bff739d93..a2c8ab9aff 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActor+KeyPolicy.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor+KeyPolicy.swift @@ -51,7 +51,9 @@ extension PacketTunnelActor { guard let self else { return } // Wait for key to propagate across relays. - try await Task.sleepUsingContinuousClock(for: timings.wgKeyPropagationDelay) + try await Task.sleep(for: timings.wgKeyPropagationDelay) + + guard !Task.isCancelled else { return } // Enqueue task to change key policy. eventChannel.send(.switchKey) diff --git a/ios/PacketTunnelCore/Actor/Task+Duration.swift b/ios/PacketTunnelCore/Actor/Task+Duration.swift deleted file mode 100644 index fb1b65c030..0000000000 --- a/ios/PacketTunnelCore/Actor/Task+Duration.swift +++ /dev/null @@ -1,67 +0,0 @@ -// -// Task+.swift -// PacketTunnelCore -// -// Created by pronebird on 11/09/2023. -// Copyright © 2025 Mullvad VPN AB. All rights reserved. -// - -import Foundation -import MullvadTypes - -private typealias TaskCancellationError = CancellationError - -extension Task where Success == Never, Failure == Never { - /** - Suspends the current task for at least the given duration. - - Negative durations are clamped to zero. - - - Parameter duration: duration that determines how long the task should be suspended. - */ - @available(iOS, introduced: 15.0, obsoleted: 16.0, message: "Replace with Task.sleep(for:tolerance:clock:).") - static func sleep(duration: Duration) async throws { - let millis = UInt64(max(0, duration.milliseconds)) - let nanos = millis.saturatingMultiplication(1_000_000) - - try await Task.sleep(nanoseconds: nanos) - } - - /** - Suspends the current task for the given duration. - - Negative durations are clamped to zero. - - - Parameter duration: duration that determines how long the task should be suspended. - */ - @available(iOS, introduced: 15.0, obsoleted: 16.0, message: "Replace with Task.sleep(for:tolerance:clock:).") - static func sleepUsingContinuousClock(for duration: Duration) async throws { - nonisolated(unsafe) let timer = DispatchSource.makeTimerSource() - - try await withTaskCancellationHandler { - try await withCheckedThrowingContinuation { continuation in - // The `continuation` handler should never be `resume`'d more than once. - // Setting the eventHandler on the timer after it has been cancelled will be ignored. - // https://github.com/apple/swift-corelibs-libdispatch/blob/77766345740dfe3075f2f60bead854b29b0cfa24/src/source.c#L338 - // Therefore, set a flag indicating `resume` has already been called to avoid `resume`ing more than once. - // Cancelling the timer does not cancel an event handler that is already running however, - // the cancel handler will be scheduled after the event handler has finished. - // https://developer.apple.com/documentation/dispatch/1385604-dispatch_source_cancel - // Therefore, there it is safe to do this here. - var hasResumed = false - timer.setEventHandler { - hasResumed = true - continuation.resume() - } - timer.setCancelHandler { - guard hasResumed == false else { return } - continuation.resume(throwing: TaskCancellationError()) - } - timer.schedule(wallDeadline: .now() + DispatchTimeInterval.milliseconds(duration.milliseconds)) - timer.activate() - } - } onCancel: { - timer.cancel() - } - } -} diff --git a/ios/PacketTunnelCoreTests/TaskSleepTests.swift b/ios/PacketTunnelCoreTests/TaskSleepTests.swift deleted file mode 100644 index 20aee5006d..0000000000 --- a/ios/PacketTunnelCoreTests/TaskSleepTests.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// TaskSleepTests.swift -// PacketTunnelCoreTests -// -// Created by pronebird on 25/09/2023. -// Copyright © 2025 Mullvad VPN AB. All rights reserved. -// - -import XCTest - -@testable import PacketTunnelCore - -final class TaskSleepTests: XCTestCase { - func testCancellation() async throws { - let task = Task { - try await Task.sleepUsingContinuousClock(for: .seconds(1)) - } - - task.cancel() - - do { - try await task.value - - XCTFail("Task must be cancelled.") - } catch { - XCTAssert(error is CancellationError) - } - } - - /// This test triggers a race condition in `sleepUsingContinuousClock` where an `AutoCancellingTask` will - /// cancel a `DispatchSourceTimer` in a `Task` trying to call `resume` on its continuation handler more than once - func testSuccessfulEventHandlerRemovesCancellation() async throws { - for _ in 0...20 { - let task = recoveryTask() - try await Task.sleep(duration: .milliseconds(10)) - task.callDummyFunctionToForceConcurrencyWait() - } - } - - private func recoveryTask() -> AutoCancellingTask { - AutoCancellingTask( - Task.detached { - while Task.isCancelled == false { - try await Task.sleepUsingContinuousClock(for: .milliseconds(10)) - } - }) - } -} - -private extension AutoCancellingTask { - /// This function is here to silence a warning about unused variables in `testSuccessfulEventHandlerRemovesCancellation` - /// The following construct `_ = recoveryTask()` cannot be used as the resulting `AutoCancellingTask` - /// would immediately get `deinit`ed, changing the test scenario. - /// A dummy function is needed to make sure the task is not cancelled before concurrency is forced - /// by having a call to `Task.sleep` - func callDummyFunctionToForceConcurrencyWait() {} -} diff --git a/ios/Shared/ApplicationLanguage.swift b/ios/Shared/ApplicationLanguage.swift index c460f92123..e51eef8fff 100644 --- a/ios/Shared/ApplicationLanguage.swift +++ b/ios/Shared/ApplicationLanguage.swift @@ -107,29 +107,18 @@ enum ApplicationLanguage: String, CaseIterable, Identifiable { let defaultCode = ApplicationLanguage.english.rawValue let fullCode = Locale.preferredLanguages.first ?? defaultCode - if #available(iOS 16, *) { - let locale = Locale(identifier: fullCode) - if let script = locale.language.script?.identifier { - switch script { - case "Hans": - return .chineseSimplified - case "Hant": - return .chineseTraditional - default: - break - } - } - } else { - if fullCode.contains("Hans") { + let locale = Locale(identifier: fullCode) + if let script = locale.language.script?.identifier { + switch script { + case "Hans": return .chineseSimplified - } else if fullCode.contains("Hant") { + case "Hant": return .chineseTraditional + default: + break } } - - // Otherwise, try to get languageCode (e.g., "en", "fr") - let locale = Locale(identifier: fullCode) - let langCode = locale.languageCode ?? defaultCode + let langCode = locale.language.languageCode?.identifier ?? defaultCode return ApplicationLanguage(rawValue: langCode) ?? .english } |
