diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 6c61206afe20..220627eaad55 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 d1651e542f96..000000000000 --- 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 000000000000..798b5e15744c --- /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 0d032f962ab2..47966b3b88e4 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 8f2277ad2a6e..2b41ba672209 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 6241b3bf04a0..46ee51640bfc 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().asSharedFlow(), - enterTransitionEndAction = MutableSharedFlow() - ) + AppTheme { + AccountScreen( + uiState = + AccountUiState( + deviceName = "Test Name", + accountNumber = "1234123412341234", + accountExpiry = null + ), + uiSideEffect = MutableSharedFlow().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 f17548eeb60f..86dcc80f289b 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 ad9a7d3af26f..7823a2849466 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 41751ad02ded..a6857dcd72e9 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 d23399c915f6..d0748afc0a03 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 adaedc618e61..8480e1c5201c 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(