Skip to content

Commit

Permalink
BITAU-89 Show save location dialog on the QR scan screen (#258)
Browse files Browse the repository at this point in the history
  • Loading branch information
ahaisting-livefront authored Oct 30, 2024
1 parent bb6255b commit 5de6dc3
Show file tree
Hide file tree
Showing 10 changed files with 786 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.bitwarden.authenticator.data.authenticator.repository.util

import com.bitwarden.authenticator.data.authenticator.repository.model.SharedVerificationCodesState

/**
* Whether or not the user has enabled sync with Bitwarden and the two apps are successfully
* syncing. This is useful to know when to show certain sync UI and also when to support
* moving codes to Bitwarden.
*/
val SharedVerificationCodesState.isSyncWithBitwardenEnabled: Boolean
get() = when (this) {
SharedVerificationCodesState.AppNotInstalled,
SharedVerificationCodesState.Error,
SharedVerificationCodesState.FeatureNotEnabled,
SharedVerificationCodesState.Loading,
SharedVerificationCodesState.OsVersionNotSupported,
SharedVerificationCodesState.SyncNotEnabled,
-> false

is SharedVerificationCodesState.Success -> true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package com.bitwarden.authenticator.ui.authenticator.feature.qrcodescan

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeightIn
import androidx.compose.foundation.layout.requiredWidthIn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
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.LocalConfiguration
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import com.bitwarden.authenticator.R
import com.bitwarden.authenticator.ui.platform.components.button.BitwardenTextButton
import com.bitwarden.authenticator.ui.platform.components.toggle.BitwardenWideSwitch
import com.bitwarden.authenticator.ui.platform.components.util.maxDialogHeight
import com.bitwarden.authenticator.ui.platform.components.util.maxDialogWidth

/**
* Displays a dialog asking the user where they would like to save a new QR code.
*
* @param onSaveHereClick Invoked when the user clicks "Save here". The boolean parameter is true if
* the user check "Save option as default".
* @param onTakeMeToBitwardenClick Invoked when the user clicks "Take me to Bitwarden". The boolean
* parameter is true if the user checked "Save option as default".
*/
@Composable
@OptIn(ExperimentalLayoutApi::class)
@Suppress("LongMethod")
fun ChooseSaveLocationDialog(
onSaveHereClick: (Boolean) -> Unit,
onTakeMeToBitwardenClick: (Boolean) -> Unit,
) {
Dialog(
onDismissRequest = { }, // Not dismissible
properties = DialogProperties(usePlatformDefaultWidth = false),
) {
var isSaveAsDefaultChecked by remember { mutableStateOf(false) }
val configuration = LocalConfiguration.current
Column(
modifier = Modifier
.requiredHeightIn(
max = configuration.maxDialogHeight,
)
.requiredWidthIn(
max = configuration.maxDialogWidth,
)
.background(
color = MaterialTheme.colorScheme.surfaceContainerHigh,
shape = RoundedCornerShape(28.dp),
),
horizontalAlignment = Alignment.End,
) {
Spacer(modifier = Modifier.height(24.dp))
Text(
modifier = Modifier
.padding(horizontal = 24.dp)
.fillMaxWidth(),
text = stringResource(R.string.verification_code_created),
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.headlineSmall,
)
Spacer(modifier = Modifier.height(16.dp))
Text(
modifier = Modifier
.weight(1f, fill = false)
.padding(horizontal = 24.dp)
.fillMaxWidth(),
text = stringResource(R.string.choose_save_location_message),
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodyMedium,
)
Spacer(Modifier.height(16.dp))
BitwardenWideSwitch(
modifier = Modifier.padding(horizontal = 16.dp),
label = stringResource(R.string.save_option_as_default),
isChecked = isSaveAsDefaultChecked,
onCheckedChange = { isSaveAsDefaultChecked = !isSaveAsDefaultChecked },
)
Spacer(Modifier.height(16.dp))
FlowRow(
horizontalArrangement = Arrangement.End,
modifier = Modifier.padding(horizontal = 8.dp),
) {
BitwardenTextButton(
modifier = Modifier
.padding(horizontal = 4.dp),
label = stringResource(R.string.save_here),
labelTextColor = MaterialTheme.colorScheme.primary,
onClick = { onSaveHereClick.invoke(isSaveAsDefaultChecked) },
)
BitwardenTextButton(
modifier = Modifier
.padding(horizontal = 4.dp),
label = stringResource(R.string.take_me_to_bitwarden),
labelTextColor = MaterialTheme.colorScheme.primary,
onClick = { onTakeMeToBitwardenClick.invoke(isSaveAsDefaultChecked) },
)
}
Spacer(modifier = Modifier.height(24.dp))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,15 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bitwarden.authenticator.R
import com.bitwarden.authenticator.ui.authenticator.feature.qrcodescan.util.QrCodeAnalyzer
import com.bitwarden.authenticator.ui.authenticator.feature.qrcodescan.util.QrCodeAnalyzerImpl
import com.bitwarden.authenticator.ui.platform.base.util.EventsEffect
import com.bitwarden.authenticator.ui.platform.base.util.asText
import com.bitwarden.authenticator.ui.platform.components.appbar.BitwardenTopAppBar
import com.bitwarden.authenticator.ui.platform.components.dialog.BasicDialogState
import com.bitwarden.authenticator.ui.platform.components.dialog.BitwardenBasicDialog
import com.bitwarden.authenticator.ui.platform.components.scaffold.BitwardenScaffold
import com.bitwarden.authenticator.ui.platform.theme.LocalNonMaterialColors
import com.bitwarden.authenticator.ui.platform.theme.clickableSpanStyle
Expand All @@ -83,9 +87,8 @@ fun QrCodeScanScreen(
qrCodeAnalyzer.onQrCodeScanned = remember(viewModel) {
{ viewModel.trySendAction(QrCodeScanAction.QrCodeScanReceive(it)) }
}

val state by viewModel.stateFlow.collectAsStateWithLifecycle()
val orientation = LocalConfiguration.current.orientation

val context = LocalContext.current

val onEnterCodeManuallyClick = remember(viewModel) {
Expand Down Expand Up @@ -147,6 +150,37 @@ fun QrCodeScanScreen(
)
}
}

when (state.dialog) {
QrCodeScanState.DialogState.ChooseSaveLocation -> {
ChooseSaveLocationDialog(
onSaveHereClick = remember(viewModel) {
{
viewModel.trySendAction(QrCodeScanAction.SaveLocallyClick(it))
}
},
onTakeMeToBitwardenClick = remember(viewModel) {
{
viewModel.trySendAction(QrCodeScanAction.SaveToBitwardenClick(it))
}
},
)
}

QrCodeScanState.DialogState.SaveToBitwardenError -> BitwardenBasicDialog(
visibilityState = BasicDialogState.Shown(
title = R.string.something_went_wrong.asText(),
message = R.string.please_try_again.asText(),
),
onDismissRequest = remember(viewModel) {
{
viewModel.trySendAction(QrCodeScanAction.SaveToBitwardenErrorDismiss)
}
},
)

null -> Unit
}
}
}

Expand Down
Loading

0 comments on commit 5de6dc3

Please sign in to comment.