diff options
| author | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2025-02-26 14:58:25 +0100 |
|---|---|---|
| committer | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2025-02-26 14:58:25 +0100 |
| commit | 5ba686d1ac45509cba636d717bb6729865105cf3 (patch) | |
| tree | e21ad8d6ca30a8ca6a02313c9b4cc786370dc91e /android | |
| parent | 5025db74b34cfb3536c43f89f3407ffc0d97ae73 (diff) | |
| parent | 38c4791b414538962f6a8ab958aecd09414f33f6 (diff) | |
| download | mullvadvpn-5ba686d1ac45509cba636d717bb6729865105cf3.tar.xz mullvadvpn-5ba686d1ac45509cba636d717bb6729865105cf3.zip | |
Merge branch 'remove-jodatime-droid-898'
Diffstat (limited to 'android')
64 files changed, 244 insertions, 233 deletions
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 4a78a198a2..ec59d0a5d3 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -399,7 +399,6 @@ dependencies { implementation(libs.compose.destinations) ksp(libs.compose.destinations.ksp) - implementation(libs.jodatime) implementation(libs.kermit) implementation(libs.koin) implementation(libs.koin.android) diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index b72bed1d05..94c8f48bda 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -13,12 +13,6 @@ -keep class java.net.InetSocketAddress { *; } -keep class java.util.ArrayList { *; } -# Joda Time --dontwarn org.joda.convert.** --dontwarn org.joda.time.** --keep class org.joda.time.** { *; } --keep interface org.joda.time.** { *; } - # grpc -keep class io.grpc.okhttp.OkHttpChannelBuilder { *; } -keep class mullvad_daemon.management_interface.** { *; } diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt index 0cdc8b8fe7..f1a81d4d91 100644 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt @@ -10,6 +10,9 @@ import io.mockk.every import io.mockk.mockk import io.mockk.unmockkAll import io.mockk.verify +import java.time.Duration +import java.time.Instant +import java.time.ZonedDateTime import net.mullvad.mullvadvpn.compose.createEdgeToEdgeComposeExtension import net.mullvad.mullvadvpn.compose.setContentWithTheme import net.mullvad.mullvadvpn.compose.state.ConnectUiState @@ -30,8 +33,6 @@ import net.mullvad.mullvadvpn.lib.model.TunnelEndpoint import net.mullvad.mullvadvpn.lib.model.TunnelState import net.mullvad.mullvadvpn.repository.InAppNotification import net.mullvad.mullvadvpn.ui.VersionInfo -import org.joda.time.DateTime -import org.joda.time.Duration import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -556,7 +557,7 @@ class ConnectScreenTest { fun testAccountExpiredNotification() { composeExtension.use { // Arrange - val expiryDate = DateTime(2020, 11, 11, 10, 10) + val expiryDate = ZonedDateTime.parse("2020-11-11T10:10Z") initScreen( state = ConnectUiState( @@ -567,7 +568,9 @@ class ConnectScreenTest { deviceName = "", daysLeftUntilExpiry = null, inAppNotification = - InAppNotification.AccountExpiry(Duration(DateTime.now(), expiryDate)), + InAppNotification.AccountExpiry( + Duration.between(Instant.now(), expiryDate) + ), isPlayBuild = false, ) ) @@ -612,7 +615,7 @@ class ConnectScreenTest { composeExtension.use { // Arrange val mockedClickHandler: () -> Unit = mockk(relaxed = true) - val expiryDate = DateTime(2020, 11, 11, 10, 10) + val expiryDate = ZonedDateTime.parse("2020-11-11T10:10Z") initScreen( onManageAccountClick = mockedClickHandler, state = @@ -624,7 +627,9 @@ class ConnectScreenTest { deviceName = "", daysLeftUntilExpiry = null, inAppNotification = - InAppNotification.AccountExpiry(Duration(DateTime.now(), expiryDate)), + InAppNotification.AccountExpiry( + Duration.between(Instant.now(), expiryDate) + ), isPlayBuild = false, ), ) diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/Scaffolding.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/Scaffolding.kt index 3d591bafba..29fa7415bb 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/Scaffolding.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/Scaffolding.kt @@ -78,7 +78,7 @@ fun ScaffoldWithTopBarAndDeviceName( isIconAndLogoVisible: Boolean = true, snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }, deviceName: String?, - timeLeft: Int?, + timeLeft: Long?, content: @Composable (PaddingValues) -> Unit, ) { Scaffold( diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/TopBar.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/TopBar.kt index 91cb49ae53..e7a58d998b 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/TopBar.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/TopBar.kt @@ -332,7 +332,7 @@ fun MullvadTopBarWithDeviceName( iconTintColor: Color, isIconAndLogoVisible: Boolean = true, deviceName: String?, - daysLeftUntilExpiry: Int?, + daysLeftUntilExpiry: Long?, ) { Column { MullvadTopBar( @@ -383,8 +383,8 @@ fun MullvadTopBarWithDeviceName( if (daysLeftUntilExpiry >= 0) { pluralStringResource( id = R.plurals.days, - daysLeftUntilExpiry, - daysLeftUntilExpiry, + daysLeftUntilExpiry.toInt(), + daysLeftUntilExpiry.toInt(), ) } else { stringResource(id = R.string.out_of_time) diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/notificationbanner/NotificationBanner.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/notificationbanner/NotificationBanner.kt index f15a054b7d..4f527a94c5 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/notificationbanner/NotificationBanner.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/notificationbanner/NotificationBanner.kt @@ -30,6 +30,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension +import java.time.Duration import net.mullvad.mullvadvpn.compose.component.MullvadTopBar import net.mullvad.mullvadvpn.compose.test.NOTIFICATION_BANNER import net.mullvad.mullvadvpn.compose.test.NOTIFICATION_BANNER_ACTION @@ -43,7 +44,6 @@ import net.mullvad.mullvadvpn.lib.theme.color.warning import net.mullvad.mullvadvpn.repository.InAppNotification import net.mullvad.mullvadvpn.ui.VersionInfo import net.mullvad.mullvadvpn.ui.notification.StatusLevel -import org.joda.time.Duration @Preview @Composable diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/RedeemVoucherDialog.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/RedeemVoucherDialog.kt index a9859c318a..93b29e5185 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/RedeemVoucherDialog.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/RedeemVoucherDialog.kt @@ -29,6 +29,7 @@ import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.result.ResultBackNavigator import com.ramcosta.composedestinations.spec.DestinationStyle +import java.util.concurrent.TimeUnit import net.mullvad.mullvadvpn.BuildConfig import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.compose.button.PrimaryButton @@ -46,7 +47,6 @@ import net.mullvad.mullvadvpn.lib.model.RedeemVoucherError import net.mullvad.mullvadvpn.lib.theme.AppTheme import net.mullvad.mullvadvpn.lib.theme.Dimens import net.mullvad.mullvadvpn.viewmodel.VoucherDialogViewModel -import org.joda.time.DateTimeConstants import org.koin.androidx.compose.koinViewModel @Preview(device = Devices.TV_720p) @@ -157,9 +157,10 @@ fun RedeemVoucherDialog( modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, ) { + TimeUnit.DAYS.toSeconds(1) if (state.voucherState is VoucherDialogState.Success) { val days: Int = - (state.voucherState.addedTime / DateTimeConstants.SECONDS_PER_DAY).toInt() + (state.voucherState.addedTime / TimeUnit.DAYS.toSeconds(1)).toInt() val message = stringResource( R.string.added_to_your_account, diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/extensions/ResourcesExtensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/extensions/ResourcesExtensions.kt index 9611897e76..28459c9a5d 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/extensions/ResourcesExtensions.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/extensions/ResourcesExtensions.kt @@ -1,16 +1,16 @@ package net.mullvad.mullvadvpn.compose.extensions import android.content.res.Resources +import java.time.Duration import net.mullvad.mullvadvpn.R -import org.joda.time.Duration private const val DAYS_IN_STANDARD_YEAR = 365 fun Resources.getExpiryQuantityString(accountExpiry: Duration): String { - val days = accountExpiry.standardDays.toInt() - val years = (accountExpiry.standardDays / DAYS_IN_STANDARD_YEAR).toInt() + val days = accountExpiry.toDays().toInt() + val years = (accountExpiry.toDays() / DAYS_IN_STANDARD_YEAR).toInt() - return if (accountExpiry.millis <= 0) { + return if (accountExpiry.toMillis() <= 0) { getString(R.string.out_of_time) } else if (years > 1) { getRemainingText(this, R.plurals.years_left, years) diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/AccountUiStatePreviewParameterProvider.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/AccountUiStatePreviewParameterProvider.kt index c15e8b2a53..f40d0697ab 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/AccountUiStatePreviewParameterProvider.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/AccountUiStatePreviewParameterProvider.kt @@ -1,6 +1,8 @@ package net.mullvad.mullvadvpn.compose.preview import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter import net.mullvad.mullvadvpn.compose.state.PaymentState import net.mullvad.mullvadvpn.lib.model.AccountNumber import net.mullvad.mullvadvpn.lib.payment.model.PaymentProduct @@ -8,7 +10,6 @@ import net.mullvad.mullvadvpn.lib.payment.model.PaymentStatus import net.mullvad.mullvadvpn.lib.payment.model.ProductId import net.mullvad.mullvadvpn.lib.payment.model.ProductPrice import net.mullvad.mullvadvpn.viewmodel.AccountUiState -import org.joda.time.DateTime class AccountUiStatePreviewParameterProvider : PreviewParameterProvider<AccountUiState> { override val values = @@ -16,7 +17,11 @@ class AccountUiStatePreviewParameterProvider : PreviewParameterProvider<AccountU AccountUiState( deviceName = "Test Name", accountNumber = AccountNumber("1234123412341234"), - accountExpiry = DateTime.parse("2050-12-01"), + accountExpiry = + ZonedDateTime.parse( + "2050-12-01T00:00:00.000Z", + DateTimeFormatter.ISO_ZONED_DATE_TIME, + ), showSitePayment = true, billingPaymentState = PaymentState.PaymentAvailable( diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/DevicePreviewData.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/DevicePreviewData.kt index b87974d18b..82bf05ba64 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/DevicePreviewData.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/DevicePreviewData.kt @@ -1,9 +1,10 @@ package net.mullvad.mullvadvpn.compose.preview +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter import net.mullvad.mullvadvpn.compose.state.DeviceItemUiState import net.mullvad.mullvadvpn.lib.model.Device import net.mullvad.mullvadvpn.lib.model.DeviceId -import org.joda.time.DateTime internal object DevicePreviewData { fun generateDevices(count: Int) = @@ -16,10 +17,11 @@ internal object DevicePreviewData { Device( id = DeviceId.fromString(id), name = name ?: "Device $index-${id.take(DEVICE_SUFFIX_LENGTH)}", - creationDate = DEVICE_CREATION_DATE.plusMonths(index), + creationDate = DEVICE_CREATION_DATE.plusMonths(index.toLong()), ) } private const val DEVICE_SUFFIX_LENGTH = 4 private const val UUID = "12345678-1234-5678-1234-567812345678" -private val DEVICE_CREATION_DATE = DateTime.parse("2024-05-27") +private val DEVICE_CREATION_DATE = + ZonedDateTime.parse("2024-05-27T00:00+00:00", DateTimeFormatter.ISO_ZONED_DATE_TIME) diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreen.kt index ddfb535faf..bd3d8b8bc5 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreen.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreen.kt @@ -38,6 +38,7 @@ import com.ramcosta.composedestinations.generated.destinations.VerificationPendi import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.result.NavResult import com.ramcosta.composedestinations.result.ResultRecipient +import java.time.ZonedDateTime import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.compose.button.ExternalButton @@ -57,13 +58,12 @@ import net.mullvad.mullvadvpn.compose.util.CollectSideEffectWithLifecycle import net.mullvad.mullvadvpn.compose.util.SecureScreenWhileInView import net.mullvad.mullvadvpn.compose.util.createCopyToClipboardHandle import net.mullvad.mullvadvpn.compose.util.showSnackbarImmediately +import net.mullvad.mullvadvpn.lib.common.util.toExpiryDateString import net.mullvad.mullvadvpn.lib.payment.model.ProductId import net.mullvad.mullvadvpn.lib.theme.AppTheme import net.mullvad.mullvadvpn.lib.theme.Dimens -import net.mullvad.mullvadvpn.util.toExpiryDateString import net.mullvad.mullvadvpn.viewmodel.AccountUiState import net.mullvad.mullvadvpn.viewmodel.AccountViewModel -import org.joda.time.DateTime import org.koin.androidx.compose.koinViewModel @OptIn(ExperimentalMaterial3Api::class) @@ -256,7 +256,7 @@ private fun AccountNumberRow(accountNumber: String, onCopyAccountNumber: (String } @Composable -private fun PaidUntilRow(accountExpiry: DateTime?) { +private fun PaidUntilRow(accountExpiry: ZonedDateTime?) { Column(modifier = Modifier.fillMaxWidth()) { Text( style = MaterialTheme.typography.labelMedium, diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceListScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceListScreen.kt index 34ac8d98b7..0a9504f46c 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceListScreen.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceListScreen.kt @@ -56,6 +56,7 @@ import net.mullvad.mullvadvpn.compose.state.DeviceListUiState import net.mullvad.mullvadvpn.compose.transitions.DefaultTransition import net.mullvad.mullvadvpn.compose.util.CollectSideEffectWithLifecycle import net.mullvad.mullvadvpn.compose.util.showSnackbarImmediately +import net.mullvad.mullvadvpn.lib.common.util.formatDate import net.mullvad.mullvadvpn.lib.model.AccountNumber import net.mullvad.mullvadvpn.lib.model.Device import net.mullvad.mullvadvpn.lib.model.DeviceId @@ -64,7 +65,6 @@ import net.mullvad.mullvadvpn.lib.theme.Dimens import net.mullvad.mullvadvpn.lib.theme.color.selected import net.mullvad.mullvadvpn.lib.theme.typeface.listItemSubText import net.mullvad.mullvadvpn.lib.theme.typeface.listItemText -import net.mullvad.mullvadvpn.util.formatDate import net.mullvad.mullvadvpn.viewmodel.DeviceListSideEffect import net.mullvad.mullvadvpn.viewmodel.DeviceListViewModel import org.koin.androidx.compose.koinViewModel diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/ConnectUiState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/ConnectUiState.kt index 98b5219785..4e17b6918b 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/ConnectUiState.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/ConnectUiState.kt @@ -11,7 +11,7 @@ data class ConnectUiState( val showLocation: Boolean, val inAppNotification: InAppNotification?, val deviceName: String?, - val daysLeftUntilExpiry: Int?, + val daysLeftUntilExpiry: Long?, val isPlayBuild: Boolean, ) { diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/provider/MullvadFileProvider.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/provider/MullvadFileProvider.kt index 729fe5f2c2..b070f7c646 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/provider/MullvadFileProvider.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/provider/MullvadFileProvider.kt @@ -5,9 +5,9 @@ import android.content.Intent import android.net.Uri import androidx.core.content.FileProvider import java.io.File +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter import net.mullvad.mullvadvpn.R -import org.joda.time.DateTime -import org.joda.time.format.ISODateTimeFormat // https://developer.android.com/reference/androidx/core/content/FileProvider // From link: It is possible to use FileProvider directly instead of extending it. However, this is @@ -51,6 +51,6 @@ fun Context.createCacheFile(directory: ProviderCacheDirectory, fileName: String) } fun createShareLogFileName(): String { - val datetime = ISODateTimeFormat.basicOrdinalDateTimeNoMillis().print(DateTime.now()) + val datetime = DateTimeFormatter.ofPattern("yyyyDDD'T'HHmmssZ").format(ZonedDateTime.now()) return "mullvad_log-${datetime}.txt" } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/InAppNotificationController.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/InAppNotificationController.kt index 0fcee60bed..0e3e004f0b 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/InAppNotificationController.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/InAppNotificationController.kt @@ -1,5 +1,6 @@ package net.mullvad.mullvadvpn.repository +import java.time.Duration import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine @@ -12,7 +13,6 @@ import net.mullvad.mullvadvpn.usecase.NewChangelogNotificationUseCase import net.mullvad.mullvadvpn.usecase.NewDeviceNotificationUseCase import net.mullvad.mullvadvpn.usecase.TunnelStateNotificationUseCase import net.mullvad.mullvadvpn.usecase.VersionNotificationUseCase -import org.joda.time.Duration enum class StatusLevel { Error, diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCase.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCase.kt index 8fd97dc63d..081c632255 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCase.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCase.kt @@ -14,11 +14,11 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn +import net.mullvad.mullvadvpn.lib.common.util.millisFromNow import net.mullvad.mullvadvpn.lib.model.ErrorStateCause import net.mullvad.mullvadvpn.lib.model.TunnelState import net.mullvad.mullvadvpn.lib.shared.AccountRepository import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy -import org.joda.time.DateTime class OutOfTimeUseCase( private val connectionProxy: ConnectionProxy, @@ -60,7 +60,7 @@ class OutOfTimeUseCase( .flatMapLatest { if (it != null) { flow { - val millisUntilExpiry = it.expiryDate.millis - DateTime.now().millis + val millisUntilExpiry = it.expiryDate.millisFromNow() if (millisUntilExpiry > 0) { emit(false) delay(millisUntilExpiry) diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/DateExtensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/DateExtensions.kt deleted file mode 100644 index e11434257a..0000000000 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/DateExtensions.kt +++ /dev/null @@ -1,15 +0,0 @@ -package net.mullvad.mullvadvpn.util - -import java.text.DateFormat -import kotlin.time.Duration.Companion.milliseconds -import kotlin.time.DurationUnit -import org.joda.time.DateTime -import org.joda.time.format.ISODateTimeFormat - -fun DateTime.formatDate(): String = ISODateTimeFormat.date().print(this) - -fun DateTime.toExpiryDateString(): String = - DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT).format(this.toDate()) - -fun DateTime.daysFromNow() = - (toInstant().millis - DateTime.now().toInstant().millis).milliseconds.toInt(DurationUnit.DAYS) diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModel.kt index 8297375ee3..8dd4253553 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModel.kt @@ -3,6 +3,7 @@ package net.mullvad.mullvadvpn.viewmodel import android.app.Activity import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import java.time.ZonedDateTime import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -27,7 +28,6 @@ import net.mullvad.mullvadvpn.lib.shared.DeviceRepository import net.mullvad.mullvadvpn.usecase.PaymentUseCase import net.mullvad.mullvadvpn.util.isSuccess import net.mullvad.mullvadvpn.util.toPaymentState -import org.joda.time.DateTime class AccountViewModel( private val accountRepository: AccountRepository, @@ -155,7 +155,7 @@ class AccountViewModel( data class AccountUiState( val deviceName: String?, val accountNumber: AccountNumber?, - val accountExpiry: DateTime?, + val accountExpiry: ZonedDateTime?, val showSitePayment: Boolean, val billingPaymentState: PaymentState? = null, val showLogoutLoading: Boolean = false, diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt index 6e8b3f9009..d505c44179 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt @@ -17,6 +17,7 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.compose.state.ConnectUiState +import net.mullvad.mullvadvpn.lib.common.util.daysFromNow import net.mullvad.mullvadvpn.lib.model.ActionAfterDisconnect import net.mullvad.mullvadvpn.lib.model.ConnectError import net.mullvad.mullvadvpn.lib.model.DeviceState @@ -34,7 +35,6 @@ import net.mullvad.mullvadvpn.usecase.OutOfTimeUseCase import net.mullvad.mullvadvpn.usecase.PaymentUseCase import net.mullvad.mullvadvpn.usecase.SelectedLocationTitleUseCase import net.mullvad.mullvadvpn.util.combine -import net.mullvad.mullvadvpn.util.daysFromNow import net.mullvad.mullvadvpn.util.isSuccess import net.mullvad.mullvadvpn.util.withPrev diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModel.kt index 25a8a47da3..088b5f3ae2 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModel.kt @@ -24,6 +24,7 @@ import net.mullvad.mullvadvpn.compose.state.LoginState.Idle import net.mullvad.mullvadvpn.compose.state.LoginState.Loading import net.mullvad.mullvadvpn.compose.state.LoginState.Success import net.mullvad.mullvadvpn.compose.state.LoginUiState +import net.mullvad.mullvadvpn.lib.common.util.isBeforeNowInstant import net.mullvad.mullvadvpn.lib.model.AccountNumber import net.mullvad.mullvadvpn.lib.model.LoginAccountError import net.mullvad.mullvadvpn.lib.shared.AccountRepository @@ -137,7 +138,9 @@ class LoginViewModel( viewModelScope.launch(dispatcher) { // Find if user is out of time val isOutOfTimeDeferred = async { - accountRepository.accountData.mapNotNull { it?.expiryDate?.isBeforeNow }.first() + accountRepository.accountData + .mapNotNull { it?.expiryDate?.isBeforeNowInstant() } + .first() } // Always show successful login for some time. diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SplashViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SplashViewModel.kt index 0ed85c94cd..fe0acd83c1 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SplashViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SplashViewModel.kt @@ -13,6 +13,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.selects.onTimeout import kotlinx.coroutines.selects.select import net.mullvad.mullvadvpn.constant.ACCOUNT_EXPIRY_TIMEOUT_MS +import net.mullvad.mullvadvpn.lib.common.util.isBeforeNowInstant import net.mullvad.mullvadvpn.lib.model.DeviceState import net.mullvad.mullvadvpn.lib.shared.AccountRepository import net.mullvad.mullvadvpn.lib.shared.DeviceRepository @@ -72,7 +73,7 @@ class SplashViewModel( onTimeout(ACCOUNT_EXPIRY_TIMEOUT_MS) { null } } - return if (accountData != null && accountData.expiryDate.isBeforeNow) { + return if (accountData != null && accountData.expiryDate.isBeforeNowInstant()) { SplashUiSideEffect.NavigateToOutOfTime } else { SplashUiSideEffect.NavigateToConnect diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModel.kt index 67e0f7a54d..0e91390262 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModel.kt @@ -15,6 +15,7 @@ import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.compose.state.WelcomeUiState +import net.mullvad.mullvadvpn.lib.common.util.isAfterNowInstant import net.mullvad.mullvadvpn.lib.model.WebsiteAuthToken import net.mullvad.mullvadvpn.lib.shared.AccountRepository import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy @@ -66,7 +67,7 @@ class WelcomeViewModel( private fun hasAddedTimeEffect() = accountRepository.accountData .filterNotNull() - .filter { it.expiryDate.minusHours(MIN_HOURS_PAST_ACCOUNT_EXPIRY).isAfterNow } + .filter { it.expiryDate.minusHours(MIN_HOURS_PAST_ACCOUNT_EXPIRY).isAfterNowInstant() } .onEach { paymentUseCase.resetPurchaseResult() } .map { UiSideEffect.OpenConnectScreen } @@ -125,6 +126,6 @@ class WelcomeViewModel( } companion object { - private const val MIN_HOURS_PAST_ACCOUNT_EXPIRY = 20 + private const val MIN_HOURS_PAST_ACCOUNT_EXPIRY: Long = 20 } } diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/AccountExpiryNotificationProviderTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/AccountExpiryNotificationProviderTest.kt index d830f407ab..9a3672515d 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/AccountExpiryNotificationProviderTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/AccountExpiryNotificationProviderTest.kt @@ -5,6 +5,8 @@ import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.mockk import io.mockk.unmockkAll +import java.time.Duration +import java.time.ZonedDateTime import kotlin.test.assertEquals import kotlin.test.assertTrue import kotlin.time.Duration.Companion.minutes @@ -25,8 +27,6 @@ import net.mullvad.mullvadvpn.lib.shared.AccountRepository import net.mullvad.mullvadvpn.lib.shared.DeviceRepository import net.mullvad.mullvadvpn.service.notifications.accountexpiry.ACCOUNT_EXPIRY_CLOSE_TO_EXPIRY_THRESHOLD import net.mullvad.mullvadvpn.service.notifications.accountexpiry.AccountExpiryNotificationProvider -import org.joda.time.DateTime -import org.joda.time.Duration import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -80,7 +80,9 @@ class AccountExpiryNotificationProviderTest { @Test fun `should emit notification if expiry time is shorter than expiry warning threshold`() = runTest { - setExpiry(DateTime.now().plus(ACCOUNT_EXPIRY_CLOSE_TO_EXPIRY_THRESHOLD).minusDays(1)) + setExpiry( + ZonedDateTime.now().plus(ACCOUNT_EXPIRY_CLOSE_TO_EXPIRY_THRESHOLD).minusDays(1) + ) provider.notifications.test { assertTrue(awaitItem() is Notify) expectNoEvents() @@ -90,7 +92,7 @@ class AccountExpiryNotificationProviderTest { @Test fun `should emit cancel notification if user account is new`() = runTest { isNewDevice.value = true - setExpiry(DateTime.now().plus(ACCOUNT_EXPIRY_CLOSE_TO_EXPIRY_THRESHOLD).minusDays(1)) + setExpiry(ZonedDateTime.now().plus(ACCOUNT_EXPIRY_CLOSE_TO_EXPIRY_THRESHOLD).minusDays(1)) provider.notifications.test { assertTrue(awaitItem() is Cancel) expectNoEvents() @@ -100,7 +102,7 @@ class AccountExpiryNotificationProviderTest { @Test fun `should emit cancel notification if user account is logged out`() = runTest { setIsLoggedIn(false) - setExpiry(DateTime.now().plus(ACCOUNT_EXPIRY_CLOSE_TO_EXPIRY_THRESHOLD).minusDays(1)) + setExpiry(ZonedDateTime.now().plus(ACCOUNT_EXPIRY_CLOSE_TO_EXPIRY_THRESHOLD).minusDays(1)) provider.notifications.test { assertTrue(awaitItem() is Cancel) expectNoEvents() @@ -117,7 +119,7 @@ class AccountExpiryNotificationProviderTest { @Test fun `should emit zero duration notification when remaining time runs out`() = runTest { - setExpiry(DateTime.now().plus(Duration.standardSeconds(60))) + setExpiry(ZonedDateTime.now().plus(Duration.ofSeconds(60))) provider.notifications.test { assertTrue(awaitItem() is Notify) expectNoEvents() @@ -135,7 +137,10 @@ class AccountExpiryNotificationProviderTest { @Test fun `should emit notification when update interval is passed`() = runTest { setExpiry( - DateTime.now().plus(ACCOUNT_EXPIRY_CLOSE_TO_EXPIRY_THRESHOLD).minusDays(1).plusHours(1) + ZonedDateTime.now() + .plus(ACCOUNT_EXPIRY_CLOSE_TO_EXPIRY_THRESHOLD) + .minusDays(1) + .plusHours(1) ) provider.notifications.test { assertTrue(awaitItem() is Notify) @@ -152,12 +157,14 @@ class AccountExpiryNotificationProviderTest { @Test fun `should cancel existing notification if more time is added to account`() = runTest { - setExpiry(DateTime.now().plus(ACCOUNT_EXPIRY_CLOSE_TO_EXPIRY_THRESHOLD).minusDays(1)) + setExpiry(ZonedDateTime.now().plus(ACCOUNT_EXPIRY_CLOSE_TO_EXPIRY_THRESHOLD).minusDays(1)) provider.notifications.test { assertTrue(awaitItem() is Notify) expectNoEvents() - setExpiry(DateTime.now().plus(ACCOUNT_EXPIRY_CLOSE_TO_EXPIRY_THRESHOLD).plusDays(1)) + setExpiry( + ZonedDateTime.now().plus(ACCOUNT_EXPIRY_CLOSE_TO_EXPIRY_THRESHOLD).plusDays(1) + ) assertTrue(awaitItem() is Cancel) expectNoEvents() } @@ -165,12 +172,14 @@ class AccountExpiryNotificationProviderTest { @Test fun `should not cancel existing notification if too little time is added`() = runTest { - setExpiry(DateTime.now().plus(ACCOUNT_EXPIRY_CLOSE_TO_EXPIRY_THRESHOLD).minusDays(1)) + setExpiry(ZonedDateTime.now().plus(ACCOUNT_EXPIRY_CLOSE_TO_EXPIRY_THRESHOLD).minusDays(1)) provider.notifications.test { assertTrue(awaitItem() is Notify) expectNoEvents() - setExpiry(DateTime.now().plus(ACCOUNT_EXPIRY_CLOSE_TO_EXPIRY_THRESHOLD).minusHours(1)) + setExpiry( + ZonedDateTime.now().plus(ACCOUNT_EXPIRY_CLOSE_TO_EXPIRY_THRESHOLD).minusHours(1) + ) assertTrue(awaitItem() is Notify) expectNoEvents() } @@ -184,7 +193,7 @@ class AccountExpiryNotificationProviderTest { is Notify -> awaitItem.value } - private fun setExpiry(expiryDateTime: DateTime): DateTime { + private fun setExpiry(expiryDateTime: ZonedDateTime): ZonedDateTime { val expiry = AccountData(mockk(relaxed = true), expiryDateTime) accountData.value = expiry return expiryDateTime diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/InAppNotificationControllerTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/InAppNotificationControllerTest.kt index c8b27f2e6f..9db14ad914 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/InAppNotificationControllerTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/InAppNotificationControllerTest.kt @@ -5,6 +5,7 @@ import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.mockk import io.mockk.unmockkAll +import java.time.Duration import kotlin.test.assertEquals import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -21,7 +22,6 @@ import net.mullvad.mullvadvpn.usecase.NewChangelogNotificationUseCase import net.mullvad.mullvadvpn.usecase.NewDeviceNotificationUseCase import net.mullvad.mullvadvpn.usecase.TunnelStateNotificationUseCase import net.mullvad.mullvadvpn.usecase.VersionNotificationUseCase -import org.joda.time.Duration import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryInAppNotificationUseCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryInAppNotificationUseCaseTest.kt index 316d12addd..68b29790ac 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryInAppNotificationUseCaseTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryInAppNotificationUseCaseTest.kt @@ -7,6 +7,8 @@ import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.mockk import io.mockk.unmockkAll +import java.time.Duration +import java.time.ZonedDateTime import kotlin.test.assertEquals import kotlin.test.assertTrue import kotlin.time.Duration.Companion.seconds @@ -19,8 +21,6 @@ import net.mullvad.mullvadvpn.lib.shared.AccountRepository import net.mullvad.mullvadvpn.repository.InAppNotification import net.mullvad.mullvadvpn.service.notifications.accountexpiry.ACCOUNT_EXPIRY_CLOSE_TO_EXPIRY_THRESHOLD import net.mullvad.mullvadvpn.service.notifications.accountexpiry.ACCOUNT_EXPIRY_IN_APP_NOTIFICATION_UPDATE_INTERVAL -import org.joda.time.DateTime -import org.joda.time.Duration import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -33,7 +33,7 @@ class AccountExpiryInAppNotificationUseCaseTest { private lateinit var accountExpiryInAppNotificationUseCase: AccountExpiryInAppNotificationUseCase - private lateinit var notificationThreshold: DateTime + private lateinit var notificationThreshold: ZonedDateTime @BeforeEach fun setup() { @@ -45,7 +45,7 @@ class AccountExpiryInAppNotificationUseCaseTest { accountExpiryInAppNotificationUseCase = AccountExpiryInAppNotificationUseCase(accountRepository) - notificationThreshold = DateTime.now().plus(ACCOUNT_EXPIRY_CLOSE_TO_EXPIRY_THRESHOLD) + notificationThreshold = ZonedDateTime.now().plus(ACCOUNT_EXPIRY_CLOSE_TO_EXPIRY_THRESHOLD) } @AfterEach @@ -102,7 +102,7 @@ class AccountExpiryInAppNotificationUseCaseTest { // Set expiry to to be in the final update interval. val inLastUpdate = - DateTime.now() + ZonedDateTime.now() .plus(ACCOUNT_EXPIRY_IN_APP_NOTIFICATION_UPDATE_INTERVAL) .minusSeconds(1) val expiry = setExpiry(inLastUpdate) @@ -113,34 +113,37 @@ class AccountExpiryInAppNotificationUseCaseTest { expectNoEvents() // Advance past the delay before the while loop: - advanceTimeBy(ACCOUNT_EXPIRY_IN_APP_NOTIFICATION_UPDATE_INTERVAL.millis) + advanceTimeBy(ACCOUNT_EXPIRY_IN_APP_NOTIFICATION_UPDATE_INTERVAL.toMillis()) // Advance past the delay after the while loop: - advanceTimeBy(ACCOUNT_EXPIRY_IN_APP_NOTIFICATION_UPDATE_INTERVAL.millis) + advanceTimeBy(ACCOUNT_EXPIRY_IN_APP_NOTIFICATION_UPDATE_INTERVAL.toMillis()) assertEquals(Duration.ZERO, getExpiryNotificationDuration(expectMostRecentItem())) expectNoEvents() // Make sure we reset the list of notifications emitted when new time is added - setExpiry(DateTime.now().plus(ACCOUNT_EXPIRY_CLOSE_TO_EXPIRY_THRESHOLD).plusDays(1)) + setExpiry( + ZonedDateTime.now().plus(ACCOUNT_EXPIRY_CLOSE_TO_EXPIRY_THRESHOLD).plusDays(1) + ) assertEquals(emptyList(), expectMostRecentItem()) } } - private fun setExpiry(expiryDateTime: DateTime): DateTime { + private fun setExpiry(expiryDateTime: ZonedDateTime): ZonedDateTime { val expiry = AccountData(mockk(relaxed = true), expiryDateTime) accountExpiry.value = expiry return expiryDateTime } // Assert that we got a single AccountExpiry notification and that the expiry duration is within - // the expected range (checking exact duration value is not possible since we use DateTime.now) + // the expected range (checking exact duration value is not possible since we use + // ZonedDateTime.now) private fun assertExpiryNotificationDuration( - expiry: DateTime, + expiry: ZonedDateTime, notifications: List<InAppNotification>, ) { val notificationDuration = getExpiryNotificationDuration(notifications) - val expiresFromNow = Duration(DateTime.now(), expiry) + val expiresFromNow = Duration.between(ZonedDateTime.now(), expiry) assertTrue(expiresFromNow <= notificationDuration) - assertTrue(expiresFromNow.plus(Duration.standardSeconds(5)) > notificationDuration) + assertTrue(expiresFromNow.plus(Duration.ofSeconds(5)) > notificationDuration) } private fun getExpiryNotificationDuration(notifications: List<InAppNotification>): Duration { diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/NewDeviceNotificationUseCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/NewDeviceNotificationUseCaseTest.kt index 32cc48f2c5..2c97ea36a1 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/NewDeviceNotificationUseCaseTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/NewDeviceNotificationUseCaseTest.kt @@ -5,6 +5,7 @@ import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.unmockkAll +import java.time.ZonedDateTime import kotlin.test.assertEquals import kotlin.test.assertTrue import kotlinx.coroutines.flow.MutableStateFlow @@ -18,7 +19,6 @@ import net.mullvad.mullvadvpn.lib.model.DeviceState import net.mullvad.mullvadvpn.lib.shared.DeviceRepository import net.mullvad.mullvadvpn.repository.InAppNotification import net.mullvad.mullvadvpn.repository.NewDeviceRepository -import org.joda.time.DateTime import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -35,7 +35,7 @@ class NewDeviceNotificationUseCaseTest { Device( id = DeviceId.fromString(UUID), name = deviceName, - creationDate = DateTime.now(), + creationDate = ZonedDateTime.now(), ), ) ) diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCaseTest.kt index f8ecefbffd..4fd3ba08d1 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCaseTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCaseTest.kt @@ -4,6 +4,7 @@ import app.cash.turbine.test import io.mockk.every import io.mockk.mockk import io.mockk.unmockkAll +import java.time.ZonedDateTime import kotlin.test.assertEquals import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.minutes @@ -26,7 +27,6 @@ import net.mullvad.mullvadvpn.lib.model.ErrorStateCause import net.mullvad.mullvadvpn.lib.model.TunnelState import net.mullvad.mullvadvpn.lib.shared.AccountRepository import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy -import org.joda.time.DateTime import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -91,7 +91,7 @@ class OutOfTimeUseCaseTest { scope.runTest { // Arrange val expiredAccountExpiry = - AccountData(mockk(relaxed = true), DateTime.now().plusDays(1)) + AccountData(mockk(relaxed = true), ZonedDateTime.now().plusDays(1)) val tunnelStateChanges = listOf( TunnelState.Disconnected(), @@ -120,7 +120,7 @@ class OutOfTimeUseCaseTest { scope.runTest { // Arrange val expiredAccountExpiry = - AccountData(mockk(relaxed = true), DateTime.now().minusDays(1)) + AccountData(mockk(relaxed = true), ZonedDateTime.now().minusDays(1)) // Act, Assert outOfTimeUseCase.isOutOfTime.test { assertEquals(null, awaitItem()) @@ -134,7 +134,7 @@ class OutOfTimeUseCaseTest { scope.runTest { // Arrange val notExpiredAccountExpiry = - AccountData(mockk(relaxed = true), DateTime.now().plusDays(1)) + AccountData(mockk(relaxed = true), ZonedDateTime.now().plusDays(1)) // Act, Assert outOfTimeUseCase.isOutOfTime.test { @@ -149,7 +149,7 @@ class OutOfTimeUseCaseTest { scope.runTest { // Arrange val expiredAccountExpiry = - AccountData(mockk(relaxed = true), DateTime.now().plusSeconds(100)) + AccountData(mockk(relaxed = true), ZonedDateTime.now().plusSeconds(100)) // Act, Assert outOfTimeUseCase.isOutOfTime.test { // Initial event @@ -173,7 +173,7 @@ class OutOfTimeUseCaseTest { scope.runTest { // Arrange val initialAccountExpiry = - AccountData(mockk(relaxed = true), DateTime.now().plusSeconds(100)) + AccountData(mockk(relaxed = true), ZonedDateTime.now().plusSeconds(100)) val updatedExpiry = AccountData(mockk(relaxed = true), initialAccountExpiry.expiryDate.plusDays(30)) @@ -203,7 +203,7 @@ class OutOfTimeUseCaseTest { scope.runTest { // Arrange val initialAccountExpiry = - AccountData(mockk(relaxed = true), DateTime.now().plusSeconds(100)) + AccountData(mockk(relaxed = true), ZonedDateTime.now().plusSeconds(100)) val updatedExpiry = AccountData(mockk(relaxed = true), initialAccountExpiry.expiryDate.plusDays(30)) // Act, Assert diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModelTest.kt index 0a9d2f36f9..74a6bbd414 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModelTest.kt @@ -9,6 +9,7 @@ import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic import io.mockk.unmockkAll +import java.time.ZonedDateTime import kotlin.test.assertEquals import kotlin.test.assertIs import kotlinx.coroutines.flow.MutableStateFlow @@ -28,7 +29,6 @@ import net.mullvad.mullvadvpn.lib.payment.model.PurchaseResult import net.mullvad.mullvadvpn.lib.shared.AccountRepository import net.mullvad.mullvadvpn.lib.shared.DeviceRepository import net.mullvad.mullvadvpn.usecase.PaymentUseCase -import org.joda.time.DateTime import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -42,7 +42,11 @@ class AccountViewModelTest { private val mockPaymentUseCase: PaymentUseCase = mockk(relaxed = true) private val dummyDevice = - Device(id = DeviceId.fromString(UUID), name = "fake_name", creationDate = DateTime.now()) + Device( + id = DeviceId.fromString(UUID), + name = "fake_name", + creationDate = ZonedDateTime.now(), + ) private val dummyAccountNumber: AccountNumber = AccountNumber(DUMMY_DEVICE_NAME) private val deviceState: MutableStateFlow<DeviceState?> = diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModelTest.kt index 1a80597066..a2be07e0a6 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModelTest.kt @@ -11,6 +11,7 @@ import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.mockk +import java.time.ZonedDateTime import kotlin.test.assertIs import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -29,7 +30,6 @@ import net.mullvad.mullvadvpn.lib.model.AccountNumber import net.mullvad.mullvadvpn.lib.model.LoginAccountError import net.mullvad.mullvadvpn.lib.shared.AccountRepository import net.mullvad.mullvadvpn.usecase.InternetAvailableUseCase -import org.joda.time.DateTime import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -133,7 +133,9 @@ class LoginViewModelTest { val sideEffects = loginViewModel.uiSideEffect.testIn(backgroundScope) coEvery { mockedAccountRepository.login(any()) } returns Unit.right() coEvery { mockedAccountRepository.accountData } returns - MutableStateFlow(AccountData(mockk(relaxed = true), DateTime.now().plusDays(3))) + MutableStateFlow( + AccountData(mockk(relaxed = true), ZonedDateTime.now().plusDays(3)) + ) // Act, Assert uiStates.skipDefaultItem() diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModelTest.kt index 6c1ff9e1a2..bfaed10629 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModelTest.kt @@ -31,8 +31,6 @@ import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionState import net.mullvad.mullvadvpn.usecase.OutOfTimeUseCase import net.mullvad.mullvadvpn.usecase.PaymentUseCase -import org.joda.time.DateTime -import org.joda.time.ReadableInstant import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -132,10 +130,6 @@ class OutOfTimeViewModelTest { @Test fun `when OutOfTimeUseCase returns false uiSideEffect should emit OpenConnectScreen`() = runTest { - // Arrange - val mockExpiryDate: DateTime = mockk() - every { mockExpiryDate.isAfter(any<ReadableInstant>()) } returns true - // Act, Assert viewModel.uiSideEffect.test { outOfTimeFlow.value = false diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VoucherDialogViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VoucherDialogViewModelTest.kt index 3bd96de3cb..e5ae99d872 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VoucherDialogViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VoucherDialogViewModelTest.kt @@ -8,6 +8,7 @@ import io.mockk.coVerify import io.mockk.every import io.mockk.mockk import io.mockk.unmockkAll +import java.time.ZonedDateTime import kotlin.test.assertEquals import kotlin.test.assertIs import kotlin.test.assertTrue @@ -18,7 +19,6 @@ import net.mullvad.mullvadvpn.lib.model.RedeemVoucherError import net.mullvad.mullvadvpn.lib.model.RedeemVoucherSuccess import net.mullvad.mullvadvpn.lib.model.VoucherCode import net.mullvad.mullvadvpn.lib.shared.VoucherRepository -import org.joda.time.DateTime import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -50,7 +50,7 @@ class VoucherDialogViewModelTest { // Arrange val timeAdded = 0L - val newExpiry = DateTime() + val newExpiry = ZonedDateTime.now() coEvery { mockVoucherRepository.submitVoucher(parsedVoucher) } returns RedeemVoucherSuccess(timeAdded, newExpiry).right() @@ -89,7 +89,7 @@ class VoucherDialogViewModelTest { every { mockVoucherSubmission.timeAdded } returns 0 coEvery { mockVoucherRepository.submitVoucher(VoucherCode.fromString(voucher).getOrNull()!!) - } returns RedeemVoucherSuccess(0, DateTime()).right() + } returns RedeemVoucherSuccess(0, ZonedDateTime.now()).right() // Act, Assert viewModel.uiState.test { diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModelTest.kt index 3fe3804b8b..b2b59ba69e 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModelTest.kt @@ -9,6 +9,7 @@ import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic import io.mockk.unmockkAll +import java.time.ZonedDateTime import kotlin.test.assertEquals import kotlin.test.assertIs import kotlinx.coroutines.cancel @@ -32,7 +33,6 @@ import net.mullvad.mullvadvpn.lib.shared.DeviceRepository import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionState import net.mullvad.mullvadvpn.usecase.PaymentUseCase -import org.joda.time.DateTime import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -146,7 +146,9 @@ class WelcomeViewModelTest { @Test fun `when user has added time then uiSideEffect should emit OpenConnectScreen`() = runTest { // Arrange - accountExpiryStateFlow.emit(AccountData(mockk(relaxed = true), DateTime().plusDays(1))) + accountExpiryStateFlow.emit( + AccountData(mockk(relaxed = true), ZonedDateTime.now().plusDays(1)) + ) // Act, Assert viewModel.uiSideEffect.test { diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml index da2a4b6c54..401c5681f7 100644 --- a/android/gradle/libs.versions.toml +++ b/android/gradle/libs.versions.toml @@ -64,7 +64,6 @@ commonsvalidator = "1.9.0" # Upgrading to 12.0.1 results in task failing, DROID-1517 dependency-versions = "0.52.0" detekt = "1.23.7" -jodatime = "2.13.1" kermit = "2.0.5" konsist = "0.17.3" ktfmt = "0.22.0" @@ -160,7 +159,6 @@ mockk-android = { module = "io.mockk:mockk-android", version.ref = "mockk" } # Misc commons-validator = { module = "commons-validator:commons-validator", version.ref = "commonsvalidator" } -jodatime = { module = "joda-time:joda-time", version.ref = "jodatime" } kermit = { module = "co.touchlab:kermit", version.ref = "kermit" } konsist = { module = "com.lemonappdev:konsist", version.ref = "konsist" } leakCanary = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakcanary" } diff --git a/android/gradle/verification-metadata.xml b/android/gradle/verification-metadata.xml index af78ab4c0d..b11716d5a9 100644 --- a/android/gradle/verification-metadata.xml +++ b/android/gradle/verification-metadata.xml @@ -4749,11 +4749,6 @@ <sha256 value="91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff" origin="Generated by Gradle"/> </artifact> </component> - <component group="joda-time" name="joda-time" version="2.13.1"> - <artifact name="joda-time-2.13.1.jar"> - <sha256 value="a9be47b46390f7a6dabacc7c055b4feb6fb8f2225477a7270a8233b296e26677" origin="Generated by Gradle"/> - </artifact> - </component> <component group="junit" name="junit" version="4.13.2"> <artifact name="junit-4.13.2.jar"> <sha256 value="8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3" origin="Generated by Gradle"/> diff --git a/android/lib/common/build.gradle.kts b/android/lib/common/build.gradle.kts index c8554b52c7..24468d9815 100644 --- a/android/lib/common/build.gradle.kts +++ b/android/lib/common/build.gradle.kts @@ -34,7 +34,6 @@ dependencies { implementation(libs.arrow) implementation(libs.androidx.appcompat) - implementation(libs.jodatime) implementation(libs.kotlin.stdlib) implementation(libs.kotlinx.coroutines.android) implementation(libs.kermit) diff --git a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/CommonStringExtensions.kt b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/CommonStringExtensions.kt index 536fea3d24..e34bfa582e 100644 --- a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/CommonStringExtensions.kt +++ b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/CommonStringExtensions.kt @@ -1,22 +1,8 @@ package net.mullvad.mullvadvpn.lib.common.util -import org.joda.time.DateTime -import org.joda.time.format.DateTimeFormat - -private const val EXPIRY_FORMAT = "YYYY-MM-dd HH:mm:ss z" private const val BIG_DOT_CHAR = "●" private const val SPACE_CHAR = ' ' -fun String.parseAsDateTime(): DateTime? { - return try { - DateTime.parse(this, DateTimeFormat.forPattern(EXPIRY_FORMAT)) - } catch (ex: IllegalArgumentException) { - null - } catch (ex: UnsupportedOperationException) { - null - } -} - fun String.groupWithSpaces(groupCharSize: Int = 4): String { return fold(StringBuilder()) { formattedText, nextDigit -> if ((formattedText.length % (groupCharSize + 1)) == groupCharSize) { diff --git a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/DateExtensions.kt b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/DateExtensions.kt new file mode 100644 index 0000000000..74fe9d6ba0 --- /dev/null +++ b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/DateExtensions.kt @@ -0,0 +1,20 @@ +package net.mullvad.mullvadvpn.lib.common.util + +import java.time.Duration +import java.time.Instant +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter +import java.time.format.FormatStyle + +fun ZonedDateTime.formatDate(): String = DateTimeFormatter.ISO_LOCAL_DATE.format(this) + +fun ZonedDateTime.toExpiryDateString(): String = + DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT).format(this) + +fun ZonedDateTime.millisFromNow(): Long = Duration.between(ZonedDateTime.now(), this).toMillis() + +fun ZonedDateTime.daysFromNow(): Long = Duration.between(ZonedDateTime.now(), this).toDays() + +fun ZonedDateTime.isBeforeNowInstant(): Boolean = toInstant().isBefore(Instant.now()) + +fun ZonedDateTime.isAfterNowInstant(): Boolean = toInstant().isAfter(Instant.now()) diff --git a/android/lib/daemon-grpc/build.gradle.kts b/android/lib/daemon-grpc/build.gradle.kts index 60e2073843..f3b8799f95 100644 --- a/android/lib/daemon-grpc/build.gradle.kts +++ b/android/lib/daemon-grpc/build.gradle.kts @@ -63,7 +63,6 @@ dependencies { implementation(projects.lib.model) implementation(projects.lib.talpid) - implementation(libs.jodatime) implementation(libs.kermit) implementation(libs.kotlin.stdlib) implementation(libs.kotlinx.coroutines) diff --git a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt index fe4cf11881..717bf401f4 100644 --- a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt +++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt @@ -5,6 +5,8 @@ package net.mullvad.mullvadvpn.lib.daemon.grpc.mapper import io.grpc.ConnectivityState import java.net.InetAddress import java.net.InetSocketAddress +import java.time.Instant +import java.time.ZoneId import java.util.UUID import mullvad_daemon.management_interface.ManagementInterface import net.mullvad.mullvadvpn.lib.daemon.grpc.GrpcConnectivityState @@ -72,7 +74,6 @@ import net.mullvad.mullvadvpn.lib.model.WireguardConstraints import net.mullvad.mullvadvpn.lib.model.WireguardEndpointData import net.mullvad.mullvadvpn.lib.model.WireguardRelayEndpointData import net.mullvad.mullvadvpn.lib.model.WireguardTunnelOptions -import org.joda.time.Instant internal fun ManagementInterface.TunnelState.toDomain(): TunnelState = when (stateCase!!) { @@ -570,8 +571,10 @@ internal fun ManagementInterface.Relay.toDomain( } else false, ) +private fun Instant.atDefaultZone() = atZone(ZoneId.systemDefault()) + internal fun ManagementInterface.Device.toDomain(): Device = - Device(DeviceId.fromString(id), name, Instant.ofEpochSecond(created.seconds).toDateTime()) + Device(DeviceId.fromString(id), name, Instant.ofEpochSecond(created.seconds).atDefaultZone()) internal fun ManagementInterface.DeviceState.toDomain(): DeviceState = when (state) { @@ -587,13 +590,13 @@ internal fun ManagementInterface.DeviceState.toDomain(): DeviceState = internal fun ManagementInterface.AccountData.toDomain(): AccountData = AccountData( AccountId(UUID.fromString(id)), - expiryDate = Instant.ofEpochSecond(expiry.seconds).toDateTime(), + expiryDate = Instant.ofEpochSecond(expiry.seconds).atDefaultZone(), ) internal fun ManagementInterface.VoucherSubmission.toDomain(): RedeemVoucherSuccess = RedeemVoucherSuccess( timeAdded = secondsAdded, - newExpiryDate = Instant.ofEpochSecond(newExpiry.seconds).toDateTime(), + newExpiryDate = Instant.ofEpochSecond(newExpiry.seconds).atDefaultZone(), ) internal fun ManagementInterface.SplitTunnelSettings.toDomain(): SplitTunnelSettings = diff --git a/android/lib/model/build.gradle.kts b/android/lib/model/build.gradle.kts index c961dcc32f..f83ff3d5d4 100644 --- a/android/lib/model/build.gradle.kts +++ b/android/lib/model/build.gradle.kts @@ -35,7 +35,6 @@ android { } dependencies { - implementation(libs.jodatime) implementation(libs.kotlin.stdlib) implementation(libs.kotlinx.coroutines.android) implementation(libs.arrow) diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AccountData.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AccountData.kt index 8a4182b2e5..b1746e1bcc 100644 --- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AccountData.kt +++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/AccountData.kt @@ -1,5 +1,5 @@ package net.mullvad.mullvadvpn.lib.model -import org.joda.time.DateTime +import java.time.ZonedDateTime -data class AccountData(val id: AccountId, val expiryDate: DateTime) +data class AccountData(val id: AccountId, val expiryDate: ZonedDateTime) diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Device.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Device.kt index e8303f0eca..3eb92f2f8f 100644 --- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Device.kt +++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Device.kt @@ -1,12 +1,12 @@ package net.mullvad.mullvadvpn.lib.model import android.os.Parcelable +import java.time.ZonedDateTime import kotlinx.parcelize.Parcelize import net.mullvad.mullvadvpn.lib.model.extensions.startCase -import org.joda.time.DateTime @Parcelize -data class Device(val id: DeviceId, private val name: String, val creationDate: DateTime) : +data class Device(val id: DeviceId, private val name: String, val creationDate: ZonedDateTime) : Parcelable { fun displayName(): String = name.startCase() } diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Notification.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Notification.kt index acb8d74907..6b073988a3 100644 --- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Notification.kt +++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/Notification.kt @@ -1,6 +1,6 @@ package net.mullvad.mullvadvpn.lib.model -import org.joda.time.Duration +import java.time.Duration sealed interface Notification { val actions: List<NotificationAction> diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RedeemVoucherSuccess.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RedeemVoucherSuccess.kt index 9c81042b8c..53fa414047 100644 --- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RedeemVoucherSuccess.kt +++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RedeemVoucherSuccess.kt @@ -1,5 +1,5 @@ package net.mullvad.mullvadvpn.lib.model -import org.joda.time.DateTime +import java.time.ZonedDateTime -data class RedeemVoucherSuccess(val timeAdded: Long, val newExpiryDate: DateTime) +data class RedeemVoucherSuccess(val timeAdded: Long, val newExpiryDate: ZonedDateTime) diff --git a/android/lib/shared/build.gradle.kts b/android/lib/shared/build.gradle.kts index 4be82a9eb2..7e2730a9e9 100644 --- a/android/lib/shared/build.gradle.kts +++ b/android/lib/shared/build.gradle.kts @@ -41,7 +41,6 @@ dependencies { implementation(libs.kermit) implementation(libs.kotlin.stdlib) implementation(libs.kotlinx.coroutines.android) - implementation(libs.jodatime) testImplementation(libs.kotlin.test) testImplementation(libs.kotlinx.coroutines.test) diff --git a/android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/AccountRepository.kt b/android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/AccountRepository.kt index f6b146ecd3..a0edc2faa6 100644 --- a/android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/AccountRepository.kt +++ b/android/lib/shared/src/main/kotlin/net/mullvad/mullvadvpn/lib/shared/AccountRepository.kt @@ -2,6 +2,7 @@ package net.mullvad.mullvadvpn.lib.shared import arrow.core.Either import arrow.core.raise.nullable +import java.time.ZonedDateTime import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -21,7 +22,6 @@ import net.mullvad.mullvadvpn.lib.model.CreateAccountError import net.mullvad.mullvadvpn.lib.model.DeviceState import net.mullvad.mullvadvpn.lib.model.LoginAccountError import net.mullvad.mullvadvpn.lib.model.WebsiteAuthToken -import org.joda.time.DateTime class AccountRepository( private val managementService: ManagementService, @@ -87,7 +87,7 @@ class AccountRepository( suspend fun getWebsiteAuthToken(): WebsiteAuthToken? = managementService.getWebsiteAuthToken().getOrNull() - internal suspend fun onVoucherRedeemed(newExpiry: DateTime) { + internal suspend fun onVoucherRedeemed(newExpiry: ZonedDateTime) { accountData.value?.copy(expiryDate = newExpiry)?.let { _mutableAccountDataCache.emit(it) } } } diff --git a/android/service/build.gradle.kts b/android/service/build.gradle.kts index 8c9f0034cb..7fd560aa43 100644 --- a/android/service/build.gradle.kts +++ b/android/service/build.gradle.kts @@ -79,5 +79,4 @@ dependencies { implementation(libs.koin.android) implementation(libs.kotlin.stdlib) implementation(libs.kotlinx.coroutines.android) - implementation(libs.jodatime) } diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/accountexpiry/AccountExpiryAndroidNotification.kt b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/accountexpiry/AccountExpiryAndroidNotification.kt index db9ee92ae7..a61561c8cc 100644 --- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/accountexpiry/AccountExpiryAndroidNotification.kt +++ b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/accountexpiry/AccountExpiryAndroidNotification.kt @@ -5,12 +5,12 @@ import android.content.Context import android.content.Intent import android.content.res.Resources import androidx.core.app.NotificationCompat +import java.time.Duration import net.mullvad.mullvadvpn.lib.common.constant.MAIN_ACTIVITY_CLASS import net.mullvad.mullvadvpn.lib.common.util.SdkUtils import net.mullvad.mullvadvpn.lib.common.util.createAccountUri import net.mullvad.mullvadvpn.lib.model.Notification import net.mullvad.mullvadvpn.service.R -import org.joda.time.Duration internal fun Notification.AccountExpiry.toNotification(context: Context) = NotificationCompat.Builder(context, channelId.value) @@ -40,19 +40,19 @@ private fun Notification.AccountExpiry.contentIntent(context: Context): PendingI private fun Resources.contentTitle(remainingTime: Duration): String = when { - remainingTime.isShorterThan(Duration.ZERO) || remainingTime == Duration.ZERO -> { + remainingTime <= Duration.ZERO -> { getString(R.string.account_credit_has_expired) } - remainingTime.standardDays >= 1 -> { + remainingTime.toDays() >= 1 -> { getRemainingText( R.plurals.account_credit_expires_in_days, - remainingTime.standardDays.toInt(), + remainingTime.toDays().toInt(), ) } - remainingTime.standardHours >= 1 -> { + remainingTime.toHours() >= 1 -> { getRemainingText( R.plurals.account_credit_expires_in_hours, - remainingTime.standardHours.toInt(), + remainingTime.toHours().toInt(), ) } else -> getString(R.string.account_credit_expires_in_a_few_minutes) diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/accountexpiry/AccountExpiryConstant.kt b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/accountexpiry/AccountExpiryConstant.kt index 32190968b9..4ee9c6a533 100644 --- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/accountexpiry/AccountExpiryConstant.kt +++ b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/accountexpiry/AccountExpiryConstant.kt @@ -2,10 +2,10 @@ package net.mullvad.mullvadvpn.service.notifications.accountexpiry +import java.time.Duration import kotlin.time.Duration.Companion.seconds -import org.joda.time.Duration val ACCOUNT_EXPIRY_POLL_INTERVAL = 15.seconds -val ACCOUNT_EXPIRY_IN_APP_NOTIFICATION_UPDATE_INTERVAL: Duration = Duration.standardDays(1) -val ACCOUNT_EXPIRY_SYSTEM_NOTIFICATION_UPDATE_INTERVAL: Duration = Duration.standardDays(1) -val ACCOUNT_EXPIRY_CLOSE_TO_EXPIRY_THRESHOLD: Duration = Duration.standardDays(3) +val ACCOUNT_EXPIRY_IN_APP_NOTIFICATION_UPDATE_INTERVAL: Duration = Duration.ofDays(1) +val ACCOUNT_EXPIRY_SYSTEM_NOTIFICATION_UPDATE_INTERVAL: Duration = Duration.ofDays(1) +val ACCOUNT_EXPIRY_CLOSE_TO_EXPIRY_THRESHOLD: Duration = Duration.ofDays(3) diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/accountexpiry/AccountExpiryNotificationProvider.kt b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/accountexpiry/AccountExpiryNotificationProvider.kt index b513b490e0..be0141c3f4 100644 --- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/accountexpiry/AccountExpiryNotificationProvider.kt +++ b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/accountexpiry/AccountExpiryNotificationProvider.kt @@ -1,5 +1,6 @@ package net.mullvad.mullvadvpn.service.notifications.accountexpiry +import java.time.ZonedDateTime import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine @@ -16,7 +17,6 @@ import net.mullvad.mullvadvpn.lib.shared.AccountRepository import net.mullvad.mullvadvpn.lib.shared.DeviceRepository import net.mullvad.mullvadvpn.service.constant.IS_PLAY_BUILD import net.mullvad.mullvadvpn.service.notifications.NotificationProvider -import org.joda.time.DateTime class AccountExpiryNotificationProvider( private val channelId: NotificationChannelId, @@ -45,7 +45,7 @@ class AccountExpiryNotificationProvider( } private fun accountExpiryNotificationFlow( - expiryDate: DateTime + expiryDate: ZonedDateTime ): Flow<NotificationUpdate<Notification.AccountExpiry>> = AccountExpiryTicker.tickerFlow( expiry = expiryDate, diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/accountexpiry/AccountExpiryTicker.kt b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/accountexpiry/AccountExpiryTicker.kt index 6add0a372a..cd872eee18 100644 --- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/accountexpiry/AccountExpiryTicker.kt +++ b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/accountexpiry/AccountExpiryTicker.kt @@ -1,10 +1,12 @@ package net.mullvad.mullvadvpn.service.notifications.accountexpiry +import java.time.Duration +import java.time.Instant +import java.time.ZonedDateTime import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow -import org.joda.time.DateTime -import org.joda.time.Duration +import net.mullvad.mullvadvpn.lib.common.util.millisFromNow sealed interface AccountExpiryTicker { data object NotWithinThreshold : AccountExpiryTicker @@ -13,9 +15,9 @@ sealed interface AccountExpiryTicker { companion object { fun tickerFlow( - expiry: DateTime, + expiry: ZonedDateTime, tickStart: Duration, - updateInterval: (expiry: DateTime) -> Duration, + updateInterval: (expiry: ZonedDateTime) -> Duration, ): Flow<AccountExpiryTicker> = flow { expiry.millisFromNow().let { expiryMillis -> if (expiryMillis <= 0) { @@ -23,20 +25,20 @@ sealed interface AccountExpiryTicker { emit(Tick(Duration.ZERO)) return@flow } - if (expiryMillis > tickStart.millis) { + if (expiryMillis > tickStart.toMillis()) { // Emit NotWithinThreshold if no expiry notification should be provided. emit(NotWithinThreshold) // Delay until the time we should start emitting. - delay(expiryMillis - tickStart.millis + 1) + delay(expiryMillis - tickStart.toMillis() + 1) } } - var currentUpdateInterval = updateInterval(expiry).millis + var currentUpdateInterval = updateInterval(expiry).toMillis() do { - emit(Tick(Duration(DateTime.now(), expiry))) + emit(Tick(Duration.between(Instant.now(), expiry))) delay(millisUntilNextUpdate(expiry.millisFromNow(), currentUpdateInterval)) - currentUpdateInterval = updateInterval(expiry).millis + currentUpdateInterval = updateInterval(expiry).toMillis() } while (hasAnotherEmission(expiry.millisFromNow(), currentUpdateInterval)) // We may have remaining time if the update interval wasn't a multiple of the remaining @@ -67,5 +69,3 @@ private fun calculateDelaysNeeded( millisUntilExpiry: Long, currentUpdateIntervalMillis: Long, ): Long = millisUntilExpiry.coerceAtLeast(0) / currentUpdateIntervalMillis - -private fun DateTime.millisFromNow(): Long = Duration(DateTime.now(), this).millis diff --git a/android/test/e2e/build.gradle.kts b/android/test/e2e/build.gradle.kts index 3f4244cde7..8bcc45ab77 100644 --- a/android/test/e2e/build.gradle.kts +++ b/android/test/e2e/build.gradle.kts @@ -155,7 +155,6 @@ dependencies { implementation(libs.ktor.client.cio) implementation(libs.ktor.serialization.kotlinx.json) implementation(libs.ktor.client.content.negotiation) - implementation(libs.jodatime) androidTestUtil(libs.androidx.test.orchestrator) diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/packetCapture/Packet.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/packetCapture/Packet.kt index 5c788e5710..cd6b2b855e 100644 --- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/packetCapture/Packet.kt +++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/packetCapture/Packet.kt @@ -1,23 +1,25 @@ package net.mullvad.mullvadvpn.test.e2e.router.packetCapture +import java.time.ZonedDateTime import kotlinx.serialization.Contextual import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import net.mullvad.mullvadvpn.test.e2e.serializer.PacketSerializer -import org.joda.time.DateTime @Serializable(with = PacketSerializer::class) sealed interface Packet { - @SerialName("timestamp") val date: DateTime + @SerialName("timestamp") val date: ZonedDateTime val fromPeer: Boolean } @Serializable -data class RxPacket(@SerialName("timestamp") @Contextual override val date: DateTime) : Packet { +data class RxPacket(@SerialName("timestamp") @Contextual override val date: ZonedDateTime) : + Packet { @SerialName("from_peer") override val fromPeer: Boolean = false } @Serializable -data class TxPacket(@SerialName("timestamp") @Contextual override val date: DateTime) : Packet { +data class TxPacket(@SerialName("timestamp") @Contextual override val date: ZonedDateTime) : + Packet { @SerialName("from_peer") override val fromPeer: Boolean = true } diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/packetCapture/Stream.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/packetCapture/Stream.kt index 564d0e25cb..68eb2783cc 100644 --- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/packetCapture/Stream.kt +++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/router/packetCapture/Stream.kt @@ -1,11 +1,11 @@ package net.mullvad.mullvadvpn.test.e2e.router.packetCapture +import java.time.Duration +import java.time.ZonedDateTime import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import net.mullvad.mullvadvpn.test.e2e.router.NetworkingProtocol -import org.joda.time.DateTime -import org.joda.time.Interval @Serializable data class Stream( @@ -18,24 +18,26 @@ data class Stream( @Transient val sourceHost = Host.fromString(sourceAddressAndPort) @Transient val destinationHost = Host.fromString(destinationAddressAndPort) - @Transient private val startDate: DateTime = packets.first().date - @Transient private val endDate: DateTime = packets.last().date - @Transient private val txStartDate: DateTime? = txPackets().firstOrNull()?.date - @Transient private val txEndDate: DateTime? = txPackets().lastOrNull()?.date - @Transient private val rxStartDate: DateTime? = rxPackets().firstOrNull()?.date - @Transient private val rxEndDate: DateTime? = rxPackets().lastOrNull()?.date + @Transient private val startDate: ZonedDateTime = packets.first().date + @Transient private val endDate: ZonedDateTime = packets.last().date + @Transient private val txStartDate: ZonedDateTime? = txPackets().firstOrNull()?.date + @Transient private val txEndDate: ZonedDateTime? = txPackets().lastOrNull()?.date + @Transient private val rxStartDate: ZonedDateTime? = rxPackets().firstOrNull()?.date + @Transient private val rxEndDate: ZonedDateTime? = rxPackets().lastOrNull()?.date - @Transient val interval = Interval(startDate, endDate) + @Transient val interval = Duration.between(startDate, endDate) fun txPackets(): List<TxPacket> = packets.filterIsInstance<TxPacket>() fun rxPackets(): List<RxPacket> = packets.filterIsInstance<RxPacket>() - fun txInterval(): Interval? = - if (txStartDate != null && txEndDate != null) Interval(txStartDate, txEndDate) else null + fun txInterval(): Duration? = + if (txStartDate != null && txEndDate != null) Duration.between(txStartDate, txEndDate) + else null - fun rxInterval(): Interval? = - if (rxStartDate != null && rxEndDate != null) Interval(rxStartDate, rxEndDate) else null + fun rxInterval(): Duration? = + if (rxStartDate != null && rxEndDate != null) Duration.between(rxStartDate, rxEndDate) + else null init { require(packets.isNotEmpty()) { "Stream must contain at least one packet" } diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/serializer/NanoSecondsTimestampSerializer.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/serializer/NanoSecondsTimestampSerializer.kt index d49ca6017d..26d5f05f5b 100644 --- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/serializer/NanoSecondsTimestampSerializer.kt +++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/serializer/NanoSecondsTimestampSerializer.kt @@ -1,23 +1,25 @@ package net.mullvad.mullvadvpn.test.e2e.serializer +import java.time.Instant +import java.time.ZoneId +import java.time.ZonedDateTime import kotlinx.serialization.KSerializer import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder -import org.joda.time.DateTime -object NanoSecondsTimestampSerializer : KSerializer<DateTime> { +object NanoSecondsTimestampSerializer : KSerializer<ZonedDateTime> { override val descriptor: SerialDescriptor = - PrimitiveSerialDescriptor("DateTime", PrimitiveKind.LONG) + PrimitiveSerialDescriptor("ZonedDateTime", PrimitiveKind.LONG) - override fun deserialize(decoder: Decoder): DateTime { + override fun deserialize(decoder: Decoder): ZonedDateTime { val long = decoder.decodeLong() - return DateTime(long / 1000) + return ZonedDateTime.ofInstant(Instant.ofEpochMilli(long / 1000), ZoneId.systemDefault()) } - override fun serialize(encoder: Encoder, value: DateTime) { + override fun serialize(encoder: Encoder, value: ZonedDateTime) { throw NotImplementedError("Only interested in deserialization") } } diff --git a/android/test/mockapi/build.gradle.kts b/android/test/mockapi/build.gradle.kts index a224b8c53f..f0310107ac 100644 --- a/android/test/mockapi/build.gradle.kts +++ b/android/test/mockapi/build.gradle.kts @@ -72,7 +72,6 @@ dependencies { implementation(libs.androidx.test.rules) implementation(libs.androidx.test.uiautomator) implementation(libs.kermit) - implementation(libs.jodatime) implementation(Dependencies.junitJupiterApi) implementation(Dependencies.junit5AndroidTestExtensions) implementation(Dependencies.junit5AndroidTestRunner) diff --git a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/AccountExpiryMockApiTest.kt b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/AccountExpiryMockApiTest.kt index 2fe829841c..d89313fc45 100644 --- a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/AccountExpiryMockApiTest.kt +++ b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/AccountExpiryMockApiTest.kt @@ -1,6 +1,7 @@ package net.mullvad.mullvadvpn.test.mockapi import androidx.test.uiautomator.By +import java.time.ZonedDateTime import net.mullvad.mullvadvpn.test.common.extension.clickAgreeOnPrivacyDisclaimer import net.mullvad.mullvadvpn.test.common.extension.clickAllowOnNotificationPermissionPromptIfApiLevel33AndAbove import net.mullvad.mullvadvpn.test.common.extension.dismissChangelogDialogIfShown @@ -8,8 +9,7 @@ import net.mullvad.mullvadvpn.test.common.extension.findObjectWithTimeout import net.mullvad.mullvadvpn.test.mockapi.constant.DEFAULT_DEVICE_LIST import net.mullvad.mullvadvpn.test.mockapi.constant.DUMMY_DEVICE_NAME_2 import net.mullvad.mullvadvpn.test.mockapi.constant.DUMMY_ID_2 -import net.mullvad.mullvadvpn.test.mockapi.util.currentUtcTimeWithOffsetZero -import net.mullvad.mullvadvpn.util.toExpiryDateString +import net.mullvad.mullvadvpn.test.mockapi.util.toExpiryDateString import org.junit.jupiter.api.Test class AccountExpiryMockApiTest : MockApiTest() { @@ -18,7 +18,7 @@ class AccountExpiryMockApiTest : MockApiTest() { fun testAccountExpiryDateUpdated() { // Arrange val validAccountNumber = "1234123412341234" - val oldAccountExpiry = currentUtcTimeWithOffsetZero().plusMonths(1) + val oldAccountExpiry = ZonedDateTime.now().plusMonths(1) apiDispatcher.apply { expectedAccountNumber = validAccountNumber accountExpiry = oldAccountExpiry @@ -52,7 +52,7 @@ class AccountExpiryMockApiTest : MockApiTest() { fun testAccountTimeExpiredWhileUsingTheAppShouldShowOutOfTimeScreen() { // Arrange val validAccountNumber = "1234123412341234" - val oldAccountExpiry = currentUtcTimeWithOffsetZero().plusMonths(1) + val oldAccountExpiry = ZonedDateTime.now().plusMonths(1) apiDispatcher.apply { expectedAccountNumber = validAccountNumber accountExpiry = oldAccountExpiry diff --git a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/AccountHistoryMockApiTest.kt b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/AccountHistoryMockApiTest.kt index 7d4639019a..deb63c89b4 100644 --- a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/AccountHistoryMockApiTest.kt +++ b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/AccountHistoryMockApiTest.kt @@ -1,6 +1,7 @@ package net.mullvad.mullvadvpn.test.mockapi import androidx.test.uiautomator.By +import java.time.ZonedDateTime import net.mullvad.mullvadvpn.compose.test.LOGIN_INPUT_TEST_TAG import net.mullvad.mullvadvpn.test.common.extension.clickAgreeOnPrivacyDisclaimer import net.mullvad.mullvadvpn.test.common.extension.clickAllowOnNotificationPermissionPromptIfApiLevel33AndAbove @@ -9,7 +10,6 @@ import net.mullvad.mullvadvpn.test.common.extension.findObjectWithTimeout import net.mullvad.mullvadvpn.test.mockapi.constant.DEFAULT_DEVICE_LIST import net.mullvad.mullvadvpn.test.mockapi.constant.DUMMY_DEVICE_NAME_2 import net.mullvad.mullvadvpn.test.mockapi.constant.DUMMY_ID_2 -import net.mullvad.mullvadvpn.test.mockapi.util.currentUtcTimeWithOffsetZero import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Test @@ -21,7 +21,7 @@ class AccountHistoryMockApiTest : MockApiTest() { val validAccountNumber = "1234123412341234" apiDispatcher.apply { expectedAccountNumber = validAccountNumber - accountExpiry = currentUtcTimeWithOffsetZero().plusMonths(1) + accountExpiry = ZonedDateTime.now().plusMonths(1) devices = DEFAULT_DEVICE_LIST.toMutableMap() devicePendingToGetCreated = DUMMY_ID_2 to DUMMY_DEVICE_NAME_2 } diff --git a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/LoginMockApiTest.kt b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/LoginMockApiTest.kt index f13e5070e2..5feee8abd8 100644 --- a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/LoginMockApiTest.kt +++ b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/LoginMockApiTest.kt @@ -2,6 +2,7 @@ package net.mullvad.mullvadvpn.test.mockapi import androidx.test.uiautomator.By import androidx.test.uiautomator.Until +import java.time.ZonedDateTime import net.mullvad.mullvadvpn.compose.test.LOGIN_TITLE_TEST_TAG import net.mullvad.mullvadvpn.test.common.constant.DEFAULT_TIMEOUT import net.mullvad.mullvadvpn.test.common.extension.clickAgreeOnPrivacyDisclaimer @@ -10,7 +11,6 @@ import net.mullvad.mullvadvpn.test.common.extension.dismissChangelogDialogIfShow import net.mullvad.mullvadvpn.test.mockapi.constant.DEFAULT_DEVICE_LIST import net.mullvad.mullvadvpn.test.mockapi.constant.DUMMY_DEVICE_NAME_2 import net.mullvad.mullvadvpn.test.mockapi.constant.DUMMY_ID_2 -import net.mullvad.mullvadvpn.test.mockapi.util.currentUtcTimeWithOffsetZero import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test @@ -21,7 +21,7 @@ class LoginMockApiTest : MockApiTest() { val validAccountNumber = "1234123412341234" apiDispatcher.apply { expectedAccountNumber = null - accountExpiry = currentUtcTimeWithOffsetZero().plusDays(1) + accountExpiry = ZonedDateTime.now().plusDays(1) } app.launch(endpoint) @@ -47,7 +47,7 @@ class LoginMockApiTest : MockApiTest() { val validAccountNumber = "1234123412341234" apiDispatcher.apply { expectedAccountNumber = validAccountNumber - accountExpiry = currentUtcTimeWithOffsetZero().plusDays(1) + accountExpiry = ZonedDateTime.now().plusDays(1) devices = DEFAULT_DEVICE_LIST.toMutableMap() devicePendingToGetCreated = DUMMY_ID_2 to DUMMY_DEVICE_NAME_2 } @@ -70,7 +70,7 @@ class LoginMockApiTest : MockApiTest() { val validAccountNumber = "1234123412341234" apiDispatcher.apply { expectedAccountNumber = validAccountNumber - accountExpiry = currentUtcTimeWithOffsetZero().minusDays(1) + accountExpiry = ZonedDateTime.now().minusDays(1) devices = DEFAULT_DEVICE_LIST.toMutableMap() devicePendingToGetCreated = DUMMY_ID_2 to DUMMY_DEVICE_NAME_2 } diff --git a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/LogoutMockApiTest.kt b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/LogoutMockApiTest.kt index 3df42ac0ce..da41d41066 100644 --- a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/LogoutMockApiTest.kt +++ b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/LogoutMockApiTest.kt @@ -1,6 +1,7 @@ package net.mullvad.mullvadvpn.test.mockapi import androidx.test.uiautomator.By +import java.time.ZonedDateTime import net.mullvad.mullvadvpn.test.common.extension.clickAgreeOnPrivacyDisclaimer import net.mullvad.mullvadvpn.test.common.extension.clickAllowOnNotificationPermissionPromptIfApiLevel33AndAbove import net.mullvad.mullvadvpn.test.common.extension.dismissChangelogDialogIfShown @@ -8,7 +9,6 @@ import net.mullvad.mullvadvpn.test.common.extension.findObjectWithTimeout import net.mullvad.mullvadvpn.test.mockapi.constant.DEFAULT_DEVICE_LIST import net.mullvad.mullvadvpn.test.mockapi.constant.DUMMY_DEVICE_NAME_2 import net.mullvad.mullvadvpn.test.mockapi.constant.DUMMY_ID_2 -import net.mullvad.mullvadvpn.test.mockapi.util.currentUtcTimeWithOffsetZero import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Test @@ -20,7 +20,7 @@ class LogoutMockApiTest : MockApiTest() { val validAccountNumber = "1234123412341234" apiDispatcher.apply { expectedAccountNumber = validAccountNumber - accountExpiry = currentUtcTimeWithOffsetZero().plusMonths(1) + accountExpiry = ZonedDateTime.now().plusMonths(1) devices = DEFAULT_DEVICE_LIST.toMutableMap() devicePendingToGetCreated = DUMMY_ID_2 to DUMMY_DEVICE_NAME_2 } diff --git a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/MockApiDispatcher.kt b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/MockApiDispatcher.kt index e230152efe..f99cb48a06 100644 --- a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/MockApiDispatcher.kt +++ b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/MockApiDispatcher.kt @@ -1,6 +1,7 @@ package net.mullvad.mullvadvpn.test.mockapi import co.touchlab.kermit.Logger +import java.time.ZonedDateTime import net.mullvad.mullvadvpn.test.mockapi.constant.ACCOUNT_URL_PATH import net.mullvad.mullvadvpn.test.mockapi.constant.AUTH_TOKEN_URL_PATH import net.mullvad.mullvadvpn.test.mockapi.constant.CREATE_ACCOUNT_URL_PATH @@ -10,20 +11,18 @@ import net.mullvad.mullvadvpn.test.mockapi.constant.DUMMY_ID_1 import net.mullvad.mullvadvpn.test.mockapi.util.accessTokenJsonResponse import net.mullvad.mullvadvpn.test.mockapi.util.accountCreationJson import net.mullvad.mullvadvpn.test.mockapi.util.accountInfoJson -import net.mullvad.mullvadvpn.test.mockapi.util.currentUtcTimeWithOffsetZero import net.mullvad.mullvadvpn.test.mockapi.util.deviceJson import net.mullvad.mullvadvpn.test.mockapi.util.tooManyDevicesJsonResponse import okhttp3.mockwebserver.Dispatcher import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.RecordedRequest import okio.Buffer -import org.joda.time.DateTime import org.json.JSONArray class MockApiDispatcher : Dispatcher() { var expectedAccountNumber: String? = null - var accountExpiry: DateTime? = null + var accountExpiry: ZonedDateTime? = null var devices: MutableMap<String, String>? = null private val canAddDevices: Boolean get() = (devices?.size ?: 0) < 5 @@ -84,7 +83,7 @@ class MockApiDispatcher : Dispatcher() { .setBody( accessTokenJsonResponse( accessToken = DUMMY_ACCESS_TOKEN, - expiry = currentUtcTimeWithOffsetZero().plusDays(1), + expiry = ZonedDateTime.now().plusDays(1), ) .toString() ) @@ -115,7 +114,7 @@ class MockApiDispatcher : Dispatcher() { id = deviceId, name = devices!![deviceId]!!, // Should always exist publicKey = cachedKey, - creationDate = currentUtcTimeWithOffsetZero().minusDays(1), + creationDate = ZonedDateTime.now().minusDays(1), ) .toString() ) @@ -136,7 +135,7 @@ class MockApiDispatcher : Dispatcher() { id = devicePendingToGetCreated!!.first, name = devicePendingToGetCreated!!.second, publicKey = newKey, - creationDate = currentUtcTimeWithOffsetZero().minusDays(1), + creationDate = ZonedDateTime.now().minusDays(1), ) .toString() ) @@ -158,7 +157,7 @@ class MockApiDispatcher : Dispatcher() { id = entry.key, name = entry.value, publicKey = cachedKey, - creationDate = currentUtcTimeWithOffsetZero().minusDays(index + 1), + creationDate = ZonedDateTime.now().minusDays((index + 1).toLong()), ) ) } @@ -174,7 +173,7 @@ class MockApiDispatcher : Dispatcher() { .setBody( accountCreationJson( id = DUMMY_ID_1, - expiry = DateTime(), + expiry = ZonedDateTime.now(), accountNumber = expectedAccountNumber, ) .toString() diff --git a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/TooManyDevicesMockApiTest.kt b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/TooManyDevicesMockApiTest.kt index bde2a8f4dd..19674815c1 100644 --- a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/TooManyDevicesMockApiTest.kt +++ b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/TooManyDevicesMockApiTest.kt @@ -1,6 +1,7 @@ package net.mullvad.mullvadvpn.test.mockapi import androidx.test.uiautomator.By +import java.time.ZonedDateTime import net.mullvad.mullvadvpn.test.common.extension.clickAgreeOnPrivacyDisclaimer import net.mullvad.mullvadvpn.test.common.extension.clickAllowOnNotificationPermissionPromptIfApiLevel33AndAbove import net.mullvad.mullvadvpn.test.common.extension.dismissChangelogDialogIfShown @@ -17,7 +18,6 @@ import net.mullvad.mullvadvpn.test.mockapi.constant.DUMMY_ID_3 import net.mullvad.mullvadvpn.test.mockapi.constant.DUMMY_ID_4 import net.mullvad.mullvadvpn.test.mockapi.constant.DUMMY_ID_5 import net.mullvad.mullvadvpn.test.mockapi.constant.DUMMY_ID_6 -import net.mullvad.mullvadvpn.test.mockapi.util.currentUtcTimeWithOffsetZero import org.junit.jupiter.api.Test class TooManyDevicesMockApiTest : MockApiTest() { @@ -27,7 +27,7 @@ class TooManyDevicesMockApiTest : MockApiTest() { val validAccountNumber = "1234123412341234" apiDispatcher.apply { expectedAccountNumber = validAccountNumber - accountExpiry = currentUtcTimeWithOffsetZero().plusMonths(1) + accountExpiry = ZonedDateTime.now().plusMonths(1) devices = mutableMapOf( DUMMY_ID_1 to DUMMY_DEVICE_NAME_1, diff --git a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/util/DateTimeUtils.kt b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/util/DateTimeUtils.kt index 81042f70d2..330fc15a81 100644 --- a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/util/DateTimeUtils.kt +++ b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/util/DateTimeUtils.kt @@ -1,13 +1,14 @@ package net.mullvad.mullvadvpn.test.mockapi.util -import org.joda.time.DateTime -import org.joda.time.DateTimeZone -import org.joda.time.format.DateTimeFormat +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter +import java.time.format.FormatStyle private const val STRICT_ISO8601_AND_RFC3339_PATTERN = "yyyy-MM-dd'T'HH:mm:ssZZ" -fun currentUtcTimeWithOffsetZero() = DateTime.now(DateTimeZone.forOffsetHours(0)) - -fun DateTime.formatStrictlyAccordingToIso8601AndRfc3339(): String { - return toString(DateTimeFormat.forPattern(STRICT_ISO8601_AND_RFC3339_PATTERN)) +fun ZonedDateTime.formatStrictlyAccordingToIso8601AndRfc3339(): String { + return DateTimeFormatter.ofPattern(STRICT_ISO8601_AND_RFC3339_PATTERN).format(this) } + +fun ZonedDateTime.toExpiryDateString(): String = + DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT).format(this) diff --git a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/util/JsonUtils.kt b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/util/JsonUtils.kt index 2de385c7f8..a0e21d55ca 100644 --- a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/util/JsonUtils.kt +++ b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/util/JsonUtils.kt @@ -1,9 +1,9 @@ package net.mullvad.mullvadvpn.test.mockapi.util -import org.joda.time.DateTime +import java.time.ZonedDateTime import org.json.JSONObject -fun accountInfoJson(id: String, expiry: DateTime) = +fun accountInfoJson(id: String, expiry: ZonedDateTime) = JSONObject().apply { put("id", id) put("expiry", expiry.formatStrictlyAccordingToIso8601AndRfc3339()) @@ -11,10 +11,10 @@ fun accountInfoJson(id: String, expiry: DateTime) = put("can_add_devices", true) } -fun accountCreationJson(id: String, accountNumber: String, expiry: DateTime) = +fun accountCreationJson(id: String, accountNumber: String, expiry: ZonedDateTime) = accountInfoJson(id, expiry).apply { put("number", accountNumber) } -fun deviceJson(id: String, name: String, publicKey: String, creationDate: DateTime) = +fun deviceJson(id: String, name: String, publicKey: String, creationDate: ZonedDateTime) = JSONObject().apply { put("id", id) put("name", name) @@ -25,7 +25,7 @@ fun deviceJson(id: String, name: String, publicKey: String, creationDate: DateTi put("ipv6_address", "fc00::1/128") } -fun accessTokenJsonResponse(accessToken: String, expiry: DateTime) = +fun accessTokenJsonResponse(accessToken: String, expiry: ZonedDateTime) = JSONObject().apply { put("access_token", accessToken) put("expiry", expiry.formatStrictlyAccordingToIso8601AndRfc3339()) |
