summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJon Petersson <jon.petersson@kvadrat.se>2023-11-02 09:45:11 +0100
committerBug Magnet <marco.nikic@mullvad.net>2023-11-10 13:09:41 +0100
commit5b24541c5f4418fce498f3e673023b00801f7a77 (patch)
tree45834ad04259670e5e4db5204172f8b8a5b217aa
parent1654b732eb43b36f66545c0811cc5868268d4797 (diff)
downloadmullvadvpn-5b24541c5f4418fce498f3e673023b00801f7a77.tar.xz
mullvadvpn-5b24541c5f4418fce498f3e673023b00801f7a77.zip
Change account expiry text to display 'less than a day'
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj12
-rw-r--r--ios/MullvadVPN/Classes/CustomDateComponentsFormatting.swift24
-rw-r--r--ios/MullvadVPN/Notifications/Notification Providers/AccountExpiry.swift43
-rw-r--r--ios/MullvadVPN/Notifications/Notification Providers/AccountExpiryInAppNotificationProvider.swift41
-rw-r--r--ios/MullvadVPNTests/AccountExpiryTests.swift48
-rw-r--r--ios/MullvadVPNTests/CustomDateComponentsFormattingTests.swift4
6 files changed, 128 insertions, 44 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 61a08e84e5..3009059893 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -438,6 +438,8 @@
7A3FD1B82AD54AE60042BEA6 /* TimeServerProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BDEB9A2A98F58600F578F2 /* TimeServerProxy.swift */; };
7A42DEC92A05164100B209BE /* SettingsInputCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A42DEC82A05164100B209BE /* SettingsInputCell.swift */; };
7A6B4F592AB8412E00123853 /* TunnelMonitorTimings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6B4F582AB8412E00123853 /* TunnelMonitorTimings.swift */; };
+ 7A6F2FA52AFA3CB2006D0856 /* AccountExpiryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6F2FA42AFA3CB2006D0856 /* AccountExpiryTests.swift */; };
+ 7A6F2FA72AFBB9AE006D0856 /* AccountExpiry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6F2FA62AFBB9AE006D0856 /* AccountExpiry.swift */; };
7A7AD28D29DC677800480EF1 /* FirstTimeLaunch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7AD28C29DC677800480EF1 /* FirstTimeLaunch.swift */; };
7A818F1F29F0305800C7F0F4 /* RootConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A818F1E29F0305800C7F0F4 /* RootConfiguration.swift */; };
7A83C3FF2A55B72E00DFB83A /* MullvadVPNApp.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = 7A83C3FE2A55B72E00DFB83A /* MullvadVPNApp.xctestplan */; };
@@ -508,6 +510,7 @@
A97F1F472A1F4E1A00ECEFDE /* MullvadTransport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A97F1F412A1F4E1A00ECEFDE /* MullvadTransport.framework */; };
A97F1F482A1F4E1A00ECEFDE /* MullvadTransport.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A97F1F412A1F4E1A00ECEFDE /* MullvadTransport.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
A97FF5502A0D2FFC00900996 /* NSFileCoordinator+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97FF54F2A0D2FFC00900996 /* NSFileCoordinator+Extensions.swift */; };
+ A988A3E22AFE54AC0008D2C7 /* AccountExpiry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6F2FA62AFBB9AE006D0856 /* AccountExpiry.swift */; };
A988DF212ADD293D00D807EF /* RESTTransportStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9A1DE782AD5708E0073F689 /* RESTTransportStrategy.swift */; };
A988DF242ADD307200D807EF /* libRelaySelector.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5898D29829017DAC00EB5EBA /* libRelaySelector.a */; };
A988DF262ADE86ED00D807EF /* WireGuardObfuscationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A988DF252ADE86ED00D807EF /* WireGuardObfuscationSettings.swift */; };
@@ -533,7 +536,6 @@
A9A5F9F12ACB05160083449F /* String+Split.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5807E2BF2432038B00F5FF30 /* String+Split.swift */; };
A9A5F9F22ACB05160083449F /* NotificationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C8191729FAA2C400DEB1B4 /* NotificationConfiguration.swift */; };
A9A5F9F32ACB05160083449F /* AccountExpirySystemNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B75402668FD7700DEF7E9 /* AccountExpirySystemNotificationProvider.swift */; };
- A9A5F9F42ACB05160083449F /* AccountExpiryInAppNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58607A4C2947287800BC467D /* AccountExpiryInAppNotificationProvider.swift */; };
A9A5F9F52ACB05160083449F /* RegisteredDeviceInAppNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F07CFF1F29F2720E008C0343 /* RegisteredDeviceInAppNotificationProvider.swift */; };
A9A5F9F62ACB05160083449F /* TunnelStatusNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A94AE326CFD945001CB97C /* TunnelStatusNotificationProvider.swift */; };
A9A5F9F72ACB05160083449F /* NotificationProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B26E232943520C00D5980C /* NotificationProviderProtocol.swift */; };
@@ -1533,6 +1535,8 @@
7A42DEC82A05164100B209BE /* SettingsInputCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsInputCell.swift; sourceTree = "<group>"; };
7A42DECC2A09064C00B209BE /* SelectableSettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableSettingsCell.swift; sourceTree = "<group>"; };
7A6B4F582AB8412E00123853 /* TunnelMonitorTimings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelMonitorTimings.swift; sourceTree = "<group>"; };
+ 7A6F2FA42AFA3CB2006D0856 /* AccountExpiryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountExpiryTests.swift; sourceTree = "<group>"; };
+ 7A6F2FA62AFBB9AE006D0856 /* AccountExpiry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountExpiry.swift; sourceTree = "<group>"; };
7A7AD28C29DC677800480EF1 /* FirstTimeLaunch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstTimeLaunch.swift; sourceTree = "<group>"; };
7A818F1E29F0305800C7F0F4 /* RootConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootConfiguration.swift; sourceTree = "<group>"; };
7A83C3FE2A55B72E00DFB83A /* MullvadVPNApp.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = MullvadVPNApp.xctestplan; sourceTree = "<group>"; };
@@ -2396,6 +2400,7 @@
isa = PBXGroup;
children = (
58C8191729FAA2C400DEB1B4 /* NotificationConfiguration.swift */,
+ 7A6F2FA62AFBB9AE006D0856 /* AccountExpiry.swift */,
587B75402668FD7700DEF7E9 /* AccountExpirySystemNotificationProvider.swift */,
58607A4C2947287800BC467D /* AccountExpiryInAppNotificationProvider.swift */,
F07CFF1F29F2720E008C0343 /* RegisteredDeviceInAppNotificationProvider.swift */,
@@ -2472,6 +2477,7 @@
isa = PBXGroup;
children = (
A900E9BF2ACC661900C95F67 /* AccessTokenManager+Stubs.swift */,
+ 7A6F2FA42AFA3CB2006D0856 /* AccountExpiryTests.swift */,
A900E9B72ACC5C2B00C95F67 /* AccountsProxy+Stubs.swift */,
A9CF11FC2A0518E7001D9565 /* AddressCacheTests.swift */,
A900E9BD2ACC654100C95F67 /* APIProxy+Stubs.swift */,
@@ -4097,7 +4103,6 @@
A9A5F9F12ACB05160083449F /* String+Split.swift in Sources */,
A9A5F9F22ACB05160083449F /* NotificationConfiguration.swift in Sources */,
A9A5F9F32ACB05160083449F /* AccountExpirySystemNotificationProvider.swift in Sources */,
- A9A5F9F42ACB05160083449F /* AccountExpiryInAppNotificationProvider.swift in Sources */,
A9A5F9F52ACB05160083449F /* RegisteredDeviceInAppNotificationProvider.swift in Sources */,
A9A5F9F62ACB05160083449F /* TunnelStatusNotificationProvider.swift in Sources */,
A9A5F9F72ACB05160083449F /* NotificationProviderProtocol.swift in Sources */,
@@ -4121,6 +4126,7 @@
A9E0317A2ACB0AE70095D843 /* UIApplication+Stubs.swift in Sources */,
A9A5FA062ACB05160083449F /* SimulatorTunnelProviderManager.swift in Sources */,
A9A5FA072ACB05160083449F /* SimulatorVPNConnection.swift in Sources */,
+ 7A6F2FA52AFA3CB2006D0856 /* AccountExpiryTests.swift in Sources */,
A9A5FA082ACB05160083449F /* StorePaymentBlockObserver.swift in Sources */,
A9E0317C2ACBFC7E0095D843 /* TunnelStore+Stubs.swift in Sources */,
A9A5FA092ACB05160083449F /* SendStoreReceiptOperation.swift in Sources */,
@@ -4151,6 +4157,7 @@
A9A5FA212ACB05160083449F /* TunnelManagerErrors.swift in Sources */,
A9C342C32ACC3EE90045F00E /* RelayCacheTracker+Stubs.swift in Sources */,
A9A5FA222ACB05160083449F /* TunnelObserver.swift in Sources */,
+ A988A3E22AFE54AC0008D2C7 /* AccountExpiry.swift in Sources */,
A9E0317F2ACC331C0095D843 /* TunnelStatusBlockObserver.swift in Sources */,
A9A5FA232ACB05160083449F /* TunnelState.swift in Sources */,
A9A5FA242ACB05160083449F /* TunnelStore.swift in Sources */,
@@ -4427,6 +4434,7 @@
F0C6FA812A66E23300F521F0 /* DeleteAccountOperation.swift in Sources */,
F07CFF2029F2720E008C0343 /* RegisteredDeviceInAppNotificationProvider.swift in Sources */,
587A01FC23F1F0BE00B68763 /* SimulatorTunnelProviderHost.swift in Sources */,
+ 7A6F2FA72AFBB9AE006D0856 /* AccountExpiry.swift in Sources */,
5819C2172729595500D6EC38 /* SettingsAddDNSEntryCell.swift in Sources */,
7A1A26452A29CEF700B978AA /* RelayFilterViewController.swift in Sources */,
5862805422428EF100F5A6E1 /* TranslucentButtonBlurView.swift in Sources */,
diff --git a/ios/MullvadVPN/Classes/CustomDateComponentsFormatting.swift b/ios/MullvadVPN/Classes/CustomDateComponentsFormatting.swift
index f0b227fa14..de1c861076 100644
--- a/ios/MullvadVPN/Classes/CustomDateComponentsFormatting.swift
+++ b/ios/MullvadVPN/Classes/CustomDateComponentsFormatting.swift
@@ -15,8 +15,9 @@ extension CustomDateComponentsFormatting {
///
/// The behaviour of that method differs from `DateComponentsFormatter`:
///
- /// 1. Intervals of two years or more are formatted in years quantity.
- /// 2. Otherwise intervals matching none of the above are formatted in days quantity.
+ /// 1. Intervals of less than a day return a custom string.
+ /// 2. Intervals of two years or more are formatted in years quantity.
+ /// 3. Otherwise intervals matching none of the above are formatted in days quantity.
///
static func localizedString(
from start: Date,
@@ -24,14 +25,29 @@ extension CustomDateComponentsFormatting {
calendar: Calendar = Calendar.current,
unitsStyle: DateComponentsFormatter.UnitsStyle
) -> String? {
- let years = calendar.dateComponents([.year], from: start, to: max(start, end)).year ?? 0
+ let dateComponents = calendar.dateComponents([.year, .day], from: start, to: max(start, end))
+
+ guard !isLessThanOneDay(dateComponents: dateComponents) else {
+ return NSLocalizedString(
+ "CUSTOM_DATE_COMPONENTS_FORMATTING_LESS_THAN_ONE_DAY",
+ value: "Less than a day",
+ comment: ""
+ )
+ }
let formatter = DateComponentsFormatter()
formatter.calendar = calendar
formatter.unitsStyle = unitsStyle
formatter.maximumUnitCount = 1
- formatter.allowedUnits = years >= 2 ? .year : .day
+ formatter.allowedUnits = (dateComponents.year ?? 0) >= 2 ? .year : .day
return formatter.string(from: start, to: max(start, end))
}
+
+ private static func isLessThanOneDay(dateComponents: DateComponents) -> Bool {
+ let year = dateComponents.year ?? 0
+ let day = dateComponents.day ?? 0
+
+ return (year == 0) && (day == 0)
+ }
}
diff --git a/ios/MullvadVPN/Notifications/Notification Providers/AccountExpiry.swift b/ios/MullvadVPN/Notifications/Notification Providers/AccountExpiry.swift
new file mode 100644
index 0000000000..e38b0fd3d5
--- /dev/null
+++ b/ios/MullvadVPN/Notifications/Notification Providers/AccountExpiry.swift
@@ -0,0 +1,43 @@
+//
+// AccountExpiry.swift
+// MullvadVPN
+//
+// Created by Jon Petersson on 2023-11-08.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+struct AccountExpiry {
+ var expiryDate: Date?
+
+ var triggerDate: Date? {
+ guard let expiryDate else { return nil }
+
+ return Calendar.current.date(
+ byAdding: .day,
+ value: -NotificationConfiguration.closeToExpiryTriggerInterval,
+ to: expiryDate
+ )
+ }
+
+ var formattedDuration: String? {
+ let now = Date()
+
+ guard
+ let expiryDate,
+ let triggerDate,
+ let duration = CustomDateComponentsFormatting.localizedString(
+ from: now,
+ to: expiryDate,
+ unitsStyle: .full
+ ),
+ now >= triggerDate,
+ now < expiryDate
+ else {
+ return nil
+ }
+
+ return duration
+ }
+}
diff --git a/ios/MullvadVPN/Notifications/Notification Providers/AccountExpiryInAppNotificationProvider.swift b/ios/MullvadVPN/Notifications/Notification Providers/AccountExpiryInAppNotificationProvider.swift
index 4412fd5b3a..04658f0da9 100644
--- a/ios/MullvadVPN/Notifications/Notification Providers/AccountExpiryInAppNotificationProvider.swift
+++ b/ios/MullvadVPN/Notifications/Notification Providers/AccountExpiryInAppNotificationProvider.swift
@@ -11,7 +11,7 @@ import MullvadSettings
import MullvadTypes
final class AccountExpiryInAppNotificationProvider: NotificationProvider, InAppNotificationProvider {
- private var accountExpiry: Date?
+ private var accountExpiry = AccountExpiry()
private var tunnelObserver: TunnelBlockObserver?
private var timer: DispatchSourceTimer?
@@ -38,31 +38,10 @@ final class AccountExpiryInAppNotificationProvider: NotificationProvider, InAppN
// MARK: - InAppNotificationProvider
var notificationDescriptor: InAppNotificationDescriptor? {
- let now = Date()
- guard let accountExpiry, let triggerDate, now >= triggerDate,
- now < accountExpiry
- else {
+ guard let duration = accountExpiry.formattedDuration else {
return nil
}
- let formatter = DateComponentsFormatter()
- formatter.unitsStyle = .full
- formatter.allowedUnits = [.minute, .hour, .day]
- formatter.maximumUnitCount = 1
-
- let duration: String?
- if accountExpiry.timeIntervalSince(now) < .minutes(1) {
- duration = NSLocalizedString(
- "ACCOUNT_EXPIRY_INAPP_NOTIFICATION_LESS_THAN_ONE_MINUTE",
- value: "Less than a minute",
- comment: ""
- )
- } else {
- duration = formatter.string(from: now, to: accountExpiry)
- }
-
- guard let duration else { return nil }
-
return InAppNotificationDescriptor(
identifier: identifier,
style: .warning,
@@ -83,16 +62,6 @@ final class AccountExpiryInAppNotificationProvider: NotificationProvider, InAppN
// MARK: - Private
- private var triggerDate: Date? {
- guard let accountExpiry else { return nil }
-
- return Calendar.current.date(
- byAdding: .day,
- value: -NotificationConfiguration.closeToExpiryTriggerInterval,
- to: accountExpiry
- )
- }
-
private func invalidate(deviceState: DeviceState) {
updateExpiry(deviceState: deviceState)
updateTimer()
@@ -100,13 +69,13 @@ final class AccountExpiryInAppNotificationProvider: NotificationProvider, InAppN
}
private func updateExpiry(deviceState: DeviceState) {
- accountExpiry = deviceState.accountData?.expiry
+ accountExpiry.expiryDate = deviceState.accountData?.expiry
}
private func updateTimer() {
timer?.cancel()
- guard let triggerDate else {
+ guard let triggerDate = accountExpiry.triggerDate else {
return
}
@@ -127,7 +96,7 @@ final class AccountExpiryInAppNotificationProvider: NotificationProvider, InAppN
}
private func timerDidFire() {
- let shouldCancelTimer = accountExpiry.map { $0 <= Date() } ?? true
+ let shouldCancelTimer = accountExpiry.expiryDate.map { $0 <= Date() } ?? true
if shouldCancelTimer {
timer?.cancel()
diff --git a/ios/MullvadVPNTests/AccountExpiryTests.swift b/ios/MullvadVPNTests/AccountExpiryTests.swift
new file mode 100644
index 0000000000..cb06ab9278
--- /dev/null
+++ b/ios/MullvadVPNTests/AccountExpiryTests.swift
@@ -0,0 +1,48 @@
+//
+// AccountExpiryTests.swift
+// MullvadVPNTests
+//
+// Created by Jon Petersson on 2023-11-07.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import XCTest
+
+class AccountExpiryTests: XCTestCase {
+ func testNoDateReturnsNoDuration() {
+ let accountExpiry = AccountExpiry()
+ XCTAssertNil(accountExpiry.formattedDuration)
+ }
+
+ func testDateNowReturnsNoDuration() {
+ let accountExpiry = AccountExpiry(expiryDate: Date())
+ XCTAssertNil(accountExpiry.formattedDuration)
+ }
+
+ func testDateInPastReturnsNoDuration() {
+ let accountExpiry = AccountExpiry(expiryDate: Date().addingTimeInterval(-10))
+ XCTAssertNil(accountExpiry.formattedDuration)
+ }
+
+ func testDateWithinTriggerIntervalReturnsDuration() {
+ let date = Calendar.current.date(
+ byAdding: .day,
+ value: NotificationConfiguration.closeToExpiryTriggerInterval - 1,
+ to: Date()
+ )
+
+ let accountExpiry = AccountExpiry(expiryDate: date)
+ XCTAssertNotNil(accountExpiry.formattedDuration)
+ }
+
+ func testDateNotWithinTriggerIntervalReturnsNoDuration() {
+ let date = Calendar.current.date(
+ byAdding: .day,
+ value: NotificationConfiguration.closeToExpiryTriggerInterval + 1,
+ to: Date()
+ )
+
+ let accountExpiry = AccountExpiry(expiryDate: date)
+ XCTAssertNil(accountExpiry.formattedDuration)
+ }
+}
diff --git a/ios/MullvadVPNTests/CustomDateComponentsFormattingTests.swift b/ios/MullvadVPNTests/CustomDateComponentsFormattingTests.swift
index 58cb17d685..8c0a5a30c2 100644
--- a/ios/MullvadVPNTests/CustomDateComponentsFormattingTests.swift
+++ b/ios/MullvadVPNTests/CustomDateComponentsFormattingTests.swift
@@ -38,7 +38,7 @@ class CustomDateComponentsFormattingTests: XCTestCase {
unitsStyle: .full
)
- XCTAssertEqual(result, "0 days")
+ XCTAssertEqual(result, "Less than a day")
}
func testLessThanTwoYearsFormatting() throws {
@@ -71,7 +71,7 @@ class CustomDateComponentsFormattingTests: XCTestCase {
unitsStyle: .full
)
- XCTAssertEqual(result, "0 days")
+ XCTAssertEqual(result, "Less than a day")
}
private func makeDateRange(addingComponents dateComponents: DateComponents) -> (Date, Date) {