Skip to content

Commit

Permalink
Add device name and time left
Browse files Browse the repository at this point in the history
  • Loading branch information
Rawa committed Oct 13, 2023
1 parent 11948ce commit c147165
Show file tree
Hide file tree
Showing 34 changed files with 298 additions and 44 deletions.
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -66,6 +67,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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -107,7 +108,7 @@ fun ConnectScreen(
}
}

ScaffoldWithTopBar(
ScaffoldWithTopBarAndDeviceName(
topBarColor =
if (uiState.tunnelUiState.isSecured()) {
MaterialTheme.colorScheme.inversePrimary
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
)
}
Expand All @@ -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()
)
}
Expand All @@ -76,7 +77,8 @@ private fun PreviewOutOfTimeScreenError() {
tunnelState =
TunnelState.Error(
ErrorState(cause = ErrorStateCause.IsOffline, isBlocking = true)
)
),
deviceName = "Stable Horse"
),
uiSideEffect = MutableSharedFlow<OutOfTimeViewModel.UiSideEffect>().asSharedFlow()
)
Expand Down Expand Up @@ -106,7 +108,7 @@ fun OutOfTimeScreen(
}
}
val scrollState = rememberScrollState()
ScaffoldWithTopBar(
ScaffoldWithTopBarAndDeviceName(
topBarColor =
if (uiState.tunnelState.isSecured()) {
MaterialTheme.colorScheme.inversePrimary
Expand All @@ -128,7 +130,9 @@ fun OutOfTimeScreen(
}
.copy(alpha = AlphaTopBar),
onSettingsClicked = onSettingsClick,
onAccountClicked = onAccountClick
onAccountClicked = onAccountClick,
deviceName = uiState.deviceName,
timeLeft = null
) {
Column(
verticalArrangement = Arrangement.Bottom,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand All @@ -27,7 +29,9 @@ data class ConnectUiState(
outAddress = "",
showLocation = false,
isTunnelInfoExpanded = false,
connectNotificationState = ConnectNotificationState.HideNotification
connectNotificationState = ConnectNotificationState.HideNotification,
deviceName = null,
daysLeftUntilExpiry = null
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package net.mullvad.mullvadvpn.util

import java.text.DateFormat
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.DurationUnit
import org.joda.time.DateTime
import org.joda.time.format.ISODateTimeFormat

fun DateTime.formatDate(): String = ISODateTimeFormat.date().print(this)

fun DateTime.toExpiryDateString(): String =
DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT).format(this.toDate())

fun DateTime.daysFromNow() =
(toInstant().millis - DateTime.now().toInstant().millis).milliseconds.toInt(DurationUnit.DAYS)
Loading

0 comments on commit c147165

Please sign in to comment.