diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/MainActivity.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/MainActivity.kt index 47a14e13d1e..9f38f2078e5 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/MainActivity.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/MainActivity.kt @@ -9,17 +9,19 @@ import android.view.WindowManager import androidx.activity.compose.setContent import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.app.AppCompatDelegate import androidx.compose.runtime.getValue import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.navigation.NavHostController import androidx.navigation.compose.rememberNavController +import com.bitwarden.authenticator.data.platform.repository.SettingsRepository import com.bitwarden.authenticator.ui.platform.composition.LocalManagerProvider import com.bitwarden.authenticator.ui.platform.feature.debugmenu.manager.DebugMenuLaunchManager import com.bitwarden.authenticator.ui.platform.feature.debugmenu.navigateToDebugMenuScreen import com.bitwarden.authenticator.ui.platform.feature.rootnav.RootNavScreen -import com.bitwarden.authenticator.ui.platform.theme.AuthenticatorTheme +import com.bitwarden.ui.platform.theme.BitwardenTheme import com.bitwarden.ui.platform.util.setupEdgeToEdge import com.bitwarden.ui.platform.util.validate import dagger.hilt.android.AndroidEntryPoint @@ -39,6 +41,9 @@ class MainActivity : AppCompatActivity() { @Inject lateinit var debugLaunchManager: DebugMenuLaunchManager + @Inject + lateinit var settingsRepository: SettingsRepository + override fun onCreate(savedInstanceState: Bundle?) { intent = intent.validate() var shouldShowSplashScreen = true @@ -53,13 +58,14 @@ class MainActivity : AppCompatActivity() { ) } + AppCompatDelegate.setDefaultNightMode(settingsRepository.appTheme.osValue) setupEdgeToEdge(appThemeFlow = mainViewModel.stateFlow.map { it.theme }) setContent { val state by mainViewModel.stateFlow.collectAsStateWithLifecycle() val navController = rememberNavController() observeViewModelEvents(navController) LocalManagerProvider { - AuthenticatorTheme( + BitwardenTheme( theme = state.theme, ) { RootNavScreen( @@ -94,6 +100,9 @@ class MainActivity : AppCompatActivity() { } MainEvent.NavigateToDebugMenu -> navController.navigateToDebugMenuScreen() + is MainEvent.UpdateAppTheme -> { + AppCompatDelegate.setDefaultNightMode(event.osTheme) + } } } .launchIn(lifecycleScope) diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/MainViewModel.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/MainViewModel.kt index 41402ea83d0..20ff6ffe7a1 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/MainViewModel.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/MainViewModel.kt @@ -60,6 +60,7 @@ class MainViewModel @Inject constructor( private fun handleThemeUpdated(action: MainAction.Internal.ThemeUpdate) { mutableStateFlow.update { it.copy(theme = action.theme) } + sendEvent(MainEvent.UpdateAppTheme(osTheme = action.theme.osValue)) } private fun handleFirstIntentReceived(action: MainAction.ReceiveFirstIntent) { @@ -139,4 +140,9 @@ sealed class MainEvent { * Event indicating a change in the screen capture setting. */ data class ScreenCaptureSettingChange(val isAllowed: Boolean) : MainEvent() + + /** + * Indicates that the app theme has been updated. + */ + data class UpdateAppTheme(val osTheme: Int) : MainEvent() } diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/auth/unlock/UnlockScreen.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/auth/unlock/UnlockScreen.kt index f7ad40ad580..3395a72dd08 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/auth/unlock/UnlockScreen.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/auth/unlock/UnlockScreen.kt @@ -2,16 +2,15 @@ package com.bitwarden.authenticator.ui.auth.unlock import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column 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.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -25,18 +24,16 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.bitwarden.authenticator.ui.platform.components.button.AuthenticatorFilledTonalButton -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.dialog.BitwardenLoadingDialog -import com.bitwarden.authenticator.ui.platform.components.dialog.LoadingDialogState -import com.bitwarden.authenticator.ui.platform.components.scaffold.BitwardenScaffold import com.bitwarden.authenticator.ui.platform.composition.LocalBiometricsManager import com.bitwarden.authenticator.ui.platform.manager.biometrics.BiometricsManager import com.bitwarden.ui.platform.base.util.EventsEffect +import com.bitwarden.ui.platform.components.button.BitwardenFilledButton +import com.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog +import com.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog +import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold import com.bitwarden.ui.platform.resource.BitwardenDrawable import com.bitwarden.ui.platform.resource.BitwardenString -import com.bitwarden.ui.util.asText +import com.bitwarden.ui.platform.theme.BitwardenTheme /** * Top level composable for the unlock screen. @@ -60,10 +57,8 @@ fun UnlockScreen( when (val dialog = state.dialog) { is UnlockState.Dialog.Error -> BitwardenBasicDialog( - visibilityState = BasicDialogState.Shown( - title = BitwardenString.an_error_has_occurred.asText(), - message = dialog.message, - ), + title = stringResource(id = BitwardenString.an_error_has_occurred), + message = dialog.message(), onDismissRequest = remember(viewModel) { { viewModel.trySendAction(UnlockAction.DismissDialog) @@ -72,7 +67,7 @@ fun UnlockScreen( ) UnlockState.Dialog.Loading -> BitwardenLoadingDialog( - visibilityState = LoadingDialogState.Shown(BitwardenString.loading.asText()), + text = stringResource(id = BitwardenString.loading), ) null -> Unit @@ -107,45 +102,44 @@ fun UnlockScreen( BitwardenScaffold( modifier = Modifier .fillMaxSize(), - ) { innerPadding -> - Box { - Column( + ) { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Image( modifier = Modifier - .padding(innerPadding) - .fillMaxSize(), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Image( - modifier = Modifier - .padding(horizontal = 16.dp) - .width(220.dp) - .height(74.dp) - .fillMaxWidth(), - colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.primary), - painter = painterResource(id = BitwardenDrawable.ic_logo_horizontal), - contentDescription = stringResource(BitwardenString.bitwarden_authenticator), - ) - Spacer(modifier = Modifier.height(32.dp)) - AuthenticatorFilledTonalButton( - label = stringResource(id = BitwardenString.use_biometrics_to_unlock), - onClick = { - biometricsManager.promptBiometrics( - onSuccess = onBiometricsUnlock, - onCancel = { - // no-op - }, - onError = { - // no-op - }, - onLockOut = onBiometricsLockOut, - ) - }, - modifier = Modifier - .padding(horizontal = 16.dp) - .fillMaxWidth(), - ) - } + .padding(horizontal = 16.dp) + .width(220.dp) + .height(74.dp) + .fillMaxWidth(), + colorFilter = ColorFilter.tint(BitwardenTheme.colorScheme.icon.secondary), + painter = painterResource(id = BitwardenDrawable.ic_logo_horizontal), + contentDescription = stringResource(BitwardenString.bitwarden_authenticator), + ) + Spacer(modifier = Modifier.height(32.dp)) + BitwardenFilledButton( + label = stringResource(id = BitwardenString.use_biometrics_to_unlock), + onClick = { + biometricsManager.promptBiometrics( + onSuccess = onBiometricsUnlock, + onCancel = { + // no-op + }, + onError = { + // no-op + }, + onLockOut = onBiometricsLockOut, + ) + }, + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth(), + ) + + Spacer(modifier = Modifier.height(height = 12.dp)) + Spacer(modifier = Modifier.navigationBarsPadding()) } } } diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemScreen.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemScreen.kt index 46a3e106600..967161072fd 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemScreen.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemScreen.kt @@ -1,44 +1,31 @@ package com.bitwarden.authenticator.ui.authenticator.feature.edititem import android.widget.Toast -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Row 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.navigationBarsPadding import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FabPosition -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarState -import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.rotate import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalResources import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTag -import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel @@ -46,24 +33,23 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.bitwarden.authenticator.data.authenticator.datasource.disk.entity.AuthenticatorItemAlgorithm import com.bitwarden.authenticator.data.authenticator.datasource.disk.entity.AuthenticatorItemType import com.bitwarden.authenticator.ui.authenticator.feature.edititem.model.EditItemData -import com.bitwarden.authenticator.ui.platform.components.appbar.AuthenticatorTopAppBar -import com.bitwarden.authenticator.ui.platform.components.button.AuthenticatorTextButton -import com.bitwarden.authenticator.ui.platform.components.content.AuthenticatorErrorContent -import com.bitwarden.authenticator.ui.platform.components.content.AuthenticatorLoadingContent -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.dialog.BitwardenLoadingDialog -import com.bitwarden.authenticator.ui.platform.components.dialog.LoadingDialogState -import com.bitwarden.authenticator.ui.platform.components.dropdown.BitwardenMultiSelectButton -import com.bitwarden.authenticator.ui.platform.components.field.BitwardenPasswordField -import com.bitwarden.authenticator.ui.platform.components.field.BitwardenTextField -import com.bitwarden.authenticator.ui.platform.components.header.BitwardenListHeaderText -import com.bitwarden.authenticator.ui.platform.components.scaffold.BitwardenScaffold -import com.bitwarden.authenticator.ui.platform.components.stepper.BitwardenStepper -import com.bitwarden.authenticator.ui.platform.components.toggle.BitwardenSwitch import com.bitwarden.ui.platform.base.util.EventsEffect import com.bitwarden.ui.platform.base.util.standardHorizontalMargin -import com.bitwarden.ui.platform.components.util.rememberVectorPainter +import com.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar +import com.bitwarden.ui.platform.components.button.BitwardenTextButton +import com.bitwarden.ui.platform.components.content.BitwardenErrorContent +import com.bitwarden.ui.platform.components.content.BitwardenLoadingContent +import com.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog +import com.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog +import com.bitwarden.ui.platform.components.dropdown.BitwardenMultiSelectButton +import com.bitwarden.ui.platform.components.field.BitwardenPasswordField +import com.bitwarden.ui.platform.components.field.BitwardenTextField +import com.bitwarden.ui.platform.components.header.BitwardenExpandingHeader +import com.bitwarden.ui.platform.components.header.BitwardenListHeaderText +import com.bitwarden.ui.platform.components.model.CardStyle +import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold +import com.bitwarden.ui.platform.components.stepper.BitwardenStepper +import com.bitwarden.ui.platform.components.toggle.BitwardenSwitch import com.bitwarden.ui.platform.resource.BitwardenDrawable import com.bitwarden.ui.platform.resource.BitwardenPlurals import com.bitwarden.ui.platform.resource.BitwardenString @@ -82,7 +68,7 @@ fun EditItemScreen( val state by viewModel.stateFlow.collectAsStateWithLifecycle() val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) val context = LocalContext.current - val resources = context.resources + val resources = LocalResources.current EventsEffect(viewModel = viewModel) { event -> when (event) { @@ -112,10 +98,8 @@ fun EditItemScreen( .fillMaxSize() .nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { - AuthenticatorTopAppBar( - title = stringResource( - id = BitwardenString.edit, - ), + BitwardenTopAppBar( + title = stringResource(id = BitwardenString.edit), scrollBehavior = scrollBehavior, navigationIcon = painterResource(id = BitwardenDrawable.ic_close), navigationIconContentDescription = stringResource(id = BitwardenString.close), @@ -125,7 +109,7 @@ fun EditItemScreen( } }, actions = { - AuthenticatorTextButton( + BitwardenTextButton( label = stringResource(id = BitwardenString.save), onClick = remember(viewModel) { { viewModel.trySendAction(EditItemAction.SaveClick) } @@ -136,12 +120,10 @@ fun EditItemScreen( ) }, floatingActionButtonPosition = FabPosition.EndOverlay, - ) { innerPadding -> + ) { when (val viewState = state.viewState) { is EditItemState.ViewState.Content -> { EditItemContent( - modifier = Modifier - .padding(innerPadding), viewState = viewState, onIssuerNameTextChange = remember(viewModel) { { @@ -210,16 +192,11 @@ fun EditItemScreen( } is EditItemState.ViewState.Error -> { - AuthenticatorErrorContent( - message = viewState.message(), - modifier = Modifier.padding(innerPadding), - ) + BitwardenErrorContent(message = viewState.message()) } EditItemState.ViewState.Loading -> { - AuthenticatorLoadingContent( - modifier = Modifier.padding(innerPadding), - ) + BitwardenLoadingContent() } } } @@ -245,9 +222,11 @@ fun EditItemContent( ) { LazyColumn(modifier = modifier) { item { + Spacer(modifier = Modifier.height(height = 12.dp)) BitwardenListHeaderText( modifier = Modifier .standardHorizontalMargin() + .padding(horizontal = 16.dp) .fillMaxWidth(), label = stringResource(id = BitwardenString.information), ) @@ -264,11 +243,11 @@ fun EditItemContent( value = viewState.itemData.issuer, onValueChange = onIssuerNameTextChange, singleLine = true, + cardStyle = CardStyle.Top(), ) } item { - Spacer(modifier = Modifier.height(8.dp)) BitwardenPasswordField( modifier = Modifier .testTag(tag = "KeyTextField") @@ -278,12 +257,11 @@ fun EditItemContent( value = viewState.itemData.totpCode, onValueChange = onTotpCodeTextChange, singleLine = true, - capitalization = KeyboardCapitalization.Characters, + cardStyle = CardStyle.Middle(), ) } item { - Spacer(modifier = Modifier.height(8.dp)) BitwardenTextField( modifier = Modifier .testTag(tag = "UsernameTextField") @@ -293,15 +271,16 @@ fun EditItemContent( value = viewState.itemData.username.orEmpty(), onValueChange = onUsernameTextChange, singleLine = true, + cardStyle = CardStyle.Middle(), ) } item { - Spacer(modifier = Modifier.height(16.dp)) BitwardenSwitch( label = stringResource(id = BitwardenString.favorite), isChecked = viewState.itemData.favorite, onCheckedChange = onToggleFavorite, + cardStyle = CardStyle.Bottom, modifier = Modifier .testTag(tag = "ItemFavoriteToggle") .fillMaxWidth() @@ -310,47 +289,13 @@ fun EditItemContent( } item(key = "AdvancedOptions") { - val iconRotationDegrees = animateFloatAsState( - targetValue = if (viewState.isAdvancedOptionsExpanded) 180f else 0f, - label = "expanderIconRotationAnimation", - ) - Spacer(modifier = Modifier.height(16.dp)) - Row( + BitwardenExpandingHeader( + isExpanded = viewState.isAdvancedOptionsExpanded, + onClick = onExpandAdvancedOptionsClicked, modifier = Modifier - .testTag(tag = "CollapseAdvancedOptions") - .standardHorizontalMargin() .fillMaxWidth() - .clip(RoundedCornerShape(28.dp)) - .clickable( - indication = ripple( - bounded = true, - color = MaterialTheme.colorScheme.primary, - ), - interactionSource = remember { MutableInteractionSource() }, - onClick = onExpandAdvancedOptionsClicked, - ) - .padding(vertical = 12.dp) - .animateItem(), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = stringResource(BitwardenString.advanced), - style = MaterialTheme.typography.labelLarge, - color = MaterialTheme.colorScheme.primary, - ) - Spacer(Modifier.width(8.dp)) - Icon( - painter = rememberVectorPainter(id = BitwardenDrawable.ic_chevron_down), - contentDescription = if (viewState.isAdvancedOptionsExpanded) { - stringResource(BitwardenString.collapse_advanced_options) - } else { - stringResource(BitwardenString.expand_advanced_options) - }, - tint = MaterialTheme.colorScheme.primary, - modifier = Modifier - .rotate(degrees = iconRotationDegrees.value), - ) - } + .standardHorizontalMargin(), + ) } if (viewState.isAdvancedOptionsExpanded) { @@ -380,8 +325,7 @@ private fun LazyListScope.advancedOptions( ) { item(key = "OtpItemTypeSelector") { val possibleTypeOptions = AuthenticatorItemType.entries - val typeOptionsWithStrings = - possibleTypeOptions.associateWith { it.name } + val typeOptionsWithStrings = possibleTypeOptions.associateWith { it.name } BitwardenMultiSelectButton( modifier = Modifier .testTag(tag = "OTPItemTypePicker") @@ -391,6 +335,7 @@ private fun LazyListScope.advancedOptions( label = stringResource(id = BitwardenString.otp_type), options = typeOptionsWithStrings.values.toImmutableList(), selectedOption = viewState.itemData.type.name, + cardStyle = CardStyle.Top(), onOptionSelected = { selectedOption -> val selectedOptionName = typeOptionsWithStrings .entries @@ -404,7 +349,6 @@ private fun LazyListScope.advancedOptions( item(key = "AlgorithmItemTypeSelector") { val possibleAlgorithmOptions = AuthenticatorItemAlgorithm.entries val algorithmOptionsWithStrings = possibleAlgorithmOptions.associateWith { it.name } - Spacer(Modifier.height(8.dp)) BitwardenMultiSelectButton( modifier = Modifier .testTag(tag = "AlgorithmItemTypePicker") @@ -414,6 +358,7 @@ private fun LazyListScope.advancedOptions( label = stringResource(id = BitwardenString.algorithm), options = algorithmOptionsWithStrings.values.toImmutableList(), selectedOption = viewState.itemData.algorithm.name, + cardStyle = CardStyle.Middle(), onOptionSelected = { selectedOption -> val selectedOptionName = algorithmOptionsWithStrings .entries @@ -433,7 +378,6 @@ private fun LazyListScope.advancedOptions( formatArgs = arrayOf(it.seconds), ) } - Spacer(modifier = Modifier.height(8.dp)) BitwardenMultiSelectButton( modifier = Modifier .testTag(tag = "RefreshPeriodItemTypePicker") @@ -445,6 +389,7 @@ private fun LazyListScope.advancedOptions( selectedOption = refreshPeriodOptionsWithStrings.getValue( key = viewState.itemData.refreshPeriod, ), + cardStyle = CardStyle.Middle(), onOptionSelected = remember(viewState) { { selectedOption -> val selectedOptionName = refreshPeriodOptionsWithStrings @@ -458,7 +403,6 @@ private fun LazyListScope.advancedOptions( } item(key = "DigitsCounterItem") { - Spacer(modifier = Modifier.height(8.dp)) DigitsCounterItem( modifier = Modifier .fillMaxWidth() @@ -480,17 +424,15 @@ private fun EditItemDialogs( when (dialogState) { is EditItemState.DialogState.Generic -> { BitwardenBasicDialog( - visibilityState = BasicDialogState.Shown( - title = dialogState.title, - message = dialogState.message, - ), + title = dialogState.title(), + message = dialogState.message(), onDismissRequest = onDismissRequest, ) } is EditItemState.DialogState.Loading -> { BitwardenLoadingDialog( - visibilityState = LoadingDialogState.Shown(dialogState.message), + text = dialogState.message(), ) } @@ -511,8 +453,7 @@ private fun DigitsCounterItem( value = digits.coerceIn(minValue, maxValue), range = minValue..maxValue, onValueChange = onDigitsCounterChange, - increaseButtonTestTag = "DigitsIncreaseButton", - decreaseButtonTestTag = "DigitsDecreaseButton", + cardStyle = CardStyle.Bottom, modifier = modifier.testTag(tag = "DigitsValueLabel"), ) } diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/FirstTimeSyncSnackbarHost.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/FirstTimeSyncSnackbarHost.kt index 96c20bb6108..80dd73ffa5f 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/FirstTimeSyncSnackbarHost.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/FirstTimeSyncSnackbarHost.kt @@ -5,10 +5,8 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text @@ -21,6 +19,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.bitwarden.ui.platform.resource.BitwardenDrawable import com.bitwarden.ui.platform.resource.BitwardenString +import com.bitwarden.ui.platform.theme.BitwardenTheme /** * Show a snackbar that says "Account synced from Bitwarden app" with a close action. @@ -40,8 +39,8 @@ fun FirstTimeSyncSnackbarHost( .fillMaxWidth() .shadow(elevation = 6.dp) .background( - color = MaterialTheme.colorScheme.inverseSurface, - shape = RoundedCornerShape(8.dp), + color = BitwardenTheme.colorScheme.background.alert, + shape = BitwardenTheme.shapes.snackbar, ), verticalAlignment = Alignment.CenterVertically, ) { @@ -50,8 +49,8 @@ fun FirstTimeSyncSnackbarHost( .padding(16.dp) .weight(1f, fill = true), text = stringResource(BitwardenString.account_synced_from_bitwarden_app), - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.inverseOnSurface, + style = BitwardenTheme.typography.bodyLarge, + color = BitwardenTheme.colorScheme.text.reversed, ) IconButton( onClick = { state.currentSnackbarData?.dismiss() }, @@ -59,7 +58,7 @@ fun FirstTimeSyncSnackbarHost( Icon( painter = painterResource(id = BitwardenDrawable.ic_close), contentDescription = stringResource(id = BitwardenString.close), - tint = MaterialTheme.colorScheme.inverseOnSurface, + tint = BitwardenTheme.colorScheme.icon.reversed, modifier = Modifier .size(24.dp), ) diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreen.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreen.kt index 32c22cc4d6c..cf80346dc06 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreen.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreen.kt @@ -2,6 +2,7 @@ package com.bitwarden.authenticator.ui.authenticator.feature.itemlisting import android.Manifest import android.widget.Toast +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -10,22 +11,19 @@ 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.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.FabPosition -import androidx.compose.material3.HorizontalDivider 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.material3.TopAppBarDefaults @@ -43,10 +41,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.testTag import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -57,30 +54,29 @@ import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.model.It import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.model.VaultDropdownMenuAction import com.bitwarden.authenticator.ui.authenticator.feature.model.SharedCodesDisplayState import com.bitwarden.authenticator.ui.authenticator.feature.model.VerificationCodeDisplayItem -import com.bitwarden.authenticator.ui.platform.components.appbar.AuthenticatorMediumTopAppBar -import com.bitwarden.authenticator.ui.platform.components.appbar.AuthenticatorTopAppBar -import com.bitwarden.authenticator.ui.platform.components.appbar.action.AuthenticatorSearchActionItem -import com.bitwarden.authenticator.ui.platform.components.button.AuthenticatorFilledButton -import com.bitwarden.authenticator.ui.platform.components.button.AuthenticatorFilledTonalButton -import com.bitwarden.authenticator.ui.platform.components.button.AuthenticatorTextButton -import com.bitwarden.authenticator.ui.platform.components.card.AuthenticatorActionCard -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.dialog.BitwardenLoadingDialog -import com.bitwarden.authenticator.ui.platform.components.dialog.BitwardenTwoButtonDialog -import com.bitwarden.authenticator.ui.platform.components.dialog.LoadingDialogState -import com.bitwarden.authenticator.ui.platform.components.fab.ExpandableFabIcon -import com.bitwarden.authenticator.ui.platform.components.fab.ExpandableFloatingActionButton import com.bitwarden.authenticator.ui.platform.components.header.AuthenticatorExpandingHeader -import com.bitwarden.authenticator.ui.platform.components.header.BitwardenListHeaderTextWithSupportLabel -import com.bitwarden.authenticator.ui.platform.components.model.IconResource -import com.bitwarden.authenticator.ui.platform.components.scaffold.BitwardenScaffold import com.bitwarden.authenticator.ui.platform.composition.LocalPermissionsManager import com.bitwarden.authenticator.ui.platform.manager.permissions.PermissionsManager -import com.bitwarden.authenticator.ui.platform.theme.Typography import com.bitwarden.authenticator.ui.platform.util.startAuthenticatorAppSettings import com.bitwarden.authenticator.ui.platform.util.startBitwardenAccountSettings import com.bitwarden.ui.platform.base.util.EventsEffect +import com.bitwarden.ui.platform.base.util.standardHorizontalMargin +import com.bitwarden.ui.platform.base.util.toListItemCardStyle +import com.bitwarden.ui.platform.components.appbar.BitwardenMediumTopAppBar +import com.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar +import com.bitwarden.ui.platform.components.appbar.action.BitwardenSearchActionItem +import com.bitwarden.ui.platform.components.button.BitwardenFilledButton +import com.bitwarden.ui.platform.components.button.BitwardenTextButton +import com.bitwarden.ui.platform.components.card.BitwardenActionCard +import com.bitwarden.ui.platform.components.card.color.bitwardenCardColors +import com.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog +import com.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog +import com.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialog +import com.bitwarden.ui.platform.components.fab.BitwardenExpandableFloatingActionButton +import com.bitwarden.ui.platform.components.fab.ExpandableFabIcon +import com.bitwarden.ui.platform.components.header.BitwardenListHeaderText +import com.bitwarden.ui.platform.components.icon.model.IconData +import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold import com.bitwarden.ui.platform.components.util.rememberVectorPainter import com.bitwarden.ui.platform.composition.LocalIntentManager import com.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme @@ -89,6 +85,7 @@ import com.bitwarden.ui.platform.resource.BitwardenDrawable import com.bitwarden.ui.platform.resource.BitwardenString import com.bitwarden.ui.platform.theme.BitwardenTheme import com.bitwarden.ui.util.asText +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.launch /** @@ -310,18 +307,14 @@ private fun ItemListingDialogs( when (dialog) { ItemListingState.DialogState.Loading -> { BitwardenLoadingDialog( - visibilityState = LoadingDialogState.Shown( - text = BitwardenString.syncing.asText(), - ), + text = stringResource(id = BitwardenString.syncing), ) } is ItemListingState.DialogState.Error -> { BitwardenBasicDialog( - visibilityState = BasicDialogState.Shown( - title = dialog.title, - message = dialog.message, - ), + title = dialog.title(), + message = dialog.message(), onDismissRequest = onDismissRequest, ) } @@ -368,11 +361,11 @@ private fun ItemListingContent( .fillMaxSize() .nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { - AuthenticatorMediumTopAppBar( + BitwardenMediumTopAppBar( title = stringResource(id = BitwardenString.verification_codes), scrollBehavior = scrollBehavior, actions = { - AuthenticatorSearchActionItem( + BitwardenSearchActionItem( contentDescription = stringResource(id = BitwardenString.search_codes), onClick = onNavigateToSearch, ) @@ -380,53 +373,43 @@ private fun ItemListingContent( ) }, floatingActionButton = { - ExpandableFloatingActionButton( - modifier = Modifier - .semantics { testTag = "AddItemButton" } - .padding(bottom = 16.dp), - label = BitwardenString.add_item.asText(), - items = listOf( + BitwardenExpandableFloatingActionButton( + modifier = Modifier.testTag("AddItemButton"), + items = persistentListOf( ItemListingExpandableFabAction.ScanQrCode( label = BitwardenString.scan_a_qr_code.asText(), - icon = IconResource( - iconPainter = painterResource(id = BitwardenDrawable.ic_camera), - contentDescription = stringResource( - id = BitwardenString.scan_a_qr_code, - ), + icon = IconData.Local( + iconRes = BitwardenDrawable.ic_camera_small, + contentDescription = BitwardenString.scan_a_qr_code.asText(), testTag = "ScanQRCodeButton", ), onScanQrCodeClick = onScanQrCodeClick, ), ItemListingExpandableFabAction.EnterSetupKey( label = BitwardenString.enter_key_manually.asText(), - icon = IconResource( - iconPainter = painterResource(id = BitwardenDrawable.ic_keyboard), - contentDescription = stringResource( - id = BitwardenString.enter_key_manually, - ), + icon = IconData.Local( + iconRes = BitwardenDrawable.ic_lock_encrypted_small, + contentDescription = BitwardenString.enter_key_manually.asText(), testTag = "EnterSetupKeyButton", ), onEnterSetupKeyClick = onEnterSetupKeyClick, ), ), expandableFabIcon = ExpandableFabIcon( - iconData = IconResource( - iconPainter = painterResource(id = BitwardenDrawable.ic_plus), - contentDescription = stringResource(id = BitwardenString.add_item), + icon = IconData.Local( + iconRes = BitwardenDrawable.ic_plus, + contentDescription = BitwardenString.add_item.asText(), testTag = "AddItemButton", ), iconRotation = 45f, ), ) }, - floatingActionButtonPosition = FabPosition.EndOverlay, snackbarHost = { FirstTimeSyncSnackbarHost(state = snackbarHostState) }, - ) { paddingValues -> + ) { var isLocalHeaderExpanded by rememberSaveable { mutableStateOf(true) } LazyColumn( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues), + modifier = Modifier.fillMaxSize(), ) { item(key = "action_card") { ActionCard( @@ -437,30 +420,29 @@ private fun ItemListingContent( onSyncWithBitwardenDismissClick = onDismissSyncWithBitwardenClick, onSyncLearnMoreClick = onSyncLearnMoreClick, modifier = Modifier - .padding(all = 16.dp) + .standardHorizontalMargin() + .padding(top = 12.dp, bottom = 16.dp) .animateItem(), ) } if (state.favoriteItems.isNotEmpty()) { item(key = "favorites_header") { - BitwardenListHeaderTextWithSupportLabel( + BitwardenListHeaderText( label = stringResource(id = BitwardenString.favorites), supportingLabel = state.favoriteItems.count().toString(), modifier = Modifier .fillMaxWidth() + .standardHorizontalMargin() .padding(horizontal = 16.dp) .animateItem(), ) + Spacer(modifier = Modifier.height(height = 8.dp)) } - item { - Spacer(modifier = Modifier.height(4.dp)) - } - - items( + itemsIndexed( items = state.favoriteItems, - key = { "favorite_item_${it.id}" }, - ) { + key = { _, it -> "favorite_item_${it.id}" }, + ) { index, it -> VaultVerificationCodeItem( authCode = it.authCode, primaryLabel = it.title, @@ -475,18 +457,10 @@ private fun ItemListingContent( }, showMoveToBitwarden = it.showMoveToBitwarden, allowLongPress = it.allowLongPressActions, - modifier = Modifier.fillMaxWidth(), - ) - } - - item(key = "favorites_divider") { - HorizontalDivider( - thickness = 1.dp, - color = MaterialTheme.colorScheme.outlineVariant, + cardStyle = state.favoriteItems.toListItemCardStyle(index = index), modifier = Modifier - .fillMaxWidth() - .padding(all = 16.dp) - .animateItem(), + .standardHorizontalMargin() + .fillMaxWidth(), ) } } @@ -511,16 +485,17 @@ private fun ItemListingContent( }, modifier = Modifier .fillMaxWidth() + .standardHorizontalMargin() .animateItem(), ) } } if (isLocalHeaderExpanded) { - items( + itemsIndexed( items = state.itemList, - key = { "local_item_${it.id}" }, - ) { + key = { _, it -> "local_item_${it.id}" }, + ) { index, it -> VaultVerificationCodeItem( authCode = it.authCode, primaryLabel = it.title, @@ -535,7 +510,9 @@ private fun ItemListingContent( }, showMoveToBitwarden = it.showMoveToBitwarden, allowLongPress = it.allowLongPressActions, + cardStyle = state.itemList.toListItemCardStyle(index = index), modifier = Modifier + .standardHorizontalMargin() .fillMaxWidth() .animateItem(), ) @@ -561,14 +538,15 @@ private fun ItemListingContent( }, modifier = Modifier .fillMaxWidth() + .standardHorizontalMargin() .animateItem(), ) } if (section.isExpanded) { - items( + itemsIndexed( items = section.codes, - key = { code -> "code_${code.id}" }, - ) { + key = { _, code -> "code_${code.id}" }, + ) { index, it -> VaultVerificationCodeItem( authCode = it.authCode, primaryLabel = it.title, @@ -583,7 +561,9 @@ private fun ItemListingContent( }, showMoveToBitwarden = it.showMoveToBitwarden, allowLongPress = it.allowLongPressActions, + cardStyle = section.codes.toListItemCardStyle(index = index), modifier = Modifier + .standardHorizontalMargin() .fillMaxWidth() .animateItem(), ) @@ -596,9 +576,10 @@ private fun ItemListingContent( item(key = "shared_codes_error") { Text( text = stringResource(BitwardenString.shared_codes_error), - color = MaterialTheme.colorScheme.onSurfaceVariant, - style = MaterialTheme.typography.bodySmall, + color = BitwardenTheme.colorScheme.text.secondary, + style = BitwardenTheme.typography.bodySmall, modifier = Modifier + .standardHorizontalMargin() .padding(horizontal = 16.dp) .animateItem(), ) @@ -609,7 +590,8 @@ private fun ItemListingContent( // Add a spacer item to prevent the FAB from hiding verification codes at the // bottom of the list item { - Spacer(Modifier.height(72.dp)) + Spacer(modifier = Modifier.height(height = 88.dp)) + Spacer(modifier = Modifier.navigationBarsPadding()) } } } @@ -642,59 +624,49 @@ fun EmptyItemListingContent( .fillMaxSize() .nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { - AuthenticatorTopAppBar( + BitwardenTopAppBar( title = stringResource(id = BitwardenString.verification_codes), scrollBehavior = scrollBehavior, navigationIcon = null, - actions = { }, ) }, floatingActionButton = { - ExpandableFloatingActionButton( - modifier = Modifier - .semantics { testTag = "AddItemButton" } - .padding(bottom = 16.dp), - label = BitwardenString.add_item.asText(), - items = listOf( + BitwardenExpandableFloatingActionButton( + modifier = Modifier.testTag("AddItemButton"), + items = persistentListOf( ItemListingExpandableFabAction.ScanQrCode( label = BitwardenString.scan_a_qr_code.asText(), - icon = IconResource( - iconPainter = painterResource(id = BitwardenDrawable.ic_camera), - contentDescription = stringResource( - id = BitwardenString.scan_a_qr_code, - ), + icon = IconData.Local( + iconRes = BitwardenDrawable.ic_camera_small, + contentDescription = BitwardenString.scan_a_qr_code.asText(), testTag = "ScanQRCodeButton", ), onScanQrCodeClick = onScanQrCodeClick, ), ItemListingExpandableFabAction.EnterSetupKey( label = BitwardenString.enter_key_manually.asText(), - icon = IconResource( - iconPainter = painterResource(id = BitwardenDrawable.ic_keyboard), - contentDescription = stringResource( - id = BitwardenString.enter_key_manually, - ), + icon = IconData.Local( + iconRes = BitwardenDrawable.ic_lock_encrypted_small, + contentDescription = BitwardenString.enter_key_manually.asText(), testTag = "EnterSetupKeyButton", ), onEnterSetupKeyClick = onEnterSetupKeyClick, ), ), expandableFabIcon = ExpandableFabIcon( - iconData = IconResource( - iconPainter = painterResource(id = BitwardenDrawable.ic_plus), - contentDescription = stringResource(id = BitwardenString.add_item), + icon = IconData.Local( + iconRes = BitwardenDrawable.ic_plus, + contentDescription = BitwardenString.add_item.asText(), testTag = "AddItemButton", ), iconRotation = 45f, ), ) }, - floatingActionButtonPosition = FabPosition.EndOverlay, - ) { innerPadding -> + ) { Column( modifier = modifier .fillMaxSize() - .padding(paddingValues = innerPadding) .verticalScroll(rememberScrollState()), verticalArrangement = when (actionCardState) { ItemListingState.ActionCardState.None -> Arrangement.Center @@ -709,19 +681,15 @@ fun EmptyItemListingContent( onSyncWithBitwardenClick = onSyncWithBitwardenClick, onSyncWithBitwardenDismissClick = onDismissSyncWithBitwardenClick, onSyncLearnMoreClick = onSyncLearnMoreClick, + modifier = Modifier + .standardHorizontalMargin() + .padding(top = 12.dp, bottom = 16.dp), ) - // Add a spacer if an action card is showing: - when (actionCardState) { - ItemListingState.ActionCardState.None -> Unit - ItemListingState.ActionCardState.DownloadBitwardenApp, - ItemListingState.ActionCardState.SyncWithBitwarden, - -> Spacer(Modifier.height(16.dp)) - } Column( modifier = modifier .fillMaxSize() - .padding(horizontal = 16.dp), + .standardHorizontalMargin(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, ) { @@ -744,7 +712,7 @@ fun EmptyItemListingContent( Spacer(modifier = Modifier.height(16.dp)) Text( text = stringResource(id = BitwardenString.you_dont_have_items_to_display), - style = Typography.titleMedium, + style = BitwardenTheme.typography.titleMedium, ) Spacer(modifier = Modifier.height(16.dp)) @@ -754,13 +722,16 @@ fun EmptyItemListingContent( ) Spacer(modifier = Modifier.height(16.dp)) - AuthenticatorFilledTonalButton( + BitwardenFilledButton( modifier = Modifier - .semantics { testTag = "AddCodeButton" } + .testTag("AddCodeButton") .fillMaxWidth(), label = stringResource(BitwardenString.add_code), onClick = onAddCodeClick, ) + + Spacer(modifier = Modifier.height(height = 12.dp)) + Spacer(modifier = Modifier.navigationBarsPadding()) } } } @@ -771,26 +742,20 @@ private fun DownloadBitwardenActionCard( modifier: Modifier = Modifier, onDismissClick: () -> Unit, onDownloadBitwardenClick: () -> Unit, -) = AuthenticatorActionCard( +) = BitwardenActionCard( modifier = modifier, - actionIcon = rememberVectorPainter(BitwardenDrawable.ic_shield), - actionText = stringResource(BitwardenString.download_bitwarden_card_message), - callToActionText = stringResource(BitwardenString.download_now), - titleText = stringResource(BitwardenString.download_bitwarden_card_title), - onCardClicked = onDownloadBitwardenClick, - trailingContent = { - IconButton( - onClick = onDismissClick, - ) { - Icon( - painter = painterResource(id = BitwardenDrawable.ic_close), - contentDescription = stringResource(id = BitwardenString.close), - tint = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier - .size(24.dp), - ) - } + cardSubtitle = stringResource(BitwardenString.download_bitwarden_card_message), + actionText = stringResource(BitwardenString.download_now), + cardTitle = stringResource(BitwardenString.download_bitwarden_card_title), + onActionClick = onDownloadBitwardenClick, + leadingContent = { + Icon( + painter = rememberVectorPainter(BitwardenDrawable.ic_shield), + contentDescription = null, + tint = BitwardenTheme.colorScheme.icon.secondary, + ) }, + onDismissClick = onDismissClick, ) @Suppress("LongMethod") @@ -803,12 +768,10 @@ private fun SyncWithBitwardenActionCard( ) { Card( modifier = modifier, - shape = RoundedCornerShape(size = 16.dp), - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surfaceContainer, - disabledContainerColor = MaterialTheme.colorScheme.surfaceContainer, - ), + shape = BitwardenTheme.shapes.actionCard, + colors = bitwardenCardColors(), elevation = CardDefaults.elevatedCardElevation(), + border = BorderStroke(width = 1.dp, color = BitwardenTheme.colorScheme.stroke.border), ) { Spacer(Modifier.height(height = 4.dp)) Row(modifier = Modifier.fillMaxWidth()) { @@ -820,14 +783,14 @@ private fun SyncWithBitwardenActionCard( Icon( painter = rememberVectorPainter(id = BitwardenDrawable.ic_shield), contentDescription = null, - tint = MaterialTheme.colorScheme.primary, + tint = BitwardenTheme.colorScheme.icon.secondary, modifier = Modifier.size(size = 20.dp), ) Spacer(Modifier.width(width = 16.dp)) Text( text = stringResource(id = BitwardenString.sync_with_the_bitwarden_app), - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurface, + style = BitwardenTheme.typography.bodyLarge, + color = BitwardenTheme.colorScheme.text.primary, ) } Spacer(Modifier.weight(weight = 1f)) @@ -836,7 +799,7 @@ private fun SyncWithBitwardenActionCard( Icon( painter = painterResource(id = BitwardenDrawable.ic_close), contentDescription = stringResource(id = BitwardenString.close), - tint = MaterialTheme.colorScheme.onSurfaceVariant, + tint = BitwardenTheme.colorScheme.icon.primary, modifier = Modifier.size(size = 24.dp), ) } @@ -844,22 +807,22 @@ private fun SyncWithBitwardenActionCard( } Text( text = stringResource(id = BitwardenString.sync_with_bitwarden_action_card_message), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, + style = BitwardenTheme.typography.bodyMedium, + color = BitwardenTheme.colorScheme.text.primary, modifier = Modifier .padding(horizontal = 16.dp) .padding(start = 36.dp, end = 48.dp) .fillMaxWidth(), ) Spacer(Modifier.height(height = 16.dp)) - AuthenticatorFilledButton( + BitwardenFilledButton( label = stringResource(id = BitwardenString.take_me_to_app_settings), onClick = onAppSettingsClick, modifier = Modifier .padding(horizontal = 16.dp) .fillMaxWidth(), ) - AuthenticatorTextButton( + BitwardenTextButton( label = stringResource(id = BitwardenString.learn_more), onClick = onLearnMoreClick, modifier = Modifier diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/VaultVerificationCodeItem.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/VaultVerificationCodeItem.kt index e3937d7cd08..bcd4f6a218d 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/VaultVerificationCodeItem.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/VaultVerificationCodeItem.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.padding @@ -14,7 +15,6 @@ import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.ripple import androidx.compose.runtime.Composable @@ -24,20 +24,22 @@ 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.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.testTag import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.model.VaultDropdownMenuAction -import com.bitwarden.authenticator.ui.platform.components.indicator.AuthenticatorCircularCountdownIndicator -import com.bitwarden.authenticator.ui.platform.theme.AuthenticatorTheme +import com.bitwarden.ui.platform.base.util.cardBackground +import com.bitwarden.ui.platform.base.util.cardPadding import com.bitwarden.ui.platform.components.icon.BitwardenIcon import com.bitwarden.ui.platform.components.icon.model.IconData +import com.bitwarden.ui.platform.components.indicator.BitwardenCircularCountdownIndicator +import com.bitwarden.ui.platform.components.model.CardStyle import com.bitwarden.ui.platform.resource.BitwardenDrawable import com.bitwarden.ui.platform.resource.BitwardenString +import com.bitwarden.ui.platform.theme.BitwardenTheme /** * The verification code item displayed to the user. @@ -69,41 +71,46 @@ fun VaultVerificationCodeItem( onDropdownMenuClick: (VaultDropdownMenuAction) -> Unit, allowLongPress: Boolean, showMoveToBitwarden: Boolean, + cardStyle: CardStyle, modifier: Modifier = Modifier, ) { var shouldShowDropdownMenu by remember { mutableStateOf(value = false) } Box(modifier = modifier) { Row( modifier = Modifier - .semantics { testTag = "Item" } + .testTag(tag = "Item") + .defaultMinSize(minHeight = 60.dp) + .cardBackground(cardStyle = cardStyle) .then( if (allowLongPress) { Modifier.combinedClickable( interactionSource = remember { MutableInteractionSource() }, - indication = ripple(color = MaterialTheme.colorScheme.primary), + indication = ripple( + color = BitwardenTheme.colorScheme.background.pressed, + ), onClick = onItemClick, onLongClick = { shouldShowDropdownMenu = true }, ) } else { Modifier.clickable( interactionSource = remember { MutableInteractionSource() }, - indication = ripple(color = MaterialTheme.colorScheme.primary), + indication = ripple( + color = BitwardenTheme.colorScheme.background.pressed, + ), onClick = onItemClick, ) }, ) - .defaultMinSize(minHeight = 72.dp) - .padding( - vertical = 8.dp, - horizontal = 16.dp, - ) - .then(modifier), + .cardPadding( + cardStyle = cardStyle, + paddingValues = PaddingValues(vertical = 8.dp, horizontal = 16.dp), + ), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp), ) { BitwardenIcon( iconData = startIcon, - tint = MaterialTheme.colorScheme.onSurface, + tint = BitwardenTheme.colorScheme.icon.primary, modifier = Modifier.size(24.dp), ) @@ -114,10 +121,10 @@ fun VaultVerificationCodeItem( ) { if (!primaryLabel.isNullOrEmpty()) { Text( - modifier = Modifier.semantics { testTag = "Name" }, + modifier = Modifier.testTag("Name"), text = primaryLabel, - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurface, + style = BitwardenTheme.typography.bodyLarge, + color = BitwardenTheme.colorScheme.text.primary, maxLines = 1, overflow = TextOverflow.Ellipsis, ) @@ -125,34 +132,36 @@ fun VaultVerificationCodeItem( if (!secondaryLabel.isNullOrEmpty()) { Text( - modifier = Modifier.semantics { testTag = "Username" }, + modifier = Modifier.testTag("Username"), text = secondaryLabel, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, + style = BitwardenTheme.typography.bodyMedium, + color = BitwardenTheme.colorScheme.text.secondary, maxLines = 1, overflow = TextOverflow.Ellipsis, ) } } - AuthenticatorCircularCountdownIndicator( - modifier = Modifier.semantics { testTag = "CircularCountDown" }, + BitwardenCircularCountdownIndicator( + modifier = Modifier.testTag("CircularCountDown"), timeLeftSeconds = timeLeftSeconds, periodSeconds = periodSeconds, alertThresholdSeconds = alertThresholdSeconds, ) Text( - modifier = Modifier.semantics { testTag = "AuthCode" }, + modifier = Modifier.testTag("AuthCode"), text = authCode.chunked(3).joinToString(" "), - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurfaceVariant, + style = BitwardenTheme.typography.sensitiveInfoSmall, + color = BitwardenTheme.colorScheme.text.primary, ) } DropdownMenu( expanded = shouldShowDropdownMenu, onDismissRequest = { shouldShowDropdownMenu = false }, + shape = BitwardenTheme.shapes.menu, + containerColor = BitwardenTheme.colorScheme.background.primary, ) { DropdownMenuItem( text = { @@ -229,7 +238,7 @@ fun VaultVerificationCodeItem( @Preview(showBackground = true) @Composable private fun VerificationCodeItem_preview() { - AuthenticatorTheme { + BitwardenTheme { VaultVerificationCodeItem( authCode = "1234567890".chunked(3).joinToString(" "), primaryLabel = "Issuer, AKA Name", @@ -243,6 +252,7 @@ private fun VerificationCodeItem_preview() { allowLongPress = true, modifier = Modifier.padding(horizontal = 16.dp), showMoveToBitwarden = true, + cardStyle = CardStyle.Full, ) } } diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/model/ItemListingExpandableFabAction.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/model/ItemListingExpandableFabAction.kt index d9fd1ba8f1c..20d05982535 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/model/ItemListingExpandableFabAction.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/model/ItemListingExpandableFabAction.kt @@ -1,16 +1,16 @@ package com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.model import androidx.compose.material3.ExtendedFloatingActionButton -import com.bitwarden.authenticator.ui.platform.components.fab.ExpandableFabOption -import com.bitwarden.authenticator.ui.platform.components.model.IconResource +import com.bitwarden.ui.platform.components.fab.ExpandableFabOption +import com.bitwarden.ui.platform.components.icon.model.IconData import com.bitwarden.ui.util.Text /** * Models [ExpandableFabOption]s that can be triggered by the [ExtendedFloatingActionButton]. */ sealed class ItemListingExpandableFabAction( - label: Text?, - icon: IconResource, + label: Text, + icon: IconData.Local, onFabOptionClick: () -> Unit, ) : ExpandableFabOption(label, icon, onFabOptionClick) { @@ -18,25 +18,25 @@ sealed class ItemListingExpandableFabAction( * Indicates the Scan QR code button was clicked. */ class ScanQrCode( - label: Text?, - icon: IconResource, + label: Text, + icon: IconData.Local, onScanQrCodeClick: () -> Unit, ) : ItemListingExpandableFabAction( - label, - icon, - onScanQrCodeClick, + label = label, + icon = icon, + onFabOptionClick = onScanQrCodeClick, ) /** * Indicates the Enter Key button was clicked. */ class EnterSetupKey( - label: Text?, - icon: IconResource, + label: Text, + icon: IconData.Local, onEnterSetupKeyClick: () -> Unit, ) : ItemListingExpandableFabAction( - label, - icon, - onEnterSetupKeyClick, + label = label, + icon = icon, + onFabOptionClick = onEnterSetupKeyClick, ) } diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryScreen.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryScreen.kt index f9201092cb1..fa4fe385a39 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryScreen.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryScreen.kt @@ -10,11 +10,10 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarState @@ -24,38 +23,35 @@ 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.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.CustomAccessibilityAction -import androidx.compose.ui.semantics.customActions -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.text.input.KeyboardCapitalization +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.core.net.toUri import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.bitwarden.authenticator.ui.platform.components.appbar.AuthenticatorTopAppBar -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.dialog.BitwardenLoadingDialog -import com.bitwarden.authenticator.ui.platform.components.dialog.BitwardenTwoButtonDialog -import com.bitwarden.authenticator.ui.platform.components.dialog.LoadingDialogState -import com.bitwarden.authenticator.ui.platform.components.field.BitwardenPasswordField -import com.bitwarden.authenticator.ui.platform.components.field.BitwardenTextField -import com.bitwarden.authenticator.ui.platform.components.scaffold.BitwardenScaffold import com.bitwarden.authenticator.ui.platform.composition.LocalPermissionsManager import com.bitwarden.authenticator.ui.platform.manager.permissions.PermissionsManager import com.bitwarden.ui.platform.base.util.EventsEffect -import com.bitwarden.ui.platform.base.util.annotatedStringResource -import com.bitwarden.ui.platform.base.util.spanStyleOf import com.bitwarden.ui.platform.base.util.standardHorizontalMargin +import com.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar +import com.bitwarden.ui.platform.components.button.BitwardenTextButton +import com.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog +import com.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog +import com.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialog +import com.bitwarden.ui.platform.components.field.BitwardenPasswordField +import com.bitwarden.ui.platform.components.field.BitwardenTextField +import com.bitwarden.ui.platform.components.model.CardStyle +import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold import com.bitwarden.ui.platform.composition.LocalIntentManager import com.bitwarden.ui.platform.manager.IntentManager import com.bitwarden.ui.platform.resource.BitwardenDrawable import com.bitwarden.ui.platform.resource.BitwardenString +import com.bitwarden.ui.platform.theme.BitwardenTheme /** * The screen to manually add a totp code. @@ -134,7 +130,7 @@ fun ManualCodeEntryScreen( BitwardenScaffold( modifier = Modifier.fillMaxSize(), topBar = { - AuthenticatorTopAppBar( + BitwardenTopAppBar( title = stringResource(id = BitwardenString.create_verification_code), navigationIcon = painterResource(id = BitwardenDrawable.ic_close), navigationIconContentDescription = stringResource(id = BitwardenString.close), @@ -144,7 +140,7 @@ fun ManualCodeEntryScreen( scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()), ) }, - ) { paddingValues -> + ) { ManualCodeEntryContent( state = state, onNameChange = remember(viewModel) { @@ -168,11 +164,11 @@ fun ManualCodeEntryScreen( } } }, - modifier = Modifier.padding(paddingValues = paddingValues), ) } } +@Suppress("LongMethod") @Composable private fun ManualCodeEntryContent( state: ManualCodeEntryState, @@ -184,38 +180,40 @@ private fun ManualCodeEntryContent( modifier: Modifier = Modifier, ) { Column(modifier = modifier.verticalScroll(state = rememberScrollState())) { + Spacer(modifier = Modifier.height(height = 24.dp)) Text( text = stringResource(id = BitwardenString.enter_key_manually), - style = MaterialTheme.typography.titleMedium, + style = BitwardenTheme.typography.titleMedium, + textAlign = TextAlign.Center, modifier = Modifier .fillMaxWidth() .standardHorizontalMargin(), ) - Spacer(modifier = Modifier.height(height = 8.dp)) + Spacer(modifier = Modifier.height(height = 24.dp)) BitwardenTextField( label = stringResource(id = BitwardenString.name), value = state.issuer, onValueChange = onNameChange, + cardStyle = CardStyle.Top(), modifier = Modifier .testTag(tag = "NameTextField") .fillMaxWidth() .standardHorizontalMargin(), ) - Spacer(modifier = Modifier.height(height = 8.dp)) BitwardenPasswordField( singleLine = false, label = stringResource(id = BitwardenString.key), value = state.code, onValueChange = onKeyChange, - capitalization = KeyboardCapitalization.Characters, + cardStyle = CardStyle.Bottom, modifier = Modifier .testTag(tag = "KeyTextField") .fillMaxWidth() .standardHorizontalMargin(), ) - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(24.dp)) SaveManualCodeButtons( state = state.buttonState, onSaveLocallyClick = onSaveLocallyClick, @@ -224,57 +222,29 @@ private fun ManualCodeEntryContent( .standardHorizontalMargin() .fillMaxWidth(), ) - Spacer(modifier = Modifier.height(height = 8.dp)) + Spacer(modifier = Modifier.height(height = 24.dp)) Text( text = stringResource(id = BitwardenString.cannot_add_authenticator_key), - style = MaterialTheme.typography.bodyMedium, + color = BitwardenTheme.colorScheme.text.secondary, + style = BitwardenTheme.typography.bodyMedium, + textAlign = TextAlign.Center, modifier = Modifier .fillMaxWidth() .standardHorizontalMargin(), ) - Spacer(modifier = Modifier.height(height = 8.dp)) - ScanQrCodeText( + BitwardenTextButton( + label = stringResource(id = BitwardenString.scan_qr_code), onClick = onScanQrCodeClick, - modifier = Modifier.standardHorizontalMargin(), + modifier = Modifier + .wrapContentWidth() + .align(alignment = Alignment.CenterHorizontally) + .standardHorizontalMargin(), ) Spacer(modifier = Modifier.height(height = 16.dp)) Spacer(modifier = Modifier.navigationBarsPadding()) } } -@Composable -private fun ScanQrCodeText( - onClick: () -> Unit, - modifier: Modifier = Modifier, -) { - val accessibilityString = stringResource(id = BitwardenString.scan_qr_code) - Text( - text = annotatedStringResource( - id = BitwardenString.scan_qr_code, - emphasisHighlightStyle = spanStyleOf( - color = MaterialTheme.colorScheme.primary, - textStyle = MaterialTheme.typography.bodyMedium, - ), - onAnnotationClick = { - when (it) { - "scanQrCode" -> onClick() - } - }, - ), - modifier = modifier.semantics { - customActions = listOf( - CustomAccessibilityAction( - label = accessibilityString, - action = { - onClick() - true - }, - ), - ) - }, - ) -} - @Composable private fun ManualCodeEntryDialogs( dialog: ManualCodeEntryState.DialogState?, @@ -283,16 +253,14 @@ private fun ManualCodeEntryDialogs( when (val dialogString = dialog) { is ManualCodeEntryState.DialogState.Error -> { BitwardenBasicDialog( - visibilityState = BasicDialogState.Shown( - title = dialogString.title, - message = dialogString.message, - ), + title = dialogString.title?.invoke(), + message = dialogString.message(), onDismissRequest = onDismissRequest, ) } is ManualCodeEntryState.DialogState.Loading -> { - BitwardenLoadingDialog(visibilityState = LoadingDialogState.Shown(dialog.message)) + BitwardenLoadingDialog(text = dialog.message()) } null -> Unit diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/SaveManualCodeButtons.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/SaveManualCodeButtons.kt index 8a3dc421fa9..109a105b01b 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/SaveManualCodeButtons.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/SaveManualCodeButtons.kt @@ -1,14 +1,16 @@ package com.bitwarden.authenticator.ui.authenticator.feature.manualcodeentry import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource -import com.bitwarden.authenticator.ui.platform.components.button.AuthenticatorFilledButton -import com.bitwarden.authenticator.ui.platform.components.button.AuthenticatorFilledTonalButton -import com.bitwarden.authenticator.ui.platform.components.button.AuthenticatorOutlinedButton +import androidx.compose.ui.unit.dp +import com.bitwarden.ui.platform.components.button.BitwardenFilledButton +import com.bitwarden.ui.platform.components.button.BitwardenOutlinedButton import com.bitwarden.ui.platform.resource.BitwardenString /** @@ -28,7 +30,7 @@ fun SaveManualCodeButtons( ) { when (state) { ManualCodeEntryState.ButtonState.LocalOnly -> { - AuthenticatorFilledTonalButton( + BitwardenFilledButton( label = stringResource(id = BitwardenString.add_code), onClick = onSaveLocallyClick, modifier = modifier.testTag(tag = "AddCodeButton"), @@ -37,12 +39,13 @@ fun SaveManualCodeButtons( ManualCodeEntryState.ButtonState.SaveLocallyPrimary -> { Column(modifier = modifier) { - AuthenticatorFilledButton( + BitwardenFilledButton( label = stringResource(id = BitwardenString.save_here), onClick = onSaveLocallyClick, modifier = Modifier.fillMaxWidth(), ) - AuthenticatorOutlinedButton( + Spacer(modifier = Modifier.height(height = 8.dp)) + BitwardenOutlinedButton( label = stringResource(BitwardenString.save_to_bitwarden), onClick = onSaveToBitwardenClick, modifier = Modifier.fillMaxWidth(), @@ -52,12 +55,13 @@ fun SaveManualCodeButtons( ManualCodeEntryState.ButtonState.SaveToBitwardenPrimary -> { Column(modifier = modifier) { - AuthenticatorFilledButton( + BitwardenFilledButton( label = stringResource(id = BitwardenString.save_to_bitwarden), onClick = onSaveToBitwardenClick, modifier = Modifier.fillMaxWidth(), ) - AuthenticatorOutlinedButton( + Spacer(modifier = Modifier.height(height = 8.dp)) + BitwardenOutlinedButton( label = stringResource(BitwardenString.save_here), onClick = onSaveLocallyClick, modifier = Modifier.fillMaxWidth(), diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/navbar/AuthenticatorNavBarScreen.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/navbar/AuthenticatorNavBarScreen.kt index 4409149fabf..04a9f9387ee 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/navbar/AuthenticatorNavBarScreen.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/navbar/AuthenticatorNavBarScreen.kt @@ -1,40 +1,17 @@ package com.bitwarden.authenticator.ui.authenticator.feature.navbar import android.os.Parcelable -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.consumeWindowInsets -import androidx.compose.foundation.layout.exclude -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.ime -import androidx.compose.foundation.layout.navigationBars -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.statusBars -import androidx.compose.material3.BottomAppBar import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.NavigationBarItem -import androidx.compose.material3.NavigationBarItemDefaults -import androidx.compose.material3.ScaffoldDefaults -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.remember +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.testTag -import androidx.compose.ui.text.style.TextOverflow import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.navigation.NavBackStackEntry import androidx.navigation.NavController -import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavHostController import androidx.navigation.NavOptions @@ -46,17 +23,19 @@ import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.ItemList import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.ItemListingRoute import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.itemListingGraph import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.navigateToItemListGraph -import com.bitwarden.authenticator.ui.platform.components.scaffold.BitwardenScaffold -import com.bitwarden.authenticator.ui.platform.components.scrim.BitwardenAnimatedScrim import com.bitwarden.authenticator.ui.platform.feature.settings.SettingsGraphRoute +import com.bitwarden.authenticator.ui.platform.feature.settings.SettingsRoute import com.bitwarden.authenticator.ui.platform.feature.settings.navigateToSettingsGraph import com.bitwarden.ui.platform.base.util.EventsEffect -import com.bitwarden.ui.platform.base.util.max -import com.bitwarden.ui.platform.base.util.toDp +import com.bitwarden.ui.platform.components.navigation.model.NavigationItem +import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold +import com.bitwarden.ui.platform.components.scaffold.model.ScaffoldNavigationData import com.bitwarden.ui.platform.resource.BitwardenDrawable import com.bitwarden.ui.platform.resource.BitwardenString import com.bitwarden.ui.platform.theme.RootTransitionProviders import com.bitwarden.ui.platform.util.toObjectNavigationRoute +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.parcelize.Parcelize @@ -135,39 +114,30 @@ private fun AuthenticatorNavBarScaffold( navigateToImport: () -> Unit, navigateToTutorial: () -> Unit, ) { + var shouldDimNavBar by rememberSaveable { mutableStateOf(value = false) } + + // This scaffold will host screens that contain top bars while not hosting one itself. + // We need to ignore the all insets here and let the content screens handle it themselves. + val navBackStackEntry by navController.currentBackStackEntryAsState() BitwardenScaffold( - contentWindowInsets = ScaffoldDefaults.contentWindowInsets.exclude(WindowInsets.statusBars), - bottomBar = { - Box { - var appBarHeightPx by remember { mutableIntStateOf(0) } - AuthenticatorBottomAppBar( - modifier = Modifier - .onGloballyPositioned { - appBarHeightPx = it.size.height - }, - navController = navController, - verificationCodesTabClickedAction = verificationTabClickedAction, - settingsTabClickedAction = settingsTabClickedAction, - ) - BitwardenAnimatedScrim( - isVisible = false, - onClick = { - // Do nothing - }, - modifier = Modifier - .fillMaxWidth() - .height(appBarHeightPx.toDp()), - ) - } - }, - ) { innerPadding -> + contentWindowInsets = WindowInsets(), + navigationData = ScaffoldNavigationData( + navigationItems = AuthenticatorNavBarTab.navigationItems, + selectedNavigationItem = AuthenticatorNavBarTab + .navigationItems + .find { navBackStackEntry.isCurrentRoute(route = it.graphRoute) }, + onNavigationClick = { navigationItem -> + when (navigationItem) { + AuthenticatorNavBarTab.VerificationCodes -> verificationTabClickedAction() + AuthenticatorNavBarTab.Settings -> settingsTabClickedAction() + } + }, + shouldDimNavigation = shouldDimNavBar, + ), + ) { NavHost( navController = navController, startDestination = ItemListingGraphRoute, - modifier = Modifier - .consumeWindowInsets(WindowInsets.navigationBars) - .consumeWindowInsets(WindowInsets.ime) - .padding(innerPadding.max(WindowInsets.ime)), enterTransition = RootTransitionProviders.Enter.fadeIn, exitTransition = RootTransitionProviders.Exit.fadeOut, popEnterTransition = RootTransitionProviders.Enter.fadeIn, @@ -188,76 +158,6 @@ private fun AuthenticatorNavBarScaffold( } } -@Suppress("LongMethod") -@Composable -private fun AuthenticatorBottomAppBar( - navController: NavController, - verificationCodesTabClickedAction: () -> Unit, - settingsTabClickedAction: () -> Unit, - modifier: Modifier = Modifier, -) { - BottomAppBar( - containerColor = MaterialTheme.colorScheme.surfaceContainer, - modifier = modifier, - ) { - val destinations = listOf( - AuthenticatorNavBarTab.VerificationCodes, - AuthenticatorNavBarTab.Settings, - ) - val navBackStackEntry by navController.currentBackStackEntryAsState() - val currentDestination = navBackStackEntry?.destination - destinations.forEach { destination -> - val isSelected = currentDestination?.hierarchy?.any { - it.route == destination.route - } == true - - NavigationBarItem( - icon = { - Icon( - painter = painterResource( - id = if (isSelected) { - destination.iconResSelected - } else { - destination.iconRes - }, - ), - contentDescription = stringResource( - id = destination.contentDescriptionRes, - ), - ) - }, - label = { - Text( - text = stringResource(id = destination.labelRes), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - }, - selected = isSelected, - onClick = { - when (destination) { - AuthenticatorNavBarTab.VerificationCodes -> { - verificationCodesTabClickedAction() - } - - AuthenticatorNavBarTab.Settings -> { - settingsTabClickedAction() - } - } - }, - colors = NavigationBarItemDefaults.colors( - indicatorColor = MaterialTheme.colorScheme.secondaryContainer, - selectedIconColor = MaterialTheme.colorScheme.onSecondaryContainer, - unselectedIconColor = MaterialTheme.colorScheme.onSurface, - selectedTextColor = MaterialTheme.colorScheme.onSecondaryContainer, - unselectedTextColor = MaterialTheme.colorScheme.onSurface, - ), - modifier = Modifier.semantics { testTag = destination.testTag }, - ) - } - } -} - /** * Represents the different tabs available in the navigation bar * for the authenticator screens. @@ -271,36 +171,17 @@ private fun AuthenticatorBottomAppBar( * @property iconResSelected The resource ID for the icon representing the tab when it's selected. */ @Parcelize -private sealed class AuthenticatorNavBarTab : Parcelable { - /** - * The resource ID for the icon representing the tab when it is selected. - */ - abstract val iconResSelected: Int - - /** - * Resource id for the icon representing the tab. - */ - abstract val iconRes: Int - - /** - * Resource id for the label describing the tab. - */ - abstract val labelRes: Int - - /** - * Resource id for the content description describing the tab. - */ - abstract val contentDescriptionRes: Int - - /** - * Route of the tab. - */ - abstract val route: String - - /** - * The test tag of the tab. - */ - abstract val testTag: String +private sealed class AuthenticatorNavBarTab : NavigationItem, Parcelable { + + companion object { + /** + * The list of navigation tabs available in the authenticator. + */ + val navigationItems: ImmutableList = persistentListOf( + VerificationCodes, + Settings, + ) + } /** * Show the Verification Codes screen. @@ -311,8 +192,10 @@ private sealed class AuthenticatorNavBarTab : Parcelable { override val iconRes get() = BitwardenDrawable.ic_verification_codes override val labelRes get() = BitwardenString.verification_codes override val contentDescriptionRes get() = BitwardenString.verification_codes - override val route get() = ItemListingRoute.toObjectNavigationRoute() + override val graphRoute get() = ItemListingGraphRoute + override val startDestinationRoute get() = ItemListingRoute override val testTag get() = "VerificationCodesTab" + override val notificationCount: Int get() = 0 } /** @@ -320,12 +203,14 @@ private sealed class AuthenticatorNavBarTab : Parcelable { */ @Parcelize data object Settings : AuthenticatorNavBarTab() { - override val iconResSelected get() = BitwardenDrawable.ic_settings_solid + override val iconResSelected get() = BitwardenDrawable.ic_settings_filled override val iconRes get() = BitwardenDrawable.ic_settings override val labelRes get() = BitwardenString.settings override val contentDescriptionRes get() = BitwardenString.settings - override val route get() = SettingsGraphRoute.toObjectNavigationRoute() + override val graphRoute get() = SettingsGraphRoute + override val startDestinationRoute get() = SettingsRoute override val testTag get() = "SettingsTab" + override val notificationCount: Int get() = 0 } } @@ -340,3 +225,12 @@ private fun NavController.authenticatorNavBarScreenNavOptions(): NavOptions = launchSingleTop = true restoreState = true } + +/** + * Determine if the current destination is the same as the given tab. + */ +private fun NavBackStackEntry?.isCurrentRoute(route: Any): Boolean = + this + ?.destination + ?.parent + ?.route == route.toObjectNavigationRoute() diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/ChooseSaveLocationDialog.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/ChooseSaveLocationDialog.kt index 69a2b4b7d13..9768a984e47 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/ChooseSaveLocationDialog.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/ChooseSaveLocationDialog.kt @@ -10,8 +10,6 @@ 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 @@ -25,11 +23,14 @@ 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.ui.platform.components.button.AuthenticatorTextButton -import com.bitwarden.authenticator.ui.platform.components.toggle.BitwardenWideSwitch +import com.bitwarden.ui.platform.base.util.standardHorizontalMargin +import com.bitwarden.ui.platform.components.button.BitwardenTextButton import com.bitwarden.ui.platform.components.dialog.util.maxDialogHeight import com.bitwarden.ui.platform.components.dialog.util.maxDialogWidth +import com.bitwarden.ui.platform.components.model.CardStyle +import com.bitwarden.ui.platform.components.toggle.BitwardenSwitch import com.bitwarden.ui.platform.resource.BitwardenString +import com.bitwarden.ui.platform.theme.BitwardenTheme /** * Displays a dialog asking the user where they would like to save a new QR code. @@ -60,8 +61,8 @@ fun ChooseSaveLocationDialog( max = configuration.maxDialogWidth, ) .background( - color = MaterialTheme.colorScheme.surfaceContainerHigh, - shape = RoundedCornerShape(28.dp), + color = BitwardenTheme.colorScheme.background.primary, + shape = BitwardenTheme.shapes.dialog, ), horizontalAlignment = Alignment.End, ) { @@ -71,8 +72,8 @@ fun ChooseSaveLocationDialog( .padding(horizontal = 24.dp) .fillMaxWidth(), text = stringResource(BitwardenString.verification_code_created), - color = MaterialTheme.colorScheme.onSurface, - style = MaterialTheme.typography.headlineSmall, + color = BitwardenTheme.colorScheme.text.primary, + style = BitwardenTheme.typography.headlineSmall, ) Spacer(modifier = Modifier.height(16.dp)) Text( @@ -81,33 +82,34 @@ fun ChooseSaveLocationDialog( .padding(horizontal = 24.dp) .fillMaxWidth(), text = stringResource(BitwardenString.choose_save_location_message), - color = MaterialTheme.colorScheme.onSurfaceVariant, - style = MaterialTheme.typography.bodyMedium, + color = BitwardenTheme.colorScheme.text.primary, + style = BitwardenTheme.typography.bodyMedium, ) Spacer(Modifier.height(16.dp)) - BitwardenWideSwitch( - modifier = Modifier.padding(horizontal = 16.dp), + BitwardenSwitch( + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), label = stringResource(BitwardenString.save_option_as_default), isChecked = isSaveAsDefaultChecked, onCheckedChange = { isSaveAsDefaultChecked = !isSaveAsDefaultChecked }, + cardStyle = CardStyle.Full, ) Spacer(Modifier.height(16.dp)) FlowRow( horizontalArrangement = Arrangement.End, modifier = Modifier.padding(horizontal = 8.dp), ) { - AuthenticatorTextButton( + BitwardenTextButton( modifier = Modifier .padding(horizontal = 4.dp), label = stringResource(BitwardenString.save_here), - labelTextColor = MaterialTheme.colorScheme.primary, onClick = { onSaveHereClick.invoke(isSaveAsDefaultChecked) }, ) - AuthenticatorTextButton( + BitwardenTextButton( modifier = Modifier .padding(horizontal = 4.dp), label = stringResource(BitwardenString.save_to_bitwarden), - labelTextColor = MaterialTheme.colorScheme.primary, onClick = { onTakeMeToBitwardenClick.invoke(isSaveAsDefaultChecked) }, ) } diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/QrCodeScanScreen.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/QrCodeScanScreen.kt index c80211d4d90..812f904f722 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/QrCodeScanScreen.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/QrCodeScanScreen.kt @@ -21,7 +21,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarState @@ -53,19 +52,17 @@ import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle 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.components.appbar.AuthenticatorTopAppBar -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.util.isPortrait import com.bitwarden.ui.platform.base.util.EventsEffect import com.bitwarden.ui.platform.base.util.annotatedStringResource import com.bitwarden.ui.platform.base.util.spanStyleOf import com.bitwarden.ui.platform.base.util.standardHorizontalMargin +import com.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar +import com.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog +import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold import com.bitwarden.ui.platform.resource.BitwardenDrawable import com.bitwarden.ui.platform.resource.BitwardenString -import com.bitwarden.ui.util.asText +import com.bitwarden.ui.platform.theme.BitwardenTheme import java.util.concurrent.Executors import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine @@ -117,7 +114,7 @@ fun QrCodeScanScreen( BitwardenScaffold( modifier = Modifier.fillMaxSize(), topBar = { - AuthenticatorTopAppBar( + BitwardenTopAppBar( title = stringResource(id = BitwardenString.scan_qr_code), navigationIcon = painterResource(id = BitwardenDrawable.ic_close), navigationIconContentDescription = stringResource(id = BitwardenString.close), @@ -127,24 +124,21 @@ fun QrCodeScanScreen( scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()), ) }, - ) { innerPadding -> + ) { CameraPreview( cameraErrorReceive = remember(viewModel) { { viewModel.trySendAction(QrCodeScanAction.CameraSetupErrorReceive) } }, qrCodeAnalyzer = qrCodeAnalyzer, - modifier = Modifier.padding(innerPadding), ) if (LocalConfiguration.current.isPortrait) { PortraitQRCodeContent( onEnterCodeManuallyClick = onEnterCodeManuallyClick, - modifier = Modifier.padding(paddingValues = innerPadding), ) } else { LandscapeQRCodeContent( onEnterCodeManuallyClick = onEnterCodeManuallyClick, - modifier = Modifier.padding(paddingValues = innerPadding), ) } } @@ -167,10 +161,8 @@ private fun QrCodeScanDialogs( QrCodeScanState.DialogState.SaveToBitwardenError -> { BitwardenBasicDialog( - visibilityState = BasicDialogState.Shown( - title = BitwardenString.something_went_wrong.asText(), - message = BitwardenString.please_try_again.asText(), - ), + title = stringResource(id = BitwardenString.something_went_wrong), + message = stringResource(id = BitwardenString.please_try_again), onDismissRequest = onDismissRequest, ) } @@ -207,7 +199,7 @@ private fun PortraitQRCodeContent( text = stringResource(id = BitwardenString.point_your_camera_at_the_qr_code), textAlign = TextAlign.Center, color = Color.White, - style = MaterialTheme.typography.bodyMedium, + style = BitwardenTheme.typography.bodyMedium, modifier = Modifier.padding(horizontal = 16.dp), ) @@ -248,7 +240,7 @@ private fun LandscapeQRCodeContent( text = stringResource(id = BitwardenString.point_your_camera_at_the_qr_code), textAlign = TextAlign.Center, color = Color.White, - style = MaterialTheme.typography.bodySmall, + style = BitwardenTheme.typography.bodySmall, ) BottomClickableText( @@ -345,7 +337,7 @@ private fun QrCodeSquare( modifier: Modifier = Modifier, squareOutlineSize: Dp, ) { - val color = MaterialTheme.colorScheme.primary + val color = BitwardenTheme.colorScheme.text.primary Box( contentAlignment = Alignment.Center, @@ -443,12 +435,12 @@ private fun BottomClickableText( text = annotatedStringResource( id = BitwardenString.cannot_scan_qr_code_enter_key_manually, linkHighlightStyle = spanStyleOf( - color = LocalNonMaterialColors.current.qrCodeClickableText, - textStyle = MaterialTheme.typography.bodyMedium, + color = BitwardenTheme.colorScheme.text.interaction, + textStyle = BitwardenTheme.typography.bodyMedium, ), style = spanStyleOf( color = Color.White, - textStyle = MaterialTheme.typography.bodyMedium, + textStyle = BitwardenTheme.typography.bodyMedium, ), onAnnotationClick = { when (it) { diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/ItemSearchContent.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/ItemSearchContent.kt index eac297cd4a5..61846943cfb 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/ItemSearchContent.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/ItemSearchContent.kt @@ -7,8 +7,7 @@ import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope -import androidx.compose.foundation.lazy.items -import androidx.compose.material3.MaterialTheme +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -17,8 +16,12 @@ import androidx.compose.ui.unit.dp import com.bitwarden.authenticator.ui.authenticator.feature.model.SharedCodesDisplayState import com.bitwarden.authenticator.ui.authenticator.feature.model.VerificationCodeDisplayItem import com.bitwarden.authenticator.ui.authenticator.feature.search.handlers.SearchHandlers -import com.bitwarden.authenticator.ui.platform.components.header.BitwardenListHeaderText +import com.bitwarden.ui.platform.base.util.standardHorizontalMargin +import com.bitwarden.ui.platform.base.util.toListItemCardStyle +import com.bitwarden.ui.platform.components.header.BitwardenListHeaderText +import com.bitwarden.ui.platform.components.model.CardStyle import com.bitwarden.ui.platform.resource.BitwardenString +import com.bitwarden.ui.platform.theme.BitwardenTheme /** * The content state for the item search screen. @@ -30,32 +33,37 @@ fun ItemSearchContent( modifier: Modifier = Modifier, ) { LazyColumn(modifier = modifier) { + item { + Spacer(Modifier.height(height = 12.dp)) + } + if (viewState.hasLocalAndSharedItems) { item { BitwardenListHeaderText( label = viewState.localListHeader(), modifier = Modifier .fillMaxWidth() + .standardHorizontalMargin() .padding(horizontal = 16.dp), ) + Spacer(Modifier.height(height = 8.dp)) } } - items(viewState.itemList) { + itemsIndexed(viewState.itemList) { index, it -> VaultVerificationCodeItem( displayItem = it, onCopyClick = searchHandlers.onItemClick, + cardStyle = viewState.itemList.toListItemCardStyle(index = index), modifier = Modifier .fillMaxWidth() - // There is some built-in padding to the menu button that - // makes up the visual difference here. - .padding(start = 16.dp, end = 12.dp), + .standardHorizontalMargin(), ) } if (viewState.hasLocalAndSharedItems) { item { - Spacer(Modifier.height(height = 8.dp)) + Spacer(Modifier.height(height = 12.dp)) } } @@ -83,14 +91,17 @@ private fun LazyListScope.sharedCodes( label = section.label(), modifier = Modifier .fillMaxWidth() + .standardHorizontalMargin() .padding(horizontal = 16.dp), ) + Spacer(Modifier.height(height = 12.dp)) } - items(section.codes) { + itemsIndexed(section.codes) { index, it -> VaultVerificationCodeItem( displayItem = it, onCopyClick = onCopyClick, + cardStyle = section.codes.toListItemCardStyle(index = index), modifier = Modifier .fillMaxWidth() // There is some built-in padding to the menu button that @@ -105,8 +116,8 @@ private fun LazyListScope.sharedCodes( item { Text( text = stringResource(BitwardenString.shared_codes_error), - color = MaterialTheme.colorScheme.onSurfaceVariant, - style = MaterialTheme.typography.bodySmall, + color = BitwardenTheme.colorScheme.text.secondary, + style = BitwardenTheme.typography.bodySmall, modifier = Modifier.padding(horizontal = 16.dp), ) } @@ -117,6 +128,7 @@ private fun LazyListScope.sharedCodes( @Composable private fun VaultVerificationCodeItem( displayItem: VerificationCodeDisplayItem, + cardStyle: CardStyle, onCopyClick: (authCode: String) -> Unit, modifier: Modifier = Modifier, ) { @@ -130,6 +142,7 @@ private fun VaultVerificationCodeItem( startIcon = displayItem.startIcon, onCopyClick = { onCopyClick(displayItem.authCode) }, onItemClick = { onCopyClick(displayItem.authCode) }, + cardStyle = cardStyle, modifier = modifier, ) } diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/ItemSearchEmptyContent.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/ItemSearchEmptyContent.kt index 0fea2acbf71..17ae1000c61 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/ItemSearchEmptyContent.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/ItemSearchEmptyContent.kt @@ -6,20 +6,19 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.testTag import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import com.bitwarden.ui.platform.base.util.standardHorizontalMargin import com.bitwarden.ui.platform.resource.BitwardenDrawable +import com.bitwarden.ui.platform.theme.BitwardenTheme /** * The empty state for the item search screen. @@ -37,10 +36,10 @@ fun ItemSearchEmptyContent( Icon( painter = painterResource(id = BitwardenDrawable.ic_search_wide), contentDescription = null, - tint = MaterialTheme.colorScheme.onSurfaceVariant, + tint = BitwardenTheme.colorScheme.icon.primary, modifier = Modifier .size(74.dp) - .padding(horizontal = 16.dp), + .standardHorizontalMargin(), ) Spacer(modifier = Modifier.height(24.dp)) @@ -49,11 +48,11 @@ fun ItemSearchEmptyContent( Text( textAlign = TextAlign.Center, modifier = Modifier - .semantics { testTag = "NoSearchResultsLabel" } + .testTag("NoSearchResultsLabel") .fillMaxWidth() - .padding(horizontal = 16.dp), + .standardHorizontalMargin(), text = it(), - style = MaterialTheme.typography.bodyMedium, + style = BitwardenTheme.typography.bodyMedium, ) } diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/ItemSearchScreen.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/ItemSearchScreen.kt index 615f11ee666..d8c67ea6a07 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/ItemSearchScreen.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/ItemSearchScreen.kt @@ -2,7 +2,6 @@ package com.bitwarden.authenticator.ui.authenticator.feature.search import android.widget.Toast import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarState @@ -12,18 +11,18 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalResources +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.testTag import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.bitwarden.authenticator.ui.authenticator.feature.search.handlers.SearchHandlers -import com.bitwarden.authenticator.ui.platform.components.appbar.AuthenticatorSearchTopAppBar -import com.bitwarden.authenticator.ui.platform.components.scaffold.BitwardenScaffold import com.bitwarden.ui.platform.base.util.EventsEffect import com.bitwarden.ui.platform.base.util.bottomDivider +import com.bitwarden.ui.platform.components.appbar.BitwardenSearchTopAppBar import com.bitwarden.ui.platform.components.appbar.NavigationIcon +import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold import com.bitwarden.ui.platform.resource.BitwardenDrawable import com.bitwarden.ui.platform.resource.BitwardenString @@ -40,7 +39,7 @@ fun ItemSearchScreen( val state by viewModel.stateFlow.collectAsStateWithLifecycle() val searchHandlers = remember(viewModel) { SearchHandlers.create(viewModel) } val context = LocalContext.current - val resources = context.resources + val resources = LocalResources.current EventsEffect(viewModel = viewModel) { event -> when (event) { @@ -58,14 +57,15 @@ fun ItemSearchScreen( modifier = Modifier .nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { - AuthenticatorSearchTopAppBar( + BitwardenSearchTopAppBar( modifier = Modifier - .semantics { testTag = "SearchFieldEntry" } + .testTag("SearchFieldEntry") .bottomDivider(), searchTerm = state.searchTerm, placeholder = stringResource(id = BitwardenString.search_codes), onSearchTermChange = searchHandlers.onSearchTermChange, scrollBehavior = scrollBehavior, + clearIconContentDescription = stringResource(id = BitwardenString.clear), navigationIcon = NavigationIcon( navigationIcon = painterResource(id = BitwardenDrawable.ic_back), navigationIconContentDescription = stringResource(id = BitwardenString.back), @@ -73,24 +73,20 @@ fun ItemSearchScreen( ), ) }, - ) { innerPadding -> + ) { when (val viewState = state.viewState) { is ItemSearchState.ViewState.Content -> { ItemSearchContent( viewState = viewState, searchHandlers = searchHandlers, - modifier = Modifier - .fillMaxSize() - .padding(paddingValues = innerPadding), + modifier = Modifier.fillMaxSize(), ) } is ItemSearchState.ViewState.Empty -> { ItemSearchEmptyContent( viewState = viewState, - modifier = Modifier - .fillMaxSize() - .padding(paddingValues = innerPadding), + modifier = Modifier.fillMaxSize(), ) } } diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/VaultVerificationCodeItem.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/VaultVerificationCodeItem.kt index 65c07e8d35a..eac365c012b 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/VaultVerificationCodeItem.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/VaultVerificationCodeItem.kt @@ -1,7 +1,5 @@ package com.bitwarden.authenticator.ui.authenticator.feature.search -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -10,11 +8,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.material3.ripple import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource @@ -22,12 +17,14 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.bitwarden.authenticator.ui.platform.components.indicator.AuthenticatorCircularCountdownIndicator -import com.bitwarden.authenticator.ui.platform.theme.AuthenticatorTheme +import com.bitwarden.ui.platform.base.util.cardStyle import com.bitwarden.ui.platform.components.icon.BitwardenIcon import com.bitwarden.ui.platform.components.icon.model.IconData +import com.bitwarden.ui.platform.components.indicator.BitwardenCircularCountdownIndicator +import com.bitwarden.ui.platform.components.model.CardStyle import com.bitwarden.ui.platform.resource.BitwardenDrawable import com.bitwarden.ui.platform.resource.BitwardenString +import com.bitwarden.ui.platform.theme.BitwardenTheme /** * The verification code item displayed to the user. @@ -53,25 +50,24 @@ fun VaultVerificationCodeItem( startIcon: IconData, onCopyClick: () -> Unit, onItemClick: () -> Unit, + cardStyle: CardStyle, modifier: Modifier = Modifier, supportingLabel: String? = null, ) { Row( - modifier = Modifier - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = ripple(color = MaterialTheme.colorScheme.primary), + modifier = modifier + .defaultMinSize(minHeight = 60.dp) + .cardStyle( + cardStyle = cardStyle, onClick = onItemClick, - ) - .defaultMinSize(minHeight = 72.dp) - .padding(vertical = 8.dp) - .then(modifier), + paddingStart = 16.dp, + ), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp), ) { BitwardenIcon( iconData = startIcon, - tint = MaterialTheme.colorScheme.onSurface, + tint = BitwardenTheme.colorScheme.icon.primary, modifier = Modifier.size(24.dp), ) @@ -83,8 +79,8 @@ fun VaultVerificationCodeItem( if (!issuer.isNullOrEmpty()) { Text( text = issuer, - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurface, + style = BitwardenTheme.typography.bodyLarge, + color = BitwardenTheme.colorScheme.text.primary, maxLines = 1, overflow = TextOverflow.Ellipsis, ) @@ -92,15 +88,15 @@ fun VaultVerificationCodeItem( if (!supportingLabel.isNullOrEmpty()) { Text( text = supportingLabel, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, + style = BitwardenTheme.typography.bodyMedium, + color = BitwardenTheme.colorScheme.text.secondary, maxLines = 1, overflow = TextOverflow.Ellipsis, ) } } - AuthenticatorCircularCountdownIndicator( + BitwardenCircularCountdownIndicator( timeLeftSeconds = timeLeftSeconds, periodSeconds = periodSeconds, alertThresholdSeconds = alertThresholdSeconds, @@ -108,8 +104,8 @@ fun VaultVerificationCodeItem( Text( text = authCode.chunked(3).joinToString(" "), - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurfaceVariant, + style = BitwardenTheme.typography.sensitiveInfoSmall, + color = BitwardenTheme.colorScheme.text.primary, ) IconButton( @@ -118,7 +114,7 @@ fun VaultVerificationCodeItem( Icon( painter = painterResource(id = BitwardenDrawable.ic_copy), contentDescription = stringResource(id = BitwardenString.copy), - tint = MaterialTheme.colorScheme.primary, + tint = BitwardenTheme.colorScheme.icon.primary, modifier = Modifier.size(24.dp), ) } @@ -129,7 +125,7 @@ fun VaultVerificationCodeItem( @Preview(showBackground = true) @Composable private fun VerificationCodeItem_preview() { - AuthenticatorTheme { + BitwardenTheme { VaultVerificationCodeItem( startIcon = IconData.Local(BitwardenDrawable.ic_login_item), issuer = "Sample Label", @@ -141,6 +137,7 @@ private fun VerificationCodeItem_preview() { onItemClick = {}, modifier = Modifier.padding(horizontal = 16.dp), alertThresholdSeconds = 7, + cardStyle = CardStyle.Full, ) } } diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/appbar/AuthenticatorMediumTopAppBar.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/appbar/AuthenticatorMediumTopAppBar.kt deleted file mode 100644 index 143ead4ec0e..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/appbar/AuthenticatorMediumTopAppBar.kt +++ /dev/null @@ -1,87 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.appbar - -import androidx.compose.foundation.layout.RowScope -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.MediumTopAppBar -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.TopAppBarScrollBehavior -import androidx.compose.material3.rememberTopAppBarState -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.testTag -import androidx.compose.ui.tooling.preview.Preview -import com.bitwarden.ui.platform.resource.BitwardenDrawable - -/** - * A custom Bitwarden-themed medium top app bar with support for actions. - * - * This app bar wraps around Material 3's [MediumTopAppBar] and customizes its appearance - * and behavior according to the app theme. - * It provides a title and an optional set of actions on the trailing side. - * These actions are arranged within a custom action row tailored to the app's design requirements. - * - * @param title The text to be displayed as the title of the app bar. - * @param scrollBehavior Defines the scrolling behavior of the app bar. It controls how the app bar - * behaves in conjunction with scrolling content. - * @param actions A lambda containing the set of actions (usually icons or similar) to display - * in the app bar's trailing side. This lambda extends [RowScope], allowing flexibility in - * defining the layout of the actions. - */ -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun AuthenticatorMediumTopAppBar( - title: String, - scrollBehavior: TopAppBarScrollBehavior, - modifier: Modifier = Modifier, - actions: @Composable RowScope.() -> Unit = {}, -) { - MediumTopAppBar( - colors = TopAppBarDefaults.largeTopAppBarColors( - containerColor = MaterialTheme.colorScheme.surface, - scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer, - navigationIconContentColor = MaterialTheme.colorScheme.onSurface, - titleContentColor = MaterialTheme.colorScheme.onSurface, - actionIconContentColor = MaterialTheme.colorScheme.onSurfaceVariant, - ), - scrollBehavior = scrollBehavior, - title = { - Text( - text = title, - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.semantics { testTag = "PageTitleLabel" }, - ) - }, - modifier = modifier.semantics { testTag = "HeaderBarComponent" }, - actions = actions, - ) -} - -@OptIn(ExperimentalMaterial3Api::class) -@Preview(showBackground = true) -@Composable -private fun AuthenticatorMediumTopAppBar_preview() { - MaterialTheme { - AuthenticatorMediumTopAppBar( - title = "Preview Title", - scrollBehavior = TopAppBarDefaults - .exitUntilCollapsedScrollBehavior( - rememberTopAppBarState(), - ), - actions = { - IconButton(onClick = { }) { - Icon( - painter = painterResource(id = BitwardenDrawable.ic_ellipsis_vertical), - contentDescription = "", - tint = MaterialTheme.colorScheme.onSurface, - ) - } - }, - ) - } -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/appbar/AuthenticatorSearchTopAppBar.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/appbar/AuthenticatorSearchTopAppBar.kt deleted file mode 100644 index 92b16497604..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/appbar/AuthenticatorSearchTopAppBar.kt +++ /dev/null @@ -1,105 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.appbar - -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.TextField -import androidx.compose.material3.TextFieldDefaults -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.TopAppBarScrollBehavior -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.testTag -import androidx.compose.ui.text.input.ImeAction -import com.bitwarden.ui.platform.base.util.mirrorIfRtl -import com.bitwarden.ui.platform.components.appbar.NavigationIcon -import com.bitwarden.ui.platform.resource.BitwardenDrawable -import com.bitwarden.ui.platform.resource.BitwardenString - -/** - * Represents a Bitwarden styled [TopAppBar] that assumes the following components: - * - * - an optional single navigation control in the upper-left defined by [navigationIcon]. - * - an editable [TextField] populated by a [searchTerm] in the middle. - */ -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun AuthenticatorSearchTopAppBar( - searchTerm: String, - placeholder: String, - onSearchTermChange: (String) -> Unit, - scrollBehavior: TopAppBarScrollBehavior, - navigationIcon: NavigationIcon?, - modifier: Modifier = Modifier, - autoFocus: Boolean = true, -) { - val focusRequester = remember { FocusRequester() } - TopAppBar( - modifier = modifier.semantics { testTag = "HeaderBarComponent" }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.surface, - scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer, - navigationIconContentColor = MaterialTheme.colorScheme.onSurface, - titleContentColor = MaterialTheme.colorScheme.onSurface, - actionIconContentColor = MaterialTheme.colorScheme.onSurfaceVariant, - ), - scrollBehavior = scrollBehavior, - navigationIcon = { - navigationIcon?.let { - IconButton( - onClick = it.onNavigationIconClick, - modifier = Modifier.semantics { testTag = "CloseButton" }, - ) { - Icon( - modifier = Modifier.mirrorIfRtl(), - painter = it.navigationIcon, - contentDescription = it.navigationIconContentDescription, - ) - } - } - }, - title = { - TextField( - colors = TextFieldDefaults.colors( - focusedContainerColor = Color.Transparent, - unfocusedContainerColor = Color.Transparent, - focusedIndicatorColor = Color.Transparent, - unfocusedIndicatorColor = Color.Transparent, - ), - placeholder = { Text(text = placeholder) }, - value = searchTerm, - onValueChange = onSearchTermChange, - trailingIcon = { - IconButton( - onClick = { onSearchTermChange("") }, - ) { - Icon( - painter = painterResource(id = BitwardenDrawable.ic_close), - contentDescription = stringResource(id = BitwardenString.clear), - ) - } - }, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - modifier = Modifier - .focusRequester(focusRequester) - .fillMaxWidth(), - ) - }, - ) - if (autoFocus) { - LaunchedEffect(Unit) { focusRequester.requestFocus() } - } -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/appbar/AuthenticatorTopAppBar.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/appbar/AuthenticatorTopAppBar.kt deleted file mode 100644 index 105bfa92087..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/appbar/AuthenticatorTopAppBar.kt +++ /dev/null @@ -1,136 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.appbar - -import androidx.compose.foundation.layout.RowScope -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.TopAppBarScrollBehavior -import androidx.compose.material3.rememberTopAppBarState -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.testTag -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import com.bitwarden.authenticator.ui.platform.theme.AuthenticatorTheme -import com.bitwarden.ui.platform.base.util.mirrorIfRtl -import com.bitwarden.ui.platform.components.appbar.NavigationIcon -import com.bitwarden.ui.platform.resource.BitwardenDrawable -import com.bitwarden.ui.platform.resource.BitwardenString - -/** - * Represents a Bitwarden styled [TopAppBar] that assumes the following components: - * - * - a single navigation control in the upper-left defined by [navigationIcon], - * [navigationIconContentDescription], and [onNavigationIconClick]. - * - a [title] in the middle. - * - a [actions] lambda containing the set of actions (usually icons or similar) to display - * in the app bar's trailing side. This lambda extends [RowScope], allowing flexibility in - * defining the layout of the actions. - */ -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun AuthenticatorTopAppBar( - title: String, - scrollBehavior: TopAppBarScrollBehavior, - navigationIcon: Painter, - navigationIconContentDescription: String, - onNavigationIconClick: () -> Unit, - modifier: Modifier = Modifier, - actions: @Composable RowScope.() -> Unit = { }, -) { - AuthenticatorTopAppBar( - title = title, - scrollBehavior = scrollBehavior, - navigationIcon = NavigationIcon( - navigationIcon = navigationIcon, - navigationIconContentDescription = navigationIconContentDescription, - onNavigationIconClick = onNavigationIconClick, - ), - modifier = modifier, - actions = actions, - ) -} - -/** - * Represents a Bitwarden styled [TopAppBar] that assumes the following components: - * - * - an optional single navigation control in the upper-left defined by [navigationIcon]. - * - a [title] in the middle. - * - a [actions] lambda containing the set of actions (usually icons or similar) to display - * in the app bar's trailing side. This lambda extends [RowScope], allowing flexibility in - * defining the layout of the actions. - */ -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun AuthenticatorTopAppBar( - title: String, - scrollBehavior: TopAppBarScrollBehavior, - navigationIcon: NavigationIcon?, - modifier: Modifier = Modifier, - actions: @Composable RowScope.() -> Unit = {}, -) { - TopAppBar( - colors = TopAppBarDefaults.largeTopAppBarColors( - containerColor = MaterialTheme.colorScheme.surface, - scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer, - navigationIconContentColor = MaterialTheme.colorScheme.onSurface, - titleContentColor = MaterialTheme.colorScheme.onSurface, - actionIconContentColor = MaterialTheme.colorScheme.onSurfaceVariant, - ), - scrollBehavior = scrollBehavior, - navigationIcon = { - navigationIcon?.let { - IconButton( - onClick = it.onNavigationIconClick, - modifier = Modifier.semantics { testTag = "CloseButton" }, - ) { - Icon( - modifier = Modifier.mirrorIfRtl(), - painter = it.navigationIcon, - contentDescription = it.navigationIconContentDescription, - ) - } - } - }, - title = { - Text( - text = title, - style = MaterialTheme.typography.titleLarge, - maxLines = 1, - softWrap = false, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.semantics { testTag = "PageTitleLabel" }, - ) - }, - modifier = modifier.semantics { testTag = "HeaderBarComponent" }, - actions = actions, - ) -} - -@OptIn(ExperimentalMaterial3Api::class) -@Preview -@Composable -private fun AuthenticatorTopAppBar_preview() { - AuthenticatorTheme { - AuthenticatorTopAppBar( - title = "Title", - scrollBehavior = TopAppBarDefaults - .exitUntilCollapsedScrollBehavior( - rememberTopAppBarState(), - ), - navigationIcon = NavigationIcon( - navigationIcon = painterResource(id = BitwardenDrawable.ic_close), - navigationIconContentDescription = stringResource(id = BitwardenString.close), - onNavigationIconClick = { }, - ), - ) - } -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/appbar/action/AuthenticatorSearchActionItem.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/appbar/action/AuthenticatorSearchActionItem.kt deleted file mode 100644 index ec7ef3ccafc..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/appbar/action/AuthenticatorSearchActionItem.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.appbar.action - -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.tooling.preview.Preview -import com.bitwarden.ui.platform.components.util.rememberVectorPainter -import com.bitwarden.ui.platform.resource.BitwardenDrawable - -/** - * Represents the Authenticator search action item. - * - * This is an [Icon] composable tailored specifically for the search functionality - * in the Authenticator app. - * It presents the search icon and offers an `onClick` callback for when the icon is tapped. - * - * @param contentDescription A description of the UI element, used for accessibility purposes. - * @param onClick A callback to be invoked when this action item is clicked. - */ -@Composable -fun AuthenticatorSearchActionItem( - contentDescription: String, - onClick: () -> Unit, -) { - IconButton( - onClick = onClick, - modifier = Modifier.testTag("SearchButton"), - ) { - Icon( - painter = rememberVectorPainter(id = BitwardenDrawable.ic_search_wide), - contentDescription = contentDescription, - ) - } -} - -@Preview(showBackground = true) -@Composable -private fun AuthenticatorSearchActionItem_preview() { - AuthenticatorSearchActionItem( - contentDescription = "Search", - onClick = {}, - ) -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/button/AuthenticatorFilledButton.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/button/AuthenticatorFilledButton.kt deleted file mode 100644 index a5fb0bc46d9..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/button/AuthenticatorFilledButton.kt +++ /dev/null @@ -1,64 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.button - -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp - -/** - * Represents a Bitwarden Authenticator-styled filled [Button]. - * - * @param label The label for the button. - * @param onClick The callback when the button is clicked. - * @param modifier The [Modifier] to be applied to the button. - * @param isEnabled Whether or not the button is enabled. - */ -@Composable -fun AuthenticatorFilledButton( - label: String, - onClick: () -> Unit, - modifier: Modifier = Modifier, - isEnabled: Boolean = true, -) { - Button( - onClick = onClick, - modifier = modifier.semantics(mergeDescendants = true) {}, - enabled = isEnabled, - contentPadding = PaddingValues( - vertical = 10.dp, - horizontal = 24.dp, - ), - colors = ButtonDefaults.buttonColors(), - ) { - Text( - text = label, - style = MaterialTheme.typography.labelLarge, - ) - } -} - -@Preview -@Composable -private fun AuthenticatorFilledButton_preview_isEnabled() { - AuthenticatorFilledButton( - label = "Label", - onClick = {}, - isEnabled = true, - ) -} - -@Preview -@Composable -private fun BitwardenFilledButton_preview_isNotEnabled() { - AuthenticatorFilledButton( - label = "Label", - onClick = {}, - isEnabled = false, - ) -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/button/AuthenticatorFilledTonalButton.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/button/AuthenticatorFilledTonalButton.kt deleted file mode 100644 index 134c86c8142..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/button/AuthenticatorFilledTonalButton.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.button - -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.bitwarden.authenticator.ui.platform.theme.AuthenticatorTheme - -/** - * A filled tonal button for the Bitwarden Authenticator UI with a customized appearance. - * - * This button uses the `secondaryContainer` color from the current [MaterialTheme.colorScheme] - * for its background and the `onSecondaryContainer` color for its label text. - * - * @param label The text to be displayed on the button. - * @param onClick A lambda which will be invoked when the button is clicked. - * @param isEnabled Whether or not the button is enabled. - * @param modifier A [Modifier] for this composable, allowing for adjustments to its appearance - * or behavior. This can be used to apply padding, layout, and other Modifiers. - */ -@Composable -fun AuthenticatorFilledTonalButton( - label: String, - onClick: () -> Unit, - modifier: Modifier = Modifier, - isEnabled: Boolean = true, -) { - Button( - onClick = onClick, - contentPadding = PaddingValues( - vertical = 10.dp, - horizontal = 24.dp, - ), - enabled = isEnabled, - colors = ButtonDefaults.filledTonalButtonColors(), - modifier = modifier, - ) { - Text( - text = label, - style = MaterialTheme.typography.labelLarge, - ) - } -} - -@Preview(showBackground = true) -@Composable -private fun AuthenticatorFilledTonalButton_preview() { - AuthenticatorTheme { - AuthenticatorFilledTonalButton( - label = "Sample Text", - onClick = {}, - modifier = Modifier.padding(horizontal = 16.dp), - ) - } -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/button/AuthenticatorOutlinedButton.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/button/AuthenticatorOutlinedButton.kt deleted file mode 100644 index 5c56b8f01cc..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/button/AuthenticatorOutlinedButton.kt +++ /dev/null @@ -1,65 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.button - -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedButton -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp - -/** - * Represents a Bitwarden Authenticator-styled filled [OutlinedButton]. - * - * @param label The label for the button. - * @param onClick The callback when the button is clicked. - * @param modifier The [Modifier] to be applied to the button. - * @param isEnabled Whether or not the button is enabled. - */ -@Composable -fun AuthenticatorOutlinedButton( - label: String, - onClick: () -> Unit, - modifier: Modifier = Modifier, - isEnabled: Boolean = true, -) { - OutlinedButton( - onClick = onClick, - modifier = modifier - .semantics(mergeDescendants = true) { }, - enabled = isEnabled, - contentPadding = PaddingValues( - vertical = 10.dp, - horizontal = 24.dp, - ), - colors = ButtonDefaults.outlinedButtonColors(), - ) { - Text( - text = label, - style = MaterialTheme.typography.labelLarge, - ) - } -} - -@Preview -@Composable -private fun AuthenticatorOutlinedButton_preview_isEnabled() { - AuthenticatorOutlinedButton( - label = "Label", - onClick = {}, - isEnabled = true, - ) -} - -@Preview -@Composable -private fun AuthenticatorOutlinedButton_preview_isNotEnabled() { - AuthenticatorOutlinedButton( - label = "Label", - onClick = {}, - isEnabled = false, - ) -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/button/AuthenticatorTextButton.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/button/AuthenticatorTextButton.kt deleted file mode 100644 index a391ef655f4..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/button/AuthenticatorTextButton.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.button - -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp - -/** - * Represents a Bitwarden Authenticator-styled [TextButton]. - * - * @param label The label for the button. - * @param onClick The callback when the button is clicked. - * @param modifier The [Modifier] to be applied to the button. - * @param labelTextColor The color for the label text. - */ -@Composable -fun AuthenticatorTextButton( - label: String, - onClick: () -> Unit, - modifier: Modifier = Modifier, - isEnabled: Boolean = true, - labelTextColor: Color? = null, -) { - val defaultColors = if (labelTextColor != null) { - ButtonDefaults.textButtonColors( - contentColor = labelTextColor, - ) - } else { - ButtonDefaults.textButtonColors() - } - - TextButton( - onClick = onClick, - modifier = modifier, - enabled = isEnabled, - colors = defaultColors, - ) { - Text( - text = label, - style = MaterialTheme.typography.labelLarge, - modifier = Modifier - .padding( - vertical = 10.dp, - horizontal = 12.dp, - ), - ) - } -} - -@Preview -@Composable -private fun AuthenticatorTextButton_preview() { - AuthenticatorTextButton( - label = "Label", - onClick = {}, - ) -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/card/AuthenticatorActionCard.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/card/AuthenticatorActionCard.kt deleted file mode 100644 index 0b5cd084eaa..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/card/AuthenticatorActionCard.kt +++ /dev/null @@ -1,132 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.card - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.BoxScope -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -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.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.vector.VectorPainter -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 com.bitwarden.ui.platform.components.util.rememberVectorPainter -import com.bitwarden.ui.platform.resource.BitwardenDrawable -import com.bitwarden.ui.platform.resource.BitwardenString - -/** - * A reusable card for displaying actions to the user. - */ -@Composable -fun AuthenticatorActionCard( - actionIcon: VectorPainter, - titleText: String, - actionText: String, - callToActionText: String, - onCardClicked: () -> Unit, - modifier: Modifier = Modifier, - trailingContent: (@Composable BoxScope.() -> Unit)? = null, -) { - Card( - onClick = onCardClicked, - shape = RoundedCornerShape(size = 16.dp), - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surfaceContainer, - disabledContainerColor = MaterialTheme.colorScheme.surfaceContainer, - ), - modifier = modifier, - elevation = CardDefaults.elevatedCardElevation(), - ) { - Row( - modifier = Modifier - .fillMaxWidth(), - ) { - Icon( - painter = actionIcon, - contentDescription = null, - tint = MaterialTheme.colorScheme.primary, - modifier = Modifier - .padding(start = 16.dp, top = 16.dp) - .size(24.dp), - ) - Spacer(modifier = Modifier.width(16.dp)) - Column( - modifier = Modifier - .weight(weight = 1f) - .padding(vertical = 16.dp), - ) { - Text( - text = titleText, - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurface, - ) - Text( - text = actionText, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = callToActionText, - style = MaterialTheme.typography.labelLarge, - color = MaterialTheme.colorScheme.primary, - ) - } - Spacer(modifier = Modifier.width(16.dp)) - Box { - trailingContent?.invoke(this) - } - } - } -} - -@Preview -@Composable -private fun ActionCardPreview() { - AuthenticatorActionCard( - actionIcon = rememberVectorPainter(id = BitwardenDrawable.ic_close), - actionText = "This is an action.", - callToActionText = "Take action", - titleText = "This is a title", - onCardClicked = { }, - ) -} - -@Preview -@Composable -private fun ActionCardWithTrailingPreview() { - AuthenticatorActionCard( - actionIcon = rememberVectorPainter(id = BitwardenDrawable.ic_shield), - actionText = "An action with trailing content", - titleText = "This is a title", - callToActionText = "Take action", - onCardClicked = {}, - trailingContent = { - IconButton( - onClick = {}, - ) { - Icon( - painter = painterResource(id = BitwardenDrawable.ic_close), - contentDescription = stringResource(id = BitwardenString.close), - tint = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier - .size(24.dp), - ) - } - }, - ) -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/content/AuthenticatorErrorContent.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/content/AuthenticatorErrorContent.kt deleted file mode 100644 index 86e5fd20b96..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/content/AuthenticatorErrorContent.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.content - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import com.bitwarden.authenticator.ui.platform.components.button.AuthenticatorTextButton -import com.bitwarden.ui.platform.resource.BitwardenString - -/** - * A Bitwarden-themed, re-usable error state. - * - * Note that when [onTryAgainClick] is absent, there will be no "Try again" button displayed. - */ -@Composable -fun AuthenticatorErrorContent( - message: String, - modifier: Modifier = Modifier, - onTryAgainClick: (() -> Unit)? = null, -) { - Column( - modifier = modifier.verticalScroll(rememberScrollState()), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Spacer(modifier = Modifier.weight(1f)) - Text( - text = message, - color = MaterialTheme.colorScheme.onSurface, - style = MaterialTheme.typography.bodyMedium, - textAlign = TextAlign.Center, - modifier = Modifier - .padding(horizontal = 16.dp) - .fillMaxWidth(), - ) - onTryAgainClick?.let { - Spacer(modifier = Modifier.height(16.dp)) - AuthenticatorTextButton( - label = stringResource(id = BitwardenString.try_again), - onClick = it, - modifier = Modifier.padding(horizontal = 16.dp), - ) - } - Spacer(modifier = Modifier.weight(1f)) - Spacer(modifier = Modifier.navigationBarsPadding()) - } -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/content/AuthenticatorLoadingContent.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/content/AuthenticatorLoadingContent.kt deleted file mode 100644 index e197215deb2..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/content/AuthenticatorLoadingContent.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.content - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier - -/** - * A Bitwarden-themed, re-usable loading state. - */ -@Composable -fun AuthenticatorLoadingContent( - modifier: Modifier = Modifier, -) { - Column( - modifier = modifier, - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally, - ) { - CircularProgressIndicator() - Spacer(modifier = Modifier.navigationBarsPadding()) - } -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/dialog/BitwardenBasicDialog.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/dialog/BitwardenBasicDialog.kt deleted file mode 100644 index e8cb6004d0c..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/dialog/BitwardenBasicDialog.kt +++ /dev/null @@ -1,91 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.dialog - -import android.os.Parcelable -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import com.bitwarden.authenticator.ui.platform.components.button.AuthenticatorTextButton -import com.bitwarden.authenticator.ui.platform.theme.AuthenticatorTheme -import com.bitwarden.ui.platform.resource.BitwardenString -import com.bitwarden.ui.util.Text -import com.bitwarden.ui.util.asText -import kotlinx.parcelize.Parcelize - -/** - * Represents a Bitwarden-styled dialog that is hidden or shown based on [visibilityState]. - * - * @param visibilityState the [BasicDialogState] used to populate the dialog. - * @param onDismissRequest called when the user has requested to dismiss the dialog, whether by - * tapping "OK", tapping outside the dialog, or pressing the back button. - */ -@Composable -fun BitwardenBasicDialog( - visibilityState: BasicDialogState, - onDismissRequest: () -> Unit, -): Unit = when (visibilityState) { - BasicDialogState.Hidden -> Unit - is BasicDialogState.Shown -> { - AlertDialog( - onDismissRequest = onDismissRequest, - confirmButton = { - AuthenticatorTextButton( - label = stringResource(id = BitwardenString.okay), - onClick = onDismissRequest, - ) - }, - title = visibilityState.title?.let { - { - Text( - text = it(), - style = MaterialTheme.typography.headlineSmall, - ) - } - }, - text = { - Text( - text = visibilityState.message(), - style = MaterialTheme.typography.bodyMedium, - ) - }, - containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, - ) - } -} - -@Preview -@Composable -private fun BitwardenBasicDialog_preview() { - AuthenticatorTheme { - BitwardenBasicDialog( - visibilityState = BasicDialogState.Shown( - title = "An error has occurred".asText(), - message = "Username or password is incorrect. Try again.".asText(), - ), - onDismissRequest = {}, - ) - } -} - -/** - * Models display of a [BitwardenBasicDialog]. - */ -sealed class BasicDialogState : Parcelable { - - /** - * Hide the dialog. - */ - @Parcelize - data object Hidden : BasicDialogState() - - /** - * Show the dialog with the given values. - */ - @Parcelize - data class Shown( - val title: Text?, - val message: Text, - ) : BasicDialogState() -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/dialog/BitwardenLoadingDialog.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/dialog/BitwardenLoadingDialog.kt deleted file mode 100644 index 6cd2618e4cb..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/dialog/BitwardenLoadingDialog.kt +++ /dev/null @@ -1,107 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.dialog - -import android.os.Parcelable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog -import androidx.compose.ui.window.DialogProperties -import com.bitwarden.authenticator.ui.platform.theme.AuthenticatorTheme -import com.bitwarden.ui.util.Text -import com.bitwarden.ui.util.asText -import kotlinx.parcelize.Parcelize - -/** - * Represents a Bitwarden-styled loading dialog that shows text and a circular progress indicator. - * - * @param visibilityState the [LoadingDialogState] used to populate the dialog. - */ -@Composable -fun BitwardenLoadingDialog( - visibilityState: LoadingDialogState, -) { - when (visibilityState) { - is LoadingDialogState.Hidden -> Unit - is LoadingDialogState.Shown -> { - Dialog( - onDismissRequest = {}, - properties = DialogProperties( - dismissOnBackPress = false, - dismissOnClickOutside = false, - ), - ) { - Card( - shape = RoundedCornerShape(28.dp), - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, - ), - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight(), - ) { - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Text( - text = visibilityState.text(), - modifier = Modifier.padding( - top = 24.dp, - bottom = 8.dp, - ), - ) - CircularProgressIndicator( - modifier = Modifier.padding( - top = 8.dp, - bottom = 24.dp, - ), - ) - } - } - } - } - } -} - -@Preview -@Composable -private fun BitwardenLoadingDialog_preview() { - AuthenticatorTheme { - BitwardenLoadingDialog( - visibilityState = LoadingDialogState.Shown( - text = "Loading...".asText(), - ), - ) - } -} - -/** - * Models display of a [BitwardenLoadingDialog]. - */ -sealed class LoadingDialogState : Parcelable { - /** - * Hide the dialog. - */ - @Parcelize - data object Hidden : LoadingDialogState() - - /** - * Show the dialog with the given values. - */ - @Parcelize - data class Shown(val text: Text) : LoadingDialogState() -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/dialog/BitwardenSelectionDialog.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/dialog/BitwardenSelectionDialog.kt deleted file mode 100644 index 11c789d5e46..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/dialog/BitwardenSelectionDialog.kt +++ /dev/null @@ -1,120 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.dialog - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ColumnScope -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.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -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.semantics.semantics -import androidx.compose.ui.semantics.testTagsAsResourceId -import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog -import com.bitwarden.authenticator.ui.platform.components.button.AuthenticatorTextButton -import com.bitwarden.ui.platform.components.dialog.util.maxDialogHeight -import com.bitwarden.ui.platform.resource.BitwardenString - -/** - * Displays a dialog with a title and "Cancel" button. - * - * @param title Title to display. - * @param subtitle Optional subtitle to display below the title. - * @param dismissLabel Label to show on the dismiss button at the bottom of the dialog. - * @param onDismissRequest Invoked when the user dismisses the dialog. - * @param onDismissActionClick Invoked when the user dismisses the via the dismiss action button. - * By default, this just defers to onDismissRequest. - * @param selectionItems Lambda containing selection items to show to the user. See - * [BitwardenSelectionRow]. - */ -@Suppress("LongMethod") -@Composable -fun BitwardenSelectionDialog( - title: String, - subtitle: String? = null, - dismissLabel: String = stringResource(BitwardenString.cancel), - onDismissRequest: () -> Unit, - onDismissActionClick: () -> Unit = onDismissRequest, - selectionItems: @Composable ColumnScope.() -> Unit = {}, -) { - Dialog( - onDismissRequest = onDismissRequest, - ) { - val configuration = LocalConfiguration.current - val scrollState = rememberScrollState() - Column( - modifier = Modifier - .semantics { testTagsAsResourceId = true } - .requiredHeightIn( - max = configuration.maxDialogHeight, - ) - // This background is necessary for the dialog to not be transparent. - .background( - color = MaterialTheme.colorScheme.surfaceContainerHigh, - shape = RoundedCornerShape(28.dp), - ), - horizontalAlignment = Alignment.End, - ) { - Text( - modifier = Modifier - .padding(24.dp) - .fillMaxWidth(), - text = title, - color = MaterialTheme.colorScheme.onSurface, - style = MaterialTheme.typography.headlineSmall, - ) - subtitle?.let { - Text( - modifier = Modifier - .padding( - start = 24.dp, - end = 24.dp, - bottom = 24.dp, - ) - .fillMaxWidth(), - text = subtitle, - color = MaterialTheme.colorScheme.onSurfaceVariant, - style = MaterialTheme.typography.bodyMedium, - ) - } - if (scrollState.canScrollBackward) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(1.dp) - .background(MaterialTheme.colorScheme.outlineVariant), - ) - } - Column( - modifier = Modifier - .weight(1f, fill = false) - .verticalScroll(scrollState), - content = selectionItems, - ) - if (scrollState.canScrollForward) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(1.dp) - .background(MaterialTheme.colorScheme.outlineVariant), - ) - } - AuthenticatorTextButton( - modifier = Modifier.padding(24.dp), - label = dismissLabel, - onClick = onDismissActionClick, - ) - } - } -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/dialog/BitwardenSelectionRow.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/dialog/BitwardenSelectionRow.kt deleted file mode 100644 index 4ac460bae53..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/dialog/BitwardenSelectionRow.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.dialog - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.RadioButton -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.semantics.selected -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.unit.dp -import com.bitwarden.ui.util.Text - -/** - * A clickable item that displays a radio button and text. - * - * @param text The text to display. - * @param onClick Invoked when either the radio button or text is clicked. - * @param isSelected Whether or not the radio button should be checked. - */ -@Composable -fun BitwardenSelectionRow( - text: Text, - onClick: () -> Unit, - isSelected: Boolean, - modifier: Modifier = Modifier, -) { - Row( - modifier = modifier - .fillMaxWidth() - .clickable(onClick = onClick) - .semantics(mergeDescendants = true) { - selected = isSelected - }, - verticalAlignment = Alignment.CenterVertically, - ) { - RadioButton( - modifier = Modifier.padding(16.dp), - selected = isSelected, - onClick = null, - ) - Text( - text = text(), - color = MaterialTheme.colorScheme.onSurface, - style = MaterialTheme.typography.bodyLarge, - ) - } -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/dialog/BitwardenTwoButtonDialog.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/dialog/BitwardenTwoButtonDialog.kt deleted file mode 100644 index ffb8c5fb5f5..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/dialog/BitwardenTwoButtonDialog.kt +++ /dev/null @@ -1,68 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.dialog - -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.Color -import com.bitwarden.authenticator.ui.platform.components.button.AuthenticatorTextButton - -/** - * Represents a Bitwarden-styled dialog with two buttons. - * - * @param title the optional title to show. - * @param message message to show. - * @param confirmButtonText text to show on confirm button. - * @param dismissButtonText text to show on dismiss button. - * @param onConfirmClick called when the confirm button is clicked. - * @param onDismissClick called when the dismiss button is clicked. - * @param onDismissRequest called when the user attempts to dismiss the dialog (for example by - * tapping outside of it). - * @param confirmTextColor The color of the confirm text. - * @param dismissTextColor The color of the dismiss text. - */ -@Composable -fun BitwardenTwoButtonDialog( - title: String?, - message: String, - confirmButtonText: String, - dismissButtonText: String, - onConfirmClick: () -> Unit, - onDismissClick: () -> Unit, - onDismissRequest: () -> Unit, - confirmTextColor: Color? = null, - dismissTextColor: Color? = null, -) { - AlertDialog( - onDismissRequest = onDismissRequest, - dismissButton = { - AuthenticatorTextButton( - label = dismissButtonText, - labelTextColor = dismissTextColor, - onClick = onDismissClick, - ) - }, - confirmButton = { - AuthenticatorTextButton( - label = confirmButtonText, - labelTextColor = confirmTextColor, - onClick = onConfirmClick, - ) - }, - title = title?.let { - { - Text( - text = it, - style = MaterialTheme.typography.headlineSmall, - ) - } - }, - text = { - Text( - text = message, - style = MaterialTheme.typography.bodyMedium, - ) - }, - containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, - ) -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/dialog/row/BitwardenBasicDialogRow.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/dialog/row/BitwardenBasicDialogRow.kt deleted file mode 100644 index ee48f23eea1..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/dialog/row/BitwardenBasicDialogRow.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.dialog.row - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.ripple -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import com.bitwarden.authenticator.ui.platform.components.dialog.BitwardenSelectionDialog - -/** - * A simple clickable row for use within a [BitwardenSelectionDialog] as an alternative to a - * [BitwardenSelectionRow]. - * - * @param text The text to display in the row. - * @param onClick A callback to be invoked when the row is clicked. - * @param modifier A [Modifier] for the composable. - */ -@Composable -fun BitwardenBasicDialogRow( - text: String, - onClick: () -> Unit, - modifier: Modifier = Modifier, -) { - Text( - text = text, - style = MaterialTheme.typography.bodyLarge, - modifier = modifier - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = ripple(color = MaterialTheme.colorScheme.primary), - onClick = onClick, - ) - .padding( - vertical = 16.dp, - horizontal = 24.dp, - ) - .fillMaxWidth(), - ) -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/dialog/row/BitwardenSelectionRow.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/dialog/row/BitwardenSelectionRow.kt deleted file mode 100644 index 7b9cf7b7859..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/dialog/row/BitwardenSelectionRow.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.dialog.row - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.RadioButton -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.semantics.selected -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.unit.dp -import com.bitwarden.ui.util.Text - -/** - * A clickable item that displays a radio button and text. - * - * @param text The text to display. - * @param onClick Invoked when either the radio button or text is clicked. - * @param isSelected Whether or not the radio button should be checked. - */ -@Composable -fun BitwardenSelectionRow( - text: Text, - onClick: () -> Unit, - isSelected: Boolean, - modifier: Modifier = Modifier, -) { - Row( - modifier = modifier - .fillMaxWidth() - .clickable(onClick = onClick) - .semantics(mergeDescendants = true) { - selected = isSelected - }, - verticalAlignment = Alignment.CenterVertically, - ) { - RadioButton( - modifier = Modifier.padding(16.dp), - selected = isSelected, - onClick = null, - ) - Text( - text = text(), - color = MaterialTheme.colorScheme.onSurface, - style = MaterialTheme.typography.bodyLarge, - ) - } -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/dropdown/BitwardenMultiSelectButton.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/dropdown/BitwardenMultiSelectButton.kt deleted file mode 100644 index bbe1061deb8..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/dropdown/BitwardenMultiSelectButton.kt +++ /dev/null @@ -1,186 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.dropdown - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.IconButtonDefaults -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.OutlinedTextFieldDefaults -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.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.semantics.CustomAccessibilityAction -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.semantics.clearAndSetSemantics -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.customActions -import androidx.compose.ui.semantics.role -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.bitwarden.authenticator.ui.platform.components.dialog.BitwardenSelectionDialog -import com.bitwarden.authenticator.ui.platform.components.dialog.BitwardenSelectionRow -import com.bitwarden.authenticator.ui.platform.theme.AuthenticatorTheme -import com.bitwarden.ui.platform.components.model.TooltipData -import com.bitwarden.ui.platform.resource.BitwardenDrawable -import com.bitwarden.ui.util.asText -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf - -/** - * A custom composable representing a multi-select button. - * - * This composable displays an [OutlinedTextField] with a dropdown icon as a trailing icon. - * When the field is clicked, a dropdown menu appears with a list of options to select from. - * - * @param label The descriptive text label for the [OutlinedTextField]. - * @param options A list of strings representing the available options in the dialog. - * @param selectedOption The currently selected option that is displayed in the [OutlinedTextField] - * (or `null` if no option is selected). - * @param onOptionSelected A lambda that is invoked when an option - * is selected from the dropdown menu. - * @param isEnabled Whether or not the button is enabled. - * @param modifier A [Modifier] that you can use to apply custom modifications to the composable. - * @param supportingText A optional supporting text that will appear below the text field. - * @param tooltip A nullable [TooltipData], representing the tooltip icon. - */ -@Suppress("LongMethod") -@Composable -fun BitwardenMultiSelectButton( - label: String, - options: ImmutableList, - selectedOption: String?, - onOptionSelected: (String) -> Unit, - modifier: Modifier = Modifier, - isEnabled: Boolean = true, - supportingText: String? = null, - tooltip: TooltipData? = null, -) { - var shouldShowDialog by rememberSaveable { mutableStateOf(false) } - - OutlinedTextField( - modifier = modifier - .clearAndSetSemantics { - role = Role.DropdownList - contentDescription = supportingText - ?.let { "$selectedOption. $label. $it" } - ?: "$selectedOption. $label" - customActions = listOfNotNull( - tooltip?.let { - CustomAccessibilityAction( - label = it.contentDescription, - action = { - it.onClick() - true - }, - ) - }, - ) - } - .fillMaxWidth() - .clickable( - indication = null, - enabled = isEnabled, - interactionSource = remember { MutableInteractionSource() }, - ) { - shouldShowDialog = !shouldShowDialog - }, - textStyle = MaterialTheme.typography.bodyLarge, - readOnly = true, - label = { - Row { - Text( - text = label, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - tooltip?.let { - Spacer(modifier = Modifier.width(3.dp)) - IconButton( - onClick = it.onClick, - enabled = isEnabled, - colors = IconButtonDefaults.iconButtonColors( - contentColor = MaterialTheme.colorScheme.primary, - ), - modifier = Modifier.size(16.dp), - ) { - Icon( - painter = painterResource(id = BitwardenDrawable.ic_tooltip_small), - contentDescription = it.contentDescription, - ) - } - } - } - }, - value = selectedOption.orEmpty(), - onValueChange = onOptionSelected, - enabled = shouldShowDialog, - trailingIcon = { - Icon( - painter = painterResource(id = BitwardenDrawable.ic_region_select_dropdown), - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurfaceVariant, - ) - }, - colors = OutlinedTextFieldDefaults.colors( - disabledTextColor = MaterialTheme.colorScheme.onSurface, - disabledBorderColor = MaterialTheme.colorScheme.outline, - disabledLeadingIconColor = MaterialTheme.colorScheme.onSurfaceVariant, - disabledTrailingIconColor = MaterialTheme.colorScheme.onSurfaceVariant, - disabledLabelColor = MaterialTheme.colorScheme.onSurfaceVariant, - disabledPlaceholderColor = MaterialTheme.colorScheme.onSurfaceVariant, - disabledSupportingTextColor = MaterialTheme.colorScheme.onSurfaceVariant, - ), - supportingText = supportingText?.let { - { - Text( - text = supportingText, - style = MaterialTheme.typography.bodySmall, - ) - } - }, - ) - if (shouldShowDialog) { - BitwardenSelectionDialog( - title = label, - onDismissRequest = { shouldShowDialog = false }, - ) { - options.forEach { optionString -> - BitwardenSelectionRow( - text = optionString.asText(), - isSelected = optionString == selectedOption, - onClick = { - shouldShowDialog = false - onOptionSelected(optionString) - }, - ) - } - } - } -} - -@Preview -@Composable -private fun BitwardenMultiSelectButton_preview() { - AuthenticatorTheme { - BitwardenMultiSelectButton( - label = "Label", - options = persistentListOf("a", "b"), - selectedOption = "", - onOptionSelected = {}, - ) - } -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/fab/ExpandableFloatingActionButton.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/fab/ExpandableFloatingActionButton.kt deleted file mode 100644 index e4ad09fbaca..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/fab/ExpandableFloatingActionButton.kt +++ /dev/null @@ -1,210 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.fab - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.expandHorizontally -import androidx.compose.animation.expandVertically -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.shrinkHorizontally -import androidx.compose.animation.shrinkVertically -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentSize -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.ExtendedFloatingActionButton -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.SmallFloatingActionButton -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.rotate -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.unit.dp -import com.bitwarden.authenticator.ui.platform.components.model.IconResource -import com.bitwarden.authenticator.ui.platform.theme.Typography -import com.bitwarden.ui.platform.resource.BitwardenString -import com.bitwarden.ui.util.Text - -/** - * A FAB that expands, when clicked, to display a collection of options that can be clicked. - * - * @param label [Text] displayed when the FAB is expanded. - * @param items [ExpandableFabOption] buttons displayed when the FAB is expanded. - * @param expandableFabState [ExpandableFabIcon] displayed in the FAB. - * @param onStateChange Lambda invoked when the FAB expanded state changes. - */ -@Suppress("LongMethod") -@Composable -fun ExpandableFloatingActionButton( - modifier: Modifier = Modifier, - label: Text?, - items: List, - expandableFabState: MutableState = rememberExpandableFabState(), - expandableFabIcon: ExpandableFabIcon, - onStateChange: (expandableFabState: ExpandableFabState) -> Unit = { }, -) { - val rotation by animateFloatAsState( - targetValue = if (expandableFabState.value == ExpandableFabState.Expanded) { - expandableFabIcon.iconRotation ?: 0f - } else { - 0f - }, - label = stringResource(BitwardenString.add_item_rotation), - ) - Column( - modifier = modifier.wrapContentSize(), - horizontalAlignment = Alignment.End, - ) { - AnimatedVisibility( - visible = expandableFabState.value.isExpanded(), - enter = fadeIn() + expandVertically(), - exit = fadeOut() + shrinkVertically(), - ) { - LazyColumn( - modifier = Modifier - .wrapContentSize() - .padding(bottom = 16.dp), - horizontalAlignment = Alignment.End, - verticalArrangement = Arrangement.spacedBy(16.dp), - ) { - items(items) { expandableFabOption -> - ExpandableFabOption( - onFabOptionClick = { - expandableFabState.value = expandableFabState.value.toggleValue() - onStateChange(expandableFabState.value) - expandableFabOption.onFabOptionClick() - }, - expandableFabOption = expandableFabOption, - ) - } - } - } - - ExtendedFloatingActionButton( - onClick = { - expandableFabState.value = expandableFabState.value.toggleValue() - onStateChange(expandableFabState.value) - }, - containerColor = MaterialTheme.colorScheme.primaryContainer, - ) { - - if (label != null) { - AnimatedVisibility( - visible = expandableFabState.value.isExpanded(), - enter = fadeIn() + expandHorizontally(), - exit = fadeOut() + shrinkHorizontally(), - ) { - Text( - modifier = Modifier.padding(end = 8.dp), - text = label(), - ) - } - } - - Icon( - modifier = Modifier - .rotate(rotation) - .semantics { expandableFabIcon.iconData.testTag }, - painter = expandableFabIcon.iconData.iconPainter, - contentDescription = expandableFabIcon.iconData.contentDescription, - ) - } - } -} - -@Composable -private fun ExpandableFabOption( - expandableFabOption: T, - onFabOptionClick: (option: T) -> Unit, -) { - SmallFloatingActionButton( - onClick = { onFabOptionClick(expandableFabOption) }, - ) { - Row( - modifier = Modifier - .wrapContentSize() - .padding(end = 8.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - expandableFabOption.label?.let { label -> - Text( - modifier = Modifier - .clip(RoundedCornerShape(size = 8.dp)) - .padding(all = 8.dp), - text = label(), - style = Typography.labelSmall, - ) - } - - Icon( - painter = expandableFabOption.iconData.iconPainter, - contentDescription = expandableFabOption.iconData.contentDescription, - ) - } - } -} - -@Composable -private fun rememberExpandableFabState() = - remember { mutableStateOf(ExpandableFabState.Collapsed) } - -/** - * Represents options displayed when the FAB is expanded. - */ -abstract class ExpandableFabOption( - val label: Text?, - val iconData: IconResource, - val onFabOptionClick: () -> Unit, -) - -/** - * Models data for an expandable FAB icon. - */ -data class ExpandableFabIcon( - val iconData: IconResource, - val iconRotation: Float?, -) - -/** - * Models the state of the expandable FAB. - */ -sealed class ExpandableFabState { - - /** - * Indicates if the FAB is expanded. - */ - fun isExpanded() = this is Expanded - - /** - * Invert the state of the FAB. - */ - fun toggleValue() = if (isExpanded()) { - Collapsed - } else { - Expanded - } - - /** - * Indicates the FAB is collapsed. - */ - data object Collapsed : ExpandableFabState() - - /** - * Indicates the FAB is expanded. - */ - data object Expanded : ExpandableFabState() -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/field/BitwardenPasswordField.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/field/BitwardenPasswordField.kt deleted file mode 100644 index 9d17c000a0e..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/field/BitwardenPasswordField.kt +++ /dev/null @@ -1,235 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.field - -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -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.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.testTag -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.KeyboardCapitalization -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.input.PasswordVisualTransformation -import androidx.compose.ui.text.input.VisualTransformation -import androidx.compose.ui.tooling.preview.Preview -import com.bitwarden.ui.platform.components.util.nonLetterColorVisualTransformation -import com.bitwarden.ui.platform.resource.BitwardenDrawable -import com.bitwarden.ui.platform.resource.BitwardenString - -/** - * Represents a Bitwarden-styled password field that hoists show/hide password state to the caller. - * - * See overloaded [BitwardenPasswordField] for self managed show/hide state. - * - * @param label Label for the text field. - * @param value Current next on the text field. - * @param showPassword Whether or not password should be shown. - * @param showPasswordChange Lambda that is called when user request show/hide be toggled. - * @param onValueChange Callback that is triggered when the password changes. - * @param modifier Modifier for the composable. - * @param readOnly `true` if the input should be read-only and not accept user interactions. - * @param singleLine when `true`, this text field becomes a single line that horizontally scrolls - * instead of wrapping onto multiple lines. - * @param hint optional hint text that will appear below the text input. - * @param showPasswordTestTag The test tag to be used on the show password button (testing tool). - * @param autoFocus When set to true, the view will request focus after the first recomposition. - * Setting this to true on multiple fields at once may have unexpected consequences. - * @param keyboardType The type of keyboard the user has access to when inputting values into - * the password field. - * @param imeAction the preferred IME action for the keyboard to have. - */ -@Composable -fun BitwardenPasswordField( - label: String, - value: String, - showPassword: Boolean, - showPasswordChange: (Boolean) -> Unit, - onValueChange: (String) -> Unit, - modifier: Modifier = Modifier, - readOnly: Boolean = false, - singleLine: Boolean = true, - hint: String? = null, - showPasswordTestTag: String? = null, - autoFocus: Boolean = false, - keyboardType: KeyboardType = KeyboardType.Password, - imeAction: ImeAction = ImeAction.Default, - capitalization: KeyboardCapitalization = KeyboardCapitalization.None, -) { - val focusRequester = remember { FocusRequester() } - OutlinedTextField( - modifier = modifier.focusRequester(focusRequester), - textStyle = MaterialTheme.typography.bodyLarge, - label = { Text(text = label) }, - value = value, - onValueChange = onValueChange, - visualTransformation = when { - !showPassword -> PasswordVisualTransformation() - readOnly -> nonLetterColorVisualTransformation( - digitColor = MaterialTheme.colorScheme.primary, - specialCharacterColor = MaterialTheme.colorScheme.error, - ) - - else -> VisualTransformation.None - }, - singleLine = singleLine, - readOnly = readOnly, - keyboardOptions = KeyboardOptions( - capitalization = capitalization, - keyboardType = keyboardType, - imeAction = imeAction, - ), - supportingText = hint?.let { - { - Text( - text = hint, - style = MaterialTheme.typography.bodySmall, - ) - } - }, - trailingIcon = { - IconButton( - onClick = { showPasswordChange.invoke(!showPassword) }, - ) { - @DrawableRes - val painterRes = if (showPassword) { - BitwardenDrawable.ic_visibility_off - } else { - BitwardenDrawable.ic_visibility - } - - @StringRes - val contentDescriptionRes = - if (showPassword) BitwardenString.hide else BitwardenString.show - Icon( - modifier = Modifier.semantics { showPasswordTestTag?.let { testTag = it } }, - painter = painterResource(id = painterRes), - contentDescription = stringResource(id = contentDescriptionRes), - tint = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - }, - ) - if (autoFocus) { - LaunchedEffect(Unit) { focusRequester.requestFocus() } - } -} - -/** - * Represents a Bitwarden-styled password field that manages the state of a show/hide indicator - * internally. - * - * @param label Label for the text field. - * @param value Current next on the text field. - * @param onValueChange Callback that is triggered when the password changes. - * @param modifier Modifier for the composable. - * @param readOnly `true` if the input should be read-only and not accept user interactions. - * @param singleLine when `true`, this text field becomes a single line that horizontally scrolls - * instead of wrapping onto multiple lines. - * @param hint optional hint text that will appear below the text input. - * @param initialShowPassword The initial state of the show/hide password control. A value of - * `false` (the default) indicates that that password should begin in the hidden state. - * @param showPasswordTestTag The test tag to be used on the show password button (testing tool). - * @param autoFocus When set to true, the view will request focus after the first recomposition. - * Setting this to true on multiple fields at once may have unexpected consequences. - * @param keyboardType The type of keyboard the user has access to when inputting values into - * the password field. - * @param imeAction the preferred IME action for the keyboard to have. - */ -@Composable -fun BitwardenPasswordField( - label: String, - value: String, - onValueChange: (String) -> Unit, - modifier: Modifier = Modifier, - readOnly: Boolean = false, - singleLine: Boolean = true, - hint: String? = null, - initialShowPassword: Boolean = false, - showPasswordTestTag: String? = null, - autoFocus: Boolean = false, - keyboardType: KeyboardType = KeyboardType.Password, - imeAction: ImeAction = ImeAction.Default, - capitalization: KeyboardCapitalization = KeyboardCapitalization.None, -) { - var showPassword by rememberSaveable { mutableStateOf(initialShowPassword) } - BitwardenPasswordField( - modifier = modifier, - label = label, - value = value, - showPassword = showPassword, - showPasswordChange = { showPassword = !showPassword }, - onValueChange = onValueChange, - readOnly = readOnly, - singleLine = singleLine, - hint = hint, - showPasswordTestTag = showPasswordTestTag, - autoFocus = autoFocus, - keyboardType = keyboardType, - imeAction = imeAction, - capitalization = capitalization, - ) -} - -@Preview(showBackground = true) -@Composable -private fun BitwardenPasswordField_preview_withInput_hidePassword() { - BitwardenPasswordField( - label = "Label", - value = "Password", - onValueChange = {}, - initialShowPassword = false, - hint = "Hint", - ) -} - -@Preview(showBackground = true) -@Composable -private fun BitwardenPasswordField_preview_withInput_showPassword() { - BitwardenPasswordField( - label = "Label", - value = "Password", - onValueChange = {}, - initialShowPassword = true, - hint = "Hint", - ) -} - -@Preview(showBackground = true) -@Composable -private fun BitwardenPasswordField_preview_withoutInput_hidePassword() { - BitwardenPasswordField( - label = "Label", - value = "", - onValueChange = {}, - initialShowPassword = false, - hint = "Hint", - ) -} - -@Preview(showBackground = true) -@Composable -private fun BitwardenPasswordField_preview_withoutInput_showPassword() { - BitwardenPasswordField( - label = "Label", - value = "", - onValueChange = {}, - initialShowPassword = true, - hint = "Hint", - ) -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/field/BitwardenTextField.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/field/BitwardenTextField.kt deleted file mode 100644 index 07f6472c008..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/field/BitwardenTextField.kt +++ /dev/null @@ -1,138 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.field - -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.Icon -import androidx.compose.material3.LocalTextStyle -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.input.VisualTransformation -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.bitwarden.authenticator.ui.platform.components.model.IconResource -import com.bitwarden.ui.platform.base.util.toPx -import com.bitwarden.ui.platform.base.util.withLineBreaksAtWidth - -/** - * Component that allows the user to input text. This composable will manage the state of - * the user's input. - * @param label label for the text field. - * @param value current next on the text field. - * @param modifier modifier for the composable. - * @param onValueChange callback that is triggered when the input of the text field changes. - * @param placeholder the optional placeholder to be displayed when the text field is in focus and - * the [value] is empty. - * @param leadingIconResource the optional resource for the leading icon on the text field. - * @param trailingIconContent the content for the trailing icon in the text field. - * @param hint optional hint text that will appear below the text input. - * @param singleLine when `true`, this text field becomes a single line that horizontally scrolls - * instead of wrapping onto multiple lines. - * @param readOnly `true` if the input should be read-only and not accept user interactions. - * @param enabled Whether or not the text field is enabled. - * @param textStyle An optional style that may be used to override the default used. - * @param shouldAddCustomLineBreaks If `true`, line breaks will be inserted to allow for filling - * an entire line before breaking. `false` by default. - * @param visualTransformation Transforms the visual representation of the input [value]. - * @param keyboardType the preferred type of keyboard input. - */ -@Composable -fun BitwardenTextField( - label: String, - value: String, - onValueChange: (String) -> Unit, - modifier: Modifier = Modifier, - placeholder: String? = null, - leadingIconResource: IconResource? = null, - trailingIconContent: (@Composable () -> Unit)? = null, - hint: String? = null, - singleLine: Boolean = true, - readOnly: Boolean = false, - enabled: Boolean = true, - textStyle: TextStyle? = null, - shouldAddCustomLineBreaks: Boolean = false, - keyboardType: KeyboardType = KeyboardType.Text, - isError: Boolean = false, - visualTransformation: VisualTransformation = VisualTransformation.None, -) { - var widthPx by remember { mutableIntStateOf(0) } - - val currentTextStyle = textStyle ?: LocalTextStyle.current - val formattedText = if (shouldAddCustomLineBreaks) { - value.withLineBreaksAtWidth( - // Adjust for built in padding - widthPx = widthPx - 16.dp.toPx(), - monospacedTextStyle = currentTextStyle, - ) - } else { - value - } - - OutlinedTextField( - modifier = modifier - .onGloballyPositioned { widthPx = it.size.width }, - enabled = enabled, - label = { Text(text = label) }, - value = formattedText, - leadingIcon = leadingIconResource?.let { iconResource -> - { - Icon( - painter = iconResource.iconPainter, - contentDescription = iconResource.contentDescription, - tint = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - }, - trailingIcon = trailingIconContent?.let { - trailingIconContent - }, - placeholder = placeholder?.let { - { Text(text = it) } - }, - supportingText = hint?.let { - { - Text( - text = hint, - style = MaterialTheme.typography.bodySmall, - ) - } - }, - onValueChange = onValueChange, - singleLine = singleLine, - readOnly = readOnly, - textStyle = currentTextStyle, - keyboardOptions = KeyboardOptions.Default.copy(keyboardType = keyboardType), - isError = isError, - visualTransformation = visualTransformation, - ) -} - -@Preview -@Composable -private fun BitwardenTextField_preview_withInput() { - BitwardenTextField( - label = "Label", - value = "Input", - onValueChange = {}, - hint = "Hint", - ) -} - -@Preview -@Composable -private fun BitwardenTextField_preview_withoutInput() { - BitwardenTextField( - label = "Label", - value = "", - onValueChange = {}, - hint = "Hint", - ) -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/field/BitwardenTextFieldWithActions.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/field/BitwardenTextFieldWithActions.kt deleted file mode 100644 index 1822f59128c..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/field/BitwardenTextFieldWithActions.kt +++ /dev/null @@ -1,100 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.field - -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material3.Icon -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.testTag -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.input.VisualTransformation -import androidx.compose.ui.tooling.preview.Preview -import com.bitwarden.authenticator.ui.platform.theme.AuthenticatorTheme -import com.bitwarden.ui.platform.components.row.BitwardenRowOfActions -import com.bitwarden.ui.platform.resource.BitwardenDrawable - -/** - * Represents a Bitwarden-styled text field accompanied by a series of actions. - * This component allows for a more versatile design by accepting - * icons or actions next to the text field. - * - * @param label Label for the text field. - * @param value Current text in the text field. - * @param onValueChange Callback that is triggered when the text content changes. - * @param modifier [Modifier] applied to this layout composable. - * @param readOnly `true` if the input should be read-only and not accept user interactions. - * @param singleLine when `true`, this text field becomes a single line that horizontally scrolls - * instead of wrapping onto multiple lines. - * @param trailingIconContent the content for the trailing icon in the text field. - * @param actions A lambda containing the set of actions (usually icons or similar) to display - * next to the text field. This lambda extends [RowScope], - * providing flexibility in the layout definition. - * @param textFieldTestTag The test tag to be used on the text field. - */ -@Composable -fun BitwardenTextFieldWithActions( - label: String, - value: String, - onValueChange: (String) -> Unit, - modifier: Modifier = Modifier, - textStyle: TextStyle? = null, - shouldAddCustomLineBreaks: Boolean = false, - visualTransformation: VisualTransformation = VisualTransformation.None, - readOnly: Boolean = false, - singleLine: Boolean = true, - keyboardType: KeyboardType = KeyboardType.Text, - trailingIconContent: (@Composable () -> Unit)? = null, - actions: @Composable RowScope.() -> Unit = {}, - actionsTestTag: String? = null, - textFieldTestTag: String? = null, -) { - Row( - modifier = modifier - .fillMaxWidth() - .semantics(mergeDescendants = true) { }, - verticalAlignment = Alignment.CenterVertically, - ) { - BitwardenTextField( - modifier = Modifier - .semantics { textFieldTestTag?.let { testTag = it } } - .weight(1f), - label = label, - value = value, - readOnly = readOnly, - singleLine = singleLine, - onValueChange = onValueChange, - keyboardType = keyboardType, - trailingIconContent = trailingIconContent, - textStyle = textStyle, - shouldAddCustomLineBreaks = shouldAddCustomLineBreaks, - visualTransformation = visualTransformation, - ) - BitwardenRowOfActions( - modifier = Modifier.run { actionsTestTag?.let { semantics { testTag = it } } ?: this }, - actions = actions, - ) - } -} - -@Preview(showBackground = true) -@Composable -private fun BitwardenTextFieldWithActions_preview() { - AuthenticatorTheme { - BitwardenTextFieldWithActions( - label = "Username", - value = "user@example.com", - onValueChange = {}, - actions = { - Icon( - painter = painterResource(id = BitwardenDrawable.ic_tooltip), - contentDescription = "Action 1", - ) - }, - ) - } -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/header/AuthenticatorExpandingHeader.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/header/AuthenticatorExpandingHeader.kt index b7bb16ea869..38a1eaa9873 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/header/AuthenticatorExpandingHeader.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/header/AuthenticatorExpandingHeader.kt @@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.minimumInteractiveComponentSize import androidx.compose.runtime.Composable @@ -22,6 +21,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.bitwarden.ui.platform.components.util.rememberVectorPainter import com.bitwarden.ui.platform.resource.BitwardenDrawable +import com.bitwarden.ui.platform.theme.BitwardenTheme /** * A header that can be expanded and collapsed. @@ -54,15 +54,15 @@ fun AuthenticatorExpandingHeader( ) Text( text = label, - style = MaterialTheme.typography.labelMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, + style = BitwardenTheme.typography.labelMedium, + color = BitwardenTheme.colorScheme.text.secondary, modifier = Modifier.weight(1f, fill = false), ) Spacer(modifier = Modifier.width(width = 8.dp)) Icon( painter = rememberVectorPainter(id = BitwardenDrawable.ic_chevron_up_small), contentDescription = null, - tint = MaterialTheme.colorScheme.onSurfaceVariant, + tint = BitwardenTheme.colorScheme.icon.primary, modifier = Modifier.rotate(degrees = iconRotationDegrees.value), ) } diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/header/BitwardenListHeaderText.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/header/BitwardenListHeaderText.kt deleted file mode 100644 index 434235aeffd..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/header/BitwardenListHeaderText.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.header - -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.bitwarden.authenticator.ui.platform.theme.AuthenticatorTheme - -/** - * Represents a Bitwarden-styled label text. - * - * @param label The text content for the label. - * @param modifier The [Modifier] to be applied to the label. - */ -@Composable -fun BitwardenListHeaderText( - label: String, - modifier: Modifier = Modifier, -) { - Text( - text = label, - style = MaterialTheme.typography.labelMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = modifier.padding( - top = 12.dp, - bottom = 4.dp, - ), - ) -} - -@Preview(showBackground = true) -@Composable -private fun BitwardenListHeaderText_preview() { - AuthenticatorTheme { - BitwardenListHeaderText( - label = "Sample Label", - modifier = Modifier, - ) - } -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/header/BitwardenListHeaderTextWithSupportLabel.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/header/BitwardenListHeaderTextWithSupportLabel.kt deleted file mode 100644 index 5bb48f07c67..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/header/BitwardenListHeaderTextWithSupportLabel.kt +++ /dev/null @@ -1,62 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.header - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.bitwarden.authenticator.ui.platform.theme.AuthenticatorTheme - -/** - * Represents a Bitwarden-styled label text. - * - * @param label The text content for the label. - * @param supportingLabel The text for the supporting label. - * @param modifier The [Modifier] to be applied to the label. - */ -@Composable -fun BitwardenListHeaderTextWithSupportLabel( - label: String, - supportingLabel: String, - modifier: Modifier = Modifier, -) { - Row( - modifier = modifier - .padding( - top = 12.dp, - bottom = 4.dp, - end = 8.dp, - ) - .semantics(mergeDescendants = true) { }, - horizontalArrangement = Arrangement.SpaceBetween, - ) { - Text( - text = label, - style = MaterialTheme.typography.labelMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - - Text( - text = supportingLabel, - style = MaterialTheme.typography.labelSmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } -} - -@Preview(showBackground = true) -@Composable -private fun BitwardenListHeaderTextWithSupportLabel_preview() { - AuthenticatorTheme { - BitwardenListHeaderTextWithSupportLabel( - label = "Sample Label", - supportingLabel = "0", - modifier = Modifier, - ) - } -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/icon/BitwardenIconButtonWithResource.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/icon/BitwardenIconButtonWithResource.kt deleted file mode 100644 index 468c97a549b..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/icon/BitwardenIconButtonWithResource.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.icon - -import androidx.compose.material3.FilledIconButton -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButtonColors -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.tooling.preview.Preview -import com.bitwarden.authenticator.ui.platform.components.model.IconResource -import com.bitwarden.authenticator.ui.platform.theme.AuthenticatorTheme -import com.bitwarden.ui.platform.resource.BitwardenDrawable - -/** - * An icon button that displays an icon from the provided [IconResource]. - * - * @param iconRes Icon to display on the button. - * @param onClick Callback for when the icon button is clicked. - * @param isEnabled Whether or not the button should be enabled. - * @param modifier A [Modifier] for the composable. - */ -@Composable -fun BitwardenIconButtonWithResource( - iconRes: IconResource, - onClick: () -> Unit, - modifier: Modifier = Modifier, - isEnabled: Boolean = true, -) { - FilledIconButton( - modifier = modifier.semantics(mergeDescendants = true) {}, - onClick = onClick, - colors = IconButtonColors( - containerColor = MaterialTheme.colorScheme.secondaryContainer, - contentColor = MaterialTheme.colorScheme.onSecondaryContainer, - disabledContainerColor = MaterialTheme.colorScheme.onSurface.copy(alpha = .12f), - disabledContentColor = MaterialTheme.colorScheme.onSecondaryContainer, - ), - enabled = isEnabled, - ) { - Icon( - painter = iconRes.iconPainter, - contentDescription = iconRes.contentDescription, - ) - } -} - -@Preview(showBackground = true) -@Composable -private fun BitwardenIconButtonWithResource_preview() { - AuthenticatorTheme { - BitwardenIconButtonWithResource( - iconRes = IconResource( - iconPainter = painterResource(id = BitwardenDrawable.ic_tooltip), - contentDescription = "Sample Icon", - ), - onClick = {}, - ) - } -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/indicator/AuthenticatorCircularCountdownIndicator.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/indicator/AuthenticatorCircularCountdownIndicator.kt deleted file mode 100644 index 46f0f5899d5..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/indicator/AuthenticatorCircularCountdownIndicator.kt +++ /dev/null @@ -1,70 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.indicator - -import androidx.compose.animation.core.LinearOutSlowInEasing -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.tween -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.size -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.StrokeCap -import androidx.compose.ui.unit.dp - -/** - * A countdown timer displayed to the user. - * - * @param timeLeftSeconds The seconds left on the timer. - * @param periodSeconds The period for the timer countdown. - * @param modifier A [Modifier] for the composable. - */ -@Composable -fun AuthenticatorCircularCountdownIndicator( - modifier: Modifier = Modifier, - timeLeftSeconds: Int, - periodSeconds: Int, - alertThresholdSeconds: Int = -1, - alertIndicatorColor: Color = MaterialTheme.colorScheme.error, -) { - val progressAnimate by animateFloatAsState( - targetValue = timeLeftSeconds.toFloat() / periodSeconds, - animationSpec = tween( - durationMillis = periodSeconds, - delayMillis = 0, - easing = LinearOutSlowInEasing, - ), - label = "CircularCountDownAnimation", - ) - - Box( - contentAlignment = Alignment.Center, - modifier = modifier, - ) { - CircularProgressIndicator( - progress = { progressAnimate }, - modifier = Modifier.size(size = 30.dp), - color = if (timeLeftSeconds > alertThresholdSeconds) { - MaterialTheme.colorScheme.primary - } else { - alertIndicatorColor - }, - strokeWidth = 3.dp, - strokeCap = StrokeCap.Round, - ) - - Text( - text = timeLeftSeconds.toString(), - style = MaterialTheme.typography.bodySmall, - color = if (timeLeftSeconds > alertThresholdSeconds) { - MaterialTheme.colorScheme.onSurfaceVariant - } else { - alertIndicatorColor - }, - ) - } -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/listitem/BitwardenListItem.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/listitem/BitwardenListItem.kt deleted file mode 100644 index 839dcdc2a9a..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/listitem/BitwardenListItem.kt +++ /dev/null @@ -1,189 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.listitem - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -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.defaultMinSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.ripple -import androidx.compose.runtime.Composable -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.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.testTag -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.bitwarden.authenticator.ui.platform.components.dialog.BitwardenSelectionDialog -import com.bitwarden.authenticator.ui.platform.components.dialog.row.BitwardenBasicDialogRow -import com.bitwarden.authenticator.ui.platform.components.model.IconResource -import com.bitwarden.authenticator.ui.platform.theme.AuthenticatorTheme -import com.bitwarden.ui.platform.components.icon.BitwardenIcon -import com.bitwarden.ui.platform.components.icon.model.IconData -import com.bitwarden.ui.platform.resource.BitwardenDrawable -import com.bitwarden.ui.platform.resource.BitwardenString -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf - -/** - * A Composable function that displays a row item. - * - * @param label The primary text label to display for the item. - * @param startIcon The [Painter] object used to draw the icon at the start of the item. - * @param onClick The lambda to be invoked when the item is clicked. - * @param selectionDataList A list of all the selection items to be displayed in the overflow - * dialog. - * @param modifier An optional [Modifier] for this Composable, defaulting to an empty Modifier. - * This allows the caller to specify things like padding, size, etc. - * @param labelTestTag The optional test tag for the [label]. - * @param optionsTestTag The optional test tag for the options button. - * @param supportingLabel An optional secondary text label to display beneath the label. - * @param supportingLabelTestTag The optional test tag for the [supportingLabel]. - * @param trailingLabelIcons An optional list of small icons to be displayed after the [label]. - */ -@Suppress("LongMethod") -@Composable -fun BitwardenListItem( - label: String, - startIcon: IconData, - onClick: () -> Unit, - selectionDataList: ImmutableList, - modifier: Modifier = Modifier, - labelTestTag: String? = null, - optionsTestTag: String? = null, - supportingLabel: String? = null, - supportingLabelTestTag: String? = null, - trailingLabelIcons: ImmutableList = persistentListOf(), -) { - var shouldShowDialog by rememberSaveable { mutableStateOf(false) } - Row( - modifier = Modifier - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = ripple(color = MaterialTheme.colorScheme.primary), - onClick = onClick, - ) - .defaultMinSize(minHeight = 72.dp) - .padding(vertical = 8.dp) - .then(modifier), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(16.dp), - ) { - BitwardenIcon( - iconData = startIcon, - tint = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.size(24.dp), - ) - - Column(modifier = Modifier.weight(1f)) { - Row( - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = label, - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurface, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier - .semantics { labelTestTag?.let { testTag = it } } - .weight(weight = 1f, fill = false), - ) - - trailingLabelIcons.forEach { iconResource -> - Spacer(modifier = Modifier.width(8.dp)) - Icon( - painter = iconResource.iconPainter, - contentDescription = iconResource.contentDescription, - tint = MaterialTheme.colorScheme.secondary, - modifier = Modifier - .semantics { iconResource.testTag?.let { testTag = it } } - .size(16.dp), - ) - } - } - - supportingLabel?.let { supportLabel -> - Text( - text = supportLabel, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.semantics { supportingLabelTestTag?.let { testTag = it } }, - ) - } - } - - if (selectionDataList.isNotEmpty()) { - IconButton( - onClick = { shouldShowDialog = true }, - modifier = Modifier.semantics { optionsTestTag?.let { testTag = it } }, - ) { - Icon( - painter = painterResource(id = BitwardenDrawable.ic_more_horizontal), - contentDescription = stringResource(id = BitwardenString.options), - tint = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.size(24.dp), - ) - } - } - } - - if (shouldShowDialog) { - BitwardenSelectionDialog( - title = label, - onDismissRequest = { shouldShowDialog = false }, - selectionItems = { - selectionDataList.forEach { itemData -> - BitwardenBasicDialogRow( - modifier = Modifier.semantics { itemData.testTag?.let { testTag = it } }, - text = itemData.text, - onClick = { - shouldShowDialog = false - itemData.onClick() - }, - ) - } - }, - ) - } -} - -/** - * Wrapper for the an individual selection item's data. - */ -data class SelectionItemData( - val text: String, - val onClick: () -> Unit, - val testTag: String? = null, -) - -@Preview(showBackground = true) -@Composable -private fun BitwardenListItem_preview() { - AuthenticatorTheme { - BitwardenListItem( - label = "Sample Label", - supportingLabel = "Jan 3, 2024, 10:35 AM", - startIcon = IconData.Local(BitwardenDrawable.ic_login_item), - onClick = {}, - selectionDataList = persistentListOf(), - ) - } -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/model/IconResource.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/model/IconResource.kt deleted file mode 100644 index ca8e8fd0cfa..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/model/IconResource.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.model - -import androidx.compose.ui.graphics.painter.Painter - -/** - * Data class representing the resources required for an icon. - * - * @property iconPainter Painter for the icon. - * @property contentDescription String for the icon's content description. - * @property testTag The optional test tag to associate with this icon. - */ -data class IconResource( - val iconPainter: Painter, - val contentDescription: String, - val testTag: String? = null, -) diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/row/BitwardenExternalLinkRow.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/row/BitwardenExternalLinkRow.kt deleted file mode 100644 index b769c6422a8..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/row/BitwardenExternalLinkRow.kt +++ /dev/null @@ -1,89 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.row - -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -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.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import com.bitwarden.authenticator.ui.platform.components.dialog.BitwardenTwoButtonDialog -import com.bitwarden.authenticator.ui.platform.theme.AuthenticatorTheme -import com.bitwarden.ui.platform.base.util.mirrorIfRtl -import com.bitwarden.ui.platform.components.util.rememberVectorPainter -import com.bitwarden.ui.platform.resource.BitwardenDrawable -import com.bitwarden.ui.platform.resource.BitwardenString - -/** - * Represents a row of text that can be clicked on and contains an external link. - * A confirmation dialog will always be displayed before [onConfirmClick] is invoked. - * - * @param text The label for the row as a [String]. - * @param onConfirmClick The callback when the confirm button of the dialog is clicked. - * @param modifier The modifier to be applied to the layout. - * @param withDivider Indicates if a divider should be drawn on the bottom of the row, defaults - * to `true`. - * @param dialogTitle The title of the dialog displayed when the user clicks this item. - * @param dialogMessage The message of the dialog displayed when the user clicks this item. - * @param dialogConfirmButtonText The text on the confirm button of the dialog displayed when the - * user clicks this item. - * @param dialogDismissButtonText The text on the dismiss button of the dialog displayed when the - * user clicks this item. - */ -@Composable -fun BitwardenExternalLinkRow( - text: String, - onConfirmClick: () -> Unit, - modifier: Modifier = Modifier, - withDivider: Boolean = true, - dialogTitle: String, - dialogMessage: String, - dialogConfirmButtonText: String = stringResource(id = BitwardenString.continue_text), - dialogDismissButtonText: String = stringResource(id = BitwardenString.cancel), -) { - var shouldShowDialog by rememberSaveable { mutableStateOf(false) } - BitwardenTextRow( - text = text, - onClick = { shouldShowDialog = true }, - modifier = modifier, - withDivider = withDivider, - ) { - Icon( - modifier = Modifier.mirrorIfRtl(), - painter = rememberVectorPainter(id = BitwardenDrawable.ic_external_link), - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurface, - ) - } - - if (shouldShowDialog) { - BitwardenTwoButtonDialog( - title = dialogTitle, - message = dialogMessage, - confirmButtonText = dialogConfirmButtonText, - dismissButtonText = dialogDismissButtonText, - onConfirmClick = { - shouldShowDialog = false - onConfirmClick() - }, - onDismissClick = { shouldShowDialog = false }, - onDismissRequest = { shouldShowDialog = false }, - ) - } -} - -@Preview -@Composable -private fun BitwardenExternalLinkRow_preview() { - AuthenticatorTheme { - BitwardenExternalLinkRow( - text = "Linked Text", - onConfirmClick = { }, - dialogTitle = "", - dialogMessage = "", - ) - } -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/row/BitwardenTextRow.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/row/BitwardenTextRow.kt deleted file mode 100644 index 0a33a63f9f8..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/row/BitwardenTextRow.kt +++ /dev/null @@ -1,91 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.row - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.defaultMinSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.ripple -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.unit.dp - -/** - * Represents a clickable row of text and can contains an optional [content] that appears to the - * right of the [text]. - * - * @param text The label for the row as a [String]. - * @param onClick The callback when the row is clicked. - * @param modifier The modifier to be applied to the layout. - * @param description An optional description label to be displayed below the [text]. - * @param withDivider Indicates if a divider should be drawn on the bottom of the row, defaults - * to `false`. - * @param content The content of the [BitwardenTextRow]. - */ -@Composable -fun BitwardenTextRow( - text: String, - onClick: () -> Unit, - modifier: Modifier = Modifier, - description: AnnotatedString? = null, - withDivider: Boolean = false, - content: (@Composable () -> Unit)? = null, -) { - Box( - contentAlignment = Alignment.BottomCenter, - modifier = modifier - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = ripple(color = MaterialTheme.colorScheme.primary), - onClick = onClick, - ) - .semantics(mergeDescendants = true) { }, - ) { - Row( - modifier = Modifier - .defaultMinSize(minHeight = 56.dp) - .padding(start = 16.dp, end = 24.dp, top = 8.dp, bottom = 8.dp) - .fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - Column( - modifier = Modifier - .padding(end = 16.dp) - .weight(1f), - ) { - Text( - text = text, - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurface, - ) - description?.let { - Text( - text = it, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - } - content?.invoke() - } - if (withDivider) { - HorizontalDivider( - modifier = Modifier.padding(start = 16.dp), - thickness = 1.dp, - color = MaterialTheme.colorScheme.outlineVariant, - ) - } - } -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/scaffold/BitwardenScaffold.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/scaffold/BitwardenScaffold.kt deleted file mode 100644 index 6309683c8e9..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/scaffold/BitwardenScaffold.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.scaffold - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.exclude -import androidx.compose.foundation.layout.navigationBars -import androidx.compose.material3.FabPosition -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.ScaffoldDefaults -import androidx.compose.material3.contentColorFor -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.testTagsAsResourceId - -/** - * Direct passthrough to [Scaffold] but contains a few specific override values. Everything is - * still overridable if necessary. - */ -@Composable -fun BitwardenScaffold( - modifier: Modifier = Modifier, - topBar: @Composable () -> Unit = { }, - bottomBar: @Composable () -> Unit = { }, - snackbarHost: @Composable () -> Unit = { }, - floatingActionButton: @Composable () -> Unit = { }, - floatingActionButtonPosition: FabPosition = FabPosition.End, - containerColor: Color = MaterialTheme.colorScheme.surface, - contentColor: Color = contentColorFor(containerColor), - contentWindowInsets: WindowInsets = ScaffoldDefaults - .contentWindowInsets - .exclude(WindowInsets.navigationBars), - content: @Composable (PaddingValues) -> Unit, -) { - Scaffold( - modifier = Modifier - .semantics { testTagsAsResourceId = true } - .then(modifier), - topBar = topBar, - bottomBar = bottomBar, - snackbarHost = snackbarHost, - floatingActionButton = floatingActionButton, - floatingActionButtonPosition = floatingActionButtonPosition, - containerColor = containerColor, - contentColor = contentColor, - contentWindowInsets = contentWindowInsets, - content = { paddingValues -> - Box { - content(paddingValues) - } - }, - ) -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/scrim/BitwardenAnimatedScrim.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/scrim/BitwardenAnimatedScrim.kt deleted file mode 100644 index 03f0002dce7..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/scrim/BitwardenAnimatedScrim.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.scrim - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Box -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color - -/** - * A scrim that animates its visibility. - * - * @param isVisible Whether or not the scrim should be visible. This controls the animation. - * @param onClick A callback that is triggered when the scrim is clicked. No ripple will be - * performed. - * @param modifier A [Modifier] for the scrim's content. - */ -@Composable -fun BitwardenAnimatedScrim( - isVisible: Boolean, - onClick: () -> Unit, - modifier: Modifier = Modifier, -) { - AnimatedVisibility( - visible = isVisible, - enter = fadeIn(), - exit = fadeOut(), - ) { - Box( - modifier = modifier - .background(Color.Black.copy(alpha = 0.40f)) - .clickable( - interactionSource = remember { MutableInteractionSource() }, - // Clear the ripple - indication = null, - onClick = onClick, - ), - ) - } -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/stepper/BitwardenStepper.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/stepper/BitwardenStepper.kt deleted file mode 100644 index b9a97ff2904..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/stepper/BitwardenStepper.kt +++ /dev/null @@ -1,110 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.stepper - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.testTag -import androidx.compose.ui.text.input.KeyboardType -import com.bitwarden.authenticator.ui.platform.components.field.BitwardenTextFieldWithActions -import com.bitwarden.authenticator.ui.platform.components.icon.BitwardenIconButtonWithResource -import com.bitwarden.authenticator.ui.platform.components.model.IconResource -import com.bitwarden.ui.platform.base.util.ZERO_WIDTH_CHARACTER -import com.bitwarden.ui.platform.base.util.orNullIfBlank -import com.bitwarden.ui.platform.components.util.rememberVectorPainter -import com.bitwarden.ui.platform.resource.BitwardenDrawable - -/** - * Displays a stepper that allows the user to increment and decrement an int value. - * - * @param label Label for the stepper. - * @param value Value to display. Null will display nothing. Will be clamped to [range] before - * display. - * @param onValueChange callback invoked when the user increments or decrements the count. Note - * that this will not be called if the attempts to move value outside of [range]. - * @param modifier Modifier. - * @param range Range of valid values. - * @param isIncrementEnabled whether or not the increment button should be enabled. - * @param isDecrementEnabled whether or not the decrement button should be enabled. - * @param textFieldReadOnly whether or not the text field should be read only. The stepper - * increment and decrement buttons function regardless of this value. - */ -@Suppress("LongMethod") -@Composable -fun BitwardenStepper( - label: String, - value: Int?, - onValueChange: (Int) -> Unit, - modifier: Modifier = Modifier, - range: ClosedRange = 1..Int.MAX_VALUE, - isIncrementEnabled: Boolean = true, - isDecrementEnabled: Boolean = true, - textFieldReadOnly: Boolean = true, - stepperActionsTestTag: String? = null, - increaseButtonTestTag: String? = null, - decreaseButtonTestTag: String? = null, -) { - val clampedValue = value?.coerceIn(range) - if (clampedValue != value && clampedValue != null) { - onValueChange(clampedValue) - } - BitwardenTextFieldWithActions( - label = label, - // We use the zero width character instead of an empty string to make sure label is shown - // small and above the input - value = clampedValue - ?.toString() - ?: ZERO_WIDTH_CHARACTER, - actionsTestTag = stepperActionsTestTag, - actions = { - BitwardenIconButtonWithResource( - iconRes = IconResource( - iconPainter = rememberVectorPainter(id = BitwardenDrawable.ic_minus), - contentDescription = "\u2212", - ), - onClick = { - val decrementedValue = ((value ?: 0) - 1).coerceIn(range) - if (decrementedValue != value) { - onValueChange(decrementedValue) - } - }, - isEnabled = isDecrementEnabled, - modifier = Modifier.semantics { - if (decreaseButtonTestTag != null) { - testTag = decreaseButtonTestTag - } - }, - ) - BitwardenIconButtonWithResource( - iconRes = IconResource( - iconPainter = rememberVectorPainter(id = BitwardenDrawable.ic_plus), - contentDescription = "+", - ), - onClick = { - val incrementedValue = ((value ?: 0) + 1).coerceIn(range) - if (incrementedValue != value) { - onValueChange(incrementedValue) - } - }, - isEnabled = isIncrementEnabled, - modifier = Modifier.semantics { - if (increaseButtonTestTag != null) { - testTag = increaseButtonTestTag - } - }, - ) - }, - readOnly = textFieldReadOnly, - keyboardType = KeyboardType.Number, - onValueChange = { newValue -> - onValueChange( - newValue - // Make sure the placeholder is gone, since it will mess up the int conversion - .replace(ZERO_WIDTH_CHARACTER, "") - .orNullIfBlank() - ?.let { it.toIntOrNull()?.coerceIn(range) ?: value } - ?: range.start, - ) - }, - modifier = modifier, - ) -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/toggle/BitwardenSwitch.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/toggle/BitwardenSwitch.kt deleted file mode 100644 index 1ba749c8d0f..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/toggle/BitwardenSwitch.kt +++ /dev/null @@ -1,118 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.toggle - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -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.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Switch -import androidx.compose.material3.Text -import androidx.compose.material3.ripple -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.toggleableState -import androidx.compose.ui.state.ToggleableState -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp - -/** - * Represents a Bitwarden-styled [Switch]. - * - * @param label The label for the switch. - * @param isChecked Whether or not the switch is currently checked. - * @param onCheckedChange A callback for when the checked state changes. - * @param modifier The [Modifier] to be applied to the button. - * @param description The description of the switch to be displayed below the [label]. - */ -@Composable -fun BitwardenSwitch( - label: String, - isChecked: Boolean, - onCheckedChange: ((Boolean) -> Unit)?, - modifier: Modifier = Modifier, - description: String? = null, -) { - Row( - horizontalArrangement = Arrangement.Start, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .run { - if (onCheckedChange != null) { - this.clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = ripple(color = MaterialTheme.colorScheme.primary), - onClick = { onCheckedChange.invoke(!isChecked) }, - ) - } else { - this - } - } - .semantics(mergeDescendants = true) { - toggleableState = ToggleableState(isChecked) - } - .then(modifier), - ) { - Switch( - modifier = Modifier - .padding(vertical = 8.dp) - .height(32.dp) - .width(52.dp), - checked = isChecked, - onCheckedChange = null, - ) - - Spacer(modifier = Modifier.width(16.dp)) - - Column { - Spacer(modifier = Modifier.height(4.dp)) - - Text( - text = label, - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurface, - ) - - description?.let { - Text( - text = it, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - - Spacer(modifier = Modifier.height(4.dp)) - } - } -} - -@Preview(showBackground = true) -@Composable -private fun BitwardenSwitch_preview_isChecked() { - BitwardenSwitch( - label = "Label", - description = "Description", - isChecked = true, - onCheckedChange = {}, - modifier = Modifier.fillMaxWidth(), - ) -} - -@Preview(showBackground = true) -@Composable -private fun BitwardenSwitch_preview_isNotChecked() { - BitwardenSwitch( - label = "Label", - isChecked = false, - onCheckedChange = {}, - modifier = Modifier.fillMaxWidth(), - ) -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/toggle/BitwardenWideSwitch.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/toggle/BitwardenWideSwitch.kt deleted file mode 100644 index 7088a5df2ae..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/toggle/BitwardenWideSwitch.kt +++ /dev/null @@ -1,130 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.components.toggle - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -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.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Switch -import androidx.compose.material3.Text -import androidx.compose.material3.ripple -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.toggleableState -import androidx.compose.ui.state.ToggleableState -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.bitwarden.authenticator.ui.platform.theme.AuthenticatorTheme - -/** - * A wide custom switch composable - * - * @param label The descriptive text label to be displayed adjacent to the switch. - * @param isChecked The current state of the switch (either checked or unchecked). - * @param onCheckedChange A lambda that is invoked when the switch's state changes. - * @param modifier A [Modifier] that you can use to apply custom modifications to the composable. - * @param description An optional description label to be displayed below the [label]. - * @param contentDescription A description of the switch's UI for accessibility purposes. - * @param readOnly Disables the click functionality without modifying the other UI characteristics. - * @param enabled Whether or not this switch is enabled. This is similar to setting [readOnly] but - * comes with some additional visual changes. - */ -@Composable -fun BitwardenWideSwitch( - label: String, - isChecked: Boolean, - onCheckedChange: ((Boolean) -> Unit)?, - modifier: Modifier = Modifier, - description: String? = null, - contentDescription: String? = null, - readOnly: Boolean = false, - enabled: Boolean = true, -) { - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .wrapContentHeight() - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = ripple(color = MaterialTheme.colorScheme.primary), - onClick = { onCheckedChange?.invoke(!isChecked) }, - enabled = !readOnly && enabled, - ) - .semantics(mergeDescendants = true) { - toggleableState = ToggleableState(isChecked) - contentDescription?.let { this.contentDescription = it } - } - .then(modifier), - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp), - ) { - Text( - text = label, - style = MaterialTheme.typography.bodyLarge, - color = if (enabled) { - MaterialTheme.colorScheme.onSurface - } else { - MaterialTheme.colorScheme.outline - }, - ) - description?.let { - Text( - text = it, - style = MaterialTheme.typography.bodyMedium, - color = if (enabled) { - MaterialTheme.colorScheme.onSurfaceVariant - } else { - MaterialTheme.colorScheme.outline - }, - ) - } - } - - Spacer(modifier = Modifier.width(16.dp)) - - Switch( - modifier = Modifier - .height(56.dp), - checked = isChecked, - onCheckedChange = null, - ) - } -} - -@Preview -@Composable -private fun BitwardenWideSwitch_preview_isChecked() { - AuthenticatorTheme { - BitwardenWideSwitch( - label = "Label", - isChecked = true, - onCheckedChange = {}, - ) - } -} - -@Preview -@Composable -private fun BitwardenWideSwitch_preview_isNotChecked() { - AuthenticatorTheme { - BitwardenWideSwitch( - label = "Label", - isChecked = false, - onCheckedChange = {}, - ) - } -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/debugmenu/DebugMenuScreen.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/debugmenu/DebugMenuScreen.kt index 120ad7c86f7..901fff669a6 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/debugmenu/DebugMenuScreen.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/debugmenu/DebugMenuScreen.kt @@ -5,11 +5,11 @@ 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.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -21,21 +21,21 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.bitwarden.authenticator.ui.platform.components.appbar.AuthenticatorTopAppBar -import com.bitwarden.authenticator.ui.platform.components.button.AuthenticatorFilledButton -import com.bitwarden.authenticator.ui.platform.components.content.AuthenticatorErrorContent -import com.bitwarden.authenticator.ui.platform.components.header.BitwardenListHeaderText -import com.bitwarden.authenticator.ui.platform.components.scaffold.BitwardenScaffold -import com.bitwarden.authenticator.ui.platform.feature.debugmenu.components.ListItemContent -import com.bitwarden.authenticator.ui.platform.theme.AuthenticatorTheme import com.bitwarden.core.data.manager.model.FlagKey import com.bitwarden.ui.platform.base.util.EventsEffect import com.bitwarden.ui.platform.base.util.standardHorizontalMargin +import com.bitwarden.ui.platform.base.util.toListItemCardStyle +import com.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar import com.bitwarden.ui.platform.components.appbar.NavigationIcon -import com.bitwarden.ui.platform.components.divider.BitwardenHorizontalDivider +import com.bitwarden.ui.platform.components.button.BitwardenFilledButton +import com.bitwarden.ui.platform.components.content.BitwardenErrorContent +import com.bitwarden.ui.platform.components.debug.ListItemContent +import com.bitwarden.ui.platform.components.header.BitwardenListHeaderText +import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold import com.bitwarden.ui.platform.components.util.rememberVectorPainter import com.bitwarden.ui.platform.resource.BitwardenDrawable import com.bitwarden.ui.platform.resource.BitwardenString +import com.bitwarden.ui.platform.theme.BitwardenTheme /** * Top level screen for the debug menu. @@ -60,7 +60,7 @@ fun DebugMenuScreen( .fillMaxSize() .nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { - AuthenticatorTopAppBar( + BitwardenTopAppBar( title = stringResource(BitwardenString.debug_menu), scrollBehavior = scrollBehavior, navigationIcon = NavigationIcon( @@ -74,13 +74,11 @@ fun DebugMenuScreen( ), ) }, - ) { innerPadding -> + ) { if (state.featureFlags.isEmpty()) { - AuthenticatorErrorContent( + BitwardenErrorContent( message = stringResource(id = BitwardenString.empty_item_list), - modifier = Modifier - .padding(paddingValues = innerPadding) - .fillMaxSize(), + modifier = Modifier.fillMaxSize(), ) } else { FeatureFlagContent( @@ -94,8 +92,7 @@ fun DebugMenuScreen( { viewModel.trySendAction(DebugMenuAction.ResetFeatureFlagValues) } }, modifier = Modifier - .verticalScroll(rememberScrollState()) - .padding(paddingValues = innerPadding), + .verticalScroll(rememberScrollState()), ) } } @@ -111,29 +108,26 @@ private fun FeatureFlagContent( Column( modifier = modifier, ) { - Spacer(modifier = Modifier.height(8.dp)) + Spacer(modifier = Modifier.height(12.dp)) BitwardenListHeaderText( label = stringResource(BitwardenString.feature_flags), - modifier = Modifier.standardHorizontalMargin(), + modifier = Modifier + .standardHorizontalMargin() + .padding(horizontal = 16.dp), ) Spacer(modifier = Modifier.height(8.dp)) - BitwardenHorizontalDivider( - color = MaterialTheme.colorScheme.outline, - thickness = 1.dp, - ) - featureFlagMap.forEach { featureFlag -> + featureFlagMap.onEach { featureFlag -> featureFlag.key.ListItemContent( currentValue = featureFlag.value, onValueChange = onValueChange, + cardStyle = featureFlagMap.keys.toListItemCardStyle( + index = featureFlagMap.keys.indexOf(element = featureFlag.key), + ), modifier = Modifier.standardHorizontalMargin(), ) - BitwardenHorizontalDivider( - color = MaterialTheme.colorScheme.outline, - thickness = 1.dp, - ) } Spacer(modifier = Modifier.height(12.dp)) - AuthenticatorFilledButton( + BitwardenFilledButton( label = stringResource(BitwardenString.reset_values), onClick = onResetValues, modifier = Modifier @@ -141,13 +135,14 @@ private fun FeatureFlagContent( .fillMaxWidth(), ) Spacer(modifier = Modifier.height(8.dp)) + Spacer(modifier = Modifier.navigationBarsPadding()) } } @Preview(showBackground = true) @Composable private fun FeatureFlagContent_preview() { - AuthenticatorTheme { + BitwardenTheme { FeatureFlagContent( featureFlagMap = mapOf( FlagKey.BitwardenAuthenticationEnabled to true, diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/debugmenu/components/FeatureFlagListItems.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/debugmenu/components/FeatureFlagListItems.kt deleted file mode 100644 index 73057349579..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/debugmenu/components/FeatureFlagListItems.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.feature.debugmenu.components - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import com.bitwarden.authenticator.ui.platform.components.toggle.BitwardenWideSwitch -import com.bitwarden.core.data.manager.model.FlagKey -import com.bitwarden.ui.platform.resource.BitwardenString - -/** - * Creates a list item for a [FlagKey]. - */ -@Suppress("UNCHECKED_CAST") -@Composable -fun FlagKey.ListItemContent( - currentValue: T, - onValueChange: (key: FlagKey, value: T) -> Unit, - modifier: Modifier = Modifier, -) = when (val flagKey = this) { - is FlagKey.DummyInt, - FlagKey.DummyString, - -> Unit - - FlagKey.DummyBoolean, - FlagKey.BitwardenAuthenticationEnabled, - FlagKey.CipherKeyEncryption, - FlagKey.CredentialExchangeProtocolExport, - FlagKey.CredentialExchangeProtocolImport, - -> BooleanFlagItem( - label = flagKey.getDisplayLabel(), - key = flagKey as FlagKey, - currentValue = currentValue as Boolean, - onValueChange = onValueChange as (FlagKey, Boolean) -> Unit, - modifier = modifier, - ) -} - -/** - * The UI layout for a boolean backed flag key. - */ -@Composable -private fun BooleanFlagItem( - label: String, - key: FlagKey, - currentValue: Boolean, - onValueChange: (key: FlagKey, value: Boolean) -> Unit, - modifier: Modifier = Modifier, -) { - BitwardenWideSwitch( - label = label, - isChecked = currentValue, - onCheckedChange = { - onValueChange(key, it) - }, - modifier = modifier, - ) -} - -@Composable -private fun FlagKey.getDisplayLabel(): String = when (this) { - FlagKey.DummyBoolean, - is FlagKey.DummyInt, - FlagKey.DummyString, - -> this.keyName - - FlagKey.CredentialExchangeProtocolImport -> stringResource(BitwardenString.cxp_import) - FlagKey.CredentialExchangeProtocolExport -> stringResource(BitwardenString.cxp_export) - FlagKey.CipherKeyEncryption -> stringResource(BitwardenString.cipher_key_encryption) - FlagKey.BitwardenAuthenticationEnabled -> { - stringResource(BitwardenString.bitwarden_authentication_enabled) - } -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsScreen.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsScreen.kt index cdd10fd0a50..0a5625f1dc8 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsScreen.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsScreen.kt @@ -4,29 +4,24 @@ package com.bitwarden.authenticator.ui.platform.feature.settings import android.content.Intent import android.content.res.Resources -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.rememberTopAppBarState -import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -42,38 +37,39 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.testTag import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.net.toUri import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.bitwarden.authenticator.ui.platform.components.appbar.AuthenticatorMediumTopAppBar -import com.bitwarden.authenticator.ui.platform.components.dialog.BitwardenSelectionDialog -import com.bitwarden.authenticator.ui.platform.components.dialog.BitwardenSelectionRow -import com.bitwarden.authenticator.ui.platform.components.header.BitwardenListHeaderText -import com.bitwarden.authenticator.ui.platform.components.row.BitwardenExternalLinkRow -import com.bitwarden.authenticator.ui.platform.components.row.BitwardenTextRow -import com.bitwarden.authenticator.ui.platform.components.scaffold.BitwardenScaffold -import com.bitwarden.authenticator.ui.platform.components.toggle.BitwardenWideSwitch import com.bitwarden.authenticator.ui.platform.composition.LocalBiometricsManager import com.bitwarden.authenticator.ui.platform.feature.settings.data.model.DefaultSaveOption import com.bitwarden.authenticator.ui.platform.manager.biometrics.BiometricsManager -import com.bitwarden.authenticator.ui.platform.theme.AuthenticatorTheme import com.bitwarden.authenticator.ui.platform.util.displayLabel import com.bitwarden.ui.platform.base.util.EventsEffect import com.bitwarden.ui.platform.base.util.annotatedStringResource +import com.bitwarden.ui.platform.base.util.cardStyle import com.bitwarden.ui.platform.base.util.mirrorIfRtl -import com.bitwarden.ui.platform.base.util.spanStyleOf +import com.bitwarden.ui.platform.base.util.standardHorizontalMargin +import com.bitwarden.ui.platform.components.appbar.BitwardenMediumTopAppBar +import com.bitwarden.ui.platform.components.dropdown.BitwardenMultiSelectButton +import com.bitwarden.ui.platform.components.header.BitwardenListHeaderText +import com.bitwarden.ui.platform.components.model.CardStyle +import com.bitwarden.ui.platform.components.row.BitwardenExternalLinkRow +import com.bitwarden.ui.platform.components.row.BitwardenTextRow +import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold +import com.bitwarden.ui.platform.components.toggle.BitwardenSwitch import com.bitwarden.ui.platform.components.util.rememberVectorPainter import com.bitwarden.ui.platform.composition.LocalIntentManager import com.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme import com.bitwarden.ui.platform.manager.IntentManager import com.bitwarden.ui.platform.resource.BitwardenDrawable import com.bitwarden.ui.platform.resource.BitwardenString +import com.bitwarden.ui.platform.theme.BitwardenTheme import com.bitwarden.ui.platform.util.displayLabel import com.bitwarden.ui.util.Text import com.bitwarden.ui.util.asText +import kotlinx.collections.immutable.toImmutableList /** * Display the settings screen. @@ -90,8 +86,7 @@ fun SettingsScreen( onNavigateToImport: () -> Unit, ) { val state by viewModel.stateFlow.collectAsStateWithLifecycle() - val scrollBehavior = - TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState()) + val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() EventsEffect(viewModel = viewModel) { event -> when (event) { @@ -141,15 +136,14 @@ fun SettingsScreen( .fillMaxSize() .nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { - AuthenticatorMediumTopAppBar( + BitwardenMediumTopAppBar( title = stringResource(id = BitwardenString.settings), scrollBehavior = scrollBehavior, ) }, - ) { innerPadding -> + ) { Column( modifier = Modifier - .padding(innerPadding) .fillMaxSize() .verticalScroll(state = rememberScrollState()), ) { @@ -245,10 +239,12 @@ fun SettingsScreen( Text( modifier = Modifier.padding(end = 16.dp), text = state.copyrightInfo.invoke(), - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurface, + style = BitwardenTheme.typography.bodySmall, + color = BitwardenTheme.colorScheme.text.primary, ) } + Spacer(modifier = Modifier.height(height = 12.dp)) + Spacer(modifier = Modifier.navigationBarsPadding()) } } } @@ -262,9 +258,11 @@ private fun SecuritySettings( onBiometricToggle: (Boolean) -> Unit, ) { if (!biometricsManager.isBiometricsSupported) return - + Spacer(modifier = Modifier.height(height = 12.dp)) BitwardenListHeaderText( - modifier = Modifier.padding(horizontal = 16.dp), + modifier = Modifier + .standardHorizontalMargin() + .padding(horizontal = 16.dp), label = stringResource(id = BitwardenString.security), ) Spacer(modifier = Modifier.height(8.dp)) @@ -272,7 +270,7 @@ private fun SecuritySettings( modifier = Modifier .testTag("UnlockWithBiometricsSwitch") .fillMaxWidth() - .padding(horizontal = 16.dp), + .standardHorizontalMargin(), isChecked = state.isUnlockWithBiometricsEnabled, onBiometricToggle = { onBiometricToggle(it) }, biometricsManager = biometricsManager, @@ -285,8 +283,7 @@ private fun SecuritySettings( @Composable @Suppress("LongMethod") -private fun VaultSettings( - modifier: Modifier = Modifier, +private fun ColumnScope.VaultSettings( defaultSaveOption: DefaultSaveOption, onExportClick: () -> Unit, onImportClick: () -> Unit, @@ -298,70 +295,66 @@ private fun VaultSettings( shouldShowDefaultSaveOptions: Boolean, ) { BitwardenListHeaderText( - modifier = Modifier.padding(horizontal = 16.dp), + modifier = Modifier + .standardHorizontalMargin() + .padding(horizontal = 16.dp), label = stringResource(id = BitwardenString.data), ) - Spacer(modifier = Modifier.height(8.dp)) + Spacer(modifier = Modifier.height(height = 8.dp)) BitwardenTextRow( text = stringResource(id = BitwardenString.import_vault), onClick = onImportClick, - modifier = modifier - .semantics { testTag = "Import" }, - withDivider = true, + modifier = Modifier + .standardHorizontalMargin() + .testTag("Import"), + cardStyle = CardStyle.Top(), content = { Icon( - modifier = Modifier - .mirrorIfRtl() - .size(24.dp), - painter = painterResource(id = BitwardenDrawable.ic_navigate_next), + modifier = Modifier.mirrorIfRtl(), + painter = painterResource(id = BitwardenDrawable.ic_chevron_right), contentDescription = null, - tint = MaterialTheme.colorScheme.onSurface, + tint = BitwardenTheme.colorScheme.icon.primary, ) }, ) - Spacer(modifier = Modifier.height(8.dp)) BitwardenTextRow( text = stringResource(id = BitwardenString.export), onClick = onExportClick, - modifier = modifier - .semantics { testTag = "Export" }, - withDivider = true, + modifier = Modifier + .standardHorizontalMargin() + .testTag("Export"), + cardStyle = CardStyle.Middle(), content = { Icon( - modifier = Modifier - .mirrorIfRtl() - .size(24.dp), - painter = painterResource(id = BitwardenDrawable.ic_navigate_next), + modifier = Modifier.mirrorIfRtl(), + painter = painterResource(id = BitwardenDrawable.ic_chevron_right), contentDescription = null, - tint = MaterialTheme.colorScheme.onSurface, + tint = BitwardenTheme.colorScheme.icon.primary, ) }, ) - Spacer(modifier = Modifier.height(8.dp)) BitwardenExternalLinkRow( text = stringResource(BitwardenString.backup), onConfirmClick = onBackupClick, - modifier = modifier - .semantics { testTag = "Backup" }, + modifier = Modifier + .standardHorizontalMargin() + .testTag("Backup"), + withDivider = false, dialogTitle = stringResource(BitwardenString.data_backup_title), dialogMessage = stringResource(BitwardenString.data_backup_message), dialogConfirmButtonText = stringResource(BitwardenString.learn_more), dialogDismissButtonText = stringResource(BitwardenString.okay), + cardStyle = if (shouldShowSyncWithBitwardenApp || shouldShowDefaultSaveOptions) { + CardStyle.Middle() + } else { + CardStyle.Bottom + }, ) if (shouldShowSyncWithBitwardenApp) { - Spacer(modifier = Modifier.height(8.dp)) BitwardenTextRow( text = stringResource(id = BitwardenString.sync_with_bitwarden_app), description = annotatedStringResource( id = BitwardenString.learn_more_link, - style = spanStyleOf( - color = MaterialTheme.colorScheme.onSurfaceVariant, - textStyle = MaterialTheme.typography.bodyMedium, - ), - linkHighlightStyle = spanStyleOf( - color = MaterialTheme.colorScheme.primary, - textStyle = MaterialTheme.typography.labelLarge, - ), onAnnotationClick = { when (it) { "learnMore" -> onSyncLearnMoreClick() @@ -369,14 +362,18 @@ private fun VaultSettings( }, ), onClick = onSyncWithBitwardenClick, - modifier = modifier, - withDivider = true, + modifier = Modifier.standardHorizontalMargin(), + cardStyle = if (shouldShowDefaultSaveOptions) { + CardStyle.Middle() + } else { + CardStyle.Bottom + }, content = { Icon( modifier = Modifier.mirrorIfRtl(), painter = painterResource(id = BitwardenDrawable.ic_external_link), contentDescription = null, - tint = MaterialTheme.colorScheme.onSurface, + tint = BitwardenTheme.colorScheme.icon.primary, ) }, ) @@ -385,6 +382,7 @@ private fun VaultSettings( DefaultSaveOptionSelectionRow( currentSelection = defaultSaveOption, onSaveOptionUpdated = onDefaultSaveOptionUpdated, + modifier = Modifier.standardHorizontalMargin(), ) } } @@ -394,45 +392,22 @@ private fun DefaultSaveOptionSelectionRow( currentSelection: DefaultSaveOption, onSaveOptionUpdated: (DefaultSaveOption) -> Unit, modifier: Modifier = Modifier, + resources: Resources = LocalResources.current, ) { - var shouldShowDefaultSaveOptionDialog by remember { mutableStateOf(false) } - - BitwardenTextRow( - text = stringResource(id = BitwardenString.default_save_option), - onClick = { shouldShowDefaultSaveOptionDialog = true }, + BitwardenMultiSelectButton( + label = stringResource(id = BitwardenString.default_save_option), + dialogSubtitle = stringResource(id = BitwardenString.default_save_options_subtitle), + options = DefaultSaveOption.entries.map { it.displayLabel() }.toImmutableList(), + selectedOption = currentSelection.displayLabel(), + onOptionSelected = { selectedOptionLabel -> + val selectedOption = DefaultSaveOption + .entries + .first { it.displayLabel(resources) == selectedOptionLabel } + onSaveOptionUpdated(selectedOption) + }, + cardStyle = CardStyle.Bottom, modifier = modifier, - withDivider = true, - ) { - Text( - modifier = Modifier.padding(vertical = 20.dp), - text = currentSelection.displayLabel(), - style = MaterialTheme.typography.labelSmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - var dialogSelection by remember { mutableStateOf(currentSelection) } - if (shouldShowDefaultSaveOptionDialog) { - BitwardenSelectionDialog( - title = stringResource(id = BitwardenString.default_save_option), - subtitle = stringResource(id = BitwardenString.default_save_options_subtitle), - dismissLabel = stringResource(id = BitwardenString.confirm), - onDismissRequest = { shouldShowDefaultSaveOptionDialog = false }, - onDismissActionClick = { - onSaveOptionUpdated(dialogSelection) - shouldShowDefaultSaveOptionDialog = false - }, - ) { - DefaultSaveOption.entries.forEach { option -> - BitwardenSelectionRow( - text = option.displayLabel, - isSelected = option == dialogSelection, - onClick = { - dialogSelection = DefaultSaveOption.entries.first { it == option } - }, - ) - } - } - } + ) } @Composable @@ -444,8 +419,9 @@ private fun UnlockWithBiometricsRow( ) { if (!biometricsManager.isBiometricsSupported) return var showBiometricsPrompt by rememberSaveable { mutableStateOf(false) } - BitwardenWideSwitch( + BitwardenSwitch( modifier = modifier, + cardStyle = CardStyle.Full, label = stringResource(BitwardenString.unlock_with_biometrics), isChecked = isChecked || showBiometricsPrompt, onCheckedChange = { toggled -> @@ -472,19 +448,23 @@ private fun UnlockWithBiometricsRow( //region Appearance settings @Composable -private fun AppearanceSettings( +private fun ColumnScope.AppearanceSettings( state: SettingsState, onThemeSelection: (theme: AppTheme) -> Unit, ) { BitwardenListHeaderText( - modifier = Modifier.padding(horizontal = 16.dp), + modifier = Modifier + .standardHorizontalMargin() + .padding(horizontal = 16.dp), label = stringResource(id = BitwardenString.appearance), ) + Spacer(modifier = Modifier.height(height = 8.dp)) ThemeSelectionRow( currentSelection = state.appearance.theme, onThemeSelection = onThemeSelection, modifier = Modifier - .semantics { testTag = "ThemeChooser" } + .testTag("ThemeChooser") + .standardHorizontalMargin() .fillMaxWidth(), ) } @@ -494,46 +474,21 @@ private fun ThemeSelectionRow( currentSelection: AppTheme, onThemeSelection: (AppTheme) -> Unit, modifier: Modifier = Modifier, + resources: Resources = LocalResources.current, ) { - var shouldShowThemeSelectionDialog by remember { mutableStateOf(false) } - - BitwardenTextRow( - text = stringResource(id = BitwardenString.theme), - onClick = { shouldShowThemeSelectionDialog = true }, + BitwardenMultiSelectButton( + label = stringResource(id = BitwardenString.theme), + options = AppTheme.entries.map { it.displayLabel() }.toImmutableList(), + selectedOption = currentSelection.displayLabel(), + onOptionSelected = { selectedOptionLabel -> + val selectedOption = AppTheme + .entries + .first { it.displayLabel(resources) == selectedOptionLabel } + onThemeSelection(selectedOption) + }, + cardStyle = CardStyle.Full, modifier = modifier, - withDivider = true, - ) { - Icon( - modifier = Modifier - .mirrorIfRtl() - .size(24.dp), - painter = painterResource( - id = BitwardenDrawable.ic_navigate_next, - ), - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurface, - ) - } - - if (shouldShowThemeSelectionDialog) { - BitwardenSelectionDialog( - title = stringResource(id = BitwardenString.theme), - onDismissRequest = { shouldShowThemeSelectionDialog = false }, - ) { - AppTheme.entries.forEach { option -> - BitwardenSelectionRow( - text = option.displayLabel, - isSelected = option == currentSelection, - onClick = { - shouldShowThemeSelectionDialog = false - onThemeSelection( - AppTheme.entries.first { it == option }, - ) - }, - ) - } - } - } + ) } //endregion Appearance settings @@ -541,32 +496,37 @@ private fun ThemeSelectionRow( //region Help settings @Composable -private fun HelpSettings( - modifier: Modifier = Modifier, +private fun ColumnScope.HelpSettings( onTutorialClick: () -> Unit, onHelpCenterClick: () -> Unit, ) { BitwardenListHeaderText( - modifier = Modifier.padding(horizontal = 16.dp), + modifier = Modifier + .standardHorizontalMargin() + .padding(horizontal = 16.dp), label = stringResource(id = BitwardenString.help), ) + Spacer(modifier = Modifier.height(height = 8.dp)) BitwardenTextRow( text = stringResource(id = BitwardenString.launch_tutorial), onClick = onTutorialClick, - modifier = modifier - .semantics { testTag = "LaunchTutorial" }, - withDivider = true, + modifier = Modifier + .testTag("LaunchTutorial") + .standardHorizontalMargin(), + cardStyle = CardStyle.Top(), ) - Spacer(modifier = Modifier.height(8.dp)) BitwardenExternalLinkRow( text = stringResource(id = BitwardenString.bitwarden_help_center), onConfirmClick = onHelpCenterClick, - modifier = modifier - .semantics { testTag = "BitwardenHelpCenter" }, + modifier = Modifier + .standardHorizontalMargin() + .testTag("BitwardenHelpCenter"), + withDivider = false, dialogTitle = stringResource(id = BitwardenString.continue_to_help_center), dialogMessage = stringResource( BitwardenString.learn_more_about_how_to_use_bitwarden_authenticator_on_the_help_center, ), + cardStyle = CardStyle.Bottom, ) } @@ -574,38 +534,45 @@ private fun HelpSettings( //region About settings @Composable -private fun AboutSettings( - modifier: Modifier = Modifier, +private fun ColumnScope.AboutSettings( state: SettingsState, onSubmitCrashLogsCheckedChange: (Boolean) -> Unit, onPrivacyPolicyClick: () -> Unit, onVersionClick: () -> Unit, ) { BitwardenListHeaderText( - modifier = modifier.padding(horizontal = 16.dp), + modifier = Modifier + .standardHorizontalMargin() + .padding(horizontal = 16.dp), label = stringResource(id = BitwardenString.about), ) - BitwardenWideSwitch( - modifier = modifier - .padding(horizontal = 16.dp) - .semantics { testTag = "SubmitCrashLogs" }, + Spacer(modifier = Modifier.height(height = 8.dp)) + BitwardenSwitch( + modifier = Modifier + .standardHorizontalMargin() + .testTag("SubmitCrashLogs"), label = stringResource(id = BitwardenString.submit_crash_logs), isChecked = state.isSubmitCrashLogsEnabled, onCheckedChange = onSubmitCrashLogsCheckedChange, + cardStyle = CardStyle.Top(), ) BitwardenExternalLinkRow( text = stringResource(id = BitwardenString.privacy_policy), - modifier = modifier - .semantics { testTag = "PrivacyPolicy" }, + modifier = Modifier + .standardHorizontalMargin() + .testTag("PrivacyPolicy"), + withDivider = false, onConfirmClick = onPrivacyPolicyClick, dialogTitle = stringResource(id = BitwardenString.continue_to_privacy_policy), dialogMessage = stringResource( id = BitwardenString.privacy_policy_description_long, ), + cardStyle = CardStyle.Middle(), ) CopyRow( text = state.version, onClick = onVersionClick, + modifier = Modifier.standardHorizontalMargin(), ) } @@ -617,21 +584,17 @@ private fun CopyRow( resources: Resources = LocalResources.current, ) { Box( - contentAlignment = Alignment.BottomCenter, + contentAlignment = Alignment.Center, modifier = modifier - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = ripple(color = MaterialTheme.colorScheme.primary), - onClick = onClick, - ) + .defaultMinSize(minHeight = 60.dp) + .cardStyle(cardStyle = CardStyle.Bottom, onClick = onClick) .semantics(mergeDescendants = true) { contentDescription = text.toString(resources) }, ) { Row( modifier = Modifier - .defaultMinSize(minHeight = 56.dp) - .padding(start = 16.dp, end = 24.dp, top = 8.dp, bottom = 8.dp) + .padding(horizontal = 16.dp) .fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, @@ -641,20 +604,15 @@ private fun CopyRow( .padding(end = 16.dp) .weight(1f), text = text(), - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurface, + style = BitwardenTheme.typography.bodyLarge, + color = BitwardenTheme.colorScheme.text.primary, ) Icon( painter = rememberVectorPainter(id = BitwardenDrawable.ic_copy), contentDescription = null, - tint = MaterialTheme.colorScheme.onSurface, + tint = BitwardenTheme.colorScheme.icon.primary, ) } - HorizontalDivider( - modifier = Modifier.padding(start = 16.dp), - thickness = 1.dp, - color = MaterialTheme.colorScheme.outlineVariant, - ) } } @@ -663,7 +621,7 @@ private fun CopyRow( @Preview @Composable private fun CopyRow_preview() { - AuthenticatorTheme { + BitwardenTheme { CopyRow( text = "Copyable Text".asText(), onClick = { }, diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/export/ExportScreen.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/export/ExportScreen.kt index cf19684e8e0..d16e83c4d44 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/export/ExportScreen.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/export/ExportScreen.kt @@ -7,7 +7,7 @@ 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.navigationBarsPadding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api @@ -22,25 +22,24 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalResources +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.testTag import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.bitwarden.authenticator.ui.platform.components.appbar.AuthenticatorTopAppBar -import com.bitwarden.authenticator.ui.platform.components.button.AuthenticatorFilledTonalButton -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.dialog.BitwardenLoadingDialog -import com.bitwarden.authenticator.ui.platform.components.dialog.BitwardenTwoButtonDialog -import com.bitwarden.authenticator.ui.platform.components.dialog.LoadingDialogState -import com.bitwarden.authenticator.ui.platform.components.dropdown.BitwardenMultiSelectButton -import com.bitwarden.authenticator.ui.platform.components.scaffold.BitwardenScaffold import com.bitwarden.authenticator.ui.platform.feature.settings.export.model.ExportVaultFormat import com.bitwarden.authenticator.ui.platform.util.displayLabel import com.bitwarden.ui.platform.base.util.EventsEffect +import com.bitwarden.ui.platform.base.util.standardHorizontalMargin +import com.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar +import com.bitwarden.ui.platform.components.button.BitwardenFilledButton +import com.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog +import com.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog +import com.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialog +import com.bitwarden.ui.platform.components.dropdown.BitwardenMultiSelectButton +import com.bitwarden.ui.platform.components.model.CardStyle +import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold import com.bitwarden.ui.platform.composition.LocalIntentManager import com.bitwarden.ui.platform.manager.IntentManager import com.bitwarden.ui.platform.resource.BitwardenDrawable @@ -114,10 +113,8 @@ fun ExportScreen( when (val dialog = state.dialogState) { is ExportState.DialogState.Error -> { BitwardenBasicDialog( - visibilityState = BasicDialogState.Shown( - title = dialog.title, - message = dialog.message, - ), + title = dialog.title?.invoke(), + message = dialog.message(), onDismissRequest = remember(viewModel) { { viewModel.trySendAction(ExportAction.DialogDismiss) @@ -127,11 +124,7 @@ fun ExportScreen( } is ExportState.DialogState.Loading -> { - BitwardenLoadingDialog( - visibilityState = LoadingDialogState.Shown( - text = dialog.message, - ), - ) + BitwardenLoadingDialog(text = dialog.message()) } null -> Unit @@ -143,7 +136,7 @@ fun ExportScreen( .fillMaxSize() .nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { - AuthenticatorTopAppBar( + BitwardenTopAppBar( title = stringResource(id = BitwardenString.export), scrollBehavior = scrollBehavior, navigationIcon = painterResource(id = BitwardenDrawable.ic_close), @@ -155,11 +148,9 @@ fun ExportScreen( }, ) }, - ) { paddingValues -> + ) { ExportScreenContent( - modifier = Modifier - .padding(paddingValues) - .fillMaxSize(), + modifier = Modifier.fillMaxSize(), state = state, onExportFormatOptionSelected = remember(viewModel) { { @@ -183,6 +174,7 @@ private fun ExportScreenContent( .verticalScroll(rememberScrollState()), ) { val resources = LocalResources.current + Spacer(modifier = Modifier.height(height = 12.dp)) BitwardenMultiSelectButton( label = stringResource(id = BitwardenString.file_format), options = ExportVaultFormat.entries.map { it.displayLabel() }.toImmutableList(), @@ -193,21 +185,25 @@ private fun ExportScreenContent( .first { it.displayLabel(resources) == selectedOptionLabel } onExportFormatOptionSelected(selectedOption) }, + cardStyle = CardStyle.Full, modifier = Modifier - .semantics { testTag = "FileFormatPicker" } - .padding(horizontal = 16.dp) + .testTag("FileFormatPicker") + .standardHorizontalMargin() .fillMaxWidth(), ) - Spacer(modifier = Modifier.height(8.dp)) + Spacer(modifier = Modifier.height(height = 24.dp)) - AuthenticatorFilledTonalButton( + BitwardenFilledButton( label = stringResource(id = BitwardenString.export), onClick = onExportClick, modifier = Modifier - .semantics { testTag = "ExportVaultButton" } - .padding(horizontal = 16.dp) + .testTag("ExportVaultButton") + .standardHorizontalMargin() .fillMaxWidth(), ) + + Spacer(modifier = Modifier.height(height = 12.dp)) + Spacer(modifier = Modifier.navigationBarsPadding()) } } diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/importing/ImportingScreen.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/importing/ImportingScreen.kt index 3a27ab1e9ef..511a7d57949 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/importing/ImportingScreen.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/importing/ImportingScreen.kt @@ -6,7 +6,7 @@ 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.navigationBarsPadding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api @@ -19,24 +19,24 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalResources +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.testTag import androidx.compose.ui.unit.dp import androidx.core.net.toUri import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.bitwarden.authenticator.data.platform.manager.imports.model.ImportFileFormat -import com.bitwarden.authenticator.ui.platform.components.appbar.AuthenticatorTopAppBar -import com.bitwarden.authenticator.ui.platform.components.button.AuthenticatorFilledTonalButton -import com.bitwarden.authenticator.ui.platform.components.dialog.BitwardenLoadingDialog -import com.bitwarden.authenticator.ui.platform.components.dialog.BitwardenTwoButtonDialog -import com.bitwarden.authenticator.ui.platform.components.dialog.LoadingDialogState -import com.bitwarden.authenticator.ui.platform.components.dropdown.BitwardenMultiSelectButton -import com.bitwarden.authenticator.ui.platform.components.scaffold.BitwardenScaffold import com.bitwarden.authenticator.ui.platform.util.displayLabel import com.bitwarden.ui.platform.base.util.EventsEffect +import com.bitwarden.ui.platform.base.util.standardHorizontalMargin +import com.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar +import com.bitwarden.ui.platform.components.button.BitwardenFilledButton +import com.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog +import com.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialog +import com.bitwarden.ui.platform.components.dropdown.BitwardenMultiSelectButton +import com.bitwarden.ui.platform.components.model.CardStyle +import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold import com.bitwarden.ui.platform.composition.LocalIntentManager import com.bitwarden.ui.platform.manager.IntentManager import com.bitwarden.ui.platform.model.FileData @@ -108,11 +108,7 @@ fun ImportingScreen( } is ImportState.DialogState.Loading -> { - BitwardenLoadingDialog( - visibilityState = LoadingDialogState.Shown( - text = dialog.message, - ), - ) + BitwardenLoadingDialog(text = dialog.message()) } null -> Unit @@ -124,7 +120,7 @@ fun ImportingScreen( .fillMaxSize() .nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { - AuthenticatorTopAppBar( + BitwardenTopAppBar( title = stringResource(id = BitwardenString.import_vault), scrollBehavior = scrollBehavior, navigationIcon = painterResource(id = BitwardenDrawable.ic_close), @@ -136,11 +132,9 @@ fun ImportingScreen( }, ) }, - ) { paddingValues -> + ) { ImportScreenContent( - modifier = Modifier - .padding(paddingValues) - .fillMaxSize(), + modifier = Modifier.fillMaxSize(), state = state, onImportFormatOptionSelected = remember(viewModel) { { @@ -168,6 +162,7 @@ private fun ImportScreenContent( .verticalScroll(rememberScrollState()), ) { val resources = LocalResources.current + Spacer(modifier = Modifier.height(height = 12.dp)) BitwardenMultiSelectButton( label = stringResource(id = BitwardenString.file_format), options = ImportFileFormat.entries.map { it.displayLabel() }.toImmutableList(), @@ -178,21 +173,25 @@ private fun ImportScreenContent( .first { it.displayLabel(resources) == selectedOptionLabel } onImportFormatOptionSelected(selectedOption) }, + cardStyle = CardStyle.Full, modifier = Modifier - .semantics { testTag = "FileFormatPicker" } - .padding(horizontal = 16.dp) + .testTag("FileFormatPicker") + .standardHorizontalMargin() .fillMaxWidth(), ) - Spacer(modifier = Modifier.height(8.dp)) + Spacer(modifier = Modifier.height(height = 24.dp)) - AuthenticatorFilledTonalButton( + BitwardenFilledButton( label = stringResource(id = BitwardenString.import_vault), onClick = onImportClick, modifier = Modifier - .semantics { testTag = "ImportVaultButton" } - .padding(horizontal = 16.dp) + .testTag("ImportVaultButton") + .standardHorizontalMargin() .fillMaxWidth(), ) + + Spacer(modifier = Modifier.height(height = 12.dp)) + Spacer(modifier = Modifier.navigationBarsPadding()) } } diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/tutorial/TutorialScreen.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/tutorial/TutorialScreen.kt index 82402bf5d5d..ae8df6c66e9 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/tutorial/TutorialScreen.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/tutorial/TutorialScreen.kt @@ -23,7 +23,6 @@ import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -41,14 +40,15 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.bitwarden.authenticator.ui.platform.components.button.AuthenticatorFilledTonalButton -import com.bitwarden.authenticator.ui.platform.components.button.AuthenticatorTextButton -import com.bitwarden.authenticator.ui.platform.components.scaffold.BitwardenScaffold import com.bitwarden.authenticator.ui.platform.util.isPortrait import com.bitwarden.ui.platform.base.util.EventsEffect import com.bitwarden.ui.platform.base.util.standardHorizontalMargin +import com.bitwarden.ui.platform.components.button.BitwardenFilledButton +import com.bitwarden.ui.platform.components.button.BitwardenTextButton +import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold import com.bitwarden.ui.platform.components.util.rememberVectorPainter import com.bitwarden.ui.platform.resource.BitwardenString +import com.bitwarden.ui.platform.theme.BitwardenTheme import kotlinx.coroutines.launch /** @@ -155,7 +155,7 @@ private fun TutorialScreenContent( .height(44.dp), ) - AuthenticatorFilledTonalButton( + BitwardenFilledButton( label = state.actionButtonText, onClick = { continueClick(state.index) }, modifier = Modifier @@ -163,7 +163,7 @@ private fun TutorialScreenContent( .fillMaxWidth(), ) - AuthenticatorTextButton( + BitwardenTextButton( isEnabled = !state.isLastPage, label = stringResource(id = BitwardenString.skip), onClick = skipClick, @@ -196,7 +196,7 @@ private fun TutorialScreenPortrait( Text( text = stringResource(id = state.title), textAlign = TextAlign.Center, - style = MaterialTheme.typography.headlineMedium, + style = BitwardenTheme.typography.headlineMedium, modifier = Modifier .padding( top = 48.dp, @@ -206,7 +206,7 @@ private fun TutorialScreenPortrait( Text( text = stringResource(id = state.message), textAlign = TextAlign.Center, - style = MaterialTheme.typography.bodyLarge, + style = BitwardenTheme.typography.bodyLarge, ) } } @@ -235,14 +235,14 @@ private fun TutorialScreenLandscape( Text( text = stringResource(id = state.title), textAlign = TextAlign.Center, - style = MaterialTheme.typography.headlineMedium, + style = BitwardenTheme.typography.headlineMedium, modifier = Modifier.padding(bottom = 16.dp), ) Text( text = stringResource(id = state.message), textAlign = TextAlign.Center, - style = MaterialTheme.typography.bodyLarge, + style = BitwardenTheme.typography.bodyLarge, ) } @@ -264,7 +264,7 @@ private fun IndicatorDots( ) { items(totalCount) { index -> val color = animateColorAsState( - targetValue = MaterialTheme.colorScheme.primary.copy( + targetValue = BitwardenTheme.colorScheme.icon.secondary.copy( alpha = if (index == selectedIndexProvider()) 1.0f else 0.3f, ), label = "dotColor", diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/theme/AuthenticatorTheme.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/theme/AuthenticatorTheme.kt deleted file mode 100644 index 2e1006ab9ec..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/theme/AuthenticatorTheme.kt +++ /dev/null @@ -1,176 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.theme - -import android.content.Context -import android.os.Build -import androidx.annotation.ColorRes -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material3.ColorScheme -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.dynamicDarkColorScheme -import androidx.compose.material3.dynamicLightColorScheme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.ProvidableCompositionLocal -import androidx.compose.runtime.compositionLocalOf -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext -import com.bitwarden.authenticator.R -import com.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme -import com.bitwarden.ui.platform.util.isDarkMode - -/** - * The overall application theme. This can be configured to support a [theme] and [dynamicColor]. - */ -@Composable -fun AuthenticatorTheme( - theme: AppTheme = AppTheme.DEFAULT, - dynamicColor: Boolean = false, - content: @Composable () -> Unit, -) { - val darkTheme = theme.isDarkMode(isSystemDarkMode = isSystemInDarkTheme()) - // Get the current scheme - val context = LocalContext.current - val colorScheme = when { - dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { - if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) - } - - darkTheme -> darkColorScheme(context) - else -> lightColorScheme(context) - } - - val nonMaterialColors = if (darkTheme) { - darkNonMaterialColors(context) - } else { - lightNonMaterialColors(context) - } - - CompositionLocalProvider( - LocalNonMaterialColors provides nonMaterialColors, - LocalNonMaterialTypography provides nonMaterialTypography, - ) { - // Set overall theme based on color scheme and typography settings - MaterialTheme( - colorScheme = colorScheme, - typography = Typography, - content = content, - ) - } -} - -private fun darkColorScheme(context: Context): ColorScheme = - androidx.compose.material3.darkColorScheme( - primary = R.color.dark_primary.toColor(context), - onPrimary = R.color.dark_on_primary.toColor(context), - primaryContainer = R.color.dark_primary_container.toColor(context), - onPrimaryContainer = R.color.dark_on_primary_container.toColor(context), - secondary = R.color.dark_secondary.toColor(context), - onSecondary = R.color.dark_on_secondary.toColor(context), - secondaryContainer = R.color.dark_secondary_container.toColor(context), - onSecondaryContainer = R.color.dark_on_secondary_container.toColor(context), - tertiary = R.color.dark_tertiary.toColor(context), - onTertiary = R.color.dark_on_tertiary.toColor(context), - tertiaryContainer = R.color.dark_tertiary_container.toColor(context), - onTertiaryContainer = R.color.dark_on_tertiary_container.toColor(context), - error = R.color.dark_error.toColor(context), - onError = R.color.dark_on_error.toColor(context), - errorContainer = R.color.dark_error_container.toColor(context), - onErrorContainer = R.color.dark_on_error_container.toColor(context), - surface = R.color.dark_surface.toColor(context), - surfaceBright = R.color.dark_surface_bright.toColor(context), - surfaceContainer = R.color.dark_surface_container.toColor(context), - surfaceContainerHigh = R.color.dark_surface_container_high.toColor(context), - surfaceContainerHighest = R.color.dark_surface_container_highest.toColor(context), - surfaceContainerLow = R.color.dark_surface_container_low.toColor(context), - surfaceContainerLowest = R.color.dark_surface_container_lowest.toColor(context), - surfaceVariant = R.color.dark_surface_variant.toColor(context), - surfaceDim = R.color.dark_surface_dim.toColor(context), - onSurface = R.color.dark_on_surface.toColor(context), - onSurfaceVariant = R.color.dark_on_surface_variant.toColor(context), - outline = R.color.dark_outline.toColor(context), - outlineVariant = R.color.dark_outline_variant.toColor(context), - inverseSurface = R.color.dark_inverse_surface.toColor(context), - inverseOnSurface = R.color.dark_inverse_on_surface.toColor(context), - inversePrimary = R.color.dark_inverse_primary.toColor(context), - scrim = R.color.dark_scrim.toColor(context), - ) - -private fun lightColorScheme(context: Context): ColorScheme = - androidx.compose.material3.lightColorScheme( - primary = R.color.primary.toColor(context), - onPrimary = R.color.on_primary.toColor(context), - primaryContainer = R.color.primary_container.toColor(context), - onPrimaryContainer = R.color.on_primary_container.toColor(context), - secondary = R.color.secondary.toColor(context), - onSecondary = R.color.on_secondary.toColor(context), - secondaryContainer = R.color.secondary_container.toColor(context), - onSecondaryContainer = R.color.on_secondary_container.toColor(context), - tertiary = R.color.tertiary.toColor(context), - onTertiary = R.color.on_tertiary.toColor(context), - tertiaryContainer = R.color.tertiary_container.toColor(context), - onTertiaryContainer = R.color.on_tertiary_container.toColor(context), - error = R.color.error.toColor(context), - onError = R.color.on_error.toColor(context), - errorContainer = R.color.error_container.toColor(context), - onErrorContainer = R.color.on_error_container.toColor(context), - surface = R.color.surface.toColor(context), - surfaceBright = R.color.surface_bright.toColor(context), - surfaceContainer = R.color.surface_container.toColor(context), - surfaceContainerHigh = R.color.surface_container_high.toColor(context), - surfaceContainerHighest = R.color.surface_container_highest.toColor(context), - surfaceContainerLow = R.color.surface_container_low.toColor(context), - surfaceContainerLowest = R.color.surface_container_lowest.toColor(context), - surfaceVariant = R.color.surface_variant.toColor(context), - surfaceDim = R.color.surface_dim.toColor(context), - onSurface = R.color.on_surface.toColor(context), - onSurfaceVariant = R.color.on_surface_variant.toColor(context), - outline = R.color.outline.toColor(context), - outlineVariant = R.color.outline_variant.toColor(context), - inverseSurface = R.color.inverse_surface.toColor(context), - inverseOnSurface = R.color.inverse_on_surface.toColor(context), - inversePrimary = R.color.inverse_primary.toColor(context), - scrim = R.color.scrim.toColor(context), - ) - -@ColorRes -private fun Int.toColor(context: Context): Color = - Color(context.getColor(this)) - -/** - * Provides access to non material theme typography throughout the app. - */ -val LocalNonMaterialTypography: ProvidableCompositionLocal = - compositionLocalOf { nonMaterialTypography } - -/** - * Provides access to non material theme colors throughout the app. - */ -val LocalNonMaterialColors: ProvidableCompositionLocal = - compositionLocalOf { - // Default value here will immediately be overridden in BitwardenTheme, similar - // to how MaterialTheme works. - NonMaterialColors( - fingerprint = Color.Transparent, - qrCodeClickableText = Color.Transparent, - ) - } - -/** - * Models colors that live outside of the Material Theme spec. - */ -data class NonMaterialColors( - val fingerprint: Color, - val qrCodeClickableText: Color, -) - -private fun lightNonMaterialColors(context: Context): NonMaterialColors = - NonMaterialColors( - fingerprint = R.color.light_fingerprint.toColor(context), - qrCodeClickableText = R.color.qr_code_clickable_text.toColor(context), - ) - -private fun darkNonMaterialColors(context: Context): NonMaterialColors = - NonMaterialColors( - fingerprint = R.color.dark_fingerprint.toColor(context), - qrCodeClickableText = R.color.qr_code_clickable_text.toColor(context), - ) diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/theme/Type.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/theme/Type.kt deleted file mode 100644 index ef44dca9f5f..00000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/theme/Type.kt +++ /dev/null @@ -1,255 +0,0 @@ -package com.bitwarden.authenticator.ui.platform.theme - -import androidx.compose.material3.Typography -import androidx.compose.ui.text.PlatformTextStyle -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.Font -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.LineHeightStyle -import androidx.compose.ui.unit.sp -import com.bitwarden.authenticator.R - -val Typography: Typography = Typography( - displayLarge = TextStyle( - fontSize = 57.sp, - lineHeight = 64.sp, - fontFamily = FontFamily(Font(R.font.roboto_regular)), - fontWeight = FontWeight.W400, - letterSpacing = (-0.25).sp, - lineHeightStyle = LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.None, - ), - platformStyle = PlatformTextStyle(includeFontPadding = false), - ), - displayMedium = TextStyle( - fontSize = 45.sp, - lineHeight = 52.sp, - fontFamily = FontFamily(Font(R.font.roboto_regular)), - fontWeight = FontWeight.W400, - letterSpacing = (0).sp, - lineHeightStyle = LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.None, - ), - platformStyle = PlatformTextStyle(includeFontPadding = false), - ), - displaySmall = TextStyle( - fontSize = 36.sp, - lineHeight = 44.sp, - fontFamily = FontFamily(Font(R.font.roboto_regular)), - fontWeight = FontWeight.W400, - letterSpacing = 0.sp, - lineHeightStyle = LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.None, - ), - platformStyle = PlatformTextStyle(includeFontPadding = false), - ), - headlineLarge = TextStyle( - fontSize = 32.sp, - lineHeight = 40.sp, - fontFamily = FontFamily(Font(R.font.roboto_regular)), - fontWeight = FontWeight.W400, - letterSpacing = 0.sp, - lineHeightStyle = LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.None, - ), - platformStyle = PlatformTextStyle(includeFontPadding = false), - ), - headlineMedium = TextStyle( - fontSize = 28.sp, - lineHeight = 36.sp, - fontFamily = FontFamily(Font(R.font.roboto_regular)), - fontWeight = FontWeight.W400, - letterSpacing = 0.sp, - lineHeightStyle = LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.None, - ), - platformStyle = PlatformTextStyle(includeFontPadding = false), - ), - headlineSmall = TextStyle( - fontSize = 24.sp, - lineHeight = 32.sp, - fontFamily = FontFamily(Font(R.font.roboto_regular)), - fontWeight = FontWeight.W400, - letterSpacing = 0.sp, - lineHeightStyle = LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.None, - ), - platformStyle = PlatformTextStyle(includeFontPadding = false), - ), - titleLarge = TextStyle( - fontSize = 22.sp, - lineHeight = 28.sp, - fontFamily = FontFamily(Font(R.font.roboto_regular)), - fontWeight = FontWeight.W400, - letterSpacing = 0.sp, - lineHeightStyle = LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.None, - ), - platformStyle = PlatformTextStyle(includeFontPadding = false), - ), - titleMedium = TextStyle( - fontSize = 16.sp, - lineHeight = 24.sp, - fontFamily = FontFamily(Font(R.font.roboto_medium)), - fontWeight = FontWeight.W500, - letterSpacing = 0.15.sp, - lineHeightStyle = LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.None, - ), - platformStyle = PlatformTextStyle(includeFontPadding = false), - ), - titleSmall = TextStyle( - fontSize = 14.sp, - lineHeight = 20.sp, - fontFamily = FontFamily(Font(R.font.roboto_medium)), - fontWeight = FontWeight.W500, - letterSpacing = 0.1.sp, - lineHeightStyle = LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.None, - ), - platformStyle = PlatformTextStyle(includeFontPadding = false), - ), - bodyLarge = TextStyle( - fontSize = 16.sp, - lineHeight = 24.sp, - fontFamily = FontFamily(Font(R.font.roboto_regular)), - fontWeight = FontWeight.W400, - letterSpacing = 0.5.sp, - lineHeightStyle = LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.None, - ), - platformStyle = PlatformTextStyle(includeFontPadding = false), - ), - bodyMedium = TextStyle( - fontSize = 14.sp, - lineHeight = 20.sp, - fontFamily = FontFamily(Font(R.font.roboto_regular)), - fontWeight = FontWeight.W400, - letterSpacing = 0.25.sp, - lineHeightStyle = LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.None, - ), - platformStyle = PlatformTextStyle(includeFontPadding = false), - ), - bodySmall = TextStyle( - fontSize = 12.sp, - lineHeight = 16.sp, - fontFamily = FontFamily(Font(R.font.roboto_regular)), - fontWeight = FontWeight.W400, - letterSpacing = 0.4.sp, - lineHeightStyle = LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.None, - ), - platformStyle = PlatformTextStyle(includeFontPadding = false), - ), - labelLarge = TextStyle( - fontSize = 14.sp, - lineHeight = 20.sp, - fontFamily = FontFamily(Font(R.font.roboto_medium)), - fontWeight = FontWeight.W500, - letterSpacing = 0.1.sp, - lineHeightStyle = LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.None, - ), - platformStyle = PlatformTextStyle(includeFontPadding = false), - ), - labelMedium = TextStyle( - fontSize = 12.sp, - lineHeight = 16.sp, - fontFamily = FontFamily(Font(R.font.roboto_medium)), - fontWeight = FontWeight.W500, - letterSpacing = 0.5.sp, - lineHeightStyle = LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.None, - ), - platformStyle = PlatformTextStyle(includeFontPadding = false), - ), - labelSmall = TextStyle( - fontSize = 11.sp, - lineHeight = 16.sp, - fontFamily = FontFamily(Font(R.font.roboto_medium)), - fontWeight = FontWeight.W500, - letterSpacing = 0.5.sp, - lineHeightStyle = LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.None, - ), - platformStyle = PlatformTextStyle(includeFontPadding = false), - ), -) - -val nonMaterialTypography: NonMaterialTypography = NonMaterialTypography( - sensitiveInfoSmall = TextStyle( - fontSize = 14.sp, - lineHeight = 20.sp, - fontFamily = FontFamily(Font(R.font.roboto_regular_mono)), - fontWeight = FontWeight.W400, - letterSpacing = 0.5.sp, - lineHeightStyle = LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.None, - ), - platformStyle = PlatformTextStyle(includeFontPadding = false), - ), - sensitiveInfoMedium = TextStyle( - fontSize = 16.sp, - lineHeight = 24.sp, - fontFamily = FontFamily(Font(R.font.roboto_regular_mono)), - fontWeight = FontWeight.W400, - letterSpacing = 0.5.sp, - lineHeightStyle = LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.None, - ), - platformStyle = PlatformTextStyle(includeFontPadding = false), - ), - bodySmallProminent = TextStyle( - fontSize = 12.sp, - lineHeight = 16.sp, - fontFamily = FontFamily(Font(R.font.roboto_regular)), - fontWeight = FontWeight.W700, - letterSpacing = 0.4.sp, - lineHeightStyle = LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.None, - ), - platformStyle = PlatformTextStyle(includeFontPadding = false), - ), - labelMediumProminent = TextStyle( - fontSize = 12.sp, - lineHeight = 16.sp, - fontFamily = FontFamily(Font(R.font.roboto_regular)), - fontWeight = FontWeight.W600, - letterSpacing = 0.5.sp, - lineHeightStyle = LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.None, - ), - platformStyle = PlatformTextStyle(includeFontPadding = false), - ), -) - -/** - * Models typography that live outside of the Material Theme spec. - */ -data class NonMaterialTypography( - val bodySmallProminent: TextStyle, - val labelMediumProminent: TextStyle, - val sensitiveInfoSmall: TextStyle, - val sensitiveInfoMedium: TextStyle, -) diff --git a/authenticator/src/test/kotlin/com/bitwarden/authenticator/MainViewModelTest.kt b/authenticator/src/test/kotlin/com/bitwarden/authenticator/MainViewModelTest.kt index 44bc6142574..2b5a951adff 100644 --- a/authenticator/src/test/kotlin/com/bitwarden/authenticator/MainViewModelTest.kt +++ b/authenticator/src/test/kotlin/com/bitwarden/authenticator/MainViewModelTest.kt @@ -11,7 +11,6 @@ import io.mockk.verify import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test class MainViewModelTest : BaseViewModelTest() { @@ -24,35 +23,28 @@ class MainViewModelTest : BaseViewModelTest() { every { isScreenCaptureAllowedStateFlow } returns mutableScreenCaptureAllowedFlow } private val fakeServerConfigRepository = FakeServerConfigRepository() - private lateinit var mainViewModel: MainViewModel - - @BeforeEach - fun setUp() { - mainViewModel = MainViewModel( - settingsRepository, - fakeServerConfigRepository, - ) - } + private val mainViewModel: MainViewModel = MainViewModel( + settingsRepository = settingsRepository, + configRepository = fakeServerConfigRepository, + ) @Test - fun `on AppThemeChanged should update state`() { - assertEquals( - MainState( - theme = AppTheme.DEFAULT, - ), - mainViewModel.stateFlow.value, - ) - mainViewModel.trySendAction( - MainAction.Internal.ThemeUpdate( - theme = AppTheme.DARK, - ), - ) - assertEquals( - MainState( - theme = AppTheme.DARK, - ), - mainViewModel.stateFlow.value, - ) + fun `on AppThemeChanged should update state`() = runTest { + mainViewModel.stateEventFlow(backgroundScope) { stateFlow, eventFlow -> + assertEquals( + MainState(theme = AppTheme.DEFAULT), + stateFlow.awaitItem(), + ) + mainViewModel.trySendAction(MainAction.Internal.ThemeUpdate(theme = AppTheme.DARK)) + assertEquals( + MainState(theme = AppTheme.DARK), + stateFlow.awaitItem(), + ) + assertEquals( + MainEvent.UpdateAppTheme(osTheme = AppTheme.DARK.osValue), + eventFlow.expectMostRecentItem(), + ) + } verify { settingsRepository.appTheme @@ -62,10 +54,10 @@ class MainViewModelTest : BaseViewModelTest() { @Test fun `send NavigateToDebugMenu action when OpenDebugMenu action is sent`() = runTest { - mainViewModel.trySendAction(MainAction.OpenDebugMenu) - mainViewModel.eventFlow.test { - awaitItem() // ignore first event + // Ignore the events that are fired off by flows in the ViewModel init + skipItems(2) + mainViewModel.trySendAction(MainAction.OpenDebugMenu) assertEquals(MainEvent.NavigateToDebugMenu, awaitItem()) } } diff --git a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemScreenTest.kt b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemScreenTest.kt index 50395d69222..9b7fb4ccd2b 100644 --- a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemScreenTest.kt +++ b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemScreenTest.kt @@ -7,9 +7,9 @@ import androidx.compose.ui.test.hasAnyAncestor import androidx.compose.ui.test.hasContentDescription import androidx.compose.ui.test.isDialog import androidx.compose.ui.test.onAllNodesWithText +import androidx.compose.ui.test.onChildren import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText -import androidx.compose.ui.test.onSiblings import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performTextInput import com.bitwarden.authenticator.data.authenticator.datasource.disk.entity.AuthenticatorItemAlgorithm @@ -152,12 +152,12 @@ class EditItemScreenTest : AuthenticatorComposeTest() { mutableStateFlow.update { it.copy(viewState = DEFAULT_CONTENT) } - composeTestRule.onNodeWithTextAfterScroll(text = "Information").assertIsDisplayed() + composeTestRule.onNodeWithTextAfterScroll(text = "INFORMATION").assertIsDisplayed() composeTestRule.onNodeWithTextAfterScroll(text = "Name").assertIsDisplayed() composeTestRule.onNodeWithTextAfterScroll(text = "Key").assertIsDisplayed() composeTestRule.onNodeWithTextAfterScroll(text = "Username").assertIsDisplayed() composeTestRule.onNodeWithTextAfterScroll(text = "Favorite").assertIsDisplayed() - composeTestRule.onNodeWithTextAfterScroll(text = "Advanced").assertIsDisplayed() + composeTestRule.onNodeWithTextAfterScroll(text = "Additional options").assertIsDisplayed() } @Test @@ -211,7 +211,7 @@ class EditItemScreenTest : AuthenticatorComposeTest() { fun `advanced click should send ExpandAdvancedOptionsClick`() { mutableStateFlow.update { it.copy(viewState = DEFAULT_CONTENT) } composeTestRule - .onNodeWithTextAfterScroll(text = "Advanced") + .onNodeWithTextAfterScroll(text = "Additional options") .performClick() verify(exactly = 1) { viewModel.trySendAction(EditItemAction.ExpandAdvancedOptionsClick) @@ -356,7 +356,7 @@ class EditItemScreenTest : AuthenticatorComposeTest() { } composeTestRule .onNodeWithTextAfterScroll(text = "Number of digits") - .onSiblings() + .onChildren() .filterToOne(hasContentDescription("+")) .performClick() @@ -372,7 +372,7 @@ class EditItemScreenTest : AuthenticatorComposeTest() { } composeTestRule .onNodeWithTextAfterScroll(text = "Number of digits") - .onSiblings() + .onChildren() .filterToOne(hasContentDescription("\u2212")) .performClick() diff --git a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryScreenTest.kt b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryScreenTest.kt index fecd35aa877..c26b0fae7d9 100644 --- a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryScreenTest.kt +++ b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryScreenTest.kt @@ -14,7 +14,6 @@ import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow import com.bitwarden.ui.platform.manager.IntentManager import com.bitwarden.ui.util.asText import com.bitwarden.ui.util.assertNoDialogExists -import com.bitwarden.ui.util.performCustomAccessibilityAction import io.mockk.every import io.mockk.just import io.mockk.mockk @@ -194,7 +193,7 @@ class ManualCodeEntryScreenTest : AuthenticatorComposeTest() { } @Test - fun `on permission dialog Settings clock should emit SettingsClick`() { + fun `on permission dialog Settings click should emit SettingsClick`() { permissionsManager.checkPermissionResult = false permissionsManager.getPermissionsResult = false composeTestRule @@ -227,21 +226,6 @@ class ManualCodeEntryScreenTest : AuthenticatorComposeTest() { } } - @Suppress("MaxLineLength") - @Test - fun `on Scan QR code accessibility action without permission and permission is granted should emit ScanQrCodeTextClick`() { - permissionsManager.checkPermissionResult = false - permissionsManager.getPermissionsResult = true - composeTestRule - .onNodeWithText(text = "Scan QR code") - .performScrollTo() - .performCustomAccessibilityAction(label = "Scan QR code") - - verify(exactly = 1) { - viewModel.trySendAction(ManualCodeEntryAction.ScanQrCodeTextClick) - } - } - @Test fun `on dialog should updates according to state`() { composeTestRule.assertNoDialogExists() diff --git a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/platform/base/AuthenticatorComposeTest.kt b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/platform/base/AuthenticatorComposeTest.kt index dafc4728464..38b8ad33e1b 100644 --- a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/platform/base/AuthenticatorComposeTest.kt +++ b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/platform/base/AuthenticatorComposeTest.kt @@ -5,10 +5,10 @@ import com.bitwarden.authenticator.ui.platform.composition.LocalManagerProvider import com.bitwarden.authenticator.ui.platform.manager.biometrics.BiometricsManager import com.bitwarden.authenticator.ui.platform.manager.exit.ExitManager import com.bitwarden.authenticator.ui.platform.manager.permissions.PermissionsManager -import com.bitwarden.authenticator.ui.platform.theme.AuthenticatorTheme import com.bitwarden.ui.platform.base.BaseComposeTest import com.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme import com.bitwarden.ui.platform.manager.IntentManager +import com.bitwarden.ui.platform.theme.BitwardenTheme import io.mockk.mockk /** @@ -19,7 +19,7 @@ abstract class AuthenticatorComposeTest : BaseComposeTest() { /** * Helper for testing a basic Composable function that only requires a Composable environment - * with the [AuthenticatorTheme]. + * with the [BitwardenTheme]. */ @Suppress("LongParameterList") protected fun setContent( @@ -31,7 +31,7 @@ abstract class AuthenticatorComposeTest : BaseComposeTest() { test: @Composable () -> Unit, ) { setTestContent { - AuthenticatorTheme(theme = theme) { + BitwardenTheme(theme = theme) { LocalManagerProvider( permissionsManager = permissionsManager, intentManager = intentManager, diff --git a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsScreenTest.kt b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsScreenTest.kt index 76216aec556..d2d0863f096 100644 --- a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsScreenTest.kt +++ b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsScreenTest.kt @@ -143,7 +143,7 @@ class SettingsScreenTest : AuthenticatorComposeTest() { @Test @Suppress("MaxLineLength") - fun `Default Save Option dialog should send DefaultSaveOptionUpdated when confirm is clicked`() = + fun `Default Save Option dialog should send DefaultSaveOptionUpdated when selection is made`() = runTest { val expectedSaveOption = DefaultSaveOption.BITWARDEN_APP mutableStateFlow.value = DEFAULT_STATE @@ -164,12 +164,6 @@ class SettingsScreenTest : AuthenticatorComposeTest() { .assertIsDisplayed() .performClick() - // Click confirm: - composeTestRule - .onNodeWithText("Confirm") - .assertIsDisplayed() - .performClick() - verify { viewModel.trySendAction( SettingsAction.DataClick.DefaultSaveOptionUpdated(expectedSaveOption), diff --git a/ui/src/main/kotlin/com/bitwarden/ui/platform/theme/type/Typography.kt b/ui/src/main/kotlin/com/bitwarden/ui/platform/theme/type/Typography.kt index 598da765433..356b768f576 100644 --- a/ui/src/main/kotlin/com/bitwarden/ui/platform/theme/type/Typography.kt +++ b/ui/src/main/kotlin/com/bitwarden/ui/platform/theme/type/Typography.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.Typography import androidx.compose.runtime.Composable @@ -20,6 +19,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.bitwarden.ui.R +import com.bitwarden.ui.platform.theme.BitwardenTheme /** * The default [BitwardenTypography] for the app. @@ -279,7 +279,7 @@ fun BitwardenTypography.toMaterialTypography(): Typography = Typography( @Preview(showBackground = true) @Composable private fun BitwardenTypography_preview() { - MaterialTheme { + BitwardenTheme { Column( modifier = Modifier .padding(8.dp) diff --git a/ui/src/main/res/drawable-night/ic_verification_codes.xml b/ui/src/main/res/drawable-night/ic_verification_codes.xml new file mode 100644 index 00000000000..4f1e53ad3d0 --- /dev/null +++ b/ui/src/main/res/drawable-night/ic_verification_codes.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/ui/src/main/res/drawable/ic_lock_encrypted_small.xml b/ui/src/main/res/drawable/ic_lock_encrypted_small.xml new file mode 100644 index 00000000000..e7aeb63c9be --- /dev/null +++ b/ui/src/main/res/drawable/ic_lock_encrypted_small.xml @@ -0,0 +1,13 @@ + + + + diff --git a/ui/src/main/res/drawable/ic_verification_codes.xml b/ui/src/main/res/drawable/ic_verification_codes.xml index 9717383a8fb..3be95360d0d 100644 --- a/ui/src/main/res/drawable/ic_verification_codes.xml +++ b/ui/src/main/res/drawable/ic_verification_codes.xml @@ -1,13 +1,18 @@ - - - - + android:height="25dp" + android:viewportWidth="24" + android:viewportHeight="25"> + + + diff --git a/ui/src/main/res/drawable/ic_verification_codes_filled.xml b/ui/src/main/res/drawable/ic_verification_codes_filled.xml index 3a9a4cdde33..3388ecc38cc 100644 --- a/ui/src/main/res/drawable/ic_verification_codes_filled.xml +++ b/ui/src/main/res/drawable/ic_verification_codes_filled.xml @@ -1,14 +1,29 @@ - - - - + android:viewportHeight="25"> + + + + + diff --git a/ui/src/main/res/values/strings.xml b/ui/src/main/res/values/strings.xml index 5c3283ba4f7..12758d864e5 100644 --- a/ui/src/main/res/values/strings.xml +++ b/ui/src/main/res/values/strings.xml @@ -993,7 +993,6 @@ Do you want to switch to this account? Delete %s Failed to decrypt cipher. Contact support. Decryption error - Add Item Rotation Scan a QR code Cannot add authenticator key? Enable camera permission to use the scanner