Skip to content

Commit

Permalink
Make settings use scaffold snackbar instead
Browse files Browse the repository at this point in the history
  • Loading branch information
Rawa committed Nov 10, 2023
1 parent deae7fb commit 99a3672
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 4,067 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,25 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.button.AnimatedIconButton
import net.mullvad.mullvadvpn.lib.common.util.SdkUtils
import net.mullvad.mullvadvpn.lib.theme.AppTheme
import net.mullvad.mullvadvpn.ui.extension.copyToClipboard

@Preview
@Composable
private fun PreviewCopyableObfuscationView() {
AppTheme { CopyableObfuscationView("1111222233334444", modifier = Modifier.fillMaxWidth()) }
AppTheme { CopyableObfuscationView("1111222233334444", {}, modifier = Modifier.fillMaxWidth()) }
}

@Composable
fun CopyableObfuscationView(content: String, modifier: Modifier = Modifier) {
fun CopyableObfuscationView(
content: String,
onCopyClicked: (String) -> Unit,
modifier: Modifier = Modifier
) {
var obfuscationEnabled by remember { mutableStateOf(true) }

Row(verticalAlignment = CenterVertically, modifier = modifier) {
Expand All @@ -45,19 +46,7 @@ fun CopyableObfuscationView(content: String, modifier: Modifier = Modifier) {
onClick = { obfuscationEnabled = !obfuscationEnabled }
)

val context = LocalContext.current
val copy = {
context.copyToClipboard(
content = content,
clipboardLabel = context.getString(R.string.mullvad_account_number)
)
SdkUtils.showCopyToastIfNeeded(
context,
context.getString(R.string.copied_mullvad_account_number)
)
}

CopyAnimatedIconButton(onClick = copy)
CopyAnimatedIconButton(onClick = { onCopyClicked(content) })
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ fun ScaffoldWithMediumTopBar(
actions: @Composable RowScope.() -> Unit = {},
lazyListState: LazyListState = rememberLazyListState(),
scrollbarColor: Color = MaterialTheme.colorScheme.onBackground.copy(alpha = AlphaScrollbar),
snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
content: @Composable (modifier: Modifier, lazyListState: LazyListState) -> Unit
) {

Expand All @@ -128,6 +129,12 @@ fun ScaffoldWithMediumTopBar(
scrollBehavior = if (canScroll) scrollBehavior else null
)
},
snackbarHost = {
SnackbarHost(
snackbarHostState,
snackbar = { snackbarData -> MullvadSnackbar(snackbarData = snackbarData) }
)
},
content = {
content(
Modifier.fillMaxSize()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

package net.mullvad.mullvadvpn.compose.screen

import android.os.Build
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
Expand All @@ -14,6 +15,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
Expand All @@ -24,16 +26,19 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.tooling.preview.Preview
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.ramcosta.composedestinations.navigation.popUpTo
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.NavGraphs
import net.mullvad.mullvadvpn.compose.button.ExternalButton
Expand Down Expand Up @@ -94,6 +99,7 @@ fun Account(navigator: DestinationsNavigator) {
popUpTo(NavGraphs.root) { inclusive = true }
}
},
onCopyAccountNumber = vm::onCopyAccountNumber,
onBackClick = { navigator.navigateUp() }
)
}
Expand All @@ -103,6 +109,7 @@ fun Account(navigator: DestinationsNavigator) {
fun AccountScreen(
uiState: AccountUiState,
uiSideEffect: SharedFlow<AccountViewModel.UiSideEffect>,
onCopyAccountNumber: (String) -> Unit = {},
onRedeemVoucherClick: () -> Unit = {},
onManageAccountClick: () -> Unit = {},
onLogoutClick: () -> Unit = {},
Expand All @@ -118,14 +125,25 @@ fun AccountScreen(
}

val context = LocalContext.current
val clipboardManager = LocalClipboardManager.current
val snackbarHostState = remember { SnackbarHostState() }
val copyTextString = stringResource(id = R.string.copied_mullvad_account_number)
LaunchedEffect(Unit) {
uiSideEffect.collect { uiSideEffect ->
when (uiSideEffect) {
AccountViewModel.UiSideEffect.NavigateToLogin -> navigateToLogin()
is AccountViewModel.UiSideEffect.OpenAccountManagementPageInBrowser ->
context.openAccountPageInBrowser(uiSideEffect.token)
is AccountViewModel.UiSideEffect.CopyAccountNumber ->
launch {
clipboardManager.setText(AnnotatedString(uiSideEffect.accountNumber))

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
snackbarHostState.currentSnackbarData?.dismiss()
snackbarHostState.showSnackbar(message = copyTextString)
}
}
}
if (uiSideEffect is AccountViewModel.UiSideEffect.OpenAccountManagementPageInBrowser) {}
}
}

Expand All @@ -140,7 +158,7 @@ fun AccountScreen(
) {
DeviceNameRow(deviceName = uiState.deviceName ?: "") { showDeviceNameInfoDialog = true }

AccountNumberRow(accountNumber = uiState.accountNumber ?: "")
AccountNumberRow(accountNumber = uiState.accountNumber ?: "", onCopyAccountNumber)

PaidUntilRow(accountExpiry = uiState.accountExpiry)

Expand Down Expand Up @@ -192,14 +210,15 @@ private fun DeviceNameRow(deviceName: String, onInfoClick: () -> Unit) {
}

@Composable
private fun AccountNumberRow(accountNumber: String) {
private fun AccountNumberRow(accountNumber: String, onCopyAccountNumber: (String) -> Unit) {
Column(modifier = Modifier.fillMaxWidth()) {
Text(
style = MaterialTheme.typography.labelMedium,
text = stringResource(id = R.string.account_number),
)
CopyableObfuscationView(
content = accountNumber,
onCopyClicked = { onCopyAccountNumber(accountNumber) },
modifier = Modifier.heightIn(min = Dimens.accountRowMinHeight).fillMaxWidth()
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package net.mullvad.mullvadvpn.compose.screen

import android.widget.Toast
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
Expand All @@ -11,18 +10,19 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.Divider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
Expand All @@ -36,7 +36,7 @@ import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.cell.BaseCell
import net.mullvad.mullvadvpn.compose.cell.ContentBlockersDisableModeCellSubtitle
Expand Down Expand Up @@ -85,6 +85,7 @@ import net.mullvad.mullvadvpn.util.hasValue
import net.mullvad.mullvadvpn.util.isCustom
import net.mullvad.mullvadvpn.util.toDisplayCustomPort
import net.mullvad.mullvadvpn.viewmodel.CustomDnsItem
import net.mullvad.mullvadvpn.viewmodel.VpnSettingsSideEffect
import net.mullvad.mullvadvpn.viewmodel.VpnSettingsViewModel
import org.koin.androidx.compose.koinViewModel

Expand All @@ -100,6 +101,7 @@ private fun PreviewVpnSettings() {
isCustomDnsEnabled = true,
customDnsItems = listOf(CustomDnsItem("0.0.0.0", false)),
),
sideEffect = MutableSharedFlow<VpnSettingsSideEffect>().asSharedFlow(),
onMtuCellClick = {},
onSaveMtuClick = {},
onRestoreMtuClick = {},
Expand All @@ -124,7 +126,6 @@ private fun PreviewVpnSettings() {
onCustomDnsInfoClick = {},
onDismissInfoClick = {},
onBackClick = {},
toastMessagesSharedFlow = MutableSharedFlow<String>().asSharedFlow(),
onStopEvent = {},
onSelectObfuscationSetting = {},
onObfuscationInfoClick = {},
Expand All @@ -147,6 +148,7 @@ fun VpnSettings(navigator: DestinationsNavigator) {
val state = vm.uiState.collectAsState().value
VpnSettingsScreen(
uiState = state,
sideEffect = vm.sideEffect,
onMtuCellClick = vm::onMtuCellClick,
onSaveMtuClick = vm::onSaveMtuClick,
onRestoreMtuClick = vm::onRestoreMtuClick,
Expand All @@ -172,7 +174,6 @@ fun VpnSettings(navigator: DestinationsNavigator) {
onDismissInfoClick = vm::onDismissInfoClick,
onBackClick = navigator::navigateUp,
onStopEvent = vm::onStopEvent,
toastMessagesSharedFlow = vm.toastMessages,
onSelectObfuscationSetting = vm::onSelectObfuscationSetting,
onObfuscationInfoClick = vm::onObfuscationInfoClick,
onSelectQuantumResistanceSetting = vm::onSelectQuantumResistanceSetting,
Expand All @@ -190,6 +191,7 @@ fun VpnSettings(navigator: DestinationsNavigator) {
fun VpnSettingsScreen(
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
uiState: VpnSettingsUiState,
sideEffect: SharedFlow<VpnSettingsSideEffect>,
onMtuCellClick: () -> Unit = {},
onSaveMtuClick: (Int) -> Unit = {},
onRestoreMtuClick: () -> Unit = {},
Expand All @@ -215,7 +217,6 @@ fun VpnSettingsScreen(
onDismissInfoClick: () -> Unit = {},
onBackClick: () -> Unit = {},
onStopEvent: () -> Unit = {},
toastMessagesSharedFlow: SharedFlow<String>,
onSelectObfuscationSetting: (selectedObfuscation: SelectedObfuscation) -> Unit = {},
onObfuscationInfoClick: () -> Unit = {},
onSelectQuantumResistanceSetting: (quantumResistant: QuantumResistantState) -> Unit = {},
Expand All @@ -228,6 +229,19 @@ fun VpnSettingsScreen(
) {
val savedCustomPort = rememberSaveable { mutableStateOf<Constraint<Port>>(Constraint.Any()) }

val snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect(Unit) {
sideEffect.collect {
when (it) {
is VpnSettingsSideEffect.ShowToast ->
launch {
snackbarHostState.currentSnackbarData?.dismiss()
snackbarHostState.showSnackbar(message = it.message)
}
}
}
}

when (val dialog = uiState.dialog) {
is VpnSettingsDialog.Mtu -> {
MtuDialog(
Expand Down Expand Up @@ -301,12 +315,6 @@ fun VpnSettingsScreen(
}
}

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) {
Expand All @@ -319,6 +327,7 @@ fun VpnSettingsScreen(
ScaffoldWithMediumTopBar(
appBarTitle = stringResource(id = R.string.settings_vpn),
navigationIcon = { NavigateBackIconButton(onBackClick) },
snackbarHostState = snackbarHostState
) { modifier, lazyListState ->
LazyColumn(
modifier = modifier.testTag(LAZY_LIST_TEST_TAG).animateContentSize(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,16 @@ class AccountViewModel(
viewModelScope.launch { _uiSideEffect.emit(UiSideEffect.NavigateToLogin) }
}

fun onCopyAccountNumber(accountNumber: String) {
viewModelScope.launch { _uiSideEffect.emit(UiSideEffect.CopyAccountNumber(accountNumber)) }
}

sealed class UiSideEffect {
data object NavigateToLogin : UiSideEffect()

data class OpenAccountManagementPageInBrowser(val token: String) : UiSideEffect()

data class CopyAccountNumber(val accountNumber: String) : UiSideEffect()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ import net.mullvad.mullvadvpn.usecase.RelayListUseCase
import net.mullvad.mullvadvpn.util.isValidMtu
import org.apache.commons.validator.routines.InetAddressValidator

sealed interface VpnSettingsSideEffect {
data class ShowToast(val message: String): VpnSettingsSideEffect
}

class VpnSettingsViewModel(
private val repository: SettingsRepository,
private val inetAddressValidator: InetAddressValidator,
Expand All @@ -44,9 +48,8 @@ class VpnSettingsViewModel(
private val dispatcher: CoroutineDispatcher = Dispatchers.IO
) : ViewModel() {

private val _toastMessages = MutableSharedFlow<String>(extraBufferCapacity = 1)
@Suppress("konsist.ensure public properties use permitted names")
val toastMessages = _toastMessages.asSharedFlow()
private val _sideEffect = MutableSharedFlow<VpnSettingsSideEffect>(extraBufferCapacity = 1)
val sideEffect = _sideEffect.asSharedFlow()

private val dialogState = MutableStateFlow<VpnSettingsDialogState?>(null)

Expand Down Expand Up @@ -236,6 +239,7 @@ class VpnSettingsViewModel(
if (isEnabled && vmState.value.customDnsList.isEmpty()) {
onDnsClick(null)
}
Log.d("VPNSettings", "Toggle dns")
showApplySettingChangesWarningToast()
}

Expand Down Expand Up @@ -431,7 +435,10 @@ class VpnSettingsViewModel(
}

private fun showApplySettingChangesWarningToast() {
_toastMessages.tryEmit(resources.getString(R.string.settings_changes_effect_warning_short))
Log.d("VPNSettings", "SHOWING TOAST")
viewModelScope.launch {
_sideEffect.emit(VpnSettingsSideEffect.ShowToast(resources.getString(R.string.settings_changes_effect_warning_short)))
}
}

companion object {
Expand Down
Loading

0 comments on commit 99a3672

Please sign in to comment.