summaryrefslogtreecommitdiffhomepage
path: root/android/app/src
diff options
context:
space:
mode:
authorDavid Göransson <david.goransson90@gmail.com>2023-10-09 09:35:21 +0200
committerJonatan Rhodin <jonatan.rhodin@mullvad.net>2023-10-13 11:03:58 +0200
commitc07ad9f01246937018ad4ae8021afa208641bfa1 (patch)
tree2d54fc8f1f9656b70c7b2cfbe0d8e3722eb8d058 /android/app/src
parentd4f8725e7d508af530cd1b3134f20aa73733c96a (diff)
downloadmullvadvpn-c07ad9f01246937018ad4ae8021afa208641bfa1.tar.xz
mullvadvpn-c07ad9f01246937018ad4ae8021afa208641bfa1.zip
Add device name and time left
Diffstat (limited to 'android/app/src')
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/Scaffolding.kt47
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/TopBar.kt116
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DeviceRemovalDialog.kt5
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt7
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceListScreen.kt3
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/OutOfTimeScreen.kt16
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/ConnectUiState.kt8
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/OutOfTimeUiState.kt5
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt10
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/DateExtensions.kt5
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/FlowUtils.kt27
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt14
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModel.kt22
13 files changed, 256 insertions, 29 deletions
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 eb4d0d19a5..332c841d87 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
@@ -1,5 +1,6 @@
package net.mullvad.mullvadvpn.compose.component
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxSize
@@ -67,6 +68,52 @@ fun ScaffoldWithTopBar(
}
@Composable
+fun ScaffoldWithTopBarAndDeviceName(
+ topBarColor: Color,
+ statusBarColor: Color,
+ navigationBarColor: Color,
+ modifier: Modifier = Modifier,
+ iconTintColor: Color = MaterialTheme.colorScheme.onPrimary.copy(alpha = AlphaTopBar),
+ onSettingsClicked: (() -> Unit)?,
+ onAccountClicked: (() -> Unit)?,
+ isIconAndLogoVisible: Boolean = true,
+ snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
+ deviceName: String?,
+ timeLeft: Int?,
+ content: @Composable (PaddingValues) -> Unit,
+) {
+ val systemUiController = rememberSystemUiController()
+ LaunchedEffect(key1 = statusBarColor, key2 = navigationBarColor) {
+ systemUiController.setStatusBarColor(statusBarColor)
+ systemUiController.setNavigationBarColor(navigationBarColor)
+ }
+
+ Scaffold(
+ modifier = modifier,
+ topBar = {
+ Column {
+ MullvadTopBarWithDeviceName(
+ containerColor = topBarColor,
+ iconTintColor = iconTintColor,
+ onSettingsClicked = onSettingsClicked,
+ onAccountClicked = onAccountClicked,
+ isIconAndLogoVisible = isIconAndLogoVisible,
+ deviceName = deviceName,
+ daysLeftUntilExpiry = timeLeft
+ )
+ }
+ },
+ snackbarHost = {
+ SnackbarHost(
+ snackbarHostState,
+ snackbar = { snackbarData -> MullvadSnackbar(snackbarData = snackbarData) }
+ )
+ },
+ content = content
+ )
+}
+
+@Composable
fun MullvadSnackbar(snackbarData: SnackbarData) {
Snackbar(snackbarData = snackbarData, contentColor = MaterialTheme.colorScheme.secondary)
}
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 3c5e0e1bb7..93e1e291a2 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
@@ -2,9 +2,18 @@
package net.mullvad.mullvadvpn.compose.component
+import androidx.compose.animation.animateColorAsState
+import androidx.compose.animation.animateContentSize
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.spring
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
@@ -13,16 +22,19 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MediumTopAppBar
+import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
@@ -206,3 +218,107 @@ fun MullvadMediumTopBar(
actions = actions
)
}
+
+@Preview
+@Composable
+fun PreviewMullvadTopBarWithLongDeviceName() {
+ AppTheme {
+ Surface {
+ MullvadTopBarWithDeviceName(
+ containerColor = MaterialTheme.colorScheme.error,
+ iconTintColor = MaterialTheme.colorScheme.onError,
+ onSettingsClicked = null,
+ onAccountClicked = null,
+ deviceName = "Superstitious Hippopotamus with extra weight",
+ daysLeftUntilExpiry = 1
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+fun PreviewMullvadTopBarWithShortDeviceName() {
+ AppTheme {
+ Surface {
+ MullvadTopBarWithDeviceName(
+ containerColor = MaterialTheme.colorScheme.error,
+ iconTintColor = MaterialTheme.colorScheme.onError,
+ onSettingsClicked = null,
+ onAccountClicked = null,
+ deviceName = "Fit Ant",
+ daysLeftUntilExpiry = 1
+ )
+ }
+ }
+}
+
+@Composable
+fun MullvadTopBarWithDeviceName(
+ containerColor: Color,
+ onSettingsClicked: (() -> Unit)?,
+ onAccountClicked: (() -> Unit)?,
+ iconTintColor: Color,
+ isIconAndLogoVisible: Boolean = true,
+ deviceName: String?,
+ daysLeftUntilExpiry: Int?
+) {
+ Column {
+ MullvadTopBar(
+ containerColor,
+ onSettingsClicked,
+ onAccountClicked,
+ Modifier,
+ iconTintColor,
+ isIconAndLogoVisible,
+ )
+
+ // Align animation of extra row with the rest of the Topbar
+ val appBarContainerColor by
+ animateColorAsState(
+ targetValue = containerColor,
+ animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
+ label = "ColorAnimation"
+ )
+ Row(
+ modifier =
+ Modifier.background(appBarContainerColor)
+ .padding(
+ bottom = Dimens.smallPadding,
+ start = Dimens.mediumPadding,
+ end = Dimens.mediumPadding
+ )
+ .fillMaxWidth()
+ .animateContentSize(),
+ horizontalArrangement = Arrangement.spacedBy(Dimens.mediumPadding)
+ ) {
+ Text(
+ modifier = Modifier.weight(1f, fill = false),
+ text =
+ deviceName?.let {
+ stringResource(id = R.string.top_bar_device_name, deviceName)
+ }
+ ?: "",
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ style = MaterialTheme.typography.bodySmall
+ )
+ if (daysLeftUntilExpiry != null) {
+ Text(
+ text =
+ stringResource(
+ id = R.string.top_bar_time_left,
+ pluralStringResource(
+ id = R.plurals.days,
+ daysLeftUntilExpiry,
+ daysLeftUntilExpiry
+ )
+ ),
+ style = MaterialTheme.typography.bodySmall
+ )
+ } else {
+ Spacer(Modifier)
+ }
+ }
+ }
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DeviceRemovalDialog.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DeviceRemovalDialog.kt
index 4cbbc0d292..1ac8873fc3 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DeviceRemovalDialog.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DeviceRemovalDialog.kt
@@ -57,10 +57,7 @@ fun ShowDeviceRemovalDialog(onDismiss: () -> Unit, onConfirm: () -> Unit, device
},
text = {
val htmlFormattedDialogText =
- textResource(
- id = R.string.max_devices_confirm_removal_description,
- device.name
- )
+ textResource(id = R.string.max_devices_confirm_removal_description, device.name)
HtmlText(htmlFormattedString = htmlFormattedDialogText, textSize = 16.sp.value)
},
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt
index 69d849183e..9374f4ab9a 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt
@@ -35,6 +35,7 @@ import net.mullvad.mullvadvpn.compose.button.SwitchLocationButton
import net.mullvad.mullvadvpn.compose.component.ConnectionStatusText
import net.mullvad.mullvadvpn.compose.component.LocationInfo
import net.mullvad.mullvadvpn.compose.component.Notification
+import net.mullvad.mullvadvpn.compose.component.ScaffoldWithTopBarAndDeviceName
import net.mullvad.mullvadvpn.compose.component.ScaffoldWithTopBar
import net.mullvad.mullvadvpn.compose.component.drawVerticalScrollbar
import net.mullvad.mullvadvpn.compose.state.ConnectUiState
@@ -107,7 +108,7 @@ fun ConnectScreen(
}
}
- ScaffoldWithTopBar(
+ ScaffoldWithTopBarAndDeviceName(
topBarColor =
if (uiState.tunnelUiState.isSecured()) {
MaterialTheme.colorScheme.inversePrimary
@@ -129,7 +130,9 @@ fun ConnectScreen(
}
.copy(alpha = AlphaTopBar),
onSettingsClicked = onSettingsClick,
- onAccountClicked = onAccountClick
+ onAccountClicked = onAccountClick,
+ deviceName = uiState.deviceName,
+ timeLeft = uiState.daysLeftUntilExpiry
) {
Column(
verticalArrangement = Arrangement.Bottom,
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 7e76040ac4..4036d9547c 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
@@ -194,8 +194,7 @@ fun DeviceListScreen(
Column {
state.deviceUiItems.forEach { deviceUiState ->
ListItem(
- text =
- deviceUiState.device.name,
+ text = deviceUiState.device.name,
subText =
deviceUiState.device.created.parseAsDateTime()?.let {
creationDate ->
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/OutOfTimeScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/OutOfTimeScreen.kt
index 49de23228c..994e45b556 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/OutOfTimeScreen.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/OutOfTimeScreen.kt
@@ -28,7 +28,7 @@ import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.button.ActionButton
import net.mullvad.mullvadvpn.compose.button.RedeemVoucherButton
import net.mullvad.mullvadvpn.compose.button.SitePaymentButton
-import net.mullvad.mullvadvpn.compose.component.ScaffoldWithTopBar
+import net.mullvad.mullvadvpn.compose.component.ScaffoldWithTopBarAndDeviceName
import net.mullvad.mullvadvpn.compose.component.drawVerticalScrollbar
import net.mullvad.mullvadvpn.compose.extensions.createOpenAccountPageHook
import net.mullvad.mullvadvpn.compose.state.OutOfTimeUiState
@@ -47,7 +47,7 @@ private fun PreviewOutOfTimeScreenDisconnected() {
AppTheme {
OutOfTimeScreen(
showSitePayment = true,
- uiState = OutOfTimeUiState(tunnelState = TunnelState.Disconnected),
+ uiState = OutOfTimeUiState(tunnelState = TunnelState.Disconnected, "Heroic Frog"),
uiSideEffect = MutableSharedFlow<OutOfTimeViewModel.UiSideEffect>().asSharedFlow()
)
}
@@ -59,7 +59,8 @@ private fun PreviewOutOfTimeScreenConnecting() {
AppTheme {
OutOfTimeScreen(
showSitePayment = true,
- uiState = OutOfTimeUiState(tunnelState = TunnelState.Connecting(null, null)),
+ uiState =
+ OutOfTimeUiState(tunnelState = TunnelState.Connecting(null, null), "Strong Rabbit"),
uiSideEffect = MutableSharedFlow<OutOfTimeViewModel.UiSideEffect>().asSharedFlow()
)
}
@@ -76,7 +77,8 @@ private fun PreviewOutOfTimeScreenError() {
tunnelState =
TunnelState.Error(
ErrorState(cause = ErrorStateCause.IsOffline, isBlocking = true)
- )
+ ),
+ deviceName = "Stable Horse"
),
uiSideEffect = MutableSharedFlow<OutOfTimeViewModel.UiSideEffect>().asSharedFlow()
)
@@ -106,7 +108,7 @@ fun OutOfTimeScreen(
}
}
val scrollState = rememberScrollState()
- ScaffoldWithTopBar(
+ ScaffoldWithTopBarAndDeviceName(
topBarColor =
if (uiState.tunnelState.isSecured()) {
MaterialTheme.colorScheme.inversePrimary
@@ -128,7 +130,9 @@ fun OutOfTimeScreen(
}
.copy(alpha = AlphaTopBar),
onSettingsClicked = onSettingsClick,
- onAccountClicked = onAccountClick
+ onAccountClicked = onAccountClick,
+ deviceName = uiState.deviceName,
+ timeLeft = null
) {
Column(
verticalArrangement = Arrangement.Bottom,
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 3c9c7352fe..93b9df5b7a 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
@@ -14,7 +14,9 @@ data class ConnectUiState(
val outAddress: String,
val showLocation: Boolean,
val connectNotificationState: ConnectNotificationState,
- val isTunnelInfoExpanded: Boolean
+ val isTunnelInfoExpanded: Boolean,
+ val deviceName: String?,
+ val daysLeftUntilExpiry: Int?
) {
companion object {
val INITIAL =
@@ -27,7 +29,9 @@ data class ConnectUiState(
outAddress = "",
showLocation = false,
isTunnelInfoExpanded = false,
- connectNotificationState = ConnectNotificationState.HideNotification
+ connectNotificationState = ConnectNotificationState.HideNotification,
+ deviceName = null,
+ daysLeftUntilExpiry = null
)
}
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/OutOfTimeUiState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/OutOfTimeUiState.kt
index cc19ac7ca8..f7794e5a55 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/OutOfTimeUiState.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/OutOfTimeUiState.kt
@@ -2,4 +2,7 @@ package net.mullvad.mullvadvpn.compose.state
import net.mullvad.mullvadvpn.model.TunnelState
-data class OutOfTimeUiState(val tunnelState: TunnelState = TunnelState.Disconnected)
+data class OutOfTimeUiState(
+ val tunnelState: TunnelState = TunnelState.Disconnected,
+ val deviceName: String
+)
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 63fcf17ad2..7134f7b7d2 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
@@ -83,19 +83,21 @@ val uiModule = module {
viewModel {
ChangelogViewModel(get(), BuildConfig.VERSION_CODE, BuildConfig.ALWAYS_SHOW_CHANGELOG)
}
- viewModel { ConnectViewModel(get(), BuildConfig.ENABLE_IN_APP_VERSION_NOTIFICATIONS, get()) }
+ viewModel {
+ ConnectViewModel(get(), BuildConfig.ENABLE_IN_APP_VERSION_NOTIFICATIONS, get(), get())
+ }
viewModel { DeviceListViewModel(get(), get()) }
viewModel { DeviceRevokedViewModel(get(), get()) }
viewModel { LoginViewModel(get(), get()) }
- viewModel { OutOfTimeViewModel(get(), get()) }
viewModel { PrivacyDisclaimerViewModel(get()) }
- viewModel { ReportProblemViewModel(get()) }
viewModel { SelectLocationViewModel(get()) }
viewModel { SettingsViewModel(get(), get()) }
- viewModel { ViewLogsViewModel(get()) }
viewModel { VoucherDialogViewModel(get(), get()) }
viewModel { VpnSettingsViewModel(get(), get(), get(), get()) }
viewModel { WelcomeViewModel(get(), get(), get()) }
+ viewModel { ReportProblemViewModel(get()) }
+ viewModel { ViewLogsViewModel(get()) }
+ viewModel { OutOfTimeViewModel(get(), get(), get()) }
}
const val SELF_PACKAGE_NAME = "SELF_PACKAGE_NAME"
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
index d3be3e09aa..e11434257a 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/DateExtensions.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/DateExtensions.kt
@@ -1,6 +1,8 @@
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
@@ -8,3 +10,6 @@ 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/util/FlowUtils.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/FlowUtils.kt
index d18e4f8fc9..e782f6f439 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/FlowUtils.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/FlowUtils.kt
@@ -97,3 +97,30 @@ inline fun <T1, T2, T3, T4, T5, T6, T7, R> combine(
)
}
}
+
+inline fun <T1, T2, T3, T4, T5, T6, T7, T8, R> combine(
+ flow: Flow<T1>,
+ flow2: Flow<T2>,
+ flow3: Flow<T3>,
+ flow4: Flow<T4>,
+ flow5: Flow<T5>,
+ flow6: Flow<T6>,
+ flow7: Flow<T7>,
+ flow8: Flow<T8>,
+ crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8) -> R
+): Flow<R> {
+ return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6, flow7, flow8) {
+ args: Array<*> ->
+ @Suppress("UNCHECKED_CAST")
+ transform(
+ args[0] as T1,
+ args[1] as T2,
+ args[2] as T3,
+ args[3] as T4,
+ args[4] as T5,
+ args[5] as T6,
+ args[6] as T7,
+ args[7] as T8
+ )
+ }
+}
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 01a1c84896..01ba71ff86 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
@@ -16,6 +16,7 @@ import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -24,6 +25,7 @@ import net.mullvad.mullvadvpn.compose.state.ConnectUiState
import net.mullvad.mullvadvpn.model.AccountExpiry
import net.mullvad.mullvadvpn.model.TunnelState
import net.mullvad.mullvadvpn.repository.AccountRepository
+import net.mullvad.mullvadvpn.repository.DeviceRepository
import net.mullvad.mullvadvpn.ui.VersionInfo
import net.mullvad.mullvadvpn.ui.serviceconnection.ConnectionProxy
import net.mullvad.mullvadvpn.ui.serviceconnection.LocationInfoCache
@@ -36,6 +38,7 @@ import net.mullvad.mullvadvpn.ui.serviceconnection.connectionProxy
import net.mullvad.mullvadvpn.util.appVersionCallbackFlow
import net.mullvad.mullvadvpn.util.callbackFlowFromNotifier
import net.mullvad.mullvadvpn.util.combine
+import net.mullvad.mullvadvpn.util.daysFromNow
import net.mullvad.mullvadvpn.util.toInAddress
import net.mullvad.mullvadvpn.util.toOutAddress
import net.mullvad.talpid.tunnel.ActionAfterDisconnect
@@ -47,6 +50,7 @@ class ConnectViewModel(
private val serviceConnectionManager: ServiceConnectionManager,
private val isVersionInfoNotificationEnabled: Boolean,
accountRepository: AccountRepository,
+ private val deviceRepository: DeviceRepository,
) : ViewModel() {
private val _uiSideEffect = MutableSharedFlow<UiSideEffect>(extraBufferCapacity = 1)
val uiSideEffect = _uiSideEffect.asSharedFlow()
@@ -74,7 +78,8 @@ class ConnectViewModel(
serviceConnection.connectionProxy.tunnelUiStateFlow(),
serviceConnection.connectionProxy.tunnelRealStateFlow(),
accountRepository.accountExpiryState,
- _isTunnelInfoExpanded
+ _isTunnelInfoExpanded,
+ deviceRepository.deviceState.map { it.deviceName() }
) {
location,
relayLocation,
@@ -82,7 +87,8 @@ class ConnectViewModel(
tunnelUiState,
tunnelRealState,
accountExpiry,
- isTunnelInfoExpanded ->
+ isTunnelInfoExpanded,
+ deviceName ->
if (tunnelRealState.isTunnelErrorStateDueToExpiredAccount()) {
_uiSideEffect.tryEmit(UiSideEffect.OpenOutOfTimeView)
}
@@ -124,7 +130,9 @@ class ConnectViewModel(
tunnelUiState = tunnelUiState,
versionInfo = versionInfo,
accountExpiry = accountExpiry
- )
+ ),
+ deviceName = deviceName,
+ daysLeftUntilExpiry = accountExpiry.date()?.daysFromNow()
)
}
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModel.kt
index 8a789f62fd..b1df2d2225 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModel.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModel.kt
@@ -12,13 +12,13 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.compose.state.OutOfTimeUiState
import net.mullvad.mullvadvpn.constant.ACCOUNT_EXPIRY_POLL_INTERVAL
import net.mullvad.mullvadvpn.model.TunnelState
import net.mullvad.mullvadvpn.repository.AccountRepository
+import net.mullvad.mullvadvpn.repository.DeviceRepository
import net.mullvad.mullvadvpn.ui.serviceconnection.ConnectionProxy
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionState
@@ -31,7 +31,8 @@ import org.joda.time.DateTime
class OutOfTimeViewModel(
private val accountRepository: AccountRepository,
private val serviceConnectionManager: ServiceConnectionManager,
- private val pollAccountExpiry: Boolean = true
+ private val deviceRepository: DeviceRepository,
+ private val pollAccountExpiry: Boolean = true,
) : ViewModel() {
private val _uiSideEffect = MutableSharedFlow<UiSideEffect>(extraBufferCapacity = 1)
@@ -47,10 +48,21 @@ class OutOfTimeViewModel(
}
}
.flatMapLatest { serviceConnection ->
- serviceConnection.connectionProxy.tunnelStateFlow()
+ kotlinx.coroutines.flow.combine(
+ serviceConnection.connectionProxy.tunnelStateFlow(),
+ deviceRepository.deviceState
+ ) { tunnelState, deviceState ->
+ OutOfTimeUiState(
+ tunnelState = tunnelState,
+ deviceName = deviceState.deviceName() ?: "",
+ )
+ }
}
- .map { tunnelState -> OutOfTimeUiState(tunnelState = tunnelState) }
- .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), OutOfTimeUiState())
+ .stateIn(
+ viewModelScope,
+ SharingStarted.WhileSubscribed(),
+ OutOfTimeUiState(deviceName = "")
+ )
init {
viewModelScope.launch {