diff options
| author | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2026-02-13 01:15:34 +0100 |
|---|---|---|
| committer | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2026-02-13 10:51:48 +0100 |
| commit | f03d6437ca6070407a2adbb1e50e6ba761e0c429 (patch) | |
| tree | f7b72186cc3412bda83e1082c2edec7e2795f745 | |
| parent | 5cd0c476ee1f9509c42c1d3ef1ab5ec7475a583d (diff) | |
| download | mullvadvpn-f03d6437ca6070407a2adbb1e50e6ba761e0c429.tar.xz mullvadvpn-f03d6437ca6070407a2adbb1e50e6ba761e0c429.zip | |
Refactor login into feature module
57 files changed, 282 insertions, 224 deletions
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index bfdcd650b1..c89d94be8e 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -397,6 +397,7 @@ dependencies { implementation(projects.lib.feature.customlist.impl) implementation(projects.lib.feature.daita.impl) implementation(projects.lib.feature.filter.impl) + implementation(projects.lib.feature.login.impl) implementation(projects.lib.feature.managedevices.impl) implementation(projects.lib.feature.multihop.impl) implementation(projects.lib.feature.notification.impl) @@ -408,6 +409,7 @@ dependencies { implementation(projects.lib.feature.vpnsettings.impl) implementation(projects.lib.map) implementation(projects.lib.model) + implementation(projects.lib.pushNotification) implementation(projects.lib.navigation) implementation(projects.lib.payment) implementation(projects.lib.repository) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index b4fd266f2f..3d5137200e 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -274,7 +274,7 @@ </intent-filter> </receiver> <receiver - android:name=".receiver.NotificationAlarmReceiver" + android:name=".lib.pushnotification.receiver.NotificationAlarmReceiver" android:exported="false" /> <receiver android:name=".receiver.AutoStartVpnBootCompletedReceiver" diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/MullvadApplication.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/MullvadApplication.kt index 2fce5e116b..7992d2692f 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/MullvadApplication.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/MullvadApplication.kt @@ -12,12 +12,12 @@ import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.di.ApplicationScope import net.mullvad.mullvadvpn.di.KERMIT_FILE_LOG_DIR_NAME import net.mullvad.mullvadvpn.di.appModule +import net.mullvad.mullvadvpn.lib.pushnotification.NotificationChannelFactory +import net.mullvad.mullvadvpn.lib.pushnotification.NotificationManager +import net.mullvad.mullvadvpn.lib.pushnotification.ScheduleNotificationAlarmUseCase +import net.mullvad.mullvadvpn.lib.pushnotification.accountexpiry.AccountExpiryNotificationProvider import net.mullvad.mullvadvpn.lib.usecase.AccountExpiryNotificationActionUseCase import net.mullvad.mullvadvpn.lib.usecase.NotificationAction -import net.mullvad.mullvadvpn.service.notifications.NotificationChannelFactory -import net.mullvad.mullvadvpn.service.notifications.NotificationManager -import net.mullvad.mullvadvpn.service.notifications.accountexpiry.AccountExpiryNotificationProvider -import net.mullvad.mullvadvpn.usecase.ScheduleNotificationAlarmUseCase import net.mullvad.mullvadvpn.util.FileLogWriter import org.koin.android.ext.android.getKoin import org.koin.android.ext.koin.androidContext diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceRevokedScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceRevokedScreen.kt index b1c5f491c5..012ed00d57 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceRevokedScreen.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceRevokedScreen.kt @@ -23,7 +23,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.dropUnlessResumed import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.generated.NavGraphs -import com.ramcosta.composedestinations.generated.destinations.LoginDestination +import com.ramcosta.composedestinations.generated.login.destinations.LoginDestination import com.ramcosta.composedestinations.generated.settings.destinations.SettingsDestination import com.ramcosta.composedestinations.navigation.DestinationsNavigator import net.mullvad.mullvadvpn.R diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/MullvadApp.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/MullvadApp.kt index bfbe5c0886..9e823c7682 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/MullvadApp.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/MullvadApp.kt @@ -48,6 +48,11 @@ import com.ramcosta.composedestinations.generated.daita.destinations.DaitaDirect import com.ramcosta.composedestinations.generated.daita.destinations.DaitaDirectOnlyInfoDestination import com.ramcosta.composedestinations.generated.destinations.NoDaemonDestination import com.ramcosta.composedestinations.generated.filter.destinations.FilterDestination +import com.ramcosta.composedestinations.generated.login.destinations.ApiUnreachableInfoDestination +import com.ramcosta.composedestinations.generated.login.destinations.CreateAccountConfirmationDestination +import com.ramcosta.composedestinations.generated.login.destinations.DeviceListDestination +import com.ramcosta.composedestinations.generated.login.destinations.LoginDestination +import com.ramcosta.composedestinations.generated.login.destinations.RemoveDeviceConfirmationDestination import com.ramcosta.composedestinations.generated.managedevices.destinations.ManageDevicesDestination import com.ramcosta.composedestinations.generated.managedevices.destinations.ManageDevicesRemoveConfirmationDestination import com.ramcosta.composedestinations.generated.multihop.destinations.MultihopDestination @@ -94,12 +99,14 @@ annotation class MainGraph { @ExternalDestination<ApiAccessListDestination> @ExternalDestination<ApiAccessMethodDetailsDestination> @ExternalDestination<ApiAccessMethodInfoDestination> + @ExternalDestination<ApiUnreachableInfoDestination> @ExternalDestination<AppInfoDestination> @ExternalDestination<AppearanceDestination> @ExternalDestination<AutoConnectAndLockdownModeDestination> @ExternalDestination<ChangelogDestination> @ExternalDestination<ConnectOnStartupInfoDestination> @ExternalDestination<ContentBlockersInfoDestination> + @ExternalDestination<CreateAccountConfirmationDestination> @ExternalDestination<CreateCustomListDestination> @ExternalDestination<CustomDnsInfoDestination> @ExternalDestination<CustomListLocationsDestination> @@ -111,6 +118,7 @@ annotation class MainGraph { @ExternalDestination<DeleteApiAccessMethodConfirmationDestination> @ExternalDestination<DeleteCustomListDestination> @ExternalDestination<DeviceIpInfoDestination> + @ExternalDestination<DeviceListDestination> @ExternalDestination<DiscardApiAccessChangesDestination> @ExternalDestination<DiscardChangesDestination> @ExternalDestination<DnsDestination> @@ -121,6 +129,7 @@ annotation class MainGraph { @ExternalDestination<ImportOverridesByTextDestination> @ExternalDestination<Ipv6InfoDestination> @ExternalDestination<LocalNetworkSharingInfoDestination> + @ExternalDestination<LoginDestination> @ExternalDestination<MalwareInfoDestination> @ExternalDestination<ManageDevicesDestination> @ExternalDestination<ManageDevicesRemoveConfirmationDestination> @@ -129,6 +138,7 @@ annotation class MainGraph { @ExternalDestination<NotificationSettingsDestination> @ExternalDestination<QuantumResistanceInfoDestination> @ExternalDestination<RedeemVoucherDestination> + @ExternalDestination<RemoveDeviceConfirmationDestination> @ExternalDestination<ReportProblemDestination> @ExternalDestination<ReportProblemNoEmailDestination> @ExternalDestination<ResetServerIpOverridesConfirmationDestination> diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/PrivacyDisclaimerScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/PrivacyDisclaimerScreen.kt index 4a4694c839..0dd1583928 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/PrivacyDisclaimerScreen.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/PrivacyDisclaimerScreen.kt @@ -33,8 +33,8 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.generated.NavGraphs -import com.ramcosta.composedestinations.generated.destinations.LoginDestination import com.ramcosta.composedestinations.generated.destinations.SplashDestination +import com.ramcosta.composedestinations.generated.login.destinations.LoginDestination import com.ramcosta.composedestinations.navigation.DestinationsNavigator import kotlinx.coroutines.CancellationException import kotlinx.coroutines.launch diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SplashScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SplashScreen.kt index 827a280190..4c87d0088e 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SplashScreen.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SplashScreen.kt @@ -21,9 +21,9 @@ import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.generated.NavGraphs import com.ramcosta.composedestinations.generated.destinations.ConnectDestination import com.ramcosta.composedestinations.generated.destinations.DeviceRevokedDestination -import com.ramcosta.composedestinations.generated.destinations.LoginDestination import com.ramcosta.composedestinations.generated.destinations.OutOfTimeDestination import com.ramcosta.composedestinations.generated.destinations.PrivacyDisclaimerDestination +import com.ramcosta.composedestinations.generated.login.destinations.LoginDestination import com.ramcosta.composedestinations.navigation.DestinationsNavigator import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.common.compose.CollectSideEffectWithLifecycle diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/transitions/HomeTransition.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/transitions/HomeTransition.kt index ab837f091d..91388630c3 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/transitions/HomeTransition.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/transitions/HomeTransition.kt @@ -5,7 +5,7 @@ import androidx.compose.animation.EnterTransition import androidx.compose.animation.ExitTransition import androidx.compose.animation.fadeIn import androidx.navigation.NavBackStackEntry -import com.ramcosta.composedestinations.generated.destinations.LoginDestination +import com.ramcosta.composedestinations.generated.login.destinations.LoginDestination import com.ramcosta.composedestinations.spec.DestinationStyle import com.ramcosta.composedestinations.utils.destination diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/AppModule.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/AppModule.kt index 32855e88f6..e8f66b9518 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/AppModule.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/AppModule.kt @@ -16,6 +16,12 @@ import net.mullvad.mullvadvpn.lib.endpoint.ApiEndpointOverride import net.mullvad.mullvadvpn.lib.grpc.ManagementService import net.mullvad.mullvadvpn.lib.model.BuildVersion import net.mullvad.mullvadvpn.lib.model.NotificationChannel +import net.mullvad.mullvadvpn.lib.pushnotification.NotificationChannelFactory +import net.mullvad.mullvadvpn.lib.pushnotification.NotificationManager +import net.mullvad.mullvadvpn.lib.pushnotification.NotificationProvider +import net.mullvad.mullvadvpn.lib.pushnotification.ScheduleNotificationAlarmUseCase +import net.mullvad.mullvadvpn.lib.pushnotification.accountexpiry.AccountExpiryNotificationProvider +import net.mullvad.mullvadvpn.lib.pushnotification.tunnelstate.TunnelStateNotificationProvider import net.mullvad.mullvadvpn.lib.repository.AccountRepository import net.mullvad.mullvadvpn.lib.repository.AppObfuscationRepository import net.mullvad.mullvadvpn.lib.repository.ConnectionProxy @@ -27,12 +33,6 @@ import net.mullvad.mullvadvpn.lib.repository.UserPreferencesRepository import net.mullvad.mullvadvpn.lib.repository.UserPreferencesSerializer import net.mullvad.mullvadvpn.lib.usecase.AccountExpiryNotificationActionUseCase import net.mullvad.mullvadvpn.repository.UserPreferences -import net.mullvad.mullvadvpn.service.notifications.NotificationChannelFactory -import net.mullvad.mullvadvpn.service.notifications.NotificationManager -import net.mullvad.mullvadvpn.service.notifications.NotificationProvider -import net.mullvad.mullvadvpn.service.notifications.accountexpiry.AccountExpiryNotificationProvider -import net.mullvad.mullvadvpn.service.notifications.tunnelstate.TunnelStateNotificationProvider -import net.mullvad.mullvadvpn.usecase.ScheduleNotificationAlarmUseCase import org.koin.android.ext.koin.androidContext import org.koin.core.module.dsl.createdAtStart import org.koin.core.module.dsl.withOptions diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt index 2af820d2a8..81e5e1366c 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt @@ -28,6 +28,9 @@ import net.mullvad.mullvadvpn.feature.account.impl.AccountViewModel import net.mullvad.mullvadvpn.feature.addtime.impl.AddTimeViewModel import net.mullvad.mullvadvpn.feature.autoconnect.impl.AutoConnectAndLockdownModeViewModel import net.mullvad.mullvadvpn.feature.daita.impl.DaitaViewModel +import net.mullvad.mullvadvpn.feature.login.impl.LoginViewModel +import net.mullvad.mullvadvpn.feature.login.impl.apiunreachable.ApiUnreachableViewModel +import net.mullvad.mullvadvpn.feature.login.impl.devicelist.DeviceListViewModel import net.mullvad.mullvadvpn.feature.managedevices.impl.ManageDevicesViewModel import net.mullvad.mullvadvpn.feature.notification.impl.NotificationSettingsViewModel import net.mullvad.mullvadvpn.feature.problemreport.impl.ReportProblemViewModel @@ -100,11 +103,8 @@ import net.mullvad.mullvadvpn.serveripoverride.impl.reset.ResetServerIpOverrides import net.mullvad.mullvadvpn.ui.MainActivity import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager import net.mullvad.mullvadvpn.util.BackstackObserver -import net.mullvad.mullvadvpn.viewmodel.ApiUnreachableViewModel import net.mullvad.mullvadvpn.viewmodel.ConnectViewModel -import net.mullvad.mullvadvpn.viewmodel.DeviceListViewModel import net.mullvad.mullvadvpn.viewmodel.DeviceRevokedViewModel -import net.mullvad.mullvadvpn.viewmodel.LoginViewModel import net.mullvad.mullvadvpn.viewmodel.MullvadAppViewModel import net.mullvad.mullvadvpn.viewmodel.OutOfTimeViewModel import net.mullvad.mullvadvpn.viewmodel.PrivacyDisclaimerViewModel diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/receiver/ScheduleNotificationBootCompletedReceiver.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/receiver/ScheduleNotificationBootCompletedReceiver.kt index f5a37c86e5..dd745c2ebb 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/receiver/ScheduleNotificationBootCompletedReceiver.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/receiver/ScheduleNotificationBootCompletedReceiver.kt @@ -4,8 +4,8 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import co.touchlab.kermit.Logger +import net.mullvad.mullvadvpn.lib.pushnotification.ScheduleNotificationAlarmUseCase import net.mullvad.mullvadvpn.lib.repository.UserPreferencesRepository -import net.mullvad.mullvadvpn.usecase.ScheduleNotificationAlarmUseCase import net.mullvad.mullvadvpn.util.goAsync import org.koin.core.component.KoinComponent import org.koin.core.component.inject diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionManager.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionManager.kt index a8e054e00a..ca6f4f9d84 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionManager.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionManager.kt @@ -7,6 +7,7 @@ import android.content.pm.ServiceInfo import android.os.Build import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import net.mullvad.mullvadvpn.lib.common.serviceconnection.EmptyServiceConnection import net.mullvad.mullvadvpn.service.MullvadVpnService class ServiceConnectionManager(private val context: Context) { diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceRevokedViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceRevokedViewModel.kt index 14a8de9f6b..48e4322ce9 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceRevokedViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceRevokedViewModel.kt @@ -12,10 +12,10 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.compose.state.DeviceRevokedUiState import net.mullvad.mullvadvpn.lib.common.constant.VIEW_MODEL_STOP_TIMEOUT +import net.mullvad.mullvadvpn.lib.pushnotification.ScheduleNotificationAlarmUseCase +import net.mullvad.mullvadvpn.lib.pushnotification.accountexpiry.AccountExpiryNotificationProvider import net.mullvad.mullvadvpn.lib.repository.AccountRepository import net.mullvad.mullvadvpn.lib.repository.ConnectionProxy -import net.mullvad.mullvadvpn.service.notifications.accountexpiry.AccountExpiryNotificationProvider -import net.mullvad.mullvadvpn.usecase.ScheduleNotificationAlarmUseCase class DeviceRevokedViewModel( private val accountRepository: AccountRepository, diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceRevokedViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceRevokedViewModelTest.kt index c71a6efa4b..a56bdb8121 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceRevokedViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceRevokedViewModelTest.kt @@ -16,10 +16,10 @@ import kotlinx.coroutines.test.runTest import net.mullvad.mullvadvpn.compose.state.DeviceRevokedUiState import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule import net.mullvad.mullvadvpn.lib.model.TunnelState +import net.mullvad.mullvadvpn.lib.pushnotification.ScheduleNotificationAlarmUseCase +import net.mullvad.mullvadvpn.lib.pushnotification.accountexpiry.AccountExpiryNotificationProvider import net.mullvad.mullvadvpn.lib.repository.AccountRepository import net.mullvad.mullvadvpn.lib.repository.ConnectionProxy -import net.mullvad.mullvadvpn.service.notifications.accountexpiry.AccountExpiryNotificationProvider -import net.mullvad.mullvadvpn.usecase.ScheduleNotificationAlarmUseCase import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/Keyboard.kt b/android/lib/common-compose/src/main/kotlin/net/mullvad/mullvadvpn/common/compose/Keyboard.kt index ce2e9206cc..85830e6497 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/Keyboard.kt +++ b/android/lib/common-compose/src/main/kotlin/net/mullvad/mullvadvpn/common/compose/Keyboard.kt @@ -1,4 +1,4 @@ -package net.mullvad.mullvadvpn.compose.util +package net.mullvad.mullvadvpn.common.compose import android.content.Context import androidx.compose.ui.text.input.KeyboardType diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/EmptyServiceConnection.kt b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/serviceconnection/EmptyServiceConnection.kt index 28819f7aa0..b83bfea2e5 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/EmptyServiceConnection.kt +++ b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/serviceconnection/EmptyServiceConnection.kt @@ -1,4 +1,4 @@ -package net.mullvad.mullvadvpn.ui.serviceconnection +package net.mullvad.mullvadvpn.lib.common.serviceconnection import android.content.ComponentName import android.content.ServiceConnection diff --git a/android/lib/feature/account/impl/src/androidTest/kotlin/net/mullvad/mullvadvpn/feature/account/impl/AccountScreenTest.kt b/android/lib/feature/account/impl/src/androidTest/kotlin/net/mullvad/mullvadvpn/feature/account/impl/AccountScreenTest.kt index 41d30ea143..dccdd7269b 100644 --- a/android/lib/feature/account/impl/src/androidTest/kotlin/net/mullvad/mullvadvpn/feature/account/impl/AccountScreenTest.kt +++ b/android/lib/feature/account/impl/src/androidTest/kotlin/net/mullvad/mullvadvpn/feature/account/impl/AccountScreenTest.kt @@ -26,8 +26,7 @@ import org.koin.dsl.module @ExperimentalTestApi @OptIn(ExperimentalMaterial3Api::class) class AccountScreenTest { - @JvmField @RegisterExtension - val composeExtension = createEdgeToEdgeComposeExtension() + @JvmField @RegisterExtension val composeExtension = createEdgeToEdgeComposeExtension() private val addTimeViewModel: AddTimeViewModel = mockk(relaxed = true) @@ -36,7 +35,7 @@ class AccountScreenTest { MockKAnnotations.init(this) loadKoinModules(module { viewModel { addTimeViewModel } }) every { addTimeViewModel.uiState } returns - MutableStateFlow<Lc<Unit, AddTimeUiState>>(Lc.Loading(Unit)) + MutableStateFlow<Lc<Unit, AddTimeUiState>>(Lc.Loading(Unit)) } private fun ComposeContext.initScreen( diff --git a/android/lib/feature/account/impl/src/test/kotlin/net/mullvad/mullvadvpn/feature/account/impl/AccountViewModelTest.kt b/android/lib/feature/account/impl/src/test/kotlin/net/mullvad/mullvadvpn/feature/account/impl/AccountViewModelTest.kt index 6c8e1d9d6c..c94d097e9d 100644 --- a/android/lib/feature/account/impl/src/test/kotlin/net/mullvad/mullvadvpn/feature/account/impl/AccountViewModelTest.kt +++ b/android/lib/feature/account/impl/src/test/kotlin/net/mullvad/mullvadvpn/feature/account/impl/AccountViewModelTest.kt @@ -9,6 +9,9 @@ import io.mockk.every import io.mockk.just import io.mockk.mockk import io.mockk.unmockkAll +import java.time.ZonedDateTime +import kotlin.test.assertEquals +import kotlin.test.assertIs import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest import net.mullvad.mullvadvpn.lib.common.Lc @@ -29,9 +32,6 @@ import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith -import java.time.ZonedDateTime -import kotlin.test.assertEquals -import kotlin.test.assertIs @ExtendWith(TestCoroutineRule::class) class AccountViewModelTest { diff --git a/android/lib/feature/login/impl/build.gradle.kts b/android/lib/feature/login/impl/build.gradle.kts new file mode 100644 index 0000000000..96ccad3540 --- /dev/null +++ b/android/lib/feature/login/impl/build.gradle.kts @@ -0,0 +1,29 @@ +plugins { + alias(libs.plugins.mullvad.android.library) + alias(libs.plugins.mullvad.android.library.feature.impl) + alias(libs.plugins.mullvad.android.library.compose) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.parcelize) + alias(libs.plugins.kotlin.ksp) +} + +android { + namespace = "net.mullvad.mullvadvpn.feature.login.impl" + ksp { arg("compose-destinations.moduleName", "login") } +} + +dependencies { + implementation(projects.lib.pushNotification) + implementation(projects.lib.repository) + implementation(projects.lib.usecase) + implementation(projects.lib.feature.managedevices.impl) + implementation(projects.lib.feature.problemreport.impl) + implementation(projects.lib.feature.settings.impl) + + implementation(libs.koin.compose) + implementation(libs.arrow) + + // Destinations + implementation(libs.compose.destinations) + ksp(libs.compose.destinations.ksp) +} diff --git a/android/lib/feature/login/impl/src/main/AndroidManifest.xml b/android/lib/feature/login/impl/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..8bdb7e14b3 --- /dev/null +++ b/android/lib/feature/login/impl/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> + +</manifest> diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/CreateAccountConfirmationDialog.kt b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/CreateAccountConfirmationDialog.kt index 9ef731ee5d..4cc5ecd460 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/CreateAccountConfirmationDialog.kt +++ b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/CreateAccountConfirmationDialog.kt @@ -1,4 +1,4 @@ -package net.mullvad.mullvadvpn.compose.dialog +package net.mullvad.mullvadvpn.feature.login.impl import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -10,11 +10,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.annotation.ExternalModuleGraph import com.ramcosta.composedestinations.result.EmptyResultBackNavigator import com.ramcosta.composedestinations.result.ResultBackNavigator import com.ramcosta.composedestinations.spec.DestinationStyle -import net.mullvad.mullvadvpn.R -import net.mullvad.mullvadvpn.compose.screen.MainGraph import net.mullvad.mullvadvpn.lib.ui.component.dialog.Confirmed import net.mullvad.mullvadvpn.lib.ui.component.dialog.InfoConfirmationDialog import net.mullvad.mullvadvpn.lib.ui.component.dialog.InfoConfirmationDialogTitleType @@ -28,7 +27,7 @@ private fun PreviewCreateAccountConfirmationDialog() { } @Composable -@Destination<MainGraph>(style = DestinationStyle.Dialog::class) +@Destination<ExternalModuleGraph>(style = DestinationStyle.Dialog::class) fun CreateAccountConfirmation(navigator: ResultBackNavigator<Confirmed>) { InfoConfirmationDialog( onResult = { diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/LoginScreen.kt b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/LoginScreen.kt index 31654af42d..690176e1d0 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/LoginScreen.kt +++ b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/LoginScreen.kt @@ -1,4 +1,4 @@ -package net.mullvad.mullvadvpn.compose.screen +package net.mullvad.mullvadvpn.feature.login.impl import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.Image @@ -63,49 +63,37 @@ import androidx.compose.ui.unit.LayoutDirection import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.dropUnlessResumed import com.ramcosta.composedestinations.annotation.Destination -import com.ramcosta.composedestinations.generated.NavGraphs -import com.ramcosta.composedestinations.generated.destinations.ApiUnreachableInfoDestination -import com.ramcosta.composedestinations.generated.destinations.ConnectDestination -import com.ramcosta.composedestinations.generated.destinations.CreateAccountConfirmationDestination -import com.ramcosta.composedestinations.generated.destinations.DeviceListDestination -import com.ramcosta.composedestinations.generated.destinations.OutOfTimeDestination -import com.ramcosta.composedestinations.generated.destinations.WelcomeDestination +import com.ramcosta.composedestinations.annotation.ExternalModuleGraph +import com.ramcosta.composedestinations.generated.login.destinations.ApiUnreachableInfoDestination +import com.ramcosta.composedestinations.generated.login.destinations.CreateAccountConfirmationDestination +import com.ramcosta.composedestinations.generated.login.destinations.DeviceListDestination import com.ramcosta.composedestinations.generated.settings.destinations.SettingsDestination import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.result.ResultRecipient import kotlinx.coroutines.launch -import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.common.compose.CollectSideEffectWithLifecycle +import net.mullvad.mullvadvpn.common.compose.accountNumberKeyboardType import net.mullvad.mullvadvpn.common.compose.accountNumberVisualTransformation import net.mullvad.mullvadvpn.common.compose.clickableAnnotatedString import net.mullvad.mullvadvpn.common.compose.dropUnlessResumed import net.mullvad.mullvadvpn.common.compose.showSnackbarImmediately -import net.mullvad.mullvadvpn.compose.dialog.info.ApiUnreachableInfoDialogNavArgs -import net.mullvad.mullvadvpn.compose.dialog.info.ApiUnreachableInfoDialogResult -import net.mullvad.mullvadvpn.compose.dialog.info.LoginAction -import net.mullvad.mullvadvpn.compose.preview.LoginUiStatePreviewParameterProvider -import net.mullvad.mullvadvpn.compose.state.LoginState -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.compose.state.LoginUiStateError -import net.mullvad.mullvadvpn.compose.transitions.LoginTransition -import net.mullvad.mullvadvpn.compose.util.OnNavResultValue -import net.mullvad.mullvadvpn.compose.util.accountNumberKeyboardType +import net.mullvad.mullvadvpn.core.OnNavResultValue +import net.mullvad.mullvadvpn.core.animation.LoginTransition +import net.mullvad.mullvadvpn.feature.login.impl.apiunreachable.ApiUnreachableInfoDialogNavArgs +import net.mullvad.mullvadvpn.feature.login.impl.apiunreachable.ApiUnreachableInfoDialogResult +import net.mullvad.mullvadvpn.feature.login.impl.apiunreachable.LoginAction import net.mullvad.mullvadvpn.lib.ui.component.ScaffoldWithTopBar import net.mullvad.mullvadvpn.lib.ui.component.dialog.Confirmed import net.mullvad.mullvadvpn.lib.ui.component.textfield.mullvadWhiteTextFieldColors import net.mullvad.mullvadvpn.lib.ui.designsystem.MullvadCircularProgressIndicatorLarge import net.mullvad.mullvadvpn.lib.ui.designsystem.PrimaryButton import net.mullvad.mullvadvpn.lib.ui.designsystem.VariantButton +import net.mullvad.mullvadvpn.lib.ui.resource.R import net.mullvad.mullvadvpn.lib.ui.tag.LOGIN_INPUT_TEST_TAG import net.mullvad.mullvadvpn.lib.ui.tag.LOGIN_SCREEN_DELETE_ACCOUNT_HISTORY_TEST_TAG import net.mullvad.mullvadvpn.lib.ui.tag.LOGIN_TITLE_TEST_TAG import net.mullvad.mullvadvpn.lib.ui.theme.AppTheme import net.mullvad.mullvadvpn.lib.ui.theme.Dimens -import net.mullvad.mullvadvpn.viewmodel.LoginUiSideEffect -import net.mullvad.mullvadvpn.viewmodel.LoginViewModel import org.koin.androidx.compose.koinViewModel @Preview("Default|Loading.LoggingIn|Loading.CreatingAccount|LoginError|Success") @@ -130,7 +118,7 @@ private fun PreviewLoginScreen( private const val TOP_SPACER_WEIGHT = 1f private const val BOTTOM_SPACER_WEIGHT = 3f -@Destination<MainGraph>(style = LoginTransition::class) +@Destination<ExternalModuleGraph>(style = LoginTransition::class) @Composable fun Login( navigator: DestinationsNavigator, @@ -176,25 +164,28 @@ fun Login( CollectSideEffectWithLifecycle(vm.uiSideEffect) { when (it) { - LoginUiSideEffect.NavigateToWelcome -> - navigator.navigate(WelcomeDestination) { - launchSingleTop = true - popUpTo(NavGraphs.main) { inclusive = true } - } - is LoginUiSideEffect.NavigateToConnect -> - navigator.navigate(ConnectDestination) { - launchSingleTop = true - popUpTo(NavGraphs.main) { inclusive = true } - } + LoginUiSideEffect.NavigateToWelcome -> {} + // TODO How to solve this? + // navigator.navigate(WelcomeDestination) { + // launchSingleTop = true + // popUpTo(NavGraphs.main) { inclusive = true } + // } + is LoginUiSideEffect.NavigateToConnect -> {} + // TODO How to solve this? + // navigator.navigate(ConnectDestination) { + // launchSingleTop = true + // popUpTo(NavGraphs.main) { inclusive = true } + // } is LoginUiSideEffect.TooManyDevices -> navigator.navigate(DeviceListDestination(it.accountNumber)) { launchSingleTop = true } - LoginUiSideEffect.NavigateToOutOfTime -> - navigator.navigate(OutOfTimeDestination) { - launchSingleTop = true - popUpTo(NavGraphs.main) { inclusive = true } - } + LoginUiSideEffect.NavigateToOutOfTime -> {} + // TODO How to solve this? + // navigator.navigate(OutOfTimeDestination) { + // launchSingleTop = true + // popUpTo(NavGraphs.main) { inclusive = true } + // } LoginUiSideEffect.NavigateToCreateAccountConfirmation -> navigator.navigate(CreateAccountConfirmationDestination) LoginUiSideEffect.GenericError -> @@ -234,7 +225,7 @@ private fun LoginScreen( topBarColor = MaterialTheme.colorScheme.primary, iconTintColor = MaterialTheme.colorScheme.onPrimary, onSettingsClicked = onSettingsClick, - enabled = state.loginState is Idle, + enabled = state.loginState is LoginState.Idle, onAccountClicked = null, ) { val scrollState = rememberScrollState() @@ -260,7 +251,10 @@ private fun LoginScreen( onShowApiUnreachableDialog, ) Spacer(modifier = Modifier.weight(BOTTOM_SPACER_WEIGHT)) - CreateAccountPanel(onCreateAccountClick, isEnabled = state.loginState is Idle) + CreateAccountPanel( + onCreateAccountClick, + isEnabled = state.loginState is LoginState.Idle, + ) } } } @@ -346,19 +340,22 @@ private fun ColumnScope.LoginInput( keyboardOptions = KeyboardOptions( imeAction = if (state.loginButtonEnabled) ImeAction.Done else ImeAction.None, - keyboardType = KeyboardType.accountNumberKeyboardType(LocalContext.current), + keyboardType = + KeyboardType.Companion.accountNumberKeyboardType(LocalContext.current), ), onValueChange = onAccountNumberChange, singleLine = true, maxLines = 1, visualTransformation = accountNumberVisualTransformation(), - enabled = state.loginState is Idle, + enabled = state.loginState is LoginState.Idle, colors = mullvadWhiteTextFieldColors(), textStyle = MaterialTheme.typography.bodyLarge.copy(textDirection = TextDirection.Ltr), isError = state.loginState.isError(), ) - AnimatedVisibility(visible = state.lastUsedAccount != null && state.loginState is Idle) { + AnimatedVisibility( + visible = state.lastUsedAccount != null && state.loginState is LoginState.Idle + ) { val token = state.lastUsedAccount?.value.orEmpty() val accountTransformation = remember { accountNumberVisualTransformation() } val transformedText = @@ -374,7 +371,7 @@ private fun ColumnScope.LoginInput( onLoginClick(it.value) } }, - enabled = state.loginState is Idle, + enabled = state.loginState is LoginState.Idle, onDeleteClick = onDeleteHistoryClick, ) } @@ -385,7 +382,7 @@ private fun ColumnScope.LoginInput( private fun LoginIcon(loginState: LoginState, modifier: Modifier = Modifier) { Box(contentAlignment = Alignment.Center, modifier = modifier) { when (loginState) { - is Idle -> + is LoginState.Idle -> if (loginState.loginUiStateError != null) { Image( painter = painterResource(id = R.drawable.icon_fail), @@ -394,8 +391,8 @@ private fun LoginIcon(loginState: LoginState, modifier: Modifier = Modifier) { } else { // If view is Idle, we display empty box to keep the same size as other states } - is Loading -> MullvadCircularProgressIndicatorLarge() - Success -> + is LoginState.Loading -> MullvadCircularProgressIndicatorLarge() + LoginState.Success -> Image( painter = painterResource(id = R.drawable.icon_success), contentDescription = stringResource(id = R.string.logged_in_title), @@ -409,15 +406,15 @@ private fun LoginState.title(): String = stringResource( id = when (this) { - is Idle -> + is LoginState.Idle -> when (this.loginUiStateError) { is LoginUiStateError.LoginError -> R.string.login_fail_title is LoginUiStateError.CreateAccountError -> R.string.create_account_fail_title null -> R.string.log_in } - is Loading -> R.string.logging_in_title - Success -> R.string.logged_in_title + is LoginState.Loading -> R.string.logging_in_title + LoginState.Success -> R.string.logged_in_title } ) @@ -446,11 +443,11 @@ private fun LoginState.supportingText( onShowApiUnreachableDialog: (LoginUiStateError) -> Unit ): AnnotatedString? = when (this) { - is Idle if + is LoginState.Idle if loginUiStateError is LoginUiStateError.LoginError.ApiUnreachable || loginUiStateError is LoginUiStateError.CreateAccountError.ApiUnreachable -> apiUnreachableText(loginUiStateError, onShowApiUnreachableDialog) - is Idle -> { + is LoginState.Idle -> { when (loginUiStateError) { LoginUiStateError.LoginError.InvalidCredentials -> R.string.login_fail_description is LoginUiStateError.LoginError.InvalidInput -> R.string.login_error_invalid_input @@ -467,9 +464,9 @@ private fun LoginState.supportingText( null -> null }?.toAnnotatedString() } - is Loading.CreatingAccount -> R.string.creating_new_account.toAnnotatedString() - is Loading.LoggingIn -> R.string.logging_in_description.toAnnotatedString() - Success -> R.string.logged_in_description.toAnnotatedString() + is LoginState.Loading.CreatingAccount -> R.string.creating_new_account.toAnnotatedString() + is LoginState.Loading.LoggingIn -> R.string.logging_in_description.toAnnotatedString() + LoginState.Success -> R.string.logged_in_description.toAnnotatedString() } @Composable diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/LoginUiState.kt b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/LoginUiState.kt index 2dbde070f4..7bc16301a6 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/LoginUiState.kt +++ b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/LoginUiState.kt @@ -1,4 +1,4 @@ -package net.mullvad.mullvadvpn.compose.state +package net.mullvad.mullvadvpn.feature.login.impl import net.mullvad.mullvadvpn.lib.model.AccountNumber diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/LoginUiStatePreviewParameterProvider.kt b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/LoginUiStatePreviewParameterProvider.kt index a0e8ebbee4..9936772c3e 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/LoginUiStatePreviewParameterProvider.kt +++ b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/LoginUiStatePreviewParameterProvider.kt @@ -1,9 +1,6 @@ -package net.mullvad.mullvadvpn.compose.preview +package net.mullvad.mullvadvpn.feature.login.impl import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import net.mullvad.mullvadvpn.compose.state.LoginState -import net.mullvad.mullvadvpn.compose.state.LoginUiState -import net.mullvad.mullvadvpn.compose.state.LoginUiStateError class LoginUiStatePreviewParameterProvider : PreviewParameterProvider<LoginUiState> { override val values: Sequence<LoginUiState> diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModel.kt b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/LoginViewModel.kt index 7d8149c02e..6152058478 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModel.kt +++ b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/LoginViewModel.kt @@ -1,4 +1,4 @@ -package net.mullvad.mullvadvpn.viewmodel +package net.mullvad.mullvadvpn.feature.login.impl import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -20,12 +20,8 @@ import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import net.mullvad.mullvadvpn.compose.state.LoginState -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.compose.state.LoginUiStateError +import net.mullvad.mullvadvpn.feature.login.impl.LoginUiSideEffect.NavigateToWelcome +import net.mullvad.mullvadvpn.feature.login.impl.LoginUiSideEffect.TooManyDevices import net.mullvad.mullvadvpn.lib.common.constant.VIEW_MODEL_STOP_TIMEOUT import net.mullvad.mullvadvpn.lib.common.util.delayAtLeast import net.mullvad.mullvadvpn.lib.common.util.getOrDefault @@ -33,13 +29,11 @@ import net.mullvad.mullvadvpn.lib.common.util.isBeforeNowInstant import net.mullvad.mullvadvpn.lib.model.AccountNumber import net.mullvad.mullvadvpn.lib.model.CreateAccountError import net.mullvad.mullvadvpn.lib.model.LoginAccountError +import net.mullvad.mullvadvpn.lib.pushnotification.ScheduleNotificationAlarmUseCase +import net.mullvad.mullvadvpn.lib.pushnotification.accountexpiry.AccountExpiryNotificationProvider import net.mullvad.mullvadvpn.lib.repository.AccountRepository import net.mullvad.mullvadvpn.lib.repository.NewDeviceRepository import net.mullvad.mullvadvpn.lib.usecase.InternetAvailableUseCase -import net.mullvad.mullvadvpn.service.notifications.accountexpiry.AccountExpiryNotificationProvider -import net.mullvad.mullvadvpn.usecase.ScheduleNotificationAlarmUseCase -import net.mullvad.mullvadvpn.viewmodel.LoginUiSideEffect.NavigateToWelcome -import net.mullvad.mullvadvpn.viewmodel.LoginUiSideEffect.TooManyDevices private const val MINIMUM_LOADING_SPINNER_TIME_MILLIS = 500L @@ -116,7 +110,7 @@ class LoginViewModel( } private fun createAccount() { - _loginState.value = Loading.CreatingAccount + _loginState.value = LoginState.Loading.CreatingAccount viewModelScope.launch(dispatcher) { accountRepository .createAccount() @@ -128,7 +122,7 @@ class LoginViewModel( } fun login(accountNumber: String) { - _loginState.value = Loading.LoggingIn + _loginState.value = LoginState.Loading.LoggingIn viewModelScope.launch(dispatcher) { val uiState = // Ensure we always take at least MINIMUM_LOADING_SPINNER_TIME_MILLIS to show the @@ -140,7 +134,7 @@ class LoginViewModel( { it.toUiState() }, { onSuccessfulLogin() - Success + LoginState.Success }, ) @@ -176,27 +170,28 @@ class LoginViewModel( fun onAccountNumberChange(accountNumber: String) { _loginInput.value = accountNumber.filter { it.isDigit() } // If there is an error, clear it - _loginState.update { if (it is Idle) Idle() else it } + _loginState.update { if (it is LoginState.Idle) LoginState.Idle() else it } } private suspend fun LoginAccountError.toUiState(): LoginState = when (this) { LoginAccountError.InvalidAccount -> - Idle(LoginUiStateError.LoginError.InvalidCredentials) + LoginState.Idle(LoginUiStateError.LoginError.InvalidCredentials) is LoginAccountError.MaxDevicesReached -> - Idle().also { _uiSideEffect.send(TooManyDevices(accountNumber)) } + LoginState.Idle().also { _uiSideEffect.send(TooManyDevices(accountNumber)) } is LoginAccountError.InvalidInput -> - Idle(LoginUiStateError.LoginError.InvalidInput(accountNumber)) + LoginState.Idle(LoginUiStateError.LoginError.InvalidInput(accountNumber)) LoginAccountError.Timeout, LoginAccountError.ApiUnreachable -> if (isInternetAvailable()) { - Idle(LoginUiStateError.LoginError.ApiUnreachable) + LoginState.Idle(LoginUiStateError.LoginError.ApiUnreachable) } else { - Idle(LoginUiStateError.LoginError.NoInternetConnection) + LoginState.Idle(LoginUiStateError.LoginError.NoInternetConnection) } - LoginAccountError.TooManyAttempts -> Idle(LoginUiStateError.LoginError.TooManyAttempts) + LoginAccountError.TooManyAttempts -> + LoginState.Idle(LoginUiStateError.LoginError.TooManyAttempts) is LoginAccountError.Unknown -> - Idle(LoginUiStateError.LoginError.Unknown(this.toString())).also { + LoginState.Idle(LoginUiStateError.LoginError.Unknown(this.toString())).also { Logger.w("Login failed with error: $this", error) } } @@ -206,14 +201,14 @@ class LoginViewModel( CreateAccountError.ApiUnreachable, CreateAccountError.TimeOut -> if (isInternetAvailable()) { - Idle(LoginUiStateError.CreateAccountError.ApiUnreachable) + LoginState.Idle(LoginUiStateError.CreateAccountError.ApiUnreachable) } else { - Idle(LoginUiStateError.CreateAccountError.NoInternetConnection) + LoginState.Idle(LoginUiStateError.CreateAccountError.NoInternetConnection) } CreateAccountError.TooManyAttempts -> - Idle(LoginUiStateError.CreateAccountError.TooManyAttempts) + LoginState.Idle(LoginUiStateError.CreateAccountError.TooManyAttempts) is CreateAccountError.Unknown -> - Idle(LoginUiStateError.CreateAccountError.Unknown).also { + LoginState.Idle(LoginUiStateError.CreateAccountError.Unknown).also { Logger.w("Create account failed with error: $this", error) } } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/info/ApiUnreachableInfoDialog.kt b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/apiunreachable/ApiUnreachableInfoDialog.kt index 6fd6f4b527..1bb5ed19b9 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/info/ApiUnreachableInfoDialog.kt +++ b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/apiunreachable/ApiUnreachableInfoDialog.kt @@ -1,4 +1,4 @@ -package net.mullvad.mullvadvpn.compose.dialog.info +package net.mullvad.mullvadvpn.feature.login.impl.apiunreachable import android.content.ActivityNotFoundException import android.os.Parcelable @@ -17,23 +17,18 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.lifecycle.compose.collectAsStateWithLifecycle import co.touchlab.kermit.Logger import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.annotation.ExternalModuleGraph import com.ramcosta.composedestinations.result.ResultBackNavigator import com.ramcosta.composedestinations.spec.DestinationStyle import kotlinx.parcelize.Parcelize -import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.common.compose.CollectSideEffectWithLifecycle -import net.mullvad.mullvadvpn.compose.screen.MainGraph -import net.mullvad.mullvadvpn.compose.state.ApiUnreachableUiState -import net.mullvad.mullvadvpn.compose.util.EmailData -import net.mullvad.mullvadvpn.compose.util.SendEmail import net.mullvad.mullvadvpn.feature.problemreport.impl.provider.createShareLogFile import net.mullvad.mullvadvpn.lib.ui.component.dialog.InfoDialog import net.mullvad.mullvadvpn.lib.ui.component.textfield.ErrorSupportingText import net.mullvad.mullvadvpn.lib.ui.designsystem.PrimaryButton +import net.mullvad.mullvadvpn.lib.ui.resource.R import net.mullvad.mullvadvpn.lib.ui.theme.AppTheme import net.mullvad.mullvadvpn.lib.ui.theme.Dimens -import net.mullvad.mullvadvpn.viewmodel.ApiUnreachableSideEffect -import net.mullvad.mullvadvpn.viewmodel.ApiUnreachableViewModel import org.koin.androidx.compose.koinViewModel @Preview @@ -69,7 +64,7 @@ sealed interface ApiUnreachableInfoDialogResult : Parcelable { @Parcelize data object Error : ApiUnreachableInfoDialogResult } -@Destination<MainGraph>( +@Destination<ExternalModuleGraph>( style = DestinationStyle.Dialog::class, navArgs = ApiUnreachableInfoDialogNavArgs::class, ) diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/ApiUnreachableUiState.kt b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/apiunreachable/ApiUnreachableUiState.kt index 2a4f407934..304552ee66 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/ApiUnreachableUiState.kt +++ b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/apiunreachable/ApiUnreachableUiState.kt @@ -1,6 +1,4 @@ -package net.mullvad.mullvadvpn.compose.state - -import net.mullvad.mullvadvpn.compose.dialog.info.LoginAction +package net.mullvad.mullvadvpn.feature.login.impl.apiunreachable data class ApiUnreachableUiState( val showEnableAllAccessMethodsButton: Boolean, diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ApiUnreachableViewModel.kt b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/apiunreachable/ApiUnreachableViewModel.kt index 6d5b1c2d85..a3dd4b1bf4 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ApiUnreachableViewModel.kt +++ b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/apiunreachable/ApiUnreachableViewModel.kt @@ -1,9 +1,9 @@ -package net.mullvad.mullvadvpn.viewmodel +package net.mullvad.mullvadvpn.feature.login.impl.apiunreachable import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.ramcosta.composedestinations.generated.destinations.ApiUnreachableInfoDestination +import com.ramcosta.composedestinations.generated.login.destinations.ApiUnreachableInfoDestination import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -14,8 +14,6 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import net.mullvad.mullvadvpn.compose.dialog.info.ApiUnreachableInfoDialogNavArgs -import net.mullvad.mullvadvpn.compose.state.ApiUnreachableUiState import net.mullvad.mullvadvpn.lib.common.constant.VIEW_MODEL_STOP_TIMEOUT import net.mullvad.mullvadvpn.lib.repository.ApiAccessRepository import net.mullvad.mullvadvpn.lib.ui.component.NEWLINE_STRING diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/SendEmail.kt b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/apiunreachable/SendEmail.kt index 5d02ad555f..b595c3234a 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/SendEmail.kt +++ b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/apiunreachable/SendEmail.kt @@ -1,4 +1,4 @@ -package net.mullvad.mullvadvpn.compose.util +package net.mullvad.mullvadvpn.feature.login.impl.apiunreachable import android.content.Context import android.content.Intent diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceListScreen.kt b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/devicelist/DeviceListScreen.kt index 6b5eb9005b..f8cd65a73b 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceListScreen.kt +++ b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/devicelist/DeviceListScreen.kt @@ -1,4 +1,4 @@ -package net.mullvad.mullvadvpn.compose.screen +package net.mullvad.mullvadvpn.feature.login.impl.devicelist import androidx.compose.animation.animateContentSize import androidx.compose.foundation.Image @@ -29,19 +29,17 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.dropUnlessResumed import com.ramcosta.composedestinations.annotation.Destination -import com.ramcosta.composedestinations.generated.destinations.LoginDestination -import com.ramcosta.composedestinations.generated.destinations.RemoveDeviceConfirmationDestination +import com.ramcosta.composedestinations.annotation.ExternalModuleGraph +import com.ramcosta.composedestinations.generated.login.destinations.LoginDestination +import com.ramcosta.composedestinations.generated.login.destinations.RemoveDeviceConfirmationDestination import com.ramcosta.composedestinations.generated.settings.destinations.SettingsDestination import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.result.NavResult import com.ramcosta.composedestinations.result.ResultRecipient import kotlinx.coroutines.launch -import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.common.compose.CollectSideEffectWithLifecycle import net.mullvad.mullvadvpn.common.compose.dropUnlessResumed import net.mullvad.mullvadvpn.common.compose.showSnackbarImmediately -import net.mullvad.mullvadvpn.compose.preview.DeviceListUiStatePreviewParameterProvider -import net.mullvad.mullvadvpn.compose.state.DeviceListUiState import net.mullvad.mullvadvpn.core.animation.DefaultTransition import net.mullvad.mullvadvpn.lib.model.AccountNumber import net.mullvad.mullvadvpn.lib.model.Device @@ -53,11 +51,10 @@ import net.mullvad.mullvadvpn.lib.ui.component.positionForIndex import net.mullvad.mullvadvpn.lib.ui.designsystem.MullvadCircularProgressIndicatorLarge import net.mullvad.mullvadvpn.lib.ui.designsystem.PrimaryButton import net.mullvad.mullvadvpn.lib.ui.designsystem.VariantButton +import net.mullvad.mullvadvpn.lib.ui.resource.R import net.mullvad.mullvadvpn.lib.ui.theme.AppTheme import net.mullvad.mullvadvpn.lib.ui.theme.Dimens import net.mullvad.mullvadvpn.lib.ui.theme.color.selected -import net.mullvad.mullvadvpn.viewmodel.DeviceListSideEffect -import net.mullvad.mullvadvpn.viewmodel.DeviceListViewModel import org.koin.androidx.compose.koinViewModel @Composable @@ -80,7 +77,10 @@ private fun PreviewDeviceListScreenContent( data class DeviceListNavArgs(val accountNumber: AccountNumber) -@Destination<MainGraph>(style = DefaultTransition::class, navArgs = DeviceListNavArgs::class) +@Destination<ExternalModuleGraph>( + style = DefaultTransition::class, + navArgs = DeviceListNavArgs::class, +) @Composable fun DeviceList( navigator: DestinationsNavigator, @@ -207,7 +207,7 @@ private fun ColumnScope.DeviceListError(tryAgain: () -> Unit) { } @Composable -private fun ColumnScope.DeviceListContent( +private fun DeviceListContent( state: DeviceListUiState.Content, navigateToRemoveDeviceConfirmationDialog: (Device) -> Unit, ) { diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/DeviceListUiState.kt b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/devicelist/DeviceListUiState.kt index adc5e3d07d..5da1312a44 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/DeviceListUiState.kt +++ b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/devicelist/DeviceListUiState.kt @@ -1,4 +1,4 @@ -package net.mullvad.mullvadvpn.compose.state +package net.mullvad.mullvadvpn.feature.login.impl.devicelist import net.mullvad.mullvadvpn.lib.model.Device import net.mullvad.mullvadvpn.lib.model.GetDeviceListError diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/DeviceListUiStatePreviewParameterProvider.kt b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/devicelist/DeviceListUiStatePreviewParameterProvider.kt index 53102efb21..3d846bc7d0 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/DeviceListUiStatePreviewParameterProvider.kt +++ b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/devicelist/DeviceListUiStatePreviewParameterProvider.kt @@ -1,8 +1,7 @@ -package net.mullvad.mullvadvpn.compose.preview +package net.mullvad.mullvadvpn.feature.login.impl.devicelist import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import net.mullvad.mullvadvpn.compose.preview.DevicePreviewData.generateDevices -import net.mullvad.mullvadvpn.compose.state.DeviceListUiState +import net.mullvad.mullvadvpn.feature.login.impl.devicelist.DevicePreviewData.generateDevices import net.mullvad.mullvadvpn.lib.model.GetDeviceListError class DeviceListUiStatePreviewParameterProvider : PreviewParameterProvider<DeviceListUiState> { diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceListViewModel.kt b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/devicelist/DeviceListViewModel.kt index a1178cc2b8..341fe1c844 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceListViewModel.kt +++ b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/devicelist/DeviceListViewModel.kt @@ -1,9 +1,9 @@ -package net.mullvad.mullvadvpn.viewmodel +package net.mullvad.mullvadvpn.feature.login.impl.devicelist import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.ramcosta.composedestinations.generated.destinations.DeviceListDestination +import com.ramcosta.composedestinations.generated.login.destinations.DeviceListDestination import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.Channel @@ -18,8 +18,6 @@ import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import net.mullvad.mullvadvpn.compose.state.DeviceItemUiState -import net.mullvad.mullvadvpn.compose.state.DeviceListUiState import net.mullvad.mullvadvpn.lib.common.constant.VIEW_MODEL_STOP_TIMEOUT import net.mullvad.mullvadvpn.lib.model.AccountNumber import net.mullvad.mullvadvpn.lib.model.Device diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/DevicePreviewData.kt b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/devicelist/DevicePreviewData.kt index 138706930f..edefed7687 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/DevicePreviewData.kt +++ b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/devicelist/DevicePreviewData.kt @@ -1,8 +1,7 @@ -package net.mullvad.mullvadvpn.compose.preview +package net.mullvad.mullvadvpn.feature.login.impl.devicelist 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 diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/RemoveDeviceConfirmationDialog.kt b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/devicelist/RemoveDeviceConfirmationDialog.kt index 0715eec43d..79aaab0c05 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/RemoveDeviceConfirmationDialog.kt +++ b/android/lib/feature/login/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/login/impl/devicelist/RemoveDeviceConfirmationDialog.kt @@ -1,4 +1,4 @@ -package net.mullvad.mullvadvpn.compose.dialog +package net.mullvad.mullvadvpn.feature.login.impl.devicelist import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable @@ -10,16 +10,16 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.core.text.HtmlCompat import androidx.lifecycle.compose.dropUnlessResumed import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.annotation.ExternalModuleGraph import com.ramcosta.composedestinations.result.EmptyResultBackNavigator import com.ramcosta.composedestinations.result.ResultBackNavigator import com.ramcosta.composedestinations.spec.DestinationStyle -import net.mullvad.mullvadvpn.R -import net.mullvad.mullvadvpn.compose.screen.MainGraph import net.mullvad.mullvadvpn.feature.managedevices.impl.confirmation.ManageDeviceRemoveConfirmationPreviewParameterProvider import net.mullvad.mullvadvpn.lib.model.Device import net.mullvad.mullvadvpn.lib.model.DeviceId import net.mullvad.mullvadvpn.lib.ui.component.dialog.NegativeConfirmationDialog import net.mullvad.mullvadvpn.lib.ui.component.toAnnotatedString +import net.mullvad.mullvadvpn.lib.ui.resource.R import net.mullvad.mullvadvpn.lib.ui.theme.AppTheme @Preview @@ -30,7 +30,7 @@ private fun PreviewRemoveDeviceConfirmationDialog( AppTheme { RemoveDeviceConfirmation(EmptyResultBackNavigator(), device = device) } } -@Destination<MainGraph>(style = DestinationStyle.Dialog::class) +@Destination<ExternalModuleGraph>(style = DestinationStyle.Dialog::class) @Composable fun RemoveDeviceConfirmation(navigator: ResultBackNavigator<DeviceId>, device: Device) { val htmlFormattedString = diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceListViewModelTest.kt b/android/lib/feature/login/impl/src/test/java/net/mullvad/mullvadvpn/feature/login/impl/DeviceListViewModelTest.kt index fc9e244faa..f6d5f2f192 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceListViewModelTest.kt +++ b/android/lib/feature/login/impl/src/test/java/net/mullvad/mullvadvpn/feature/login/impl/DeviceListViewModelTest.kt @@ -1,11 +1,11 @@ -package net.mullvad.mullvadvpn.viewmodel +package net.mullvad.mullvadvpn.feature.login.impl import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import app.cash.turbine.test import arrow.core.left import arrow.core.right -import com.ramcosta.composedestinations.generated.navargs.toSavedStateHandle +import com.ramcosta.composedestinations.generated.login.navargs.toSavedStateHandle import io.mockk.coEvery import io.mockk.every import io.mockk.mockk @@ -18,8 +18,10 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest -import net.mullvad.mullvadvpn.compose.screen.DeviceListNavArgs -import net.mullvad.mullvadvpn.compose.state.DeviceListUiState +import net.mullvad.mullvadvpn.feature.login.impl.devicelist.DeviceListNavArgs +import net.mullvad.mullvadvpn.feature.login.impl.devicelist.DeviceListSideEffect +import net.mullvad.mullvadvpn.feature.login.impl.devicelist.DeviceListUiState +import net.mullvad.mullvadvpn.feature.login.impl.devicelist.DeviceListViewModel import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule import net.mullvad.mullvadvpn.lib.model.AccountNumber import net.mullvad.mullvadvpn.lib.model.DeleteDeviceError diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModelTest.kt b/android/lib/feature/login/impl/src/test/java/net/mullvad/mullvadvpn/feature/login/impl/LoginViewModelTest.kt index 7a49b3289d..409ba1ea27 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModelTest.kt +++ b/android/lib/feature/login/impl/src/test/java/net/mullvad/mullvadvpn/feature/login/impl/LoginViewModelTest.kt @@ -1,4 +1,4 @@ -package net.mullvad.mullvadvpn.viewmodel +package net.mullvad.mullvadvpn.feature.login.impl import app.cash.turbine.ReceiveTurbine import app.cash.turbine.test @@ -17,21 +17,19 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest -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.compose.state.LoginUiStateError -import net.mullvad.mullvadvpn.data.mock +import net.mullvad.mullvadvpn.feature.login.impl.LoginState.Idle +import net.mullvad.mullvadvpn.feature.login.impl.LoginState.Loading +import net.mullvad.mullvadvpn.feature.login.impl.LoginState.Success +import net.mullvad.mullvadvpn.feature.login.impl.data.mock import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule import net.mullvad.mullvadvpn.lib.model.AccountData import net.mullvad.mullvadvpn.lib.model.AccountNumber import net.mullvad.mullvadvpn.lib.model.CreateAccountError import net.mullvad.mullvadvpn.lib.model.LoginAccountError +import net.mullvad.mullvadvpn.lib.pushnotification.ScheduleNotificationAlarmUseCase +import net.mullvad.mullvadvpn.lib.pushnotification.accountexpiry.AccountExpiryNotificationProvider import net.mullvad.mullvadvpn.lib.repository.AccountRepository import net.mullvad.mullvadvpn.lib.usecase.InternetAvailableUseCase -import net.mullvad.mullvadvpn.service.notifications.accountexpiry.AccountExpiryNotificationProvider -import net.mullvad.mullvadvpn.usecase.ScheduleNotificationAlarmUseCase import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test diff --git a/android/lib/feature/login/impl/src/test/java/net/mullvad/mullvadvpn/feature/login/impl/data/AccountData.kt b/android/lib/feature/login/impl/src/test/java/net/mullvad/mullvadvpn/feature/login/impl/data/AccountData.kt new file mode 100644 index 0000000000..d9e03e76ce --- /dev/null +++ b/android/lib/feature/login/impl/src/test/java/net/mullvad/mullvadvpn/feature/login/impl/data/AccountData.kt @@ -0,0 +1,12 @@ +package net.mullvad.mullvadvpn.feature.login.impl.data + +import io.mockk.mockk +import java.time.ZonedDateTime +import net.mullvad.mullvadvpn.lib.model.AccountData + +fun AccountData.Companion.mock(expiry: ZonedDateTime): AccountData = + AccountData( + id = mockk(relaxed = true), + accountNumber = mockk(relaxed = true), + expiryDate = expiry, + ) diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/transitions/LoginTransition.kt b/android/lib/navigation/src/main/java/net/mullvad/mullvadvpn/core/animation/LoginTransition.kt index 2c82d24d7c..b43594752a 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/transitions/LoginTransition.kt +++ b/android/lib/navigation/src/main/java/net/mullvad/mullvadvpn/core/animation/LoginTransition.kt @@ -1,4 +1,4 @@ -package net.mullvad.mullvadvpn.compose.transitions +package net.mullvad.mullvadvpn.core.animation import androidx.compose.animation.AnimatedContentTransitionScope import androidx.compose.animation.EnterTransition @@ -7,12 +7,7 @@ import androidx.compose.animation.core.spring import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.navigation.NavBackStackEntry -import com.ramcosta.composedestinations.generated.destinations.ConnectDestination -import com.ramcosta.composedestinations.generated.destinations.DeviceListDestination -import com.ramcosta.composedestinations.generated.destinations.OutOfTimeDestination -import com.ramcosta.composedestinations.generated.destinations.WelcomeDestination import com.ramcosta.composedestinations.spec.DestinationStyle -import com.ramcosta.composedestinations.utils.destination object LoginTransition : DestinationStyle.Animated() { override val enterTransition: @@ -24,13 +19,15 @@ object LoginTransition : DestinationStyle.Animated() { override val exitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition = { - when (this.targetState.destination()) { - is OutOfTimeDestination, - is WelcomeDestination, - is ConnectDestination, - is DeviceListDestination -> fadeOut(spring()) - else -> ExitTransition.None - } + // TODO How to solve this? + fadeOut(spring()) + // when (this.targetState.destination()) { + // is OutOfTimeDestination, + // is WelcomeDestination, + // is ConnectDestination, + // is DeviceListDestination -> fadeOut(spring()) + // else -> ExitTransition.None + // } } override val popEnterTransition: diff --git a/android/lib/push-notification/build.gradle.kts b/android/lib/push-notification/build.gradle.kts new file mode 100644 index 0000000000..72f1751647 --- /dev/null +++ b/android/lib/push-notification/build.gradle.kts @@ -0,0 +1,22 @@ +plugins { + alias(libs.plugins.mullvad.android.library) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.parcelize) +} + +android { namespace = "net.mullvad.mullvadvpn.feature.pushnotifications" } + +dependencies { + implementation(projects.lib.common) + implementation(projects.lib.model) + implementation(projects.lib.repository) + implementation(projects.lib.ui.resource) + + implementation(libs.androidx.ktx) + implementation(libs.androidx.lifecycle.service) + implementation(libs.androidx.work.runtime.ktx) + implementation(libs.arrow) + implementation(libs.kermit) + implementation(libs.koin.android) + implementation(libs.protobuf.kotlin.lite) +} diff --git a/android/lib/push-notification/src/main/AndroidManifest.xml b/android/lib/push-notification/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..8bdb7e14b3 --- /dev/null +++ b/android/lib/push-notification/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> + +</manifest> diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/NotificationChannelFactory.kt b/android/lib/push-notification/src/main/kotlin/net/mullvad/mullvadvpn/lib/pushnotification/NotificationChannelFactory.kt index 72539e8e94..c8ec728937 100644 --- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/NotificationChannelFactory.kt +++ b/android/lib/push-notification/src/main/kotlin/net/mullvad/mullvadvpn/lib/pushnotification/NotificationChannelFactory.kt @@ -1,4 +1,4 @@ -package net.mullvad.mullvadvpn.service.notifications +package net.mullvad.mullvadvpn.lib.pushnotification import android.app.NotificationManager import android.content.res.Resources diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/NotificationManager.kt b/android/lib/push-notification/src/main/kotlin/net/mullvad/mullvadvpn/lib/pushnotification/NotificationManager.kt index ed2d2f053a..6ca4f33007 100644 --- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/NotificationManager.kt +++ b/android/lib/push-notification/src/main/kotlin/net/mullvad/mullvadvpn/lib/pushnotification/NotificationManager.kt @@ -1,4 +1,4 @@ -package net.mullvad.mullvadvpn.service.notifications +package net.mullvad.mullvadvpn.lib.pushnotification import android.Manifest import android.content.Context @@ -13,15 +13,15 @@ import kotlinx.coroutines.flow.merge import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.lib.model.Notification import net.mullvad.mullvadvpn.lib.model.NotificationUpdate -import net.mullvad.mullvadvpn.service.notifications.accountexpiry.toNotification -import net.mullvad.mullvadvpn.service.notifications.tunnelstate.toNotification +import net.mullvad.mullvadvpn.lib.pushnotification.accountexpiry.toNotification +import net.mullvad.mullvadvpn.lib.pushnotification.tunnelstate.toNotification @OptIn(FlowPreview::class) class NotificationManager( private val notificationManagerCompat: NotificationManagerCompat, notificationProviders: List<NotificationProvider<Notification>>, context: Context, - val scope: CoroutineScope, + scope: CoroutineScope, ) { init { diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/NotificationProvider.kt b/android/lib/push-notification/src/main/kotlin/net/mullvad/mullvadvpn/lib/pushnotification/NotificationProvider.kt index ecdde13d7a..71d587e030 100644 --- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/NotificationProvider.kt +++ b/android/lib/push-notification/src/main/kotlin/net/mullvad/mullvadvpn/lib/pushnotification/NotificationProvider.kt @@ -1,4 +1,4 @@ -package net.mullvad.mullvadvpn.service.notifications +package net.mullvad.mullvadvpn.lib.pushnotification import kotlinx.coroutines.flow.Flow import net.mullvad.mullvadvpn.lib.model.NotificationUpdate diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/ScheduleNotificationAlarmUseCase.kt b/android/lib/push-notification/src/main/kotlin/net/mullvad/mullvadvpn/lib/pushnotification/ScheduleNotificationAlarmUseCase.kt index 1d178a15bf..5b40970a67 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/ScheduleNotificationAlarmUseCase.kt +++ b/android/lib/push-notification/src/main/kotlin/net/mullvad/mullvadvpn/lib/pushnotification/ScheduleNotificationAlarmUseCase.kt @@ -1,4 +1,4 @@ -package net.mullvad.mullvadvpn.usecase +package net.mullvad.mullvadvpn.lib.pushnotification import android.app.AlarmManager import android.app.PendingIntent @@ -8,8 +8,8 @@ import co.touchlab.kermit.Logger import java.time.ZoneOffset import java.time.ZonedDateTime import net.mullvad.mullvadvpn.lib.common.util.accountExpiryNotificationTriggerAt +import net.mullvad.mullvadvpn.lib.pushnotification.receiver.NotificationAlarmReceiver import net.mullvad.mullvadvpn.lib.repository.UserPreferencesRepository -import net.mullvad.mullvadvpn.receiver.NotificationAlarmReceiver class ScheduleNotificationAlarmUseCase( private val applicationContext: Context, diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/ShouldBeOnForegroundProvider.kt b/android/lib/push-notification/src/main/kotlin/net/mullvad/mullvadvpn/lib/pushnotification/ShouldBeOnForegroundProvider.kt index 1fdfb1ecb0..7caa49b3c8 100644 --- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/ShouldBeOnForegroundProvider.kt +++ b/android/lib/push-notification/src/main/kotlin/net/mullvad/mullvadvpn/lib/pushnotification/ShouldBeOnForegroundProvider.kt @@ -1,4 +1,4 @@ -package net.mullvad.mullvadvpn.service.notifications +package net.mullvad.mullvadvpn.lib.pushnotification import kotlinx.coroutines.flow.Flow diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/accountexpiry/AccountExpiryAndroidNotification.kt b/android/lib/push-notification/src/main/kotlin/net/mullvad/mullvadvpn/lib/pushnotification/accountexpiry/AccountExpiryAndroidNotification.kt index 97212797cd..c167a14870 100644 --- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/accountexpiry/AccountExpiryAndroidNotification.kt +++ b/android/lib/push-notification/src/main/kotlin/net/mullvad/mullvadvpn/lib/pushnotification/accountexpiry/AccountExpiryAndroidNotification.kt @@ -1,4 +1,4 @@ -package net.mullvad.mullvadvpn.service.notifications.accountexpiry +package net.mullvad.mullvadvpn.lib.pushnotification.accountexpiry import android.app.PendingIntent import android.content.Context @@ -9,7 +9,7 @@ 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.model.Notification -import net.mullvad.mullvadvpn.service.R +import net.mullvad.mullvadvpn.lib.ui.resource.R internal fun Notification.AccountExpiry.toNotification(context: Context) = NotificationCompat.Builder(context, channelId.value) diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/accountexpiry/AccountExpiryNotificationProvider.kt b/android/lib/push-notification/src/main/kotlin/net/mullvad/mullvadvpn/lib/pushnotification/accountexpiry/AccountExpiryNotificationProvider.kt index 0be99896c9..2b72c0aac5 100644 --- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/accountexpiry/AccountExpiryNotificationProvider.kt +++ b/android/lib/push-notification/src/main/kotlin/net/mullvad/mullvadvpn/lib/pushnotification/accountexpiry/AccountExpiryNotificationProvider.kt @@ -1,4 +1,4 @@ -package net.mullvad.mullvadvpn.service.notifications.accountexpiry +package net.mullvad.mullvadvpn.lib.pushnotification.accountexpiry import co.touchlab.kermit.Logger import java.time.Duration @@ -9,7 +9,7 @@ import net.mullvad.mullvadvpn.lib.model.Notification import net.mullvad.mullvadvpn.lib.model.NotificationChannelId import net.mullvad.mullvadvpn.lib.model.NotificationId import net.mullvad.mullvadvpn.lib.model.NotificationUpdate -import net.mullvad.mullvadvpn.service.notifications.NotificationProvider +import net.mullvad.mullvadvpn.lib.pushnotification.NotificationProvider class AccountExpiryNotificationProvider(private val channelId: NotificationChannelId) : NotificationProvider<Notification.AccountExpiry> { diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/receiver/NotificationAlarmReceiver.kt b/android/lib/push-notification/src/main/kotlin/net/mullvad/mullvadvpn/lib/pushnotification/receiver/NotificationAlarmReceiver.kt index 39f92f47f0..9692d555ed 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/receiver/NotificationAlarmReceiver.kt +++ b/android/lib/push-notification/src/main/kotlin/net/mullvad/mullvadvpn/lib/pushnotification/receiver/NotificationAlarmReceiver.kt @@ -1,15 +1,16 @@ -package net.mullvad.mullvadvpn.receiver +package net.mullvad.mullvadvpn.lib.pushnotification.receiver import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.os.Build import androidx.work.Constraints import androidx.work.NetworkType import androidx.work.OneTimeWorkRequestBuilder import androidx.work.OutOfQuotaPolicy import androidx.work.WorkManager import co.touchlab.kermit.Logger -import net.mullvad.mullvadvpn.worker.ExpiryNotificationWorker +import net.mullvad.mullvadvpn.lib.pushnotification.worker.ExpiryNotificationWorker import org.koin.core.component.KoinComponent class NotificationAlarmReceiver : BroadcastReceiver(), KoinComponent { @@ -17,7 +18,7 @@ class NotificationAlarmReceiver : BroadcastReceiver(), KoinComponent { override fun onReceive(context: Context, intent: Intent?) { // It is not possible to bind to a service from a notification alarm receiver so we will use // a worker instead. - Logger.d("Account expiry alarm triggered") + Logger.Companion.d("Account expiry alarm triggered") val work = OneTimeWorkRequestBuilder<ExpiryNotificationWorker>() @@ -25,9 +26,7 @@ class NotificationAlarmReceiver : BroadcastReceiver(), KoinComponent { // Setting expedited on android 12 or lower will cause the work manager to // request a wake lock. We want to avoid using wakelocks so we disable expedited // on android 12 and lower. - if ( - android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU - ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) } } diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/tunnelstate/TunnelStateNotificationAction.kt b/android/lib/push-notification/src/main/kotlin/net/mullvad/mullvadvpn/lib/pushnotification/tunnelstate/TunnelStateNotificationAction.kt index b799de0943..05eea1c666 100644 --- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/tunnelstate/TunnelStateNotificationAction.kt +++ b/android/lib/push-notification/src/main/kotlin/net/mullvad/mullvadvpn/lib/pushnotification/tunnelstate/TunnelStateNotificationAction.kt @@ -1,4 +1,4 @@ -package net.mullvad.mullvadvpn.service.notifications.tunnelstate +package net.mullvad.mullvadvpn.lib.pushnotification.tunnelstate import android.app.PendingIntent import android.content.Context @@ -13,9 +13,9 @@ import net.mullvad.mullvadvpn.lib.model.Notification import net.mullvad.mullvadvpn.lib.model.NotificationAction import net.mullvad.mullvadvpn.lib.model.NotificationTunnelState import net.mullvad.mullvadvpn.lib.model.PrepareError -import net.mullvad.mullvadvpn.service.R +import net.mullvad.mullvadvpn.lib.ui.resource.R -internal fun Notification.Tunnel.toNotification(context: Context) = +fun Notification.Tunnel.toNotification(context: Context) = NotificationCompat.Builder(context, channelId.value) .setContentIntent(contentIntent(context)) .setContentTitle(state.contentTitleResourceId(context)) diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/tunnelstate/TunnelStateNotificationProvider.kt b/android/lib/push-notification/src/main/kotlin/net/mullvad/mullvadvpn/lib/pushnotification/tunnelstate/TunnelStateNotificationProvider.kt index e3f985acd8..f75deb7258 100644 --- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/tunnelstate/TunnelStateNotificationProvider.kt +++ b/android/lib/push-notification/src/main/kotlin/net/mullvad/mullvadvpn/lib/pushnotification/tunnelstate/TunnelStateNotificationProvider.kt @@ -1,4 +1,4 @@ -package net.mullvad.mullvadvpn.service.notifications.tunnelstate +package net.mullvad.mullvadvpn.lib.pushnotification.tunnelstate import android.content.Context import kotlinx.coroutines.CoroutineScope @@ -18,10 +18,10 @@ import net.mullvad.mullvadvpn.lib.model.NotificationTunnelState import net.mullvad.mullvadvpn.lib.model.NotificationUpdate import net.mullvad.mullvadvpn.lib.model.PrepareError import net.mullvad.mullvadvpn.lib.model.TunnelState +import net.mullvad.mullvadvpn.lib.pushnotification.NotificationProvider import net.mullvad.mullvadvpn.lib.repository.ConnectionProxy import net.mullvad.mullvadvpn.lib.repository.DeviceRepository import net.mullvad.mullvadvpn.lib.repository.UserPreferencesRepository -import net.mullvad.mullvadvpn.service.notifications.NotificationProvider class TunnelStateNotificationProvider( context: Context, @@ -31,7 +31,7 @@ class TunnelStateNotificationProvider( channelId: NotificationChannelId, scope: CoroutineScope, ) : NotificationProvider<Notification.Tunnel> { - internal val notificationId = NotificationId(2) + val notificationId = NotificationId(2) override val notifications: StateFlow<NotificationUpdate<Notification.Tunnel>> = combine( diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/worker/ExpiryNotificationWorker.kt b/android/lib/push-notification/src/main/kotlin/net/mullvad/mullvadvpn/lib/pushnotification/worker/ExpiryNotificationWorker.kt index 902ccbb50c..b4988933cb 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/worker/ExpiryNotificationWorker.kt +++ b/android/lib/push-notification/src/main/kotlin/net/mullvad/mullvadvpn/lib/pushnotification/worker/ExpiryNotificationWorker.kt @@ -1,4 +1,4 @@ -package net.mullvad.mullvadvpn.worker +package net.mullvad.mullvadvpn.lib.pushnotification.worker import android.app.Notification import android.content.Context @@ -16,13 +16,13 @@ import java.time.ZonedDateTime import kotlin.getValue import kotlinx.coroutines.withTimeoutOrNull import net.mullvad.mullvadvpn.lib.common.constant.VPN_SERVICE_CLASS +import net.mullvad.mullvadvpn.lib.common.serviceconnection.EmptyServiceConnection import net.mullvad.mullvadvpn.lib.common.util.ACCOUNT_EXPIRY_CLOSE_TO_EXPIRY_THRESHOLD import net.mullvad.mullvadvpn.lib.model.NotificationChannel +import net.mullvad.mullvadvpn.lib.pushnotification.ScheduleNotificationAlarmUseCase +import net.mullvad.mullvadvpn.lib.pushnotification.accountexpiry.AccountExpiryNotificationProvider import net.mullvad.mullvadvpn.lib.repository.AccountRepository -import net.mullvad.mullvadvpn.service.R -import net.mullvad.mullvadvpn.service.notifications.accountexpiry.AccountExpiryNotificationProvider -import net.mullvad.mullvadvpn.ui.serviceconnection.EmptyServiceConnection -import net.mullvad.mullvadvpn.usecase.ScheduleNotificationAlarmUseCase +import net.mullvad.mullvadvpn.lib.ui.resource.R import org.koin.core.component.KoinComponent import org.koin.core.component.inject diff --git a/android/service/build.gradle.kts b/android/service/build.gradle.kts index a3928105bd..14f1b7ed28 100644 --- a/android/service/build.gradle.kts +++ b/android/service/build.gradle.kts @@ -80,6 +80,7 @@ dependencies { implementation(projects.lib.grpc) implementation(projects.lib.endpoint) implementation(projects.lib.model) + implementation(projects.lib.pushNotification) implementation(projects.lib.repository) implementation(projects.lib.talpid) diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt index caaf7e127b..fd9af163f3 100644 --- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt +++ b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt @@ -19,12 +19,12 @@ import net.mullvad.mullvadvpn.lib.common.constant.KEY_DISCONNECT_ACTION import net.mullvad.mullvadvpn.lib.endpoint.ApiEndpointFromIntentHolder import net.mullvad.mullvadvpn.lib.grpc.ManagementService import net.mullvad.mullvadvpn.lib.model.TunnelState +import net.mullvad.mullvadvpn.lib.pushnotification.NotificationChannelFactory +import net.mullvad.mullvadvpn.lib.pushnotification.NotificationManager import net.mullvad.mullvadvpn.lib.repository.ConnectionProxy import net.mullvad.mullvadvpn.service.di.vpnServiceModule import net.mullvad.mullvadvpn.service.migration.MigrateSplitTunneling import net.mullvad.mullvadvpn.service.notifications.ForegroundNotificationManager -import net.mullvad.mullvadvpn.service.notifications.NotificationChannelFactory -import net.mullvad.mullvadvpn.service.notifications.NotificationManager import net.mullvad.mullvadvpn.service.util.extractAndOverwriteIfAssetMoreRecent import net.mullvad.talpid.TalpidVpnService import org.koin.android.ext.android.getKoin diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/ForegroundNotificationManager.kt b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/ForegroundNotificationManager.kt index cf324e6023..a63cb64f9a 100644 --- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/ForegroundNotificationManager.kt +++ b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/ForegroundNotificationManager.kt @@ -9,9 +9,9 @@ import net.mullvad.mullvadvpn.lib.model.Notification import net.mullvad.mullvadvpn.lib.model.NotificationChannel import net.mullvad.mullvadvpn.lib.model.NotificationTunnelState import net.mullvad.mullvadvpn.lib.model.NotificationUpdate +import net.mullvad.mullvadvpn.lib.pushnotification.tunnelstate.TunnelStateNotificationProvider +import net.mullvad.mullvadvpn.lib.pushnotification.tunnelstate.toNotification import net.mullvad.mullvadvpn.service.MullvadVpnService -import net.mullvad.mullvadvpn.service.notifications.tunnelstate.TunnelStateNotificationProvider -import net.mullvad.mullvadvpn.service.notifications.tunnelstate.toNotification class ForegroundNotificationManager( private val vpnService: MullvadVpnService, diff --git a/android/service/src/test/kotlin/net/mullvad/mullvadvpn/service/notifications/tunnelstate/TunnelStateNotificationProviderTest.kt b/android/service/src/test/kotlin/net/mullvad/mullvadvpn/service/notifications/tunnelstate/TunnelStateNotificationProviderTest.kt index 0595b3ac16..481c89956c 100644 --- a/android/service/src/test/kotlin/net/mullvad/mullvadvpn/service/notifications/tunnelstate/TunnelStateNotificationProviderTest.kt +++ b/android/service/src/test/kotlin/net/mullvad/mullvadvpn/service/notifications/tunnelstate/TunnelStateNotificationProviderTest.kt @@ -29,6 +29,7 @@ import net.mullvad.mullvadvpn.lib.model.NotificationUpdate import net.mullvad.mullvadvpn.lib.model.PrepareError import net.mullvad.mullvadvpn.lib.model.Prepared import net.mullvad.mullvadvpn.lib.model.TunnelState +import net.mullvad.mullvadvpn.lib.pushnotification.tunnelstate.TunnelStateNotificationProvider import net.mullvad.mullvadvpn.lib.repository.ConnectionProxy import net.mullvad.mullvadvpn.lib.repository.DeviceRepository import net.mullvad.mullvadvpn.lib.repository.UserPreferencesRepository diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index 7021eb4898..81742fef9a 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -48,6 +48,7 @@ include( ":lib:feature:customlist:impl", ":lib:feature:daita:impl", ":lib:feature:filter:impl", + ":lib:feature:login:impl", ":lib:feature:managedevices:impl", ":lib:feature:multihop:impl", ":lib:feature:notification:impl", @@ -61,6 +62,7 @@ include( ":lib:model", ":lib:navigation", ":lib:payment", + ":lib:push-notification", ":lib:repository", ":lib:screen-test", ":lib:talpid", |
