summaryrefslogtreecommitdiffhomepage
path: root/android
diff options
context:
space:
mode:
authorDavid Göransson <david.goransson90@gmail.com>2023-10-03 15:00:33 +0200
committerAlbin <albin@mullvad.net>2023-10-06 17:34:38 +0200
commit7cc6fc383ef5def605ab9e0cca0e08e14f76df3b (patch)
tree47bdec6199644bb285d6807895d630363bfa9c02 /android
parent7256c138ebc59463bfa0771f7c3093bac36a8f09 (diff)
downloadmullvadvpn-7cc6fc383ef5def605ab9e0cca0e08e14f76df3b.tar.xz
mullvadvpn-7cc6fc383ef5def605ab9e0cca0e08e14f76df3b.zip
Replace old topbar with Material3
Diffstat (limited to 'android')
-rw-r--r--android/app/build.gradle.kts1
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/CollapsingToolbarScaffold.kt89
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/NavigateBackIconButton.kt27
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/Scaffolding.kt136
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/TopBar.kt149
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreen.kt240
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreen.kt48
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SplitTunnelingScreen.kt222
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt72
-rw-r--r--android/buildSrc/src/main/kotlin/Dependencies.kt2
-rw-r--r--android/lib/theme/src/main/kotlin/net/mullvad/mullvadvpn/lib/theme/Theme.kt3
11 files changed, 427 insertions, 562 deletions
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index 6c61206afe..220627eaad 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -305,7 +305,6 @@ dependencies {
implementation(Dependencies.AndroidX.lifecycleRuntimeKtx)
implementation(Dependencies.AndroidX.lifecycleViewmodelKtx)
implementation(Dependencies.AndroidX.recyclerview)
- implementation(Dependencies.Compose.composeCollapsingToolbar)
implementation(Dependencies.Compose.constrainLayout)
implementation(Dependencies.Compose.foundation)
implementation(Dependencies.Compose.viewModelLifecycle)
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/CollapsingToolbarScaffold.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/CollapsingToolbarScaffold.kt
deleted file mode 100644
index d1651e542f..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/CollapsingToolbarScaffold.kt
+++ /dev/null
@@ -1,89 +0,0 @@
-package net.mullvad.mullvadvpn.compose.component
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.BoxWithConstraints
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableIntStateOf
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.layout.onGloballyPositioned
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.unit.dp
-import com.google.accompanist.systemuicontroller.rememberSystemUiController
-import me.onebone.toolbar.CollapsingToolbarScaffold
-import me.onebone.toolbar.CollapsingToolbarScaffoldScope
-import me.onebone.toolbar.CollapsingToolbarScaffoldState
-import me.onebone.toolbar.CollapsingToolbarScope
-import me.onebone.toolbar.ExperimentalToolbarApi
-import me.onebone.toolbar.ScrollStrategy
-
-@OptIn(ExperimentalToolbarApi::class)
-@Composable
-fun CollapsingToolbarScaffold(
- backgroundColor: Color,
- state: CollapsingToolbarScaffoldState,
- modifier: Modifier = Modifier,
- scrollStrategy: ScrollStrategy = ScrollStrategy.ExitUntilCollapsed,
- isEnabledWhenCollapsable: Boolean = true,
- toolbarModifier: Modifier = Modifier,
- toolbar: @Composable CollapsingToolbarScope.() -> Unit,
- body: @Composable CollapsingToolbarScaffoldScope.() -> Unit,
-) {
- val dynamic = remember { mutableStateOf(0.dp) }
- val systemUiController = rememberSystemUiController()
- systemUiController.setNavigationBarColor(backgroundColor)
-
- var isCollapsable by remember { mutableStateOf(false) }
-
- LaunchedEffect(isCollapsable) {
- if (!isCollapsable) {
- state.toolbarState.expand()
- }
- }
-
- val totalHeights = remember { mutableStateOf(0.dp) }
- val localDensity = LocalDensity.current
-
- CollapsingToolbarScaffold(
- modifier =
- modifier
- .background(backgroundColor)
- .fillMaxWidth()
- .fillMaxHeight()
- .onGloballyPositioned { coordinates ->
- totalHeights.value = with(localDensity) { coordinates.size.height.toDp() }
- },
- state = state,
- scrollStrategy = scrollStrategy,
- toolbarModifier =
- toolbarModifier.onGloballyPositioned { coordinates ->
- with(localDensity) {
- dynamic.value = totalHeights.value - coordinates.size.height.toDp()
- }
- },
- enabled = isEnabledWhenCollapsable && isCollapsable,
- toolbar = { toolbar() }
- ) {
- var bodyHeight by remember { mutableIntStateOf(0) }
-
- BoxWithConstraints(
- modifier =
- Modifier.height(dynamic.value).onGloballyPositioned { bodyHeight = it.size.height }
- ) {
- val minMaxToolbarHeightDiff =
- with(state) { toolbarState.maxHeight - toolbarState.minHeight }
- val isContentHigherThanCollapseThreshold =
- with(localDensity) { bodyHeight >= maxHeight.toPx() - minMaxToolbarHeightDiff }
- isCollapsable = isContentHigherThanCollapseThreshold
- body()
- }
- }
-}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/NavigateBackIconButton.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/NavigateBackIconButton.kt
new file mode 100644
index 0000000000..798b5e1574
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/NavigateBackIconButton.kt
@@ -0,0 +1,27 @@
+package net.mullvad.mullvadvpn.compose.component
+
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.rotate
+import androidx.compose.ui.res.painterResource
+import net.mullvad.mullvadvpn.R
+
+@Composable
+fun NavigateBackIconButton(onNavigateBack: () -> Unit) {
+ IconButton(onClick = onNavigateBack) {
+ Icon(painter = painterResource(id = R.drawable.icon_back), contentDescription = null)
+ }
+}
+
+@Composable
+fun NavigateBackDownIconButton(onNavigateBack: () -> Unit) {
+ IconButton(onClick = onNavigateBack) {
+ Icon(
+ modifier = Modifier.rotate(-90f),
+ painter = painterResource(id = R.drawable.icon_back),
+ contentDescription = null
+ )
+ }
+}
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 0d032f962a..47966b3b88 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,32 +1,32 @@
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.foundation.layout.RowScope
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.MediumTopAppBar
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Snackbar
import androidx.compose.material3.SnackbarData
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableIntStateOf
-import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.layout.onGloballyPositioned
-import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.text.style.TextOverflow
import com.google.accompanist.systemuicontroller.rememberSystemUiController
-import me.onebone.toolbar.CollapsingToolbarScaffold
-import me.onebone.toolbar.CollapsingToolbarScaffoldScope
-import me.onebone.toolbar.CollapsingToolbarScaffoldState
-import me.onebone.toolbar.CollapsingToolbarScope
-import me.onebone.toolbar.ExperimentalToolbarApi
-import me.onebone.toolbar.ScrollStrategy
import net.mullvad.mullvadvpn.lib.theme.AlphaTopBar
@Composable
@@ -51,8 +51,8 @@ fun ScaffoldWithTopBar(
Scaffold(
modifier = modifier,
topBar = {
- TopBar(
- backgroundColor = topBarColor,
+ MullvadTopBar(
+ containerColor = topBarColor,
iconTintColor = iconTintColor,
onSettingsClicked = onSettingsClicked,
onAccountClicked = onAccountClicked,
@@ -75,50 +75,76 @@ fun MullvadSnackbar(snackbarData: SnackbarData) {
}
@Composable
-@OptIn(ExperimentalToolbarApi::class)
-fun CollapsableAwareToolbarScaffold(
- backgroundColor: Color,
+@OptIn(ExperimentalMaterial3Api::class)
+fun ScaffoldWithMediumTopBar(
+ appBarTitle: String,
modifier: Modifier = Modifier,
- state: CollapsingToolbarScaffoldState,
- scrollStrategy: ScrollStrategy,
- isEnabledWhenCollapsable: Boolean = true,
- toolbarModifier: Modifier = Modifier,
- toolbar: @Composable CollapsingToolbarScope.() -> Unit,
- body: @Composable CollapsingToolbarScaffoldScope.() -> Unit
+ navigationIcon: @Composable () -> Unit = {},
+ actions: @Composable RowScope.() -> Unit = {},
+ lazyListState: LazyListState = rememberLazyListState(),
+ content: @Composable (modifier: Modifier, lazyListState: LazyListState) -> Unit
) {
- val systemUiController = rememberSystemUiController()
- systemUiController.setNavigationBarColor(backgroundColor)
-
- var isCollapsable by remember { mutableStateOf(false) }
- LaunchedEffect(isCollapsable) {
- if (!isCollapsable) {
- state.toolbarState.expand()
+ val appBarState = rememberTopAppBarState()
+ val scrollBehavior =
+ TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
+ appBarState,
+ canScroll = { lazyListState.canScrollBackward || lazyListState.canScrollForward }
+ )
+ Scaffold(
+ modifier = modifier.fillMaxSize().nestedScroll(scrollBehavior.nestedScrollConnection),
+ topBar = {
+ MediumTopAppBar(
+ title = { Text(appBarTitle, maxLines = 1, overflow = TextOverflow.Ellipsis) },
+ navigationIcon = navigationIcon,
+ scrollBehavior = scrollBehavior,
+ colors =
+ TopAppBarDefaults.mediumTopAppBarColors(
+ containerColor = MaterialTheme.colorScheme.background
+ ),
+ actions = actions
+ )
+ },
+ content = {
+ content(Modifier.fillMaxSize().padding(it).drawVerticalScrollbar(lazyListState), lazyListState)
}
- }
-
- CollapsingToolbarScaffold(
- modifier = modifier.background(backgroundColor),
- state = state,
- scrollStrategy = scrollStrategy,
- enabled = isEnabledWhenCollapsable && isCollapsable,
- toolbarModifier = toolbarModifier,
- toolbar = toolbar,
- body = {
- var bodyHeight by remember { mutableIntStateOf(0) }
+ )
+}
- BoxWithConstraints(
- modifier = Modifier.onGloballyPositioned { bodyHeight = it.size.height }
- ) {
- val minMaxToolbarHeightDiff =
- with(state) { toolbarState.maxHeight - toolbarState.minHeight }
- val isContentHigherThanCollapseThreshold =
- with(LocalDensity.current) {
- bodyHeight > maxHeight.toPx() - minMaxToolbarHeightDiff
- }
- isCollapsable = isContentHigherThanCollapseThreshold
- body()
- }
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun ScaffoldWithMediumTopBar(
+ appBarTitle: String,
+ modifier: Modifier = Modifier,
+ navigationIcon: @Composable () -> Unit = {},
+ actions: @Composable RowScope.() -> Unit = {},
+ content: @Composable (modifier: Modifier) -> Unit
+) {
+ val appBarState = rememberTopAppBarState()
+ val scrollState = rememberScrollState()
+ val scrollBehavior =
+ TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
+ appBarState,
+ canScroll = { scrollState.canScrollBackward || scrollState.canScrollForward }
+ )
+ Scaffold(
+ modifier = modifier.fillMaxSize().nestedScroll(scrollBehavior.nestedScrollConnection),
+ topBar = {
+ MediumTopAppBar(
+ title = { Text(appBarTitle, maxLines = 1, overflow = TextOverflow.Ellipsis) },
+ navigationIcon = navigationIcon,
+ scrollBehavior = scrollBehavior,
+ colors =
+ TopAppBarDefaults.mediumTopAppBarColors(
+ containerColor = MaterialTheme.colorScheme.background
+ ),
+ actions = actions
+ )
+ },
+ content = {
+ content(
+ Modifier.fillMaxSize().padding(it).drawVerticalScrollbar(scrollState).verticalScroll(scrollState)
+ )
}
)
}
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 8f2277ad2a..2b41ba6722 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
@@ -1,23 +1,25 @@
package net.mullvad.mullvadvpn.compose.component
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
+import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
+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.stringResource
import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.lib.theme.AppTheme
import net.mullvad.mullvadvpn.lib.theme.Dimens
@@ -26,8 +28,21 @@ import net.mullvad.mullvadvpn.lib.theme.Dimens
@Composable
private fun PreviewTopBar() {
AppTheme {
- TopBar(
- backgroundColor = MaterialTheme.colorScheme.inversePrimary,
+ MullvadTopBar(
+ containerColor = MaterialTheme.colorScheme.inversePrimary,
+ iconTintColor = MaterialTheme.colorScheme.onPrimary,
+ onSettingsClicked = null,
+ onAccountClicked = {}
+ )
+ }
+}
+
+@Preview(widthDp = 260)
+@Composable
+private fun PreviewSlimTopBar() {
+ AppTheme {
+ MullvadTopBar(
+ containerColor = MaterialTheme.colorScheme.inversePrimary,
iconTintColor = MaterialTheme.colorScheme.onPrimary,
onSettingsClicked = null,
onAccountClicked = {}
@@ -39,8 +54,8 @@ private fun PreviewTopBar() {
@Composable
private fun PreviewNoIconAndLogoTopBar() {
AppTheme {
- TopBar(
- backgroundColor = MaterialTheme.colorScheme.inversePrimary,
+ MullvadTopBar(
+ containerColor = MaterialTheme.colorScheme.inversePrimary,
iconTintColor = MaterialTheme.colorScheme.onPrimary,
isIconAndLogoVisible = false,
onSettingsClicked = {},
@@ -53,8 +68,8 @@ private fun PreviewNoIconAndLogoTopBar() {
@Composable
private fun PreviewNothingTopBar() {
AppTheme {
- TopBar(
- backgroundColor = MaterialTheme.colorScheme.inversePrimary,
+ MullvadTopBar(
+ containerColor = MaterialTheme.colorScheme.inversePrimary,
iconTintColor = MaterialTheme.colorScheme.onPrimary,
isIconAndLogoVisible = false,
onSettingsClicked = null,
@@ -63,63 +78,77 @@ private fun PreviewNothingTopBar() {
}
}
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun TopBar(
- backgroundColor: Color,
+fun MullvadTopBar(
+ containerColor: Color,
onSettingsClicked: (() -> Unit)?,
onAccountClicked: (() -> Unit)?,
modifier: Modifier = Modifier,
iconTintColor: Color,
isIconAndLogoVisible: Boolean = true
) {
- Row(
- modifier =
- Modifier.fillMaxWidth()
- .height(Dimens.topBarHeight)
- .background(backgroundColor)
- .then(modifier),
- verticalAlignment = Alignment.CenterVertically
- ) {
- Row(
- Modifier.height(Dimens.topBarHeight).weight(1f).padding(start = Dimens.mediumPadding),
- verticalAlignment = Alignment.CenterVertically
- ) {
+ TopAppBar(
+ modifier = modifier,
+ title = {
if (isIconAndLogoVisible) {
- Image(
- painter = painterResource(id = R.drawable.logo_icon),
- contentDescription = null, // No meaningful user info or action.
- modifier = Modifier.size(Dimens.buttonHeight)
- )
- Icon(
- painter = painterResource(id = R.drawable.logo_text),
- tint = iconTintColor,
- contentDescription = null, // No meaningful user info or action.
- modifier =
- Modifier.padding(start = Dimens.smallPadding).height(Dimens.mediumPadding)
- )
- }
- }
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Icon(
+ painter = painterResource(id = R.drawable.logo_icon),
+ contentDescription = null, // No meaningful user info or action.
+ modifier = Modifier.size(40.dp),
+ tint = Color.Unspecified
+ )
+ // Dynamically show Mullvad VPN Text if it fits, to avoid overlapping icons.
+ BoxWithConstraints {
+ val logoTextPainter = painterResource(id = R.drawable.logo_text)
+ val logoHeight = Dimens.mediumPadding
+ val logoStartEndPadding = Dimens.mediumPadding
- if (onAccountClicked != null) {
- Image(
- painter = painterResource(R.drawable.icon_account),
- contentDescription = stringResource(id = R.string.settings_account),
- modifier =
- Modifier.clickable { onAccountClicked() }
- .fillMaxHeight()
- .padding(horizontal = Dimens.mediumPadding)
- )
- }
+ val shouldShowText = remember(maxWidth) {
+ val logoHeightWidthRatio =
+ logoTextPainter.intrinsicSize.width /
+ logoTextPainter.intrinsicSize.height
+ val expectedLength = logoHeightWidthRatio * logoHeight.value
+ maxWidth > (expectedLength + logoStartEndPadding.value * 2).dp
+ }
- if (onSettingsClicked != null) {
- Image(
- painter = painterResource(R.drawable.icon_settings),
- contentDescription = stringResource(id = R.string.settings),
- modifier =
- Modifier.clickable { onSettingsClicked() }
- .fillMaxHeight()
- .padding(horizontal = Dimens.mediumPadding)
- )
- }
- }
+ if (shouldShowText) {
+ Icon(
+ painter = painterResource(id = R.drawable.logo_text),
+ tint = iconTintColor,
+ contentDescription = null, // No meaningful user info or action.
+ modifier =
+ Modifier.padding(horizontal = Dimens.mediumPadding)
+ .height(logoHeight)
+ )
+ }
+ }
+ }
+ }
+ },
+ actions = {
+ if (onAccountClicked != null) {
+ IconButton(onClick = onAccountClicked) {
+ Icon(
+ painter = painterResource(R.drawable.icon_account),
+ contentDescription = stringResource(id = R.string.settings_account),
+ )
+ }
+ }
+
+ if (onSettingsClicked != null) {
+ IconButton(onClick = onSettingsClicked) {
+ Icon(
+ painter = painterResource(R.drawable.icon_settings),
+ contentDescription = stringResource(id = R.string.settings),
+ )
+ }
+ }
+ },
+ colors =
+ TopAppBarDefaults.topAppBarColors(
+ containerColor = containerColor,
+ ),
+ )
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreen.kt
index 6241b3bf04..46ee51640b 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreen.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreen.kt
@@ -5,16 +5,12 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -32,20 +28,18 @@ import com.google.accompanist.systemuicontroller.rememberSystemUiController
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
-import me.onebone.toolbar.ScrollStrategy
-import me.onebone.toolbar.rememberCollapsingToolbarScaffoldState
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.button.ActionButton
-import net.mullvad.mullvadvpn.compose.component.CollapsingToolbarScaffold
-import net.mullvad.mullvadvpn.compose.component.CollapsingTopBar
import net.mullvad.mullvadvpn.compose.component.CopyableObfuscationView
import net.mullvad.mullvadvpn.compose.component.InformationView
import net.mullvad.mullvadvpn.compose.component.MissingPolicy
-import net.mullvad.mullvadvpn.compose.component.drawVerticalScrollbar
+import net.mullvad.mullvadvpn.compose.component.NavigateBackIconButton
+import net.mullvad.mullvadvpn.compose.component.ScaffoldWithMediumTopBar
import net.mullvad.mullvadvpn.compose.dialog.DeviceNameInfoDialog
import net.mullvad.mullvadvpn.constant.IS_PLAY_BUILD
import net.mullvad.mullvadvpn.lib.common.util.capitalizeFirstCharOfEachWord
import net.mullvad.mullvadvpn.lib.common.util.openAccountPageInBrowser
+import net.mullvad.mullvadvpn.lib.theme.AppTheme
import net.mullvad.mullvadvpn.lib.theme.Dimens
import net.mullvad.mullvadvpn.util.toExpiryDateString
import net.mullvad.mullvadvpn.viewmodel.AccountUiState
@@ -55,16 +49,18 @@ import net.mullvad.mullvadvpn.viewmodel.AccountViewModel
@Preview
@Composable
private fun PreviewAccountScreen() {
- AccountScreen(
- uiState =
- AccountUiState(
- deviceName = "Test Name",
- accountNumber = "1234123412341234",
- accountExpiry = null
- ),
- uiSideEffect = MutableSharedFlow<AccountViewModel.UiSideEffect>().asSharedFlow(),
- enterTransitionEndAction = MutableSharedFlow()
- )
+ AppTheme {
+ AccountScreen(
+ uiState =
+ AccountUiState(
+ deviceName = "Test Name",
+ accountNumber = "1234123412341234",
+ accountExpiry = null
+ ),
+ uiSideEffect = MutableSharedFlow<AccountViewModel.UiSideEffect>().asSharedFlow(),
+ enterTransitionEndAction = MutableSharedFlow()
+ )
+ }
}
@ExperimentalMaterial3Api
@@ -79,8 +75,6 @@ fun AccountScreen(
onBackClick: () -> Unit = {}
) {
val context = LocalContext.current
- val state = rememberCollapsingToolbarScaffoldState()
- val progress = state.toolbarState.progress
val backgroundColor = MaterialTheme.colorScheme.background
val systemUiController = rememberSystemUiController()
@@ -93,133 +87,73 @@ fun AccountScreen(
DeviceNameInfoDialog { showDeviceNameInfoDialog = false }
}
- CollapsingToolbarScaffold(
- backgroundColor = MaterialTheme.colorScheme.background,
- modifier = Modifier.fillMaxSize(),
- state = state,
- scrollStrategy = ScrollStrategy.ExitUntilCollapsed,
- isEnabledWhenCollapsable = false,
- toolbar = {
- val scaffoldModifier =
- Modifier.road(
- whenCollapsed = Alignment.TopCenter,
- whenExpanded = Alignment.BottomStart
- )
- CollapsingTopBar(
- backgroundColor = MaterialTheme.colorScheme.secondary,
- onBackClicked = onBackClick,
- title = stringResource(id = R.string.settings_account),
- progress = progress,
- modifier = scaffoldModifier,
- shouldRotateBackButtonDown = true
- )
- },
- ) {
- LaunchedEffect(Unit) {
- uiSideEffect.collect { uiSideEffect ->
- if (
- uiSideEffect is AccountViewModel.UiSideEffect.OpenAccountManagementPageInBrowser
- ) {
- context.openAccountPageInBrowser(uiSideEffect.token)
- }
+ LaunchedEffect(Unit) {
+ uiSideEffect.collect { uiSideEffect ->
+ if (uiSideEffect is AccountViewModel.UiSideEffect.OpenAccountManagementPageInBrowser) {
+ context.openAccountPageInBrowser(uiSideEffect.token)
}
}
+ }
- val scrollState = rememberScrollState()
-
- Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
- Column(
- verticalArrangement = Arrangement.Bottom,
- horizontalAlignment = Alignment.Start,
- modifier =
- Modifier.fillMaxSize()
- .drawVerticalScrollbar(scrollState)
- .verticalScroll(scrollState)
- .animateContentSize()
- ) {
- Text(
- style = MaterialTheme.typography.labelMedium,
- text = stringResource(id = R.string.device_name),
- modifier = Modifier.padding(start = Dimens.sideMargin, end = Dimens.sideMargin)
- )
-
- Row(verticalAlignment = Alignment.CenterVertically) {
- InformationView(
- content = uiState.deviceName?.capitalizeFirstCharOfEachWord() ?: "",
- whenMissing = MissingPolicy.SHOW_SPINNER
- )
- IconButton(
- modifier = Modifier.align(Alignment.CenterVertically),
- onClick = { showDeviceNameInfoDialog = true }
- ) {
- Icon(
- painter = painterResource(id = R.drawable.icon_info),
- contentDescription = null,
- tint = MaterialTheme.colorScheme.inverseSurface
- )
- }
- }
-
- Text(
- style = MaterialTheme.typography.labelMedium,
- text = stringResource(id = R.string.account_number),
- modifier =
- Modifier.padding(
- start = Dimens.sideMargin,
- end = Dimens.sideMargin,
- top = Dimens.smallPadding
- )
- )
- CopyableObfuscationView(content = uiState.accountNumber ?: "")
- Text(
- style = MaterialTheme.typography.labelMedium,
- text = stringResource(id = R.string.paid_until),
- modifier = Modifier.padding(start = Dimens.sideMargin, end = Dimens.sideMargin)
- )
+ ScaffoldWithMediumTopBar(
+ appBarTitle = stringResource(id = R.string.settings_account),
+ navigationIcon = { NavigateBackIconButton(onBackClick) }
+ ) { modifier ->
+ Column(
+ verticalArrangement = Arrangement.Bottom,
+ horizontalAlignment = Alignment.Start,
+ modifier = modifier.animateContentSize()
+ ) {
+ Text(
+ style = MaterialTheme.typography.labelMedium,
+ text = stringResource(id = R.string.device_name),
+ modifier = Modifier.padding(start = Dimens.sideMargin, end = Dimens.sideMargin)
+ )
+ Row(verticalAlignment = Alignment.CenterVertically) {
InformationView(
- content = uiState.accountExpiry?.toExpiryDateString() ?: "",
+ content = uiState.deviceName?.capitalizeFirstCharOfEachWord() ?: "",
whenMissing = MissingPolicy.SHOW_SPINNER
)
-
- Spacer(modifier = Modifier.weight(1f))
- if (IS_PLAY_BUILD.not()) {
- ActionButton(
- text = stringResource(id = R.string.manage_account),
- onClick = onManageAccountClick,
- modifier =
- Modifier.padding(
- start = Dimens.sideMargin,
- end = Dimens.sideMargin,
- bottom = Dimens.screenVerticalMargin
- ),
- colors =
- ButtonDefaults.buttonColors(
- contentColor = MaterialTheme.colorScheme.onPrimary,
- containerColor = MaterialTheme.colorScheme.surface
- )
+ IconButton(
+ modifier = Modifier.align(Alignment.CenterVertically),
+ onClick = { showDeviceNameInfoDialog = true }
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.icon_info),
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.inverseSurface
)
}
+ }
- ActionButton(
- text = stringResource(id = R.string.redeem_voucher),
- onClick = onRedeemVoucherClick,
- modifier =
- Modifier.padding(
- start = Dimens.sideMargin,
- end = Dimens.sideMargin,
- bottom = Dimens.screenVerticalMargin
- ),
- colors =
- ButtonDefaults.buttonColors(
- contentColor = MaterialTheme.colorScheme.onPrimary,
- containerColor = MaterialTheme.colorScheme.surface
- )
- )
+ Text(
+ style = MaterialTheme.typography.labelMedium,
+ text = stringResource(id = R.string.account_number),
+ modifier =
+ Modifier.padding(
+ start = Dimens.sideMargin,
+ end = Dimens.sideMargin,
+ top = Dimens.smallPadding
+ )
+ )
+ CopyableObfuscationView(content = uiState.accountNumber ?: "")
+ Text(
+ style = MaterialTheme.typography.labelMedium,
+ text = stringResource(id = R.string.paid_until),
+ modifier = Modifier.padding(start = Dimens.sideMargin, end = Dimens.sideMargin)
+ )
+
+ InformationView(
+ content = uiState.accountExpiry?.toExpiryDateString() ?: "",
+ whenMissing = MissingPolicy.SHOW_SPINNER
+ )
+ Spacer(modifier = Modifier.weight(1f))
+ if (IS_PLAY_BUILD.not()) {
ActionButton(
- text = stringResource(id = R.string.log_out),
- onClick = onLogoutClick,
+ text = stringResource(id = R.string.manage_account),
+ onClick = onManageAccountClick,
modifier =
Modifier.padding(
start = Dimens.sideMargin,
@@ -229,10 +163,42 @@ fun AccountScreen(
colors =
ButtonDefaults.buttonColors(
contentColor = MaterialTheme.colorScheme.onPrimary,
- containerColor = MaterialTheme.colorScheme.error
+ containerColor = MaterialTheme.colorScheme.surface
)
)
}
+
+ ActionButton(
+ text = stringResource(id = R.string.redeem_voucher),
+ onClick = onRedeemVoucherClick,
+ modifier =
+ Modifier.padding(
+ start = Dimens.sideMargin,
+ end = Dimens.sideMargin,
+ bottom = Dimens.screenVerticalMargin
+ ),
+ colors =
+ ButtonDefaults.buttonColors(
+ contentColor = MaterialTheme.colorScheme.onPrimary,
+ containerColor = MaterialTheme.colorScheme.surface
+ )
+ )
+
+ ActionButton(
+ text = stringResource(id = R.string.log_out),
+ onClick = onLogoutClick,
+ modifier =
+ Modifier.padding(
+ start = Dimens.sideMargin,
+ end = Dimens.sideMargin,
+ bottom = Dimens.screenVerticalMargin
+ ),
+ colors =
+ ButtonDefaults.buttonColors(
+ contentColor = MaterialTheme.colorScheme.onPrimary,
+ containerColor = MaterialTheme.colorScheme.error
+ )
+ )
}
}
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreen.kt
index f17548eeb6..86dcc80f28 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreen.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreen.kt
@@ -4,19 +4,14 @@ import android.net.Uri
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
@@ -25,15 +20,12 @@ import androidx.compose.ui.tooling.preview.Preview
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
-import me.onebone.toolbar.ScrollStrategy
-import me.onebone.toolbar.rememberCollapsingToolbarScaffoldState
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.cell.DefaultExternalLinkView
import net.mullvad.mullvadvpn.compose.cell.NavigationCellBody
import net.mullvad.mullvadvpn.compose.cell.NavigationComposeCell
-import net.mullvad.mullvadvpn.compose.component.CollapsableAwareToolbarScaffold
-import net.mullvad.mullvadvpn.compose.component.CollapsingTopBar
-import net.mullvad.mullvadvpn.compose.component.drawVerticalScrollbar
+import net.mullvad.mullvadvpn.compose.component.NavigateBackDownIconButton
+import net.mullvad.mullvadvpn.compose.component.ScaffoldWithMediumTopBar
import net.mullvad.mullvadvpn.compose.extensions.itemWithDivider
import net.mullvad.mullvadvpn.compose.state.SettingsUiState
import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_TEST_TAG
@@ -64,9 +56,6 @@ fun SettingsScreen(
onBackClick: () -> Unit = {}
) {
val context = LocalContext.current
- val lazyListState = rememberLazyListState()
- val state = rememberCollapsingToolbarScaffoldState()
- val progress = state.toolbarState.progress
val backgroundColor = MaterialTheme.colorScheme.background
val systemUiController = rememberSystemUiController()
@@ -74,35 +63,12 @@ fun SettingsScreen(
enterTransitionEndAction.collect { systemUiController.setStatusBarColor(backgroundColor) }
}
- CollapsableAwareToolbarScaffold(
- backgroundColor = MaterialTheme.colorScheme.background,
- modifier = Modifier.fillMaxSize(),
- state = state,
- scrollStrategy = ScrollStrategy.ExitUntilCollapsed,
- isEnabledWhenCollapsable = true,
- toolbar = {
- val scaffoldModifier =
- Modifier.road(
- whenCollapsed = Alignment.TopCenter,
- whenExpanded = Alignment.BottomStart
- )
- CollapsingTopBar(
- backgroundColor = MaterialTheme.colorScheme.secondary,
- onBackClicked = { onBackClick() },
- title = stringResource(id = R.string.settings),
- progress = progress,
- modifier = scaffoldModifier,
- shouldRotateBackButtonDown = true
- )
- },
- ) {
+ ScaffoldWithMediumTopBar(
+ appBarTitle = stringResource(id = R.string.settings_vpn),
+ navigationIcon = { NavigateBackDownIconButton(onBackClick) },
+ ) { modifier, lazyListState ->
LazyColumn(
- modifier =
- Modifier.drawVerticalScrollbar(lazyListState)
- .testTag(LAZY_LIST_TEST_TAG)
- .fillMaxWidth()
- .wrapContentHeight()
- .animateContentSize(),
+ modifier = modifier.testTag(LAZY_LIST_TEST_TAG).animateContentSize(),
state = lazyListState
) {
item { Spacer(modifier = Modifier.height(Dimens.cellLabelVerticalPadding)) }
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SplitTunnelingScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SplitTunnelingScreen.kt
index ad9a7d3af2..7823a28494 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SplitTunnelingScreen.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SplitTunnelingScreen.kt
@@ -11,10 +11,8 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
-import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@@ -24,16 +22,13 @@ import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
-import me.onebone.toolbar.ScrollStrategy
-import me.onebone.toolbar.rememberCollapsingToolbarScaffoldState
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.applist.AppData
import net.mullvad.mullvadvpn.compose.cell.BaseCell
import net.mullvad.mullvadvpn.compose.cell.HeaderSwitchComposeCell
import net.mullvad.mullvadvpn.compose.cell.SplitTunnelingCell
-import net.mullvad.mullvadvpn.compose.component.CollapsingToolbarScaffold
-import net.mullvad.mullvadvpn.compose.component.CollapsingTopBar
-import net.mullvad.mullvadvpn.compose.component.drawVerticalScrollbar
+import net.mullvad.mullvadvpn.compose.component.NavigateBackIconButton
+import net.mullvad.mullvadvpn.compose.component.ScaffoldWithMediumTopBar
import net.mullvad.mullvadvpn.compose.constant.CommonContentKey
import net.mullvad.mullvadvpn.compose.constant.ContentType
import net.mullvad.mullvadvpn.compose.constant.SplitTunnelingContentKey
@@ -86,132 +81,51 @@ fun SplitTunnelingScreen(
onBackClick: () -> Unit = {},
onResolveIcon: (String) -> Bitmap? = { null },
) {
- val state = rememberCollapsingToolbarScaffoldState()
- val progress = state.toolbarState.progress
- val lazyListState = rememberLazyListState()
val focusManager = LocalFocusManager.current
- CollapsingToolbarScaffold(
- backgroundColor = MaterialTheme.colorScheme.background,
+ ScaffoldWithMediumTopBar(
modifier = Modifier.fillMaxSize(),
- state = state,
- scrollStrategy = ScrollStrategy.ExitUntilCollapsed,
- isEnabledWhenCollapsable = true,
- toolbar = {
- val scaffoldModifier =
- Modifier.road(
- whenCollapsed = Alignment.TopCenter,
- whenExpanded = Alignment.BottomStart
- )
- CollapsingTopBar(
- backgroundColor = MaterialTheme.colorScheme.background,
- onBackClicked = { onBackClick() },
- title = stringResource(id = R.string.split_tunneling),
- progress = progress,
- modifier = scaffoldModifier
- )
- },
- ) {
- Surface(color = MaterialTheme.colorScheme.background) {
- LazyColumn(
- modifier = Modifier.drawVerticalScrollbar(state = lazyListState).fillMaxWidth(),
- horizontalAlignment = Alignment.CenterHorizontally,
- state = lazyListState
- ) {
- item(key = CommonContentKey.DESCRIPTION, contentType = ContentType.DESCRIPTION) {
- Box(modifier = Modifier.fillMaxWidth()) {
- Text(
- style = MaterialTheme.typography.labelMedium,
- text = stringResource(id = R.string.split_tunneling_description),
- modifier =
- Modifier.padding(
- start = Dimens.mediumPadding,
- end = Dimens.mediumPadding,
- bottom = Dimens.mediumPadding
- )
+ appBarTitle = stringResource(id = R.string.split_tunneling),
+ navigationIcon = { NavigateBackIconButton(onBackClick) }
+ ) { _, lazyListState ->
+ LazyColumn(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ state = lazyListState
+ ) {
+ item(key = CommonContentKey.DESCRIPTION, contentType = ContentType.DESCRIPTION) {
+ Box(modifier = Modifier.fillMaxWidth()) {
+ Text(
+ style = MaterialTheme.typography.labelMedium,
+ text = stringResource(id = R.string.split_tunneling_description),
+ modifier =
+ Modifier.padding(
+ start = Dimens.mediumPadding,
+ end = Dimens.mediumPadding,
+ bottom = Dimens.mediumPadding
+ )
+ )
+ }
+ }
+ when (uiState) {
+ SplitTunnelingUiState.Loading -> {
+ item(key = CommonContentKey.PROGRESS, contentType = ContentType.PROGRESS) {
+ CircularProgressIndicator(
+ color = MaterialTheme.colorScheme.onBackground,
+ modifier = Modifier.size(Dimens.progressIndicatorSize),
+ strokeCap = StrokeCap.Round
)
}
}
- when (uiState) {
- SplitTunnelingUiState.Loading -> {
- item(key = CommonContentKey.PROGRESS, contentType = ContentType.PROGRESS) {
- CircularProgressIndicator(
- color = MaterialTheme.colorScheme.onBackground,
- modifier = Modifier.size(Dimens.progressIndicatorSize),
- strokeCap = StrokeCap.Round
- )
- }
- }
- is SplitTunnelingUiState.ShowAppList -> {
- if (uiState.excludedApps.isNotEmpty()) {
- itemWithDivider(
- key = SplitTunnelingContentKey.EXCLUDED_APPLICATIONS,
- contentType = ContentType.HEADER
- ) {
- BaseCell(
- title = {
- Text(
- text =
- stringResource(id = R.string.exclude_applications),
- style = MaterialTheme.typography.titleMedium,
- color = MaterialTheme.colorScheme.onPrimary
- )
- },
- bodyView = {},
- background = MaterialTheme.colorScheme.primary,
- )
- }
- itemsIndexed(
- items = uiState.excludedApps,
- key = { _, listItem -> listItem.packageName },
- contentType = { _, _ -> ContentType.ITEM }
- ) { index, listItem ->
- SplitTunnelingCell(
- title = listItem.name,
- packageName = listItem.packageName,
- isSelected = true,
- modifier = Modifier.animateItemPlacement().fillMaxWidth(),
- onResolveIcon = onResolveIcon
- ) {
- // Move focus down unless the clicked item was the last in this
- // section.
- if (index < uiState.excludedApps.size - 1) {
- focusManager.moveFocus(FocusDirection.Down)
- } else {
- focusManager.moveFocus(FocusDirection.Up)
- }
-
- onIncludeAppClick(listItem.packageName)
- }
- }
- item(key = CommonContentKey.SPACER, contentType = ContentType.SPACER) {
- Spacer(
- modifier =
- Modifier.animateItemPlacement().height(Dimens.mediumPadding)
- )
- }
- }
-
+ is SplitTunnelingUiState.ShowAppList -> {
+ if (uiState.excludedApps.isNotEmpty()) {
itemWithDivider(
- key = SplitTunnelingContentKey.SHOW_SYSTEM_APPLICATIONS,
- contentType = ContentType.OTHER_ITEM
- ) {
- HeaderSwitchComposeCell(
- title = stringResource(id = R.string.show_system_apps),
- isToggled = uiState.showSystemApps,
- onCellClicked = { newValue -> onShowSystemAppsClick(newValue) },
- modifier = Modifier.animateItemPlacement()
- )
- }
- itemWithDivider(
- key = SplitTunnelingContentKey.INCLUDED_APPLICATIONS,
+ key = SplitTunnelingContentKey.EXCLUDED_APPLICATIONS,
contentType = ContentType.HEADER
) {
BaseCell(
- modifier = Modifier.animateItemPlacement(),
title = {
Text(
- text = stringResource(id = R.string.all_applications),
+ text = stringResource(id = R.string.exclude_applications),
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onPrimary
)
@@ -221,28 +135,86 @@ fun SplitTunnelingScreen(
)
}
itemsIndexed(
- items = uiState.includedApps,
+ items = uiState.excludedApps,
key = { _, listItem -> listItem.packageName },
contentType = { _, _ -> ContentType.ITEM }
) { index, listItem ->
SplitTunnelingCell(
title = listItem.name,
packageName = listItem.packageName,
- isSelected = false,
+ isSelected = true,
modifier = Modifier.animateItemPlacement().fillMaxWidth(),
onResolveIcon = onResolveIcon
) {
// Move focus down unless the clicked item was the last in this
// section.
- if (index < uiState.includedApps.size - 1) {
+ if (index < uiState.excludedApps.size - 1) {
focusManager.moveFocus(FocusDirection.Down)
} else {
focusManager.moveFocus(FocusDirection.Up)
}
- onExcludeAppClick(listItem.packageName)
+ onIncludeAppClick(listItem.packageName)
}
}
+ item(key = CommonContentKey.SPACER, contentType = ContentType.SPACER) {
+ Spacer(
+ modifier =
+ Modifier.animateItemPlacement().height(Dimens.mediumPadding)
+ )
+ }
+ }
+
+ itemWithDivider(
+ key = SplitTunnelingContentKey.SHOW_SYSTEM_APPLICATIONS,
+ contentType = ContentType.OTHER_ITEM
+ ) {
+ HeaderSwitchComposeCell(
+ title = stringResource(id = R.string.show_system_apps),
+ isToggled = uiState.showSystemApps,
+ onCellClicked = { newValue -> onShowSystemAppsClick(newValue) },
+ modifier = Modifier.animateItemPlacement()
+ )
+ }
+ itemWithDivider(
+ key = SplitTunnelingContentKey.INCLUDED_APPLICATIONS,
+ contentType = ContentType.HEADER
+ ) {
+ BaseCell(
+ modifier = Modifier.animateItemPlacement(),
+ title = {
+ Text(
+ text = stringResource(id = R.string.all_applications),
+ style = MaterialTheme.typography.titleMedium,
+ color = MaterialTheme.colorScheme.onPrimary
+ )
+ },
+ bodyView = {},
+ background = MaterialTheme.colorScheme.primary,
+ )
+ }
+ itemsIndexed(
+ items = uiState.includedApps,
+ key = { _, listItem -> listItem.packageName },
+ contentType = { _, _ -> ContentType.ITEM }
+ ) { index, listItem ->
+ SplitTunnelingCell(
+ title = listItem.name,
+ packageName = listItem.packageName,
+ isSelected = false,
+ modifier = Modifier.animateItemPlacement().fillMaxWidth(),
+ onResolveIcon = onResolveIcon
+ ) {
+ // Move focus down unless the clicked item was the last in this
+ // section.
+ if (index < uiState.includedApps.size - 1) {
+ focusManager.moveFocus(FocusDirection.Down)
+ } else {
+ focusManager.moveFocus(FocusDirection.Up)
+ }
+
+ onExcludeAppClick(listItem.packageName)
+ }
}
}
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt
index 41751ad02d..a6857dcd72 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt
@@ -5,15 +5,12 @@ import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
-import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.Divider
+import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -23,7 +20,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
@@ -39,8 +35,6 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.distinctUntilChanged
-import me.onebone.toolbar.ScrollStrategy
-import me.onebone.toolbar.rememberCollapsingToolbarScaffoldState
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.cell.BaseCell
import net.mullvad.mullvadvpn.compose.cell.ContentBlockersDisableModeCellSubtitle
@@ -55,9 +49,8 @@ import net.mullvad.mullvadvpn.compose.cell.MtuSubtitle
import net.mullvad.mullvadvpn.compose.cell.NormalSwitchComposeCell
import net.mullvad.mullvadvpn.compose.cell.SelectableCell
import net.mullvad.mullvadvpn.compose.cell.SwitchComposeSubtitleCell
-import net.mullvad.mullvadvpn.compose.component.CollapsingToolbarScaffold
-import net.mullvad.mullvadvpn.compose.component.CollapsingTopBar
-import net.mullvad.mullvadvpn.compose.component.drawVerticalScrollbar
+import net.mullvad.mullvadvpn.compose.component.NavigateBackIconButton
+import net.mullvad.mullvadvpn.compose.component.ScaffoldWithMediumTopBar
import net.mullvad.mullvadvpn.compose.dialog.ContentBlockersInfoDialog
import net.mullvad.mullvadvpn.compose.dialog.CustomDnsInfoDialog
import net.mullvad.mullvadvpn.compose.dialog.CustomPortDialog
@@ -246,12 +239,9 @@ fun VpnSettingsScreen(
}
}
- val lazyListState = rememberLazyListState()
var expandContentBlockersState by rememberSaveable { mutableStateOf(false) }
val biggerPadding = 54.dp
val topPadding = 6.dp
- val state = rememberCollapsingToolbarScaffoldState()
- val progress = state.toolbarState.progress
LaunchedEffect(uiState.selectedWireguardPort) {
if (
@@ -262,49 +252,27 @@ fun VpnSettingsScreen(
}
}
- CollapsingToolbarScaffold(
- backgroundColor = MaterialTheme.colorScheme.background,
- modifier = Modifier.fillMaxSize(),
- state = state,
- scrollStrategy = ScrollStrategy.ExitUntilCollapsed,
- isEnabledWhenCollapsable = true,
- toolbar = {
- val scaffoldModifier =
- Modifier.road(
- whenCollapsed = Alignment.TopCenter,
- whenExpanded = Alignment.BottomStart
- )
- CollapsingTopBar(
- backgroundColor = MaterialTheme.colorScheme.background,
- onBackClicked = { onBackClick() },
- title = stringResource(id = R.string.settings_vpn),
- progress = progress,
- modifier = scaffoldModifier
- )
- },
- ) {
- val context = LocalContext.current
- LaunchedEffect(Unit) {
- toastMessagesSharedFlow.distinctUntilChanged().collect { message ->
- Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
- }
+ val context = LocalContext.current
+ LaunchedEffect(Unit) {
+ toastMessagesSharedFlow.distinctUntilChanged().collect { message ->
+ Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
- DisposableEffect(lifecycleOwner) {
- val observer = LifecycleEventObserver { _, event ->
- if (event == Lifecycle.Event.ON_STOP) {
- onStopEvent()
- }
+ }
+ DisposableEffect(lifecycleOwner) {
+ val observer = LifecycleEventObserver { _, event ->
+ if (event == Lifecycle.Event.ON_STOP) {
+ onStopEvent()
}
- lifecycleOwner.lifecycle.addObserver(observer)
- onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
}
+ lifecycleOwner.lifecycle.addObserver(observer)
+ onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
+ }
+ ScaffoldWithMediumTopBar(
+ appBarTitle = stringResource(id = R.string.settings_vpn),
+ navigationIcon = { NavigateBackIconButton(onBackClick) },
+ ) { modifier, lazyListState ->
LazyColumn(
- modifier =
- Modifier.drawVerticalScrollbar(lazyListState)
- .testTag(LAZY_LIST_TEST_TAG)
- .fillMaxWidth()
- .wrapContentHeight()
- .animateContentSize(),
+ modifier = modifier.testTag(LAZY_LIST_TEST_TAG).animateContentSize(),
state = lazyListState
) {
item {
diff --git a/android/buildSrc/src/main/kotlin/Dependencies.kt b/android/buildSrc/src/main/kotlin/Dependencies.kt
index d23399c915..d0748afc0a 100644
--- a/android/buildSrc/src/main/kotlin/Dependencies.kt
+++ b/android/buildSrc/src/main/kotlin/Dependencies.kt
@@ -44,8 +44,6 @@ object Dependencies {
}
object Compose {
- const val composeCollapsingToolbar =
- "me.onebone:toolbar-compose:${Versions.Compose.composeCollapsingToolbar}"
const val constrainLayout =
"androidx.constraintlayout:constraintlayout-compose:${Versions.Compose.constrainLayout}"
const val foundation =
diff --git a/android/lib/theme/src/main/kotlin/net/mullvad/mullvadvpn/lib/theme/Theme.kt b/android/lib/theme/src/main/kotlin/net/mullvad/mullvadvpn/lib/theme/Theme.kt
index adaedc618e..8480e1c520 100644
--- a/android/lib/theme/src/main/kotlin/net/mullvad/mullvadvpn/lib/theme/Theme.kt
+++ b/android/lib/theme/src/main/kotlin/net/mullvad/mullvadvpn/lib/theme/Theme.kt
@@ -11,9 +11,11 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
import net.mullvad.mullvadvpn.lib.theme.dimensions.Dimensions
import net.mullvad.mullvadvpn.lib.theme.dimensions.defaultDimensions
import net.mullvad.mullvadvpn.lib.theme.typeface.TypeScale
@@ -28,6 +30,7 @@ private val MullvadTypography =
bodyMedium = TextStyle(fontSize = TypeScale.TextMediumPlus, fontWeight = FontWeight.Bold),
titleMedium =
TextStyle(fontSize = TypeScale.TextMediumPlus, fontWeight = FontWeight.SemiBold),
+ titleLarge = TextStyle(fontSize = 22.sp, fontFamily = FontFamily.SansSerif),
labelMedium = TextStyle(fontSize = TypeScale.TextSmall, fontWeight = FontWeight.SemiBold),
labelLarge =
TextStyle(