summaryrefslogtreecommitdiffhomepage
path: root/android/app/src
diff options
context:
space:
mode:
authorJonatan Rhodin <jonatan.rhodin@mullvad.net>2023-07-07 11:06:12 +0200
committerJonatan Rhodin <jonatan.rhodin@mullvad.net>2023-07-10 07:31:30 +0200
commit4d89c565d67cb67aca951150332c9786dd94eca3 (patch)
tree10c311178de9ca69e8453d0b438fa2960d9cea66 /android/app/src
parent9280aca5ac1060be8f7583bdaa5599ed2647d85b (diff)
downloadmullvadvpn-4d89c565d67cb67aca951150332c9786dd94eca3.tar.xz
mullvadvpn-4d89c565d67cb67aca951150332c9786dd94eca3.zip
Replace scaffold with material 3 scaffold
Diffstat (limited to 'android/app/src')
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceRevokedScreenTest.kt29
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/Scaffolding.kt4
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DeviceRemovalDialog.kt9
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceListScreen.kt309
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceRevokedScreen.kt164
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/LoadingScreen.kt9
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/PrivacyDisclaimerScreen.kt146
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/DeviceListFragment.kt24
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/DeviceRevokedFragment.kt21
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/PrivacyDisclaimerFragment.kt17
10 files changed, 402 insertions, 330 deletions
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceRevokedScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceRevokedScreenTest.kt
index a974346f5f..908207359b 100644
--- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceRevokedScreenTest.kt
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceRevokedScreenTest.kt
@@ -4,15 +4,10 @@ import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import io.mockk.MockKAnnotations
-import io.mockk.Runs
-import io.mockk.every
-import io.mockk.impl.annotations.MockK
-import io.mockk.just
+import io.mockk.mockk
import io.mockk.verify
-import kotlinx.coroutines.flow.MutableStateFlow
import net.mullvad.mullvadvpn.compose.state.DeviceRevokedUiState
import net.mullvad.mullvadvpn.compose.theme.AppTheme
-import net.mullvad.mullvadvpn.viewmodel.DeviceRevokedViewModel
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -20,21 +15,18 @@ import org.junit.Test
class DeviceRevokedScreenTest {
@get:Rule val composeTestRule = createComposeRule()
- @MockK lateinit var mockedViewModel: DeviceRevokedViewModel
-
@Before
fun setup() {
MockKAnnotations.init(this)
- every { mockedViewModel.onGoToLoginClicked() } just Runs
}
@Test
fun testUnblockWarningShowingWhenSecured() {
// Arrange
- every { mockedViewModel.uiState } returns MutableStateFlow(DeviceRevokedUiState.SECURED)
+ val state = DeviceRevokedUiState.SECURED
// Act
- composeTestRule.setContent { AppTheme { DeviceRevokedScreen(mockedViewModel) } }
+ composeTestRule.setContent { AppTheme { DeviceRevokedScreen(state) } }
// Assert
composeTestRule.onNodeWithText(UNBLOCK_WARNING).assertExists()
@@ -43,10 +35,10 @@ class DeviceRevokedScreenTest {
@Test
fun testUnblockWarningNotShowingWhenNotSecured() {
// Arrange
- every { mockedViewModel.uiState } returns MutableStateFlow(DeviceRevokedUiState.UNSECURED)
+ val state = DeviceRevokedUiState.UNSECURED
// Act
- composeTestRule.setContent { AppTheme { DeviceRevokedScreen(mockedViewModel) } }
+ composeTestRule.setContent { AppTheme { DeviceRevokedScreen(state) } }
// Assert
composeTestRule.onNodeWithText(UNBLOCK_WARNING).assertDoesNotExist()
@@ -55,14 +47,19 @@ class DeviceRevokedScreenTest {
@Test
fun testGoToLogin() {
// Arrange
- every { mockedViewModel.uiState } returns MutableStateFlow(DeviceRevokedUiState.UNSECURED)
- composeTestRule.setContent { AppTheme { DeviceRevokedScreen(mockedViewModel) } }
+ val state = DeviceRevokedUiState.UNSECURED
+ val mockOnGoToLoginClicked: () -> Unit = mockk(relaxed = true)
+ composeTestRule.setContent {
+ AppTheme {
+ DeviceRevokedScreen(state = state, onGoToLoginClicked = mockOnGoToLoginClicked)
+ }
+ }
// Act
composeTestRule.onNodeWithText(GO_TO_LOGIN_BUTTON_TEXT).performClick()
// Assert
- verify { mockedViewModel.onGoToLoginClicked() }
+ verify { mockOnGoToLoginClicked.invoke() }
}
companion object {
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 5020002bc7..fb9e3b380b 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
@@ -3,7 +3,8 @@ package net.mullvad.mullvadvpn.compose.component
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.material.Scaffold
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -22,6 +23,7 @@ import me.onebone.toolbar.CollapsingToolbarScope
import me.onebone.toolbar.ExperimentalToolbarApi
import me.onebone.toolbar.ScrollStrategy
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ScaffoldWithTopBar(
topBarColor: Color,
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 4d962d5e89..25b2a187ad 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
@@ -30,12 +30,11 @@ import net.mullvad.mullvadvpn.compose.component.HtmlText
import net.mullvad.mullvadvpn.compose.component.textResource
import net.mullvad.mullvadvpn.model.Device
import net.mullvad.mullvadvpn.util.capitalizeFirstCharOfEachWord
-import net.mullvad.mullvadvpn.viewmodel.DeviceListViewModel
@Composable
-fun ShowDeviceRemovalDialog(viewModel: DeviceListViewModel, device: Device) {
+fun ShowDeviceRemovalDialog(onDismiss: () -> Unit, onConfirm: () -> Unit, device: Device) {
AlertDialog(
- onDismissRequest = { viewModel.clearStagedDevice() },
+ onDismissRequest = { onDismiss() },
title = {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
@@ -78,7 +77,7 @@ fun ShowDeviceRemovalDialog(viewModel: DeviceListViewModel, device: Device) {
containerColor = colorResource(id = R.color.red),
contentColor = Color.White
),
- onClick = { viewModel.confirmRemovalOfStagedDevice() },
+ onClick = onConfirm,
shape = MaterialTheme.shapes.small
) {
Text(text = stringResource(id = R.string.confirm_removal), fontSize = 18.sp)
@@ -100,7 +99,7 @@ fun ShowDeviceRemovalDialog(viewModel: DeviceListViewModel, device: Device) {
containerColor = colorResource(id = R.color.blue),
contentColor = Color.White
),
- onClick = { viewModel.clearStagedDevice() },
+ onClick = { onDismiss() },
shape = MaterialTheme.shapes.small
) {
Text(text = stringResource(id = R.string.back), fontSize = 18.sp)
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 d2bc588443..adf641e965 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
@@ -17,180 +17,233 @@ import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.component.ActionButton
import net.mullvad.mullvadvpn.compose.component.ListItem
+import net.mullvad.mullvadvpn.compose.component.ScaffoldWithTopBar
import net.mullvad.mullvadvpn.compose.dialog.ShowDeviceRemovalDialog
+import net.mullvad.mullvadvpn.compose.state.DeviceListItemUiState
+import net.mullvad.mullvadvpn.compose.state.DeviceListUiState
+import net.mullvad.mullvadvpn.compose.theme.AppTheme
import net.mullvad.mullvadvpn.compose.theme.Dimens
import net.mullvad.mullvadvpn.compose.theme.MullvadBlue
import net.mullvad.mullvadvpn.compose.theme.MullvadGreen
import net.mullvad.mullvadvpn.compose.theme.MullvadGreen40
import net.mullvad.mullvadvpn.compose.theme.MullvadWhite
import net.mullvad.mullvadvpn.compose.theme.MullvadWhite80
+import net.mullvad.mullvadvpn.model.Device
import net.mullvad.mullvadvpn.util.capitalizeFirstCharOfEachWord
import net.mullvad.mullvadvpn.util.formatDate
-import net.mullvad.mullvadvpn.viewmodel.DeviceListViewModel
+
+@Composable
+@Preview
+fun PreviewDeviceListScreen() {
+ AppTheme {
+ DeviceListScreen(
+ state =
+ DeviceListUiState(
+ deviceUiItems =
+ listOf(
+ DeviceListItemUiState(
+ device =
+ Device(
+ id = "ID",
+ name = "Name",
+ pubkey = ByteArray(10),
+ ports = ArrayList(),
+ created = "2002-12-12"
+ ),
+ isLoading = false
+ )
+ ),
+ isLoading = true,
+ stagedDevice = null
+ )
+ )
+ }
+}
@Composable
fun DeviceListScreen(
- viewModel: DeviceListViewModel,
- onBackClick: () -> Unit,
- onContinueWithLogin: () -> Unit
+ state: DeviceListUiState,
+ onBackClick: () -> Unit = {},
+ onContinueWithLogin: () -> Unit = {},
+ onSettingsClicked: () -> Unit = {},
+ onDeviceRemovalClicked: (deviceId: String) -> Unit = {},
+ onDismissDeviceRemovalDialog: () -> Unit = {},
+ onConfirmDeviceRemovalDialog: () -> Unit = {}
) {
- val state = viewModel.uiState.collectAsState().value
-
if (state.stagedDevice != null) {
- ShowDeviceRemovalDialog(viewModel = viewModel, device = state.stagedDevice)
+ ShowDeviceRemovalDialog(
+ onDismiss = onDismissDeviceRemovalDialog,
+ onConfirmDeviceRemovalDialog,
+ device = state.stagedDevice
+ )
}
- ConstraintLayout(
- modifier =
- Modifier.fillMaxHeight().fillMaxWidth().background(MaterialTheme.colorScheme.secondary)
+ val topColor = colorResource(R.color.blue)
+ ScaffoldWithTopBar(
+ topBarColor = topColor,
+ statusBarColor = topColor,
+ navigationBarColor = colorResource(id = R.color.darkBlue),
+ onSettingsClicked = onSettingsClicked
) {
- val (content, buttons) = createRefs()
-
- Column(
+ ConstraintLayout(
modifier =
- Modifier.constrainAs(content) {
- top.linkTo(parent.top)
- bottom.linkTo(buttons.top)
- height = Dimension.fillToConstraints
- width = Dimension.matchParent
- }
- .verticalScroll(rememberScrollState())
+ Modifier.fillMaxHeight()
+ .fillMaxWidth()
+ .padding(it)
+ .background(MaterialTheme.colorScheme.secondary)
) {
- ConstraintLayout(modifier = Modifier.fillMaxWidth().wrapContentHeight()) {
- val (icon, message, list) = createRefs()
+ val (content, buttons) = createRefs()
- Image(
- painter =
- painterResource(
- id =
- if (state.hasTooManyDevices) {
- R.drawable.icon_fail
- } else {
- R.drawable.icon_success
- }
- ),
- contentDescription = null, // No meaningful user info or action.
- modifier =
- Modifier.constrainAs(icon) {
- top.linkTo(parent.top, margin = 30.dp)
- start.linkTo(parent.start)
- end.linkTo(parent.end)
- }
- .width(64.dp)
- .height(64.dp)
- )
+ Column(
+ modifier =
+ Modifier.constrainAs(content) {
+ top.linkTo(parent.top)
+ bottom.linkTo(buttons.top)
+ height = Dimension.fillToConstraints
+ width = Dimension.matchParent
+ }
+ .verticalScroll(rememberScrollState())
+ ) {
+ ConstraintLayout(modifier = Modifier.fillMaxWidth().wrapContentHeight()) {
+ val (icon, message, list) = createRefs()
- Column(
- modifier =
- Modifier.constrainAs(message) {
- top.linkTo(icon.bottom, margin = 16.dp)
- start.linkTo(parent.start, margin = 22.dp)
- end.linkTo(parent.end, margin = 22.dp)
- width = Dimension.fillToConstraints
- },
- ) {
- Text(
- text =
- stringResource(
+ Image(
+ painter =
+ painterResource(
id =
if (state.hasTooManyDevices) {
- R.string.max_devices_warning_title
+ R.drawable.icon_fail
} else {
- R.string.max_devices_resolved_title
+ R.drawable.icon_success
}
),
- style = MaterialTheme.typography.headlineSmall
+ contentDescription = null, // No meaningful user info or action.
+ modifier =
+ Modifier.constrainAs(icon) {
+ top.linkTo(parent.top, margin = 30.dp)
+ start.linkTo(parent.start)
+ end.linkTo(parent.end)
+ }
+ .width(64.dp)
+ .height(64.dp)
)
- Text(
- text =
- stringResource(
- id =
- if (state.hasTooManyDevices) {
- R.string.max_devices_warning_description
- } else {
- R.string.max_devices_resolved_description
- }
- ),
- style = MaterialTheme.typography.bodySmall,
+ Column(
modifier =
- Modifier.wrapContentHeight().animateContentSize().padding(top = 8.dp)
- )
- }
+ Modifier.constrainAs(message) {
+ top.linkTo(icon.bottom, margin = 16.dp)
+ start.linkTo(parent.start, margin = 22.dp)
+ end.linkTo(parent.end, margin = 22.dp)
+ width = Dimension.fillToConstraints
+ },
+ ) {
+ Text(
+ text =
+ stringResource(
+ id =
+ if (state.hasTooManyDevices) {
+ R.string.max_devices_warning_title
+ } else {
+ R.string.max_devices_resolved_title
+ }
+ ),
+ style = MaterialTheme.typography.headlineSmall
+ )
- Box(
- modifier =
- Modifier.constrainAs(list) {
- top.linkTo(message.bottom, margin = 20.dp)
- height = Dimension.wrapContent
- width = Dimension.matchParent
- }
- ) {
- Column {
- state.deviceUiItems.forEach { deviceUiState ->
- ListItem(
- text = deviceUiState.device.name.capitalizeFirstCharOfEachWord(),
- subText =
- deviceUiState.device.creationDate?.let { creationDate ->
- stringResource(
- id = R.string.created_x,
- creationDate.formatDate()
- )
- },
- height = Dimens.listItemHeightExtra,
- isLoading = deviceUiState.isLoading,
- iconResourceId = R.drawable.icon_close
- ) {
- viewModel.stageDeviceForRemoval(deviceUiState.device.id)
+ Text(
+ text =
+ stringResource(
+ id =
+ if (state.hasTooManyDevices) {
+ R.string.max_devices_warning_description
+ } else {
+ R.string.max_devices_resolved_description
+ }
+ ),
+ style = MaterialTheme.typography.bodySmall,
+ modifier =
+ Modifier.wrapContentHeight()
+ .animateContentSize()
+ .padding(top = 8.dp)
+ )
+ }
+
+ Box(
+ modifier =
+ Modifier.constrainAs(list) {
+ top.linkTo(message.bottom, margin = 20.dp)
+ height = Dimension.wrapContent
+ width = Dimension.matchParent
+ }
+ ) {
+ Column {
+ state.deviceUiItems.forEach { deviceUiState ->
+ ListItem(
+ text =
+ deviceUiState.device.name.capitalizeFirstCharOfEachWord(),
+ subText =
+ deviceUiState.device.creationDate?.let { creationDate ->
+ stringResource(
+ id = R.string.created_x,
+ creationDate.formatDate()
+ )
+ },
+ height = Dimens.listItemHeightExtra,
+ isLoading = deviceUiState.isLoading,
+ iconResourceId = R.drawable.icon_close
+ ) {
+ onDeviceRemovalClicked(deviceUiState.device.id)
+ }
}
}
}
}
}
- }
- Column(
- modifier =
- Modifier.constrainAs(buttons) {
- bottom.linkTo(parent.bottom, margin = 22.dp)
- start.linkTo(parent.start, margin = 22.dp)
- end.linkTo(parent.end, margin = 22.dp)
- width = Dimension.fillToConstraints
- }
- ) {
- ActionButton(
- text = stringResource(id = R.string.continue_login),
- onClick = onContinueWithLogin,
- isEnabled = state.hasTooManyDevices.not() && state.isLoading.not(),
- colors =
- ButtonDefaults.buttonColors(
- containerColor = MullvadGreen,
- disabledContainerColor = MullvadGreen40,
- disabledContentColor = MullvadWhite80,
- contentColor = MullvadWhite
- )
- )
+ Column(
+ modifier =
+ Modifier.constrainAs(buttons) {
+ bottom.linkTo(parent.bottom, margin = 22.dp)
+ start.linkTo(parent.start, margin = 22.dp)
+ end.linkTo(parent.end, margin = 22.dp)
+ width = Dimension.fillToConstraints
+ }
+ ) {
+ ActionButton(
+ text = stringResource(id = R.string.continue_login),
+ onClick = onContinueWithLogin,
+ isEnabled = state.hasTooManyDevices.not() && state.isLoading.not(),
+ colors =
+ ButtonDefaults.buttonColors(
+ containerColor = MullvadGreen,
+ disabledContainerColor = MullvadGreen40,
+ disabledContentColor = MullvadWhite80,
+ contentColor = MullvadWhite
+ )
+ )
- ActionButton(
- text = stringResource(id = R.string.back),
- onClick = onBackClick,
- colors =
- ButtonDefaults.buttonColors(
- containerColor = MullvadBlue,
- contentColor = MullvadWhite
- ),
- modifier = Modifier.padding(top = 16.dp)
- )
+ ActionButton(
+ text = stringResource(id = R.string.back),
+ onClick = onBackClick,
+ colors =
+ ButtonDefaults.buttonColors(
+ containerColor = MullvadBlue,
+ contentColor = MullvadWhite
+ ),
+ modifier = Modifier.padding(top = 16.dp)
+ )
+ }
}
}
}
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 242494ba1a..07a2a759b2 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
@@ -11,104 +11,132 @@ import androidx.compose.foundation.layout.width
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.component.ActionButton
+import net.mullvad.mullvadvpn.compose.component.ScaffoldWithTopBar
import net.mullvad.mullvadvpn.compose.state.DeviceRevokedUiState
-import net.mullvad.mullvadvpn.viewmodel.DeviceRevokedViewModel
+import net.mullvad.mullvadvpn.compose.theme.AppTheme
+@Preview
@Composable
-fun DeviceRevokedScreen(deviceRevokedViewModel: DeviceRevokedViewModel) {
- val state = deviceRevokedViewModel.uiState.collectAsState().value
-
- ConstraintLayout(
- modifier =
- Modifier.fillMaxHeight().fillMaxWidth().background(colorResource(id = R.color.darkBlue))
- ) {
- val (icon, body, actionButtons) = createRefs()
+fun PreivewDeviceRevokedScreen() {
+ AppTheme { DeviceRevokedScreen(state = DeviceRevokedUiState.SECURED) }
+}
- Image(
- painter = painterResource(id = R.drawable.icon_fail),
- contentDescription = null, // No meaningful user info or action.
- modifier =
- Modifier.constrainAs(icon) {
- top.linkTo(parent.top, margin = 30.dp)
- start.linkTo(parent.start)
- end.linkTo(parent.end)
- }
- .padding(horizontal = 12.dp)
- .width(80.dp)
- .height(80.dp)
+@Composable
+fun DeviceRevokedScreen(
+ state: DeviceRevokedUiState,
+ onSettingsClicked: () -> Unit = {},
+ onGoToLoginClicked: () -> Unit = {}
+) {
+ val topColor =
+ colorResource(
+ if (state == DeviceRevokedUiState.SECURED) {
+ R.color.green
+ } else {
+ R.color.red
+ }
)
- Column(
+ ScaffoldWithTopBar(
+ topBarColor = topColor,
+ statusBarColor = topColor,
+ navigationBarColor = colorResource(id = R.color.darkBlue),
+ onSettingsClicked = onSettingsClicked,
+ ) {
+ ConstraintLayout(
modifier =
- Modifier.constrainAs(body) {
- top.linkTo(icon.bottom, margin = 22.dp)
- start.linkTo(parent.start, margin = 22.dp)
- end.linkTo(parent.end, margin = 22.dp)
- width = Dimension.fillToConstraints
- },
+ Modifier.fillMaxHeight()
+ .fillMaxWidth()
+ .padding(it)
+ .background(colorResource(id = R.color.darkBlue))
) {
- Text(
- text = stringResource(id = R.string.device_inactive_title),
- fontSize = 24.sp,
- color = Color.White,
- fontWeight = FontWeight.Bold
- )
+ val (icon, body, actionButtons) = createRefs()
- Text(
- text = stringResource(id = R.string.device_inactive_description),
- fontSize = 12.sp,
- color = Color.White,
- modifier = Modifier.padding(top = 10.dp)
+ Image(
+ painter = painterResource(id = R.drawable.icon_fail),
+ contentDescription = null, // No meaningful user info or action.
+ modifier =
+ Modifier.constrainAs(icon) {
+ top.linkTo(parent.top, margin = 30.dp)
+ start.linkTo(parent.start)
+ end.linkTo(parent.end)
+ }
+ .padding(horizontal = 12.dp)
+ .width(80.dp)
+ .height(80.dp)
)
- if (state == DeviceRevokedUiState.SECURED) {
+ Column(
+ modifier =
+ Modifier.constrainAs(body) {
+ top.linkTo(icon.bottom, margin = 22.dp)
+ start.linkTo(parent.start, margin = 22.dp)
+ end.linkTo(parent.end, margin = 22.dp)
+ width = Dimension.fillToConstraints
+ },
+ ) {
Text(
- text = stringResource(id = R.string.device_inactive_unblock_warning),
+ text = stringResource(id = R.string.device_inactive_title),
+ fontSize = 24.sp,
+ color = Color.White,
+ fontWeight = FontWeight.Bold
+ )
+
+ Text(
+ text = stringResource(id = R.string.device_inactive_description),
fontSize = 12.sp,
color = Color.White,
modifier = Modifier.padding(top = 10.dp)
)
- }
- }
- Column(
- modifier =
- Modifier.constrainAs(actionButtons) {
- bottom.linkTo(parent.bottom, margin = 22.dp)
- start.linkTo(parent.start, margin = 22.dp)
- end.linkTo(parent.end, margin = 22.dp)
- width = Dimension.fillToConstraints
- }
- ) {
- ActionButton(
- text = stringResource(id = R.string.go_to_login),
- onClick = { deviceRevokedViewModel.onGoToLoginClicked() },
- colors =
- ButtonDefaults.buttonColors(
- contentColor = Color.White,
- containerColor =
- colorResource(
- if (state == DeviceRevokedUiState.SECURED) {
- R.color.red60
- } else {
- R.color.blue
- }
- )
+ if (state == DeviceRevokedUiState.SECURED) {
+ Text(
+ text = stringResource(id = R.string.device_inactive_unblock_warning),
+ fontSize = 12.sp,
+ color = Color.White,
+ modifier = Modifier.padding(top = 10.dp)
)
- )
+ }
+ }
+
+ Column(
+ modifier =
+ Modifier.constrainAs(actionButtons) {
+ bottom.linkTo(parent.bottom, margin = 22.dp)
+ start.linkTo(parent.start, margin = 22.dp)
+ end.linkTo(parent.end, margin = 22.dp)
+ width = Dimension.fillToConstraints
+ }
+ ) {
+ ActionButton(
+ text = stringResource(id = R.string.go_to_login),
+ onClick = onGoToLoginClicked,
+ colors =
+ ButtonDefaults.buttonColors(
+ contentColor = Color.White,
+ containerColor =
+ colorResource(
+ if (state == DeviceRevokedUiState.SECURED) {
+ R.color.red60
+ } else {
+ R.color.blue
+ }
+ )
+ )
+ )
+ }
}
}
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/LoadingScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/LoadingScreen.kt
index 48d86fd2f8..6c5b5324ec 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/LoadingScreen.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/LoadingScreen.kt
@@ -25,11 +25,11 @@ import net.mullvad.mullvadvpn.compose.component.ScaffoldWithTopBar
@Preview
@Composable
private fun PreviewLoadingScreen() {
- LoadingScreen {}
+ LoadingScreen()
}
@Composable
-fun LoadingScreen(onSettingsCogClicked: () -> Unit) {
+fun LoadingScreen(onSettingsCogClicked: () -> Unit = {}) {
val backgroundColor = colorResource(id = R.color.blue)
ScaffoldWithTopBar(
@@ -42,7 +42,10 @@ fun LoadingScreen(onSettingsCogClicked: () -> Unit) {
Box(
contentAlignment = Alignment.Center,
modifier =
- Modifier.background(backgroundColor).padding(bottom = 64.dp).fillMaxSize()
+ Modifier.background(backgroundColor)
+ .padding(it)
+ .padding(bottom = it.calculateTopPadding())
+ .fillMaxSize()
) {
Column(
verticalArrangement = Arrangement.Center,
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 b932a63686..29146fe634 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
@@ -24,90 +24,110 @@ import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.component.ActionButton
+import net.mullvad.mullvadvpn.compose.component.ScaffoldWithTopBar
+import net.mullvad.mullvadvpn.compose.theme.AppTheme
+
+@Preview
+@Composable
+fun PreviewPrivacyDisclaimerScreen() {
+ AppTheme { PrivacyDisclaimerScreen({}, {}) }
+}
@Composable
fun PrivacyDisclaimerScreen(
onPrivacyPolicyLinkClicked: () -> Unit,
onAcceptClicked: () -> Unit,
) {
- ConstraintLayout(
- modifier =
- Modifier.fillMaxHeight().fillMaxWidth().background(colorResource(id = R.color.darkBlue))
+ val topColor = colorResource(R.color.blue)
+ ScaffoldWithTopBar(
+ topBarColor = topColor,
+ statusBarColor = topColor,
+ navigationBarColor = colorResource(id = R.color.darkBlue),
+ onSettingsClicked = null
) {
- val (body, actionButtons) = createRefs()
- val sideMargin = dimensionResource(id = R.dimen.side_margin)
-
- Column(
+ ConstraintLayout(
modifier =
- Modifier.constrainAs(body) {
- top.linkTo(parent.top, margin = sideMargin)
- start.linkTo(parent.start, margin = sideMargin)
- end.linkTo(parent.end, margin = sideMargin)
- width = Dimension.fillToConstraints
- },
+ Modifier.fillMaxHeight()
+ .fillMaxWidth()
+ .padding(it)
+ .background(colorResource(id = R.color.darkBlue))
) {
- Text(
- text = stringResource(id = R.string.privacy_disclaimer_title),
- fontSize = 24.sp,
- color = Color.White,
- fontWeight = FontWeight.Bold
- )
-
- Text(
- text = stringResource(id = R.string.privacy_disclaimer_body),
- fontSize = 14.sp,
- color = Color.White,
- modifier = Modifier.padding(top = 10.dp)
- )
+ val (body, actionButtons) = createRefs()
+ val sideMargin = dimensionResource(id = R.dimen.side_margin)
- Row(modifier = Modifier.padding(top = 10.dp)) {
- ClickableText(
- text = AnnotatedString(stringResource(id = R.string.privacy_policy_label)),
- onClick = { onPrivacyPolicyLinkClicked.invoke() },
- style =
- TextStyle(
- fontSize = 12.sp,
- color = Color.White,
- textDecoration = TextDecoration.Underline
- )
+ Column(
+ modifier =
+ Modifier.constrainAs(body) {
+ top.linkTo(parent.top, margin = sideMargin)
+ start.linkTo(parent.start, margin = sideMargin)
+ end.linkTo(parent.end, margin = sideMargin)
+ width = Dimension.fillToConstraints
+ },
+ ) {
+ Text(
+ text = stringResource(id = R.string.privacy_disclaimer_title),
+ fontSize = 24.sp,
+ color = Color.White,
+ fontWeight = FontWeight.Bold
)
- Image(
- painter = painterResource(id = R.drawable.icon_extlink),
- contentDescription = null,
- modifier =
- Modifier.align(Alignment.CenterVertically)
- .padding(start = 2.dp, top = 2.dp)
- .width(10.dp)
- .height(10.dp)
+ Text(
+ text = stringResource(id = R.string.privacy_disclaimer_body),
+ fontSize = 14.sp,
+ color = Color.White,
+ modifier = Modifier.padding(top = 10.dp)
)
- }
- }
- Column(
- modifier =
- Modifier.constrainAs(actionButtons) {
- bottom.linkTo(parent.bottom, margin = sideMargin)
- start.linkTo(parent.start, margin = sideMargin)
- end.linkTo(parent.end, margin = sideMargin)
- width = Dimension.fillToConstraints
- }
- ) {
- ActionButton(
- text = stringResource(id = R.string.agree_and_continue),
- onClick = onAcceptClicked::invoke,
- colors =
- ButtonDefaults.buttonColors(
- contentColor = Color.White,
- containerColor = colorResource(R.color.blue)
+ Row(modifier = Modifier.padding(top = 10.dp)) {
+ ClickableText(
+ text = AnnotatedString(stringResource(id = R.string.privacy_policy_label)),
+ onClick = { onPrivacyPolicyLinkClicked.invoke() },
+ style =
+ TextStyle(
+ fontSize = 12.sp,
+ color = Color.White,
+ textDecoration = TextDecoration.Underline
+ )
+ )
+
+ Image(
+ painter = painterResource(id = R.drawable.icon_extlink),
+ contentDescription = null,
+ modifier =
+ Modifier.align(Alignment.CenterVertically)
+ .padding(start = 2.dp, top = 2.dp)
+ .width(10.dp)
+ .height(10.dp)
)
- )
+ }
+ }
+
+ Column(
+ modifier =
+ Modifier.constrainAs(actionButtons) {
+ bottom.linkTo(parent.bottom, margin = sideMargin)
+ start.linkTo(parent.start, margin = sideMargin)
+ end.linkTo(parent.end, margin = sideMargin)
+ width = Dimension.fillToConstraints
+ }
+ ) {
+ ActionButton(
+ text = stringResource(id = R.string.agree_and_continue),
+ onClick = onAcceptClicked::invoke,
+ colors =
+ ButtonDefaults.buttonColors(
+ contentColor = Color.White,
+ containerColor = colorResource(R.color.blue)
+ )
+ )
+ }
}
}
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/DeviceListFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/DeviceListFragment.kt
index 2dab40c06a..c89466f7e6 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/DeviceListFragment.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/DeviceListFragment.kt
@@ -5,8 +5,8 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
+import androidx.compose.runtime.collectAsState
import androidx.compose.ui.platform.ComposeView
-import androidx.compose.ui.res.colorResource
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
@@ -14,7 +14,6 @@ import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.R
-import net.mullvad.mullvadvpn.compose.component.ScaffoldWithTopBar
import net.mullvad.mullvadvpn.compose.screen.DeviceListScreen
import net.mullvad.mullvadvpn.compose.theme.AppTheme
import net.mullvad.mullvadvpn.ui.MainActivity
@@ -40,19 +39,16 @@ class DeviceListFragment : Fragment() {
return inflater.inflate(R.layout.fragment_compose, container, false).apply {
findViewById<ComposeView>(R.id.compose_view).setContent {
AppTheme {
- val topColor = colorResource(R.color.blue)
- ScaffoldWithTopBar(
- topBarColor = topColor,
- statusBarColor = topColor,
- navigationBarColor = colorResource(id = R.color.darkBlue),
+ val state = deviceListViewModel.uiState.collectAsState().value
+ DeviceListScreen(
+ state = state,
+ onBackClick = { openLoginView(doTriggerAutoLogin = false) },
+ onContinueWithLogin = { openLoginView(doTriggerAutoLogin = true) },
onSettingsClicked = this@DeviceListFragment::openSettings,
- content = {
- DeviceListScreen(
- viewModel = deviceListViewModel,
- onBackClick = { openLoginView(doTriggerAutoLogin = false) },
- onContinueWithLogin = { openLoginView(doTriggerAutoLogin = true) }
- )
- }
+ onDeviceRemovalClicked = deviceListViewModel::stageDeviceForRemoval,
+ onDismissDeviceRemovalDialog = deviceListViewModel::clearStagedDevice,
+ onConfirmDeviceRemovalDialog =
+ deviceListViewModel::confirmRemovalOfStagedDevice
)
}
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/DeviceRevokedFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/DeviceRevokedFragment.kt
index 62036a08ae..0e8e77622f 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/DeviceRevokedFragment.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/DeviceRevokedFragment.kt
@@ -6,12 +6,9 @@ import android.view.View
import android.view.ViewGroup
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.platform.ComposeView
-import androidx.compose.ui.res.colorResource
import androidx.fragment.app.Fragment
import net.mullvad.mullvadvpn.R
-import net.mullvad.mullvadvpn.compose.component.ScaffoldWithTopBar
import net.mullvad.mullvadvpn.compose.screen.DeviceRevokedScreen
-import net.mullvad.mullvadvpn.compose.state.DeviceRevokedUiState
import net.mullvad.mullvadvpn.compose.theme.AppTheme
import net.mullvad.mullvadvpn.ui.MainActivity
import net.mullvad.mullvadvpn.viewmodel.DeviceRevokedViewModel
@@ -29,22 +26,10 @@ class DeviceRevokedFragment : Fragment() {
findViewById<ComposeView>(R.id.compose_view).setContent {
AppTheme {
val state = deviceRevokedViewModel.uiState.collectAsState().value
-
- val topColor =
- colorResource(
- if (state == DeviceRevokedUiState.SECURED) {
- R.color.green
- } else {
- R.color.red
- }
- )
-
- ScaffoldWithTopBar(
- topBarColor = topColor,
- statusBarColor = topColor,
- navigationBarColor = colorResource(id = R.color.darkBlue),
+ DeviceRevokedScreen(
+ state = state,
onSettingsClicked = this@DeviceRevokedFragment::openSettingsView,
- content = { DeviceRevokedScreen(deviceRevokedViewModel) }
+ onGoToLoginClicked = deviceRevokedViewModel::onGoToLoginClicked
)
}
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/PrivacyDisclaimerFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/PrivacyDisclaimerFragment.kt
index 9d73cc9f2b..6bc0192c73 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/PrivacyDisclaimerFragment.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/PrivacyDisclaimerFragment.kt
@@ -7,10 +7,8 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.ui.platform.ComposeView
-import androidx.compose.ui.res.colorResource
import androidx.fragment.app.Fragment
import net.mullvad.mullvadvpn.R
-import net.mullvad.mullvadvpn.compose.component.ScaffoldWithTopBar
import net.mullvad.mullvadvpn.compose.screen.PrivacyDisclaimerScreen
import net.mullvad.mullvadvpn.compose.theme.AppTheme
import net.mullvad.mullvadvpn.lib.endpoint.getApiEndpointConfigurationExtras
@@ -33,18 +31,9 @@ class PrivacyDisclaimerFragment : Fragment(), StatusBarPainter, NavigationBarPai
return inflater.inflate(R.layout.fragment_compose, container, false).apply {
findViewById<ComposeView>(R.id.compose_view).setContent {
AppTheme {
- val topColor = colorResource(R.color.blue)
- ScaffoldWithTopBar(
- topBarColor = topColor,
- statusBarColor = topColor,
- navigationBarColor = colorResource(id = R.color.darkBlue),
- onSettingsClicked = null,
- content = {
- PrivacyDisclaimerScreen(
- onPrivacyPolicyLinkClicked = { openPrivacyPolicy() },
- onAcceptClicked = { handleAcceptedPrivacyDisclaimer() }
- )
- }
+ PrivacyDisclaimerScreen(
+ onPrivacyPolicyLinkClicked = { openPrivacyPolicy() },
+ onAcceptClicked = { handleAcceptedPrivacyDisclaimer() }
)
}
}