diff options
| author | David Göransson <david.goransson90@gmail.com> | 2023-10-09 09:35:21 +0200 |
|---|---|---|
| committer | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2023-10-13 11:03:58 +0200 |
| commit | c07ad9f01246937018ad4ae8021afa208641bfa1 (patch) | |
| tree | 2d54fc8f1f9656b70c7b2cfbe0d8e3722eb8d058 | |
| parent | d4f8725e7d508af530cd1b3134f20aa73733c96a (diff) | |
| download | mullvadvpn-c07ad9f01246937018ad4ae8021afa208641bfa1.tar.xz mullvadvpn-c07ad9f01246937018ad4ae8021afa208641bfa1.zip | |
Add device name and time left
34 files changed, 298 insertions, 44 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 { diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountExpiry.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountExpiry.kt index f5738ec21d..f856ef8c89 100644 --- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountExpiry.kt +++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountExpiry.kt @@ -1,28 +1,15 @@ package net.mullvad.mullvadvpn.model import android.os.Parcelable -import kotlin.time.Duration.Companion.milliseconds -import kotlin.time.DurationUnit import kotlinx.parcelize.Parcelize import org.joda.time.DateTime sealed class AccountExpiry : Parcelable { - @Parcelize - data class Available(val expiryDateTime: DateTime) : AccountExpiry() { - override fun daysLeft(): Int = - (expiryDateTime.toInstant().millis - DateTime.now().toInstant().millis) - .milliseconds - .toInt(DurationUnit.DAYS) - } + @Parcelize data class Available(val expiryDateTime: DateTime) : AccountExpiry() - @Parcelize - data object Missing : AccountExpiry() + @Parcelize data object Missing : AccountExpiry() fun date(): DateTime? { return (this as? Available)?.expiryDateTime } - - open fun daysLeft(): Int? { - return (this as? Available)?.daysLeft() - } } diff --git a/android/lib/resource/src/main/res/values-da/strings.xml b/android/lib/resource/src/main/res/values-da/strings.xml index bb26081112..b98455f7d9 100644 --- a/android/lib/resource/src/main/res/values-da/strings.xml +++ b/android/lib/resource/src/main/res/values-da/strings.xml @@ -175,6 +175,8 @@ <string name="switch_location">Skift placering</string> <string name="tcp">TCP</string> <string name="toggle_vpn">Slå VPN til/fra</string> + <string name="top_bar_device_name">Enhedsnavn: %1$s</string> + <string name="top_bar_time_left">Resterende tid: %1$s</string> <string name="try_again">Prøv igen</string> <string name="udp">UDP</string> <string name="udp_over_tcp_port_info">Hvilken TCP-port UDP-over-TCP tilsløringsprotokollen skal forbinde til på VPN-serveren.</string> diff --git a/android/lib/resource/src/main/res/values-de/strings.xml b/android/lib/resource/src/main/res/values-de/strings.xml index 357e4209e3..19120efa7b 100644 --- a/android/lib/resource/src/main/res/values-de/strings.xml +++ b/android/lib/resource/src/main/res/values-de/strings.xml @@ -175,6 +175,8 @@ <string name="switch_location">Ort wechseln</string> <string name="tcp">TCP</string> <string name="toggle_vpn">VPN umschalten</string> + <string name="top_bar_device_name">Gerätename: %1$s</string> + <string name="top_bar_time_left">Verbleibende Zeit: %1$s</string> <string name="try_again">Erneut versuchen</string> <string name="udp">UDP</string> <string name="udp_over_tcp_port_info">Mit welchem TCP-Port sich das UDP-über-TCP-Verschleierungsprotokoll auf dem VPN-Server verbinden soll.</string> diff --git a/android/lib/resource/src/main/res/values-es/strings.xml b/android/lib/resource/src/main/res/values-es/strings.xml index 1ca4ad0fa5..e5c5f7d657 100644 --- a/android/lib/resource/src/main/res/values-es/strings.xml +++ b/android/lib/resource/src/main/res/values-es/strings.xml @@ -175,6 +175,8 @@ <string name="switch_location">Cambiar ubicación</string> <string name="tcp">TCP</string> <string name="toggle_vpn">Alternar VPN</string> + <string name="top_bar_device_name">Nombre del dispositivo: %1$s</string> + <string name="top_bar_time_left">Tiempo restante: %1$s</string> <string name="try_again">Volver a intentarlo</string> <string name="udp">UDP</string> <string name="udp_over_tcp_port_info">El puerto TCP al que se conectará el protocolo de ofuscación de UDP sobre TCP en el servidor VPN.</string> diff --git a/android/lib/resource/src/main/res/values-fi/strings.xml b/android/lib/resource/src/main/res/values-fi/strings.xml index eb02e63756..379d8dd4bb 100644 --- a/android/lib/resource/src/main/res/values-fi/strings.xml +++ b/android/lib/resource/src/main/res/values-fi/strings.xml @@ -175,6 +175,8 @@ <string name="switch_location">Vaihda sijaintia</string> <string name="tcp">TCP</string> <string name="toggle_vpn">Vaihda VPN:ää</string> + <string name="top_bar_device_name">Laitteen nimi: %1$s</string> + <string name="top_bar_time_left">Aikaa jäljellä: %1$s</string> <string name="try_again">Yritä uudelleen</string> <string name="udp">UDP</string> <string name="udp_over_tcp_port_info">Määrittää, mihin VPN-palvelimen TCP-porttiin \"UDP TCP:n kautta\" -hämäysteknologia-protokollan tulee muodostaa yhteys.</string> diff --git a/android/lib/resource/src/main/res/values-fr/strings.xml b/android/lib/resource/src/main/res/values-fr/strings.xml index da970c6d89..9da5482c92 100644 --- a/android/lib/resource/src/main/res/values-fr/strings.xml +++ b/android/lib/resource/src/main/res/values-fr/strings.xml @@ -175,6 +175,8 @@ <string name="switch_location">Changer de localisation</string> <string name="tcp">TCP</string> <string name="toggle_vpn">Activer/désactiver le VPN</string> + <string name="top_bar_device_name">Nom de l\'appareil : %1$s</string> + <string name="top_bar_time_left">Temps restant : %1$s</string> <string name="try_again">Réessayer</string> <string name="udp">UDP</string> <string name="udp_over_tcp_port_info">Le port TCP auquel le protocole de dissimulation UDP sur TCP doit se connecter sur le serveur VPN.</string> diff --git a/android/lib/resource/src/main/res/values-it/strings.xml b/android/lib/resource/src/main/res/values-it/strings.xml index c988e760cf..e91aaecdb9 100644 --- a/android/lib/resource/src/main/res/values-it/strings.xml +++ b/android/lib/resource/src/main/res/values-it/strings.xml @@ -175,6 +175,8 @@ <string name="switch_location">Cambia posizione</string> <string name="tcp">TCP</string> <string name="toggle_vpn">Attiva/disattiva VPN</string> + <string name="top_bar_device_name">Nome del dispositivo: %1$s</string> + <string name="top_bar_time_left">Tempo rimasto: %1$s</string> <string name="try_again">Riprova</string> <string name="udp">UDP</string> <string name="udp_over_tcp_port_info">A quale porta TCP deve connettersi il protocollo di offuscamento UDP-over-TCP sul server VPN.</string> diff --git a/android/lib/resource/src/main/res/values-ja/strings.xml b/android/lib/resource/src/main/res/values-ja/strings.xml index 8c9ef84739..3112ec2b1c 100644 --- a/android/lib/resource/src/main/res/values-ja/strings.xml +++ b/android/lib/resource/src/main/res/values-ja/strings.xml @@ -175,6 +175,8 @@ <string name="switch_location">場所を切り替える</string> <string name="tcp">TCP</string> <string name="toggle_vpn">VPNの切り替え</string> + <string name="top_bar_device_name">デバイス名: %1$s</string> + <string name="top_bar_time_left">残り時間: %1$s</string> <string name="try_again">再試行</string> <string name="udp">UDP</string> <string name="udp_over_tcp_port_info">UDP-over-TCP難読化プロトコルで接続する必要のあるVPNサーバーのTCPポートです。</string> diff --git a/android/lib/resource/src/main/res/values-ko/strings.xml b/android/lib/resource/src/main/res/values-ko/strings.xml index 209023a64e..b535966911 100644 --- a/android/lib/resource/src/main/res/values-ko/strings.xml +++ b/android/lib/resource/src/main/res/values-ko/strings.xml @@ -175,6 +175,8 @@ <string name="switch_location">위치 전환</string> <string name="tcp">TCP</string> <string name="toggle_vpn">VPN 전환</string> + <string name="top_bar_device_name">장치 이름: %1$s</string> + <string name="top_bar_time_left">남은 시간: %1$s</string> <string name="try_again">다시 시도</string> <string name="udp">UDP</string> <string name="udp_over_tcp_port_info">UDP-over-TCP 난독 처리 프로토콜이 VPN 서버에서 연결해야 하는 TCP 포트입니다.</string> diff --git a/android/lib/resource/src/main/res/values-my/strings.xml b/android/lib/resource/src/main/res/values-my/strings.xml index 0aef2a2c2e..6a0f2ba377 100644 --- a/android/lib/resource/src/main/res/values-my/strings.xml +++ b/android/lib/resource/src/main/res/values-my/strings.xml @@ -175,6 +175,8 @@ <string name="switch_location">တည်နေရာ ပြောင်းရန်</string> <string name="tcp">TCP</string> <string name="toggle_vpn">VPN ရွေးသုံးရန်</string> + <string name="top_bar_device_name">စက်အမည်- %1$s</string> + <string name="top_bar_time_left">ကျန်သည့် အချိန်- %1$s</string> <string name="try_again">ထပ်ကြိုးစားရန်</string> <string name="udp">UDP</string> <string name="udp_over_tcp_port_info">VPN ဆာဗာကို ဖွင့်ရန် ၎င်း TCP ပေါ့တ် UDP-over-TCP Obfuscation ပရိုတိုကောလ်နှင့် ချိတ်ဆက်ထားသင့်ပါသည်။</string> diff --git a/android/lib/resource/src/main/res/values-nb/strings.xml b/android/lib/resource/src/main/res/values-nb/strings.xml index 2b0e370bb3..6872608306 100644 --- a/android/lib/resource/src/main/res/values-nb/strings.xml +++ b/android/lib/resource/src/main/res/values-nb/strings.xml @@ -175,6 +175,8 @@ <string name="switch_location">Bytt plassering</string> <string name="tcp">TCP</string> <string name="toggle_vpn">Velg VPN</string> + <string name="top_bar_device_name">Enhetsnavn: %1$s</string> + <string name="top_bar_time_left">Tid igjen: %1$s</string> <string name="try_again">Prøv på nytt</string> <string name="udp">UDP</string> <string name="udp_over_tcp_port_info">TCP-porten som UDP-over-TCP-tilsløringen skal koble til på VPN-serveren.</string> diff --git a/android/lib/resource/src/main/res/values-nl/strings.xml b/android/lib/resource/src/main/res/values-nl/strings.xml index cdbaa554c3..005f1c6907 100644 --- a/android/lib/resource/src/main/res/values-nl/strings.xml +++ b/android/lib/resource/src/main/res/values-nl/strings.xml @@ -175,6 +175,8 @@ <string name="switch_location">Locatie wijzigen</string> <string name="tcp">TCP</string> <string name="toggle_vpn">VPN in-/uitschakelen</string> + <string name="top_bar_device_name">Apparaatnaam: %1$s</string> + <string name="top_bar_time_left">Resterende tijd: %1$s</string> <string name="try_again">Probeer het opnieuw</string> <string name="udp">UDP</string> <string name="udp_over_tcp_port_info">Met welke TCP-poort moet het UDP-over-TCP-obfuscatieprotocol verbinding maken op de VPN-server.</string> diff --git a/android/lib/resource/src/main/res/values-pl/strings.xml b/android/lib/resource/src/main/res/values-pl/strings.xml index 2e2e6ee267..98b69a66a8 100644 --- a/android/lib/resource/src/main/res/values-pl/strings.xml +++ b/android/lib/resource/src/main/res/values-pl/strings.xml @@ -175,6 +175,8 @@ <string name="switch_location">Zmień lokalizację</string> <string name="tcp">TCP</string> <string name="toggle_vpn">Przełącz VPN</string> + <string name="top_bar_device_name">Nazwa urządzenia: %1$s</string> + <string name="top_bar_time_left">Pozostało: %1$s</string> <string name="try_again">Spróbuj ponownie</string> <string name="udp">UDP</string> <string name="udp_over_tcp_port_info">Port TCP, z którym powinien łączyć się protokół zaciemniania UDP-przez-TCP na serwerze VPN.</string> diff --git a/android/lib/resource/src/main/res/values-pt/strings.xml b/android/lib/resource/src/main/res/values-pt/strings.xml index 2fee06cab6..5dd4fd61ea 100644 --- a/android/lib/resource/src/main/res/values-pt/strings.xml +++ b/android/lib/resource/src/main/res/values-pt/strings.xml @@ -175,6 +175,8 @@ <string name="switch_location">Alterar local</string> <string name="tcp">TCP</string> <string name="toggle_vpn">Alternar VPN</string> + <string name="top_bar_device_name">Nome do dispositivo: %1$s</string> + <string name="top_bar_time_left">Tempo restante: %1$s</string> <string name="try_again">Tentar novamente</string> <string name="udp">UDP</string> <string name="udp_over_tcp_port_info">A que porta TCP o protocolo de ofuscação UDP sobre TCP deve ligar-se no servidor VPN.</string> diff --git a/android/lib/resource/src/main/res/values-ru/strings.xml b/android/lib/resource/src/main/res/values-ru/strings.xml index 0fb01c88ad..7b9acc9195 100644 --- a/android/lib/resource/src/main/res/values-ru/strings.xml +++ b/android/lib/resource/src/main/res/values-ru/strings.xml @@ -175,6 +175,8 @@ <string name="switch_location">Сменить местоположение</string> <string name="tcp">TCP</string> <string name="toggle_vpn">Включение VPN</string> + <string name="top_bar_device_name">Имя устройства: %1$s</string> + <string name="top_bar_time_left">Осталось времени: %1$s</string> <string name="try_again">Повторить попытку</string> <string name="udp">UDP</string> <string name="udp_over_tcp_port_info">TCP-порт, к которому должен подключаться протокол обфускации UDP через TCP на VPN-сервере.</string> diff --git a/android/lib/resource/src/main/res/values-sv/strings.xml b/android/lib/resource/src/main/res/values-sv/strings.xml index c65809dc5d..d8183b2435 100644 --- a/android/lib/resource/src/main/res/values-sv/strings.xml +++ b/android/lib/resource/src/main/res/values-sv/strings.xml @@ -175,6 +175,8 @@ <string name="switch_location">Växla plats</string> <string name="tcp">TCP</string> <string name="toggle_vpn">Växla VPN</string> + <string name="top_bar_device_name">Enhetsnamn: %1$s</string> + <string name="top_bar_time_left">Tid kvar: %1$s</string> <string name="try_again">Försök igen</string> <string name="udp">UDP</string> <string name="udp_over_tcp_port_info">Vilken TCP-port som UDP-över-TCP-obfuskeringsprotokoll bör ansluta till på VPN-servern.</string> diff --git a/android/lib/resource/src/main/res/values-th/strings.xml b/android/lib/resource/src/main/res/values-th/strings.xml index 3f01840e0a..7afc8a7b44 100644 --- a/android/lib/resource/src/main/res/values-th/strings.xml +++ b/android/lib/resource/src/main/res/values-th/strings.xml @@ -175,6 +175,8 @@ <string name="switch_location">สลับตำแหน่ง</string> <string name="tcp">TCP</string> <string name="toggle_vpn">เปิด/ปิด VPN</string> + <string name="top_bar_device_name">ชื่ออุปกรณ์: %1$s</string> + <string name="top_bar_time_left">เหลือเวลา: %1$s</string> <string name="try_again">ลองอีกครั้ง</string> <string name="udp">UDP</string> <string name="udp_over_tcp_port_info">พอร์ต TCP ใดที่โพรโทคอลการทำให้ข้อมูลยุ่งเหยิง UDP-ผ่าน-TCP ควรเชื่อมต่อบนเซิร์ฟเวอร์ VPN</string> diff --git a/android/lib/resource/src/main/res/values-tr/strings.xml b/android/lib/resource/src/main/res/values-tr/strings.xml index 08ff5f47e6..908f9ce2d9 100644 --- a/android/lib/resource/src/main/res/values-tr/strings.xml +++ b/android/lib/resource/src/main/res/values-tr/strings.xml @@ -175,6 +175,8 @@ <string name="switch_location">Konum değiştir</string> <string name="tcp">TCP</string> <string name="toggle_vpn">VPN\'i aç/kapat</string> + <string name="top_bar_device_name">Cihaz adı: %1$s</string> + <string name="top_bar_time_left">Kalan süre: %1$s</string> <string name="try_again">Tekrar dene</string> <string name="udp">UDP</string> <string name="udp_over_tcp_port_info">TCP üzerinden UDP gizleme protokolünün VPN sunucusunda hangi TCP portuna bağlanması gerekiyor.</string> diff --git a/android/lib/resource/src/main/res/values-zh-rCN/strings.xml b/android/lib/resource/src/main/res/values-zh-rCN/strings.xml index 174262c638..667ffbcde9 100644 --- a/android/lib/resource/src/main/res/values-zh-rCN/strings.xml +++ b/android/lib/resource/src/main/res/values-zh-rCN/strings.xml @@ -175,6 +175,8 @@ <string name="switch_location">切换位置</string> <string name="tcp">TCP</string> <string name="toggle_vpn">切换 VPN</string> + <string name="top_bar_device_name">设备名称:%1$s</string> + <string name="top_bar_time_left">剩余时间:%1$s</string> <string name="try_again">重试</string> <string name="udp">UDP</string> <string name="udp_over_tcp_port_info">UDP-over-TCP 混淆协议应连接到 VPN 服务器上的哪个 TCP 端口。</string> diff --git a/android/lib/resource/src/main/res/values-zh-rTW/strings.xml b/android/lib/resource/src/main/res/values-zh-rTW/strings.xml index 70b0d42c55..5378b92550 100644 --- a/android/lib/resource/src/main/res/values-zh-rTW/strings.xml +++ b/android/lib/resource/src/main/res/values-zh-rTW/strings.xml @@ -175,6 +175,8 @@ <string name="switch_location">切換位置</string> <string name="tcp">TCP</string> <string name="toggle_vpn">切換 VPN</string> + <string name="top_bar_device_name">裝置名稱:%1$s</string> + <string name="top_bar_time_left">剩餘時間:%1$s</string> <string name="try_again">再試一次</string> <string name="udp">UDP</string> <string name="udp_over_tcp_port_info">UDP-over-TCP 混淆通訊協定應連線到 VPN 伺服器上的哪個 TCP 連接埠。</string> diff --git a/android/lib/resource/src/main/res/values/strings.xml b/android/lib/resource/src/main/res/values/strings.xml index bc9630e974..c9c837d38d 100644 --- a/android/lib/resource/src/main/res/values/strings.xml +++ b/android/lib/resource/src/main/res/values/strings.xml @@ -224,4 +224,6 @@ <string name="verifying_voucher">Verifying voucher…</string> <string name="added_to_your_account">%s was added to your account.</string> <string name="less_than_one_day">less than one day</string> + <string name="top_bar_time_left">Time left: %s</string> + <string name="top_bar_device_name">Device name: %s</string> </resources> |
