diff options
| author | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2023-07-07 11:06:12 +0200 |
|---|---|---|
| committer | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2023-07-10 07:31:30 +0200 |
| commit | 4d89c565d67cb67aca951150332c9786dd94eca3 (patch) | |
| tree | 10c311178de9ca69e8453d0b438fa2960d9cea66 /android | |
| parent | 9280aca5ac1060be8f7583bdaa5599ed2647d85b (diff) | |
| download | mullvadvpn-4d89c565d67cb67aca951150332c9786dd94eca3.tar.xz mullvadvpn-4d89c565d67cb67aca951150332c9786dd94eca3.zip | |
Replace scaffold with material 3 scaffold
Diffstat (limited to 'android')
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() } ) } } |
