Update androidx dependecies and target API (#4212)

This commit is contained in:
David Perez 2024-11-13 10:22:55 -06:00 committed by GitHub
parent 072c3a992c
commit 911c9e4704
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
70 changed files with 727 additions and 771 deletions

View file

@ -9,7 +9,7 @@
## Compatibility
- **Minimum SDK**: 29
- **Target SDK**: 34
- **Target SDK**: 35
- **Device Types Supported**: Phone and Tablet
- **Orientations Supported**: Portrait and Landscape

View file

@ -8,7 +8,6 @@ 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.sizeIn
import androidx.compose.foundation.rememberScrollState
@ -134,14 +133,13 @@ fun SetupAutoFillScreen(
},
)
},
) { innerPadding ->
) {
SetupAutoFillContent(
state = state,
onAutofillServiceChanged = { handler.onAutofillServiceChanged(it) },
onContinueClick = handler.onContinueClick,
onTurnOnLaterClick = handler.onTurnOnLaterClick,
modifier = Modifier
.padding(innerPadding)
.verticalScroll(rememberScrollState())
.fillMaxSize(),
)

View file

@ -7,7 +7,6 @@ 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.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
@ -62,11 +61,9 @@ fun SetupCompleteScreen(
modifier = Modifier
.fillMaxSize()
.nestedScroll(scrollBehavior.nestedScrollConnection),
) { innerPadding ->
) {
SetupCompleteContent(
modifier = Modifier
.padding(innerPadding)
.verticalScroll(rememberScrollState()),
modifier = Modifier.verticalScroll(rememberScrollState()),
onContinue = setupCompleteAction,
)
}

View file

@ -128,15 +128,13 @@ fun SetupUnlockScreen(
},
)
},
) { innerPadding ->
) {
SetupUnlockScreenContent(
state = state,
showBiometricsPrompt = showBiometricsPrompt,
handler = handler,
biometricsManager = biometricsManager,
modifier = Modifier
.padding(paddingValues = innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
)
}
}

View file

@ -91,10 +91,9 @@ fun CheckEmailScreen(
onNavigationIconClick = handler.onBackClick,
)
},
) { innerPadding ->
) {
Column(
modifier = Modifier
.padding(innerPadding)
.imePadding()
.fillMaxSize()
.verticalScroll(rememberScrollState()),

View file

@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imePadding
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
@ -158,10 +157,9 @@ fun CompleteRegistrationScreen(
},
)
},
) { innerPadding ->
) {
Column(
modifier = Modifier
.padding(innerPadding)
.imePadding()
.fillMaxSize()
.verticalScroll(rememberScrollState()),

View file

@ -187,10 +187,9 @@ fun CreateAccountScreen(
},
)
},
) { innerPadding ->
) {
Column(
modifier = Modifier
.padding(innerPadding)
.imePadding()
.fillMaxSize()
.verticalScroll(rememberScrollState()),

View file

@ -126,15 +126,13 @@ fun EnterpriseSignOnScreen(
},
)
},
) { innerPadding ->
) {
EnterpriseSignOnScreenContent(
state = state,
onOrgIdentifierInputChange = remember(viewModel) {
{ viewModel.trySendAction(EnterpriseSignOnAction.OrgIdentifierInputChange(it)) }
},
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
)
}
}

View file

@ -98,10 +98,9 @@ fun EnvironmentScreen(
},
)
},
) { innerPadding ->
) {
Column(
Modifier
.padding(innerPadding)
modifier = Modifier
.fillMaxSize()
.imePadding()
.verticalScroll(rememberScrollState()),

View file

@ -7,7 +7,6 @@ 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
@ -72,7 +71,7 @@ fun ExpiredRegistrationLinkScreen(
),
)
},
) { innerPadding ->
) {
ExpiredRegistrationLinkContent(
onNavigateToLogin = remember(viewModel) {
{
@ -87,7 +86,6 @@ fun ExpiredRegistrationLinkScreen(
}
},
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
.verticalScroll(rememberScrollState()),
)

View file

@ -148,7 +148,29 @@ fun LandingScreen(
)
}
},
) { innerPadding ->
overlay = {
BitwardenAccountSwitcher(
isVisible = isAccountMenuVisible,
accountSummaries = state.accountSummaries.toImmutableList(),
onSwitchAccountClick = remember(viewModel) {
{ viewModel.trySendAction(LandingAction.SwitchAccountClick(it)) }
},
onLockAccountClick = remember(viewModel) {
{ viewModel.trySendAction(LandingAction.LockAccountClick(it)) }
},
onLogoutAccountClick = remember(viewModel) {
{ viewModel.trySendAction(LandingAction.LogoutAccountClick(it)) }
},
onAddAccountClick = {
// Not available
},
onDismissRequest = { isAccountMenuVisible = false },
isAddAccountAvailable = false,
topAppBarScrollBehavior = scrollBehavior,
modifier = Modifier.fillMaxSize(),
)
},
) {
LandingScreenContent(
state = state,
isAppBarVisible = isAppBarVisible,
@ -167,32 +189,7 @@ fun LandingScreen(
onCreateAccountClick = remember(viewModel) {
{ viewModel.trySendAction(LandingAction.CreateAccountClick) }
},
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
)
BitwardenAccountSwitcher(
isVisible = isAccountMenuVisible,
accountSummaries = state.accountSummaries.toImmutableList(),
onSwitchAccountClick = remember(viewModel) {
{ viewModel.trySendAction(LandingAction.SwitchAccountClick(it)) }
},
onLockAccountClick = remember(viewModel) {
{ viewModel.trySendAction(LandingAction.LockAccountClick(it)) }
},
onLogoutAccountClick = remember(viewModel) {
{ viewModel.trySendAction(LandingAction.LogoutAccountClick(it)) }
},
onAddAccountClick = {
// Not available
},
onDismissRequest = { isAccountMenuVisible = false },
isAddAccountAvailable = false,
topAppBarScrollBehavior = scrollBehavior,
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
)
}
}

View file

@ -145,7 +145,28 @@ fun LoginScreen(
},
)
},
) { innerPadding ->
overlay = {
BitwardenAccountSwitcher(
isVisible = isAccountMenuVisible,
accountSummaries = state.accountSummaries.toImmutableList(),
onSwitchAccountClick = remember(viewModel) {
{ viewModel.trySendAction(LoginAction.SwitchAccountClick(it)) }
},
onLockAccountClick = remember(viewModel) {
{ viewModel.trySendAction(LoginAction.LockAccountClick(it)) }
},
onLogoutAccountClick = remember(viewModel) {
{ viewModel.trySendAction(LoginAction.LogoutAccountClick(it)) }
},
onAddAccountClick = remember(viewModel) {
{ viewModel.trySendAction(LoginAction.AddAccountClick) }
},
onDismissRequest = { isAccountMenuVisible = false },
topAppBarScrollBehavior = scrollBehavior,
modifier = Modifier.fillMaxSize(),
)
},
) {
LoginScreenContent(
state = state,
onPasswordInputChanged = remember(viewModel) {
@ -169,31 +190,7 @@ fun LoginScreen(
onNotYouButtonClick = remember(viewModel) {
{ viewModel.trySendAction(LoginAction.NotYouButtonClick) }
},
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
)
BitwardenAccountSwitcher(
isVisible = isAccountMenuVisible,
accountSummaries = state.accountSummaries.toImmutableList(),
onSwitchAccountClick = remember(viewModel) {
{ viewModel.trySendAction(LoginAction.SwitchAccountClick(it)) }
},
onLockAccountClick = remember(viewModel) {
{ viewModel.trySendAction(LoginAction.LockAccountClick(it)) }
},
onLogoutAccountClick = remember(viewModel) {
{ viewModel.trySendAction(LoginAction.LogoutAccountClick(it)) }
},
onAddAccountClick = remember(viewModel) {
{ viewModel.trySendAction(LoginAction.AddAccountClick) }
},
onDismissRequest = { isAccountMenuVisible = false },
topAppBarScrollBehavior = scrollBehavior,
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
)
}
}

View file

@ -102,10 +102,7 @@ fun LoginWithDeviceScreen(
},
)
},
) { paddingValues ->
val modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
) {
when (val viewState = state.viewState) {
is LoginWithDeviceState.ViewState.Content -> {
LoginWithDeviceScreenContent(
@ -116,12 +113,12 @@ fun LoginWithDeviceScreen(
onViewAllLogInOptionsClick = remember(viewModel) {
{ viewModel.trySendAction(LoginWithDeviceAction.ViewAllLogInOptionsClick) }
},
modifier = modifier,
modifier = Modifier.fillMaxSize(),
)
}
LoginWithDeviceState.ViewState.Loading -> BitwardenLoadingContent(
modifier = modifier,
modifier = Modifier.fillMaxSize(),
)
}
}

View file

@ -98,12 +98,11 @@ fun MasterPasswordGeneratorScreen(
snackbarHost = {
BitwardenSnackbarHost(bitwardenHostState = snackbarHostState)
},
) { innerPadding ->
) {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(innerPadding),
.verticalScroll(rememberScrollState()),
) {
MasterPasswordGeneratorContent(
generatedPassword = state.generatedPassword,

View file

@ -80,12 +80,11 @@ fun MasterPasswordGuidanceScreen(
},
)
},
) { innerPadding ->
) {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(innerPadding)
.standardHorizontalMargin(),
) {
Column(

View file

@ -111,11 +111,9 @@ fun MasterPasswordHintScreen(
},
)
},
) { innerPadding ->
) {
Column(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
) {
BitwardenTextField(
modifier = Modifier

View file

@ -74,10 +74,9 @@ fun PreventAccountLockoutScreen(
},
)
},
) { innerPadding ->
) {
Column(
modifier = Modifier
.padding(innerPadding)
.fillMaxWidth()
.standardHorizontalMargin()
.verticalScroll(rememberScrollState()),

View file

@ -6,7 +6,6 @@ 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
@ -65,7 +64,7 @@ fun RemovePasswordScreen(
navigationIcon = null,
)
},
) { innerPadding ->
) {
RemovePasswordScreenContent(
state = state,
onContinueClick = remember(viewModel) {
@ -74,9 +73,7 @@ fun RemovePasswordScreen(
onInputChanged = remember(viewModel) {
{ viewModel.trySendAction(RemovePasswordAction.InputChanged(it)) }
},
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
)
}
}

View file

@ -121,7 +121,7 @@ fun ResetPasswordScreen(
},
)
},
) { innerPadding ->
) {
ResetPasswordScreenContent(
state = state,
onCurrentPasswordInputChanged = remember(viewModel) {
@ -136,9 +136,7 @@ fun ResetPasswordScreen(
onPasswordHintInputChanged = remember(viewModel) {
{ viewModel.trySendAction(ResetPasswordAction.PasswordHintInputChanged(it)) }
},
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
)
}
}

View file

@ -84,7 +84,7 @@ fun SetPasswordScreen(
},
)
},
) { innerPadding ->
) {
SetPasswordScreenContent(
state = state,
onPasswordInputChanged = remember(viewModel) {
@ -97,7 +97,6 @@ fun SetPasswordScreen(
{ viewModel.trySendAction(SetPasswordAction.PasswordHintInputChanged(it)) }
},
modifier = Modifier
.padding(innerPadding)
.imePadding()
.fillMaxSize(),
)

View file

@ -177,10 +177,9 @@ fun StartRegistrationScreen(
onNavigationIconClick = handler.onBackClick,
)
},
) { innerPadding ->
) {
Column(
modifier = Modifier
.padding(innerPadding)
.imePadding()
.fillMaxSize()
.verticalScroll(rememberScrollState()),

View file

@ -114,10 +114,9 @@ private fun TrustedDeviceScaffold(
),
)
},
) { innerPadding ->
) {
Column(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
.verticalScroll(rememberScrollState()),
) {

View file

@ -160,7 +160,7 @@ fun TwoFactorLoginScreen(
},
)
},
) { innerPadding ->
) {
TwoFactorLoginScreenContent(
state = state,
onCodeInputChange = remember(viewModel) {
@ -175,9 +175,7 @@ fun TwoFactorLoginScreen(
onResendEmailButtonClick = remember(viewModel) {
{ viewModel.trySendAction(TwoFactorLoginAction.ResendEmailClick) }
},
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
)
}
}

View file

@ -2,7 +2,6 @@ package com.x8bit.bitwarden.ui.auth.feature.vaultunlock
import android.widget.Toast
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
@ -205,99 +204,7 @@ fun VaultUnlockScreen(
},
)
},
) { innerPadding ->
Box {
Column(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
.verticalScroll(rememberScrollState()),
) {
if (!state.hideInput) {
BitwardenPasswordField(
label = state.vaultUnlockType.unlockScreenInputLabel(),
value = state.input,
onValueChange = remember(viewModel) {
{ viewModel.trySendAction(VaultUnlockAction.InputChanged(it)) }
},
keyboardType = state.vaultUnlockType.unlockScreenKeyboardType,
showPasswordTestTag = state
.vaultUnlockType
.inputFieldVisibilityToggleTestTag,
modifier = Modifier
.testTag(state.vaultUnlockType.unlockScreenInputTestTag)
.padding(horizontal = 16.dp)
.fillMaxWidth(),
autoFocus = state.showKeyboard,
imeAction = ImeAction.Done,
keyboardActions = KeyboardActions(
onDone = remember(viewModel) {
{ viewModel.trySendAction(VaultUnlockAction.UnlockClick) }
},
),
)
Spacer(modifier = Modifier.height(24.dp))
Text(
text = state.vaultUnlockType.unlockScreenMessage(),
style = BitwardenTheme.typography.bodyMedium,
color = BitwardenTheme.colorScheme.text.primary,
modifier = Modifier
.padding(horizontal = 16.dp)
.fillMaxWidth(),
)
Spacer(modifier = Modifier.height(8.dp))
}
Text(
text = stringResource(
id = R.string.logged_in_as_on,
state.email,
state.environmentUrl,
),
style = BitwardenTheme.typography.bodyMedium,
color = BitwardenTheme.colorScheme.text.primary,
modifier = Modifier
.testTag("UserAndEnvironmentDataLabel")
.padding(horizontal = 16.dp)
.fillMaxWidth(),
)
Spacer(modifier = Modifier.height(24.dp))
if (state.showBiometricLogin && biometricsManager.isBiometricsSupported) {
BitwardenOutlinedButton(
label = stringResource(id = R.string.use_biometrics_to_unlock),
onClick = remember(viewModel) {
{ viewModel.trySendAction(VaultUnlockAction.BiometricsUnlockClick) }
},
modifier = Modifier
.padding(horizontal = 16.dp)
.fillMaxWidth(),
)
Spacer(modifier = Modifier.height(12.dp))
} else if (state.showBiometricInvalidatedMessage) {
Text(
text = stringResource(R.string.account_biometric_invalidated),
textAlign = TextAlign.Start,
style = BitwardenTheme.typography.bodyMedium,
color = BitwardenTheme.colorScheme.status.error,
modifier = Modifier.padding(horizontal = 16.dp),
)
Spacer(modifier = Modifier.height(12.dp))
}
if (!state.hideInput) {
BitwardenFilledButton(
label = stringResource(id = R.string.unlock),
onClick = remember(viewModel) {
{ viewModel.trySendAction(VaultUnlockAction.UnlockClick) }
},
isEnabled = state.input.isNotEmpty(),
modifier = Modifier
.testTag("UnlockVaultButton")
.padding(horizontal = 16.dp)
.fillMaxWidth(),
)
}
Spacer(modifier = Modifier.navigationBarsPadding())
}
overlay = {
BitwardenAccountSwitcher(
isVisible = accountMenuVisible,
accountSummaries = state.accountSummaries.toImmutableList(),
@ -315,10 +222,98 @@ fun VaultUnlockScreen(
},
onDismissRequest = { accountMenuVisible = false },
topAppBarScrollBehavior = scrollBehavior,
modifier = Modifier
.fillMaxSize()
.padding(innerPadding),
modifier = Modifier.fillMaxSize(),
)
},
) {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState()),
) {
if (!state.hideInput) {
BitwardenPasswordField(
label = state.vaultUnlockType.unlockScreenInputLabel(),
value = state.input,
onValueChange = remember(viewModel) {
{ viewModel.trySendAction(VaultUnlockAction.InputChanged(it)) }
},
keyboardType = state.vaultUnlockType.unlockScreenKeyboardType,
showPasswordTestTag = state
.vaultUnlockType
.inputFieldVisibilityToggleTestTag,
modifier = Modifier
.testTag(state.vaultUnlockType.unlockScreenInputTestTag)
.padding(horizontal = 16.dp)
.fillMaxWidth(),
autoFocus = state.showKeyboard,
imeAction = ImeAction.Done,
keyboardActions = KeyboardActions(
onDone = remember(viewModel) {
{ viewModel.trySendAction(VaultUnlockAction.UnlockClick) }
},
),
)
Spacer(modifier = Modifier.height(24.dp))
Text(
text = state.vaultUnlockType.unlockScreenMessage(),
style = BitwardenTheme.typography.bodyMedium,
color = BitwardenTheme.colorScheme.text.primary,
modifier = Modifier
.padding(horizontal = 16.dp)
.fillMaxWidth(),
)
Spacer(modifier = Modifier.height(8.dp))
}
Text(
text = stringResource(
id = R.string.logged_in_as_on,
state.email,
state.environmentUrl,
),
style = BitwardenTheme.typography.bodyMedium,
color = BitwardenTheme.colorScheme.text.primary,
modifier = Modifier
.testTag("UserAndEnvironmentDataLabel")
.padding(horizontal = 16.dp)
.fillMaxWidth(),
)
Spacer(modifier = Modifier.height(24.dp))
if (state.showBiometricLogin && biometricsManager.isBiometricsSupported) {
BitwardenOutlinedButton(
label = stringResource(id = R.string.use_biometrics_to_unlock),
onClick = remember(viewModel) {
{ viewModel.trySendAction(VaultUnlockAction.BiometricsUnlockClick) }
},
modifier = Modifier
.padding(horizontal = 16.dp)
.fillMaxWidth(),
)
Spacer(modifier = Modifier.height(12.dp))
} else if (state.showBiometricInvalidatedMessage) {
Text(
text = stringResource(R.string.account_biometric_invalidated),
textAlign = TextAlign.Start,
style = BitwardenTheme.typography.bodyMedium,
color = BitwardenTheme.colorScheme.status.error,
modifier = Modifier.padding(horizontal = 16.dp),
)
Spacer(modifier = Modifier.height(12.dp))
}
if (!state.hideInput) {
BitwardenFilledButton(
label = stringResource(id = R.string.unlock),
onClick = remember(viewModel) {
{ viewModel.trySendAction(VaultUnlockAction.UnlockClick) }
},
isEnabled = state.input.isNotEmpty(),
modifier = Modifier
.testTag("UnlockVaultButton")
.padding(horizontal = 16.dp)
.fillMaxWidth(),
)
}
Spacer(modifier = Modifier.navigationBarsPadding())
}
}
}

View file

@ -81,7 +81,7 @@ fun WelcomeScreen(
modifier = Modifier.fillMaxSize(),
containerColor = BitwardenTheme.colorScheme.background.secondary,
contentColor = BitwardenTheme.colorScheme.text.secondary,
) { innerPadding ->
) {
WelcomeScreenContent(
state = state,
pagerState = pagerState,
@ -97,9 +97,7 @@ fun WelcomeScreen(
onLoginClick = remember(viewModel) {
{ viewModel.trySendAction(WelcomeAction.LoginClick) }
},
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
)
}
}

View file

@ -14,11 +14,18 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.union
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.ExperimentalMaterial3Api
@ -107,9 +114,12 @@ fun BitwardenAccountSwitcher(
onLogoutAccountClick: (AccountSummary) -> Unit,
onAddAccountClick: () -> Unit,
onDismissRequest: () -> Unit,
modifier: Modifier = Modifier,
isAddAccountAvailable: Boolean = true,
topAppBarScrollBehavior: TopAppBarScrollBehavior,
modifier: Modifier = Modifier,
windowInsets: WindowInsets = WindowInsets.displayCutout
.union(WindowInsets.navigationBars)
.only(WindowInsetsSides.Horizontal),
isAddAccountAvailable: Boolean = true,
) {
// Track the actual visibility (according to the internal transitions) so that we know when we
// can safely show dialogs.
@ -190,6 +200,7 @@ fun BitwardenAccountSwitcher(
isAddAccountAvailable = isAddAccountAvailable,
topAppBarScrollBehavior = topAppBarScrollBehavior,
currentAnimationState = { isVisibleActual = it },
windowInsets = windowInsets,
modifier = Modifier
.fillMaxWidth(),
)
@ -208,9 +219,12 @@ private fun AnimatedAccountSwitcher(
onSwitchAccountLongClick: (AccountSummary) -> Unit,
onAddAccountClick: () -> Unit,
isAddAccountAvailable: Boolean,
modifier: Modifier = Modifier,
topAppBarScrollBehavior: TopAppBarScrollBehavior,
currentAnimationState: (isVisible: Boolean) -> Unit,
modifier: Modifier = Modifier,
windowInsets: WindowInsets = WindowInsets.displayCutout
.union(WindowInsets.navigationBars)
.only(WindowInsetsSides.Horizontal),
) {
val transition = updateTransition(
targetState = isVisible,
@ -229,7 +243,8 @@ private fun AnimatedAccountSwitcher(
// bottom padding.
.padding(bottom = 24.dp)
// Match the color of the switcher the different states of the app bar.
.scrolledContainerBackground(topAppBarScrollBehavior),
.scrolledContainerBackground(topAppBarScrollBehavior)
.windowInsetsPadding(windowInsets),
) {
items(accountSummaries) { accountSummary ->
AccountSummaryItem(

View file

@ -1,6 +1,11 @@
package com.x8bit.bitwarden.ui.platform.components.appbar
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.union
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MediumTopAppBar
import androidx.compose.material3.Text
@ -32,6 +37,7 @@ import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
* @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 windowInsets The insets to be applied to this composable.
* @param dividerStyle Determines how the bottom divider should be displayed.
* @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
@ -43,10 +49,13 @@ fun BitwardenMediumTopAppBar(
title: String,
scrollBehavior: TopAppBarScrollBehavior,
modifier: Modifier = Modifier,
windowInsets: WindowInsets = TopAppBarDefaults.windowInsets
.union(WindowInsets.displayCutout.only(WindowInsetsSides.Horizontal)),
dividerStyle: TopAppBarDividerStyle = TopAppBarDividerStyle.ON_SCROLL,
actions: @Composable RowScope.() -> Unit = {},
) {
TopAppBar(
windowInsets = windowInsets,
colors = bitwardenTopAppBarColors(),
scrollBehavior = scrollBehavior,
expandedHeight = 56.dp,

View file

@ -1,11 +1,17 @@
package com.x8bit.bitwarden.ui.platform.components.appbar
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.union
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
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
@ -42,6 +48,8 @@ fun BitwardenSearchTopAppBar(
scrollBehavior: TopAppBarScrollBehavior,
navigationIcon: NavigationIcon?,
modifier: Modifier = Modifier,
windowInsets: WindowInsets = TopAppBarDefaults.windowInsets
.union(WindowInsets.displayCutout.only(WindowInsetsSides.Horizontal)),
autoFocus: Boolean = true,
) {
val focusRequester = remember { FocusRequester() }
@ -49,6 +57,7 @@ fun BitwardenSearchTopAppBar(
modifier = modifier
.testTag(tag = "HeaderBarComponent")
.bottomDivider(),
windowInsets = windowInsets,
colors = bitwardenTopAppBarColors(),
scrollBehavior = scrollBehavior,
navigationIcon = {

View file

@ -1,6 +1,11 @@
package com.x8bit.bitwarden.ui.platform.components.appbar
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.union
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MediumTopAppBar
import androidx.compose.material3.Text
@ -50,6 +55,8 @@ fun BitwardenTopAppBar(
navigationIconContentDescription: String,
onNavigationIconClick: () -> Unit,
modifier: Modifier = Modifier,
windowInsets: WindowInsets = TopAppBarDefaults.windowInsets
.union(WindowInsets.displayCutout.only(WindowInsetsSides.Horizontal)),
dividerStyle: TopAppBarDividerStyle = TopAppBarDividerStyle.ON_SCROLL,
actions: @Composable RowScope.() -> Unit = { },
) {
@ -62,6 +69,7 @@ fun BitwardenTopAppBar(
onNavigationIconClick = onNavigationIconClick,
),
modifier = modifier,
windowInsets = windowInsets,
dividerStyle = dividerStyle,
actions = actions,
)
@ -87,6 +95,8 @@ fun BitwardenTopAppBar(
scrollBehavior: TopAppBarScrollBehavior,
navigationIcon: NavigationIcon?,
modifier: Modifier = Modifier,
windowInsets: WindowInsets = TopAppBarDefaults.windowInsets
.union(WindowInsets.displayCutout.only(WindowInsetsSides.Horizontal)),
dividerStyle: TopAppBarDividerStyle = TopAppBarDividerStyle.ON_SCROLL,
actions: @Composable RowScope.() -> Unit = {},
minimunHeight: Dp = 48.dp,
@ -129,6 +139,7 @@ fun BitwardenTopAppBar(
if (titleTextHasOverflow) {
MediumTopAppBar(
windowInsets = windowInsets,
colors = bitwardenTopAppBarColors(),
scrollBehavior = scrollBehavior,
navigationIcon = navigationIconContent,
@ -149,6 +160,7 @@ fun BitwardenTopAppBar(
)
} else {
TopAppBar(
windowInsets = windowInsets,
colors = bitwardenTopAppBarColors(),
scrollBehavior = scrollBehavior,
navigationIcon = navigationIconContent,

View file

@ -1,6 +1,5 @@
package com.x8bit.bitwarden.ui.platform.components.bottomsheet
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.ExperimentalMaterial3Api
@ -45,10 +44,7 @@ fun BitwardenModalBottomSheet(
modifier: Modifier = Modifier,
showBottomSheet: Boolean = true,
sheetState: SheetState = rememberModalBottomSheetState(),
sheetContent: @Composable (
paddingValues: PaddingValues,
animatedOnDismiss: () -> Unit,
) -> Unit,
sheetContent: @Composable (animatedOnDismiss: () -> Unit) -> Unit,
) {
if (!showBottomSheet) return
ModalBottomSheet(
@ -79,8 +75,8 @@ fun BitwardenModalBottomSheet(
modifier = Modifier
.nestedScroll(scrollBehavior.nestedScrollConnection)
.fillMaxSize(),
) { paddingValues ->
sheetContent(paddingValues, animatedOnDismiss)
) {
sheetContent(animatedOnDismiss)
}
}
}

View file

@ -1,5 +1,10 @@
package com.x8bit.bitwarden.ui.platform.components.fab
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.union
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
@ -14,6 +19,7 @@ import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
* @param painter The icon for the button.
* @param contentDescription The content description for the button.
* @param modifier The [Modifier] to be applied to the button.
* @param windowInsets The insets to be applied to this composable.
*/
@Composable
fun BitwardenFloatingActionButton(
@ -21,13 +27,14 @@ fun BitwardenFloatingActionButton(
painter: Painter,
contentDescription: String,
modifier: Modifier = Modifier,
windowInsets: WindowInsets = WindowInsets.displayCutout.union(WindowInsets.navigationBars),
) {
FloatingActionButton(
containerColor = BitwardenTheme.colorScheme.filledButton.background,
contentColor = BitwardenTheme.colorScheme.filledButton.foreground,
onClick = onClick,
shape = BitwardenTheme.shapes.fab,
modifier = modifier,
modifier = modifier.windowInsetsPadding(insets = windowInsets),
) {
Icon(
painter = painter,

View file

@ -1,12 +1,14 @@
package com.x8bit.bitwarden.ui.platform.components.scaffold
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.exclude
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.union
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FabPosition
import androidx.compose.material3.Scaffold
@ -22,17 +24,25 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.compose.ui.unit.dp
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
/**
* Direct passthrough to [Scaffold] but contains a few specific override values. Everything is
* still overridable if necessary.
*
* The [utilityBar] is a nonstandard [Composable] that is placed below the [topBar] and does not
* scroll.
* The [overlay] is a nonstandard [Composable] that is placed over top the `utilityBar` and
* `content`.
*/
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
@Composable
fun BitwardenScaffold(
modifier: Modifier = Modifier,
topBar: @Composable () -> Unit = { },
utilityBar: @Composable () -> Unit = { },
overlay: @Composable () -> Unit = { },
bottomBar: @Composable () -> Unit = { },
snackbarHost: @Composable () -> Unit = { },
floatingActionButton: @Composable () -> Unit = { },
@ -42,8 +52,9 @@ fun BitwardenScaffold(
contentColor: Color = BitwardenTheme.colorScheme.text.primary,
contentWindowInsets: WindowInsets = ScaffoldDefaults
.contentWindowInsets
.exclude(WindowInsets.navigationBars),
content: @Composable (PaddingValues) -> Unit,
.union(WindowInsets.displayCutout)
.only(WindowInsetsSides.Horizontal),
content: @Composable () -> Unit,
) {
Scaffold(
modifier = Modifier
@ -52,36 +63,38 @@ fun BitwardenScaffold(
topBar = topBar,
bottomBar = bottomBar,
snackbarHost = snackbarHost,
floatingActionButton = {
Box(modifier = Modifier.navigationBarsPadding()) {
floatingActionButton()
}
},
floatingActionButton = floatingActionButton,
floatingActionButtonPosition = floatingActionButtonPosition,
containerColor = containerColor,
contentColor = contentColor,
contentWindowInsets = contentWindowInsets,
contentWindowInsets = WindowInsets(0.dp),
content = { paddingValues ->
val internalPullToRefreshState = rememberPullToRefreshState()
Box(
modifier = Modifier.pullToRefresh(
state = internalPullToRefreshState,
isRefreshing = pullToRefreshState.isRefreshing,
onRefresh = pullToRefreshState.onRefresh,
enabled = pullToRefreshState.isEnabled,
),
) {
content(paddingValues)
PullToRefreshDefaults.Indicator(
Column(modifier = Modifier.padding(paddingValues = paddingValues)) {
utilityBar()
val internalPullToRefreshState = rememberPullToRefreshState()
Box(
modifier = Modifier
.padding(paddingValues)
.align(Alignment.TopCenter),
isRefreshing = pullToRefreshState.isRefreshing,
state = internalPullToRefreshState,
containerColor = BitwardenTheme.colorScheme.background.secondary,
color = BitwardenTheme.colorScheme.icon.secondary,
)
.windowInsetsPadding(insets = contentWindowInsets)
.pullToRefresh(
state = internalPullToRefreshState,
isRefreshing = pullToRefreshState.isRefreshing,
onRefresh = pullToRefreshState.onRefresh,
enabled = pullToRefreshState.isEnabled,
),
) {
content()
PullToRefreshDefaults.Indicator(
modifier = Modifier.align(Alignment.TopCenter),
isRefreshing = pullToRefreshState.isRefreshing,
state = internalPullToRefreshState,
containerColor = BitwardenTheme.colorScheme.background.secondary,
color = BitwardenTheme.colorScheme.icon.secondary,
)
}
}
Box(modifier = Modifier.padding(paddingValues = paddingValues)) {
overlay()
}
},
)

View file

@ -3,8 +3,15 @@ package com.x8bit.bitwarden.ui.platform.components.segment
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.union
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.Text
@ -23,17 +30,22 @@ import kotlinx.collections.immutable.ImmutableList
*
* @param options List of options to display.
* @param modifier Modifier.
* @param windowInsets The insets to be applied to this composable.
*/
@Composable
fun BitwardenSegmentedButton(
options: ImmutableList<SegmentedButtonState>,
modifier: Modifier = Modifier,
windowInsets: WindowInsets = WindowInsets.displayCutout
.union(WindowInsets.navigationBars)
.only(WindowInsetsSides.Horizontal),
) {
if (options.isEmpty()) return
Box(
modifier = modifier
.background(color = BitwardenTheme.colorScheme.background.secondary)
.padding(top = 4.dp, bottom = 8.dp, start = 16.dp, end = 16.dp),
.padding(top = 4.dp, bottom = 8.dp, start = 16.dp, end = 16.dp)
.windowInsetsPadding(insets = windowInsets),
) {
SingleChoiceSegmentedButtonRow(
modifier = Modifier

View file

@ -1,5 +1,10 @@
package com.x8bit.bitwarden.ui.platform.components.snackbar
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.union
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.SnackbarHost
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@ -9,15 +14,17 @@ import androidx.compose.ui.Modifier
*
* @param bitwardenHostState The state of this snackbar.
* @param modifier The [Modifier] to be applied to the [SnackbarHost].
* @param windowInsets The insets to be applied to this composable.
*/
@Composable
fun BitwardenSnackbarHost(
bitwardenHostState: BitwardenSnackbarHostState,
modifier: Modifier = Modifier,
windowInsets: WindowInsets = WindowInsets.displayCutout.union(WindowInsets.navigationBars),
) {
SnackbarHost(
hostState = bitwardenHostState.snackbarHostState,
modifier = modifier,
modifier = modifier.windowInsetsPadding(insets = windowInsets),
) { snackbarData ->
val message = snackbarData.visuals.message
val currentCustomSnackbarData = bitwardenHostState.currentSnackbarData

View file

@ -5,7 +5,6 @@ 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.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
@ -74,11 +73,9 @@ fun DebugMenuScreen(
),
)
},
) { innerPadding ->
) {
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(innerPadding),
modifier = Modifier.verticalScroll(rememberScrollState()),
) {
Spacer(modifier = Modifier.height(16.dp))
FeatureFlagContent(

View file

@ -1,7 +1,6 @@
package com.x8bit.bitwarden.ui.platform.feature.search
import android.widget.Toast
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding
@ -91,14 +90,7 @@ fun SearchScreen(
),
)
},
modifier = Modifier
.nestedScroll(scrollBehavior.nestedScrollConnection),
) { innerPadding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding),
) {
utilityBar = {
val vaultFilterData = state.vaultFilterData
if (state.viewState.hasVaultFilter && vaultFilterData != null) {
VaultFilter(
@ -116,32 +108,39 @@ fun SearchScreen(
.fillMaxWidth(),
)
}
},
modifier = Modifier
.nestedScroll(scrollBehavior.nestedScrollConnection),
) {
when (val viewState = state.viewState) {
is SearchState.ViewState.Content -> SearchContent(
viewState = viewState,
searchHandlers = searchHandlers,
searchType = state.searchType,
modifier = Modifier
.fillMaxSize()
.imePadding(),
)
val innerModifier = Modifier
.fillMaxSize()
.imePadding()
when (val viewState = state.viewState) {
is SearchState.ViewState.Content -> SearchContent(
viewState = viewState,
searchHandlers = searchHandlers,
searchType = state.searchType,
modifier = innerModifier,
)
is SearchState.ViewState.Empty -> SearchEmptyContent(
viewState = viewState,
modifier = Modifier
.fillMaxSize()
.imePadding(),
)
is SearchState.ViewState.Empty -> SearchEmptyContent(
viewState = viewState,
modifier = innerModifier,
)
is SearchState.ViewState.Error -> BitwardenErrorContent(
message = viewState.message(),
modifier = Modifier
.fillMaxSize()
.imePadding(),
)
is SearchState.ViewState.Error -> BitwardenErrorContent(
message = viewState.message(),
modifier = innerModifier,
)
SearchState.ViewState.Loading -> BitwardenLoadingContent(
modifier = innerModifier,
)
}
SearchState.ViewState.Loading -> BitwardenLoadingContent(
modifier = Modifier
.fillMaxSize()
.imePadding(),
)
}
}
}

View file

@ -80,10 +80,9 @@ fun SettingsScreen(
)
},
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
) { innerPadding ->
) {
Column(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
.verticalScroll(state = rememberScrollState()),
) {

View file

@ -111,12 +111,10 @@ fun AboutScreen(
},
)
},
) { innerPadding ->
) {
ContentColumn(
state = state,
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
onHelpCenterClick = remember(viewModel) {
{ viewModel.trySendAction(AboutAction.HelpCenterClick) }
},

View file

@ -174,10 +174,9 @@ fun AccountSecurityScreen(
},
)
},
) { innerPadding ->
) {
Column(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
.verticalScroll(rememberScrollState()),
) {

View file

@ -118,12 +118,11 @@ fun DeleteAccountScreen(
},
)
},
) { innerPadding ->
) {
Column(
modifier = Modifier
.imePadding()
.fillMaxSize()
.padding(innerPadding)
.verticalScroll(rememberScrollState()),
) {
Spacer(modifier = Modifier.height(8.dp))

View file

@ -218,13 +218,12 @@ private fun DeleteAccountConfirmationScaffold(
onNavigationIconClick = onCloseClick,
)
},
) { innerPadding ->
) {
DeleteAccountConfirmationContent(
state = state,
onDeleteAccountClick = onDeleteAccountClick,
onResendCodeClick = onResendCodeClick,
onVerificationCodeTextChange = onVerificationCodeTextChange,
modifier = Modifier.padding(innerPadding),
)
}
}

View file

@ -104,7 +104,7 @@ fun LoginApprovalScreen(
},
)
},
) { innerPadding ->
) {
when (val viewState = state.viewState) {
is LoginApprovalState.ViewState.Content -> {
LoginApprovalContent(
@ -115,26 +115,20 @@ fun LoginApprovalScreen(
onDeclineLoginClick = remember(viewModel) {
{ viewModel.trySendAction(LoginApprovalAction.DeclineRequestClick) }
},
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
)
}
is LoginApprovalState.ViewState.Error -> {
BitwardenErrorContent(
message = stringResource(id = R.string.generic_error_message),
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
)
}
is LoginApprovalState.ViewState.Loading -> {
BitwardenLoadingContent(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
)
}
}

View file

@ -125,9 +125,8 @@ fun PendingRequestsScreen(
},
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
modifier = Modifier.statusBarsPadding(),
) { paddingValues, animatedOnDismiss ->
) { animatedOnDismiss ->
PendingRequestsBottomSheetContent(
modifier = Modifier.padding(paddingValues),
permissionsManager = permissionsManager,
onDismiss = animatedOnDismiss,
)
@ -150,13 +149,11 @@ fun PendingRequestsScreen(
)
},
pullToRefreshState = pullToRefreshState,
) { innerPadding ->
) {
when (val viewState = state.viewState) {
is PendingRequestsState.ViewState.Content -> {
PendingRequestsContent(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
state = viewState,
onDeclineAllRequestsConfirm = remember(viewModel) {
{
@ -176,22 +173,16 @@ fun PendingRequestsScreen(
}
is PendingRequestsState.ViewState.Empty -> PendingRequestsEmpty(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
)
PendingRequestsState.ViewState.Error -> BitwardenErrorContent(
message = stringResource(R.string.generic_error_message),
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
)
PendingRequestsState.ViewState.Loading -> BitwardenLoadingContent(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
)
}
}

View file

@ -73,10 +73,9 @@ fun AppearanceScreen(
},
)
},
) { innerPadding ->
) {
Column(
Modifier
.padding(innerPadding)
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState()),
) {

View file

@ -126,10 +126,9 @@ fun AutoFillScreen(
},
)
},
) { innerPadding ->
) {
Column(
Modifier
.padding(innerPadding)
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState()),
) {

View file

@ -141,11 +141,9 @@ fun BlockAutoFillScreen(
)
}
},
) { innerPadding ->
) {
LazyColumn(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
) {
when (val viewState = state.viewState) {
is BlockAutoFillState.ViewState.Content -> {
@ -191,9 +189,7 @@ fun BlockAutoFillScreen(
addItemClickAction = remember(viewModel) {
{ viewModel.trySendAction(BlockAutoFillAction.AddUriClick) }
},
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
)
}
}

View file

@ -160,7 +160,7 @@ fun ExportVaultScreen(
},
)
},
) { innerPadding ->
) {
ExportVaultScreenContent(
state = state,
onConfirmFilePasswordInputChanged = remember(viewModel) {
@ -179,9 +179,7 @@ fun ExportVaultScreen(
{ viewModel.trySendAction(ExportVaultAction.SendCodeClick) }
},
onExportVaultClick = { shouldShowConfirmationDialog = true },
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
)
}
}

View file

@ -98,7 +98,7 @@ fun FoldersScreen(
.navigationBarsPadding(),
)
},
) { innerPadding ->
) {
when (val viewState = state.value.viewState) {
is FoldersState.ViewState.Content -> {
FoldersContent(
@ -106,26 +106,20 @@ fun FoldersScreen(
onItemClick = remember(viewModel) {
{ viewModel.trySendAction(FoldersAction.FolderClick(it)) }
},
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
)
}
is FoldersState.ViewState.Error -> {
BitwardenErrorContent(
message = viewState.message(),
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
)
}
is FoldersState.ViewState.Loading -> {
BitwardenLoadingContent(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
)
}
}

View file

@ -124,13 +124,11 @@ fun FolderAddEditScreen(
},
)
},
) { innerPadding ->
) {
when (val viewState = state.viewState) {
is FolderAddEditState.ViewState.Content -> {
Column(
Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
) {
BitwardenTextField(
label = stringResource(id = R.string.name),
@ -148,17 +146,13 @@ fun FolderAddEditScreen(
is FolderAddEditState.ViewState.Error -> {
BitwardenErrorContent(
message = viewState.message(),
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
)
}
is FolderAddEditState.ViewState.Loading -> {
BitwardenLoadingContent(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
)
}
}

View file

@ -93,10 +93,9 @@ fun OtherScreen(
},
)
},
) { innerPadding ->
) {
Column(
Modifier
.padding(innerPadding)
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState()),
) {

View file

@ -100,10 +100,9 @@ fun VaultSettingsScreen(
bitwardenHostState = snackbarHostState,
)
},
) { innerPadding ->
) {
Column(
Modifier
.padding(innerPadding)
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState()),
) {

View file

@ -2,16 +2,14 @@ package com.x8bit.bitwarden.ui.platform.feature.vaultunlockednavbar
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
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.foundation.layout.only
import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.ScaffoldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
@ -21,6 +19,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavBackStackEntry
@ -35,7 +34,6 @@ import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navOptions
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
import com.x8bit.bitwarden.ui.platform.base.util.max
import com.x8bit.bitwarden.ui.platform.base.util.toDp
import com.x8bit.bitwarden.ui.platform.components.navigation.BitwardenNavigationBarItem
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
@ -177,10 +175,9 @@ private fun VaultUnlockedNavBarScaffold(
var shouldDimNavBar by remember { mutableStateOf(false) }
// This scaffold will host screens that contain top bars while not hosting one itself.
// We need to ignore the status bar insets here and let the content screens handle
// it themselves.
// We need to ignore the all insets here and let the content screens handle it themselves.
BitwardenScaffold(
contentWindowInsets = ScaffoldDefaults.contentWindowInsets.exclude(WindowInsets.statusBars),
contentWindowInsets = WindowInsets(0.dp),
bottomBar = {
Box {
var appBarHeightPx by remember { mutableIntStateOf(0) }
@ -208,17 +205,16 @@ private fun VaultUnlockedNavBarScaffold(
)
}
},
) { innerPadding ->
) {
// Because this Scaffold has a bottom navigation bar, the NavHost will:
// - consume the navigation bar insets.
// - consume the vertical navigation bar insets.
// - consume the IME insets.
NavHost(
navController = navController,
startDestination = VAULT_GRAPH_ROUTE,
modifier = Modifier
.consumeWindowInsets(WindowInsets.navigationBars)
.consumeWindowInsets(WindowInsets.ime)
.padding(innerPadding.max(WindowInsets.ime)),
.consumeWindowInsets(WindowInsets.navigationBars.only(WindowInsetsSides.Vertical))
.consumeWindowInsets(WindowInsets.ime),
enterTransition = RootTransitionProviders.Enter.fadeIn,
exitTransition = RootTransitionProviders.Exit.fadeOut,
popEnterTransition = RootTransitionProviders.Enter.fadeIn,

View file

@ -189,12 +189,7 @@ fun GeneratorScreen(
}
}
},
snackbarHost = {
BitwardenSnackbarHost(bitwardenHostState = snackbarHostState)
},
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
) { innerPadding ->
Column(modifier = Modifier.padding(innerPadding)) {
utilityBar = {
MainStateOptionsItem(
selectedType = state.selectedType,
passcodePolicyOverride = state.passcodePolicyOverride,
@ -203,20 +198,25 @@ fun GeneratorScreen(
modifier = Modifier
.scrolledContainerBottomDivider(topAppBarScrollBehavior = scrollBehavior),
)
ScrollContent(
state = state,
onRegenerateClick = onRegenerateClick,
onCopyClick = onCopyClick,
onUsernameSubStateOptionClicked = onUsernameOptionClicked,
passwordHandlers = passwordHandlers,
passphraseHandlers = passphraseHandlers,
usernameTypeHandlers = usernameTypeHandlers,
forwardedEmailAliasHandlers = forwardedEmailAliasHandlers,
plusAddressedEmailHandlers = plusAddressedEmailHandlers,
catchAllEmailHandlers = catchAllEmailHandlers,
randomWordHandlers = randomWordHandlers,
)
}
},
snackbarHost = {
BitwardenSnackbarHost(bitwardenHostState = snackbarHostState)
},
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
) {
ScrollContent(
state = state,
onRegenerateClick = onRegenerateClick,
onCopyClick = onCopyClick,
onUsernameSubStateOptionClicked = onUsernameOptionClicked,
passwordHandlers = passwordHandlers,
passphraseHandlers = passphraseHandlers,
usernameTypeHandlers = usernameTypeHandlers,
forwardedEmailAliasHandlers = forwardedEmailAliasHandlers,
plusAddressedEmailHandlers = plusAddressedEmailHandlers,
catchAllEmailHandlers = catchAllEmailHandlers,
randomWordHandlers = randomWordHandlers,
)
}
}

View file

@ -97,30 +97,24 @@ fun PasswordHistoryScreen(
},
)
},
content = { innerPadding ->
content = {
when (val viewState = state.viewState) {
is PasswordHistoryState.ViewState.Loading -> {
PasswordHistoryLoading(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
)
}
is PasswordHistoryState.ViewState.Error -> {
PasswordHistoryError(
state = viewState,
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
)
}
is PasswordHistoryState.ViewState.Empty -> {
PasswordHistoryEmpty(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
)
}
@ -129,8 +123,7 @@ fun PasswordHistoryScreen(
state = viewState,
modifier = Modifier
.fillMaxSize()
.imePadding()
.padding(innerPadding),
.imePadding(),
onPasswordCopyClick = { password ->
viewModel.trySendAction(
PasswordHistoryAction.PasswordCopyClick(password),

View file

@ -6,7 +6,6 @@ import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
@ -161,11 +160,10 @@ fun SendScreen(
}
},
pullToRefreshState = pullToRefreshState,
) { padding ->
) {
val modifier = Modifier
.imePadding()
.fillMaxSize()
.padding(padding)
when (val viewState = state.viewState) {
is SendState.ViewState.Content -> SendContent(
policyDisablesSend = state.policyDisablesSend,

View file

@ -21,7 +21,6 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@ -35,22 +34,18 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.base.util.scrolledContainerBottomDivider
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenOutlinedButton
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenTextButton
import com.x8bit.bitwarden.ui.platform.components.card.BitwardenInfoCalloutCard
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenPasswordField
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField
import com.x8bit.bitwarden.ui.platform.components.header.BitwardenListHeaderText
import com.x8bit.bitwarden.ui.platform.components.segment.BitwardenSegmentedButton
import com.x8bit.bitwarden.ui.platform.components.segment.SegmentedButtonState
import com.x8bit.bitwarden.ui.platform.components.stepper.BitwardenStepper
import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenSwitch
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
import com.x8bit.bitwarden.ui.platform.manager.permissions.PermissionsManager
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.handlers.AddSendHandlers
import kotlinx.collections.immutable.persistentListOf
/**
* Content view for the [AddSendScreen].
@ -60,7 +55,6 @@ import kotlinx.collections.immutable.persistentListOf
@Composable
fun AddSendContent(
state: AddSendState.ViewState.Content,
scrollBehavior: TopAppBarScrollBehavior,
policyDisablesSend: Boolean,
policySendOptionsInEffect: Boolean,
isAddMode: Boolean,
@ -72,201 +66,177 @@ fun AddSendContent(
val chooseFileCameraPermissionLauncher = permissionsManager.getLauncher { isGranted ->
addSendHandlers.onChooseFileClick(isGranted)
}
Column(modifier = modifier) {
if (isAddMode && !isShared) {
BitwardenSegmentedButton(
Column(
modifier = modifier.verticalScroll(rememberScrollState()),
) {
if (policyDisablesSend) {
Spacer(modifier = Modifier.height(8.dp))
BitwardenInfoCalloutCard(
text = stringResource(id = R.string.send_disabled_warning),
modifier = Modifier
.scrolledContainerBottomDivider(topAppBarScrollBehavior = scrollBehavior)
.fillMaxWidth(),
options = persistentListOf(
SegmentedButtonState(
text = stringResource(id = R.string.file),
onClick = addSendHandlers.onFileTypeSelect,
isChecked = state.isFileType,
testTag = "SendFileButton",
),
SegmentedButtonState(
text = stringResource(id = R.string.text),
onClick = addSendHandlers.onTextTypeSelect,
isChecked = state.isTextType,
testTag = "SendTextButton",
),
),
.padding(horizontal = 16.dp)
.fillMaxWidth()
.testTag("SendPolicyInEffectLabel"),
)
Spacer(modifier = Modifier.height(16.dp))
}
Column(
modifier = Modifier.verticalScroll(rememberScrollState()),
) {
if (policyDisablesSend) {
Spacer(modifier = Modifier.height(8.dp))
BitwardenInfoCalloutCard(
text = stringResource(id = R.string.send_disabled_warning),
modifier = Modifier
.padding(horizontal = 16.dp)
.fillMaxWidth()
.testTag("SendPolicyInEffectLabel"),
)
Spacer(modifier = Modifier.height(16.dp))
}
if (policySendOptionsInEffect) {
BitwardenInfoCalloutCard(
text = stringResource(id = R.string.send_options_policy_in_effect),
modifier = Modifier
.testTag(tag = "SendPolicyInEffectLabel")
.padding(horizontal = 16.dp)
.fillMaxWidth(),
)
Spacer(modifier = Modifier.height(16.dp))
}
BitwardenTextField(
if (policySendOptionsInEffect) {
BitwardenInfoCalloutCard(
text = stringResource(id = R.string.send_options_policy_in_effect),
modifier = Modifier
.testTag(tag = "SendNameEntry")
.fillMaxWidth()
.padding(horizontal = 16.dp),
label = stringResource(id = R.string.name),
hint = stringResource(id = R.string.name_info),
readOnly = policyDisablesSend,
value = state.common.name,
onValueChange = addSendHandlers.onNamChange,
.testTag(tag = "SendPolicyInEffectLabel")
.padding(horizontal = 16.dp)
.fillMaxWidth(),
)
Spacer(modifier = Modifier.height(8.dp))
when (val type = state.selectedType) {
is AddSendState.ViewState.Content.SendType.File -> {
BitwardenListHeaderText(
label = stringResource(id = R.string.file),
Spacer(modifier = Modifier.height(16.dp))
}
BitwardenTextField(
modifier = Modifier
.testTag(tag = "SendNameEntry")
.fillMaxWidth()
.padding(horizontal = 16.dp),
label = stringResource(id = R.string.name),
hint = stringResource(id = R.string.name_info),
readOnly = policyDisablesSend,
value = state.common.name,
onValueChange = addSendHandlers.onNamChange,
)
Spacer(modifier = Modifier.height(8.dp))
when (val type = state.selectedType) {
is AddSendState.ViewState.Content.SendType.File -> {
BitwardenListHeaderText(
label = stringResource(id = R.string.file),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
Spacer(modifier = Modifier.height(16.dp))
if (isShared) {
Text(
text = type.name.orEmpty(),
color = BitwardenTheme.colorScheme.text.primary,
style = BitwardenTheme.typography.bodyMedium,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(id = R.string.max_file_size),
color = BitwardenTheme.colorScheme.text.secondary,
style = BitwardenTheme.typography.bodySmall,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
} else if (isAddMode) {
Text(
modifier = Modifier
.testTag(tag = "SendCurrentFileNameLabel")
.align(Alignment.CenterHorizontally),
text = type.name ?: stringResource(id = R.string.no_file_chosen),
color = BitwardenTheme.colorScheme.text.secondary,
style = BitwardenTheme.typography.bodySmall,
)
Spacer(modifier = Modifier.height(8.dp))
BitwardenOutlinedButton(
label = stringResource(id = R.string.choose_file),
onClick = {
@Suppress("MaxLineLength")
if (permissionsManager.checkPermission(Manifest.permission.CAMERA)) {
addSendHandlers.onChooseFileClick(true)
} else {
chooseFileCameraPermissionLauncher.launch(
Manifest.permission.CAMERA,
)
}
},
modifier = Modifier
.testTag(tag = "SendChooseFileButton")
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = stringResource(id = R.string.max_file_size),
color = BitwardenTheme.colorScheme.text.secondary,
style = BitwardenTheme.typography.bodySmall,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 32.dp),
)
Spacer(modifier = Modifier.height(16.dp))
if (isShared) {
Text(
text = stringResource(id = R.string.type_file_info),
color = BitwardenTheme.colorScheme.text.secondary,
style = BitwardenTheme.typography.bodySmall,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
} else {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
) {
Text(
text = type.name.orEmpty(),
color = BitwardenTheme.colorScheme.text.primary,
style = BitwardenTheme.typography.bodyLarge,
modifier = Modifier.weight(1f),
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = type.displaySize.orEmpty(),
color = BitwardenTheme.colorScheme.text.primary,
style = BitwardenTheme.typography.bodyMedium,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(id = R.string.max_file_size),
color = BitwardenTheme.colorScheme.text.secondary,
style = BitwardenTheme.typography.bodySmall,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
} else if (isAddMode) {
Text(
modifier = Modifier
.testTag(tag = "SendCurrentFileNameLabel")
.align(Alignment.CenterHorizontally),
text = type.name ?: stringResource(id = R.string.no_file_chosen),
color = BitwardenTheme.colorScheme.text.secondary,
style = BitwardenTheme.typography.bodySmall,
)
Spacer(modifier = Modifier.height(8.dp))
BitwardenOutlinedButton(
label = stringResource(id = R.string.choose_file),
onClick = {
@Suppress("MaxLineLength")
if (permissionsManager.checkPermission(Manifest.permission.CAMERA)) {
addSendHandlers.onChooseFileClick(true)
} else {
chooseFileCameraPermissionLauncher.launch(
Manifest.permission.CAMERA,
)
}
},
modifier = Modifier
.testTag(tag = "SendChooseFileButton")
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = stringResource(id = R.string.max_file_size),
color = BitwardenTheme.colorScheme.text.secondary,
style = BitwardenTheme.typography.bodySmall,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 32.dp),
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = stringResource(id = R.string.type_file_info),
color = BitwardenTheme.colorScheme.text.secondary,
style = BitwardenTheme.typography.bodySmall,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
} else {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
) {
Text(
text = type.name.orEmpty(),
color = BitwardenTheme.colorScheme.text.primary,
style = BitwardenTheme.typography.bodyLarge,
modifier = Modifier.weight(1f),
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = type.displaySize.orEmpty(),
color = BitwardenTheme.colorScheme.text.primary,
style = BitwardenTheme.typography.bodyMedium,
)
}
}
}
is AddSendState.ViewState.Content.SendType.Text -> {
BitwardenTextField(
modifier = Modifier
.testTag(tag = "SendTextContentEntry")
.fillMaxWidth()
.padding(horizontal = 16.dp),
label = stringResource(id = R.string.text),
hint = stringResource(id = R.string.type_text_info),
readOnly = policyDisablesSend,
value = type.input,
singleLine = false,
onValueChange = addSendHandlers.onTextChange,
)
Spacer(modifier = Modifier.height(16.dp))
BitwardenSwitch(
modifier = Modifier
.testTag(tag = "SendHideTextByDefaultToggle")
.fillMaxWidth()
.padding(horizontal = 16.dp),
label = stringResource(id = R.string.hide_text_by_default),
isChecked = type.isHideByDefaultChecked,
onCheckedChange = addSendHandlers.onIsHideByDefaultToggle,
readOnly = policyDisablesSend,
)
}
}
Spacer(modifier = Modifier.height(16.dp))
AddSendOptions(
state = state,
sendRestrictionPolicy = policyDisablesSend,
isAddMode = isAddMode,
addSendHandlers = addSendHandlers,
)
Spacer(modifier = Modifier.height(24.dp))
Spacer(modifier = Modifier.navigationBarsPadding())
is AddSendState.ViewState.Content.SendType.Text -> {
BitwardenTextField(
modifier = Modifier
.testTag(tag = "SendTextContentEntry")
.fillMaxWidth()
.padding(horizontal = 16.dp),
label = stringResource(id = R.string.text),
hint = stringResource(id = R.string.type_text_info),
readOnly = policyDisablesSend,
value = type.input,
singleLine = false,
onValueChange = addSendHandlers.onTextChange,
)
Spacer(modifier = Modifier.height(16.dp))
BitwardenSwitch(
modifier = Modifier
.testTag(tag = "SendHideTextByDefaultToggle")
.fillMaxWidth()
.padding(horizontal = 16.dp),
label = stringResource(id = R.string.hide_text_by_default),
isChecked = type.isHideByDefaultChecked,
onCheckedChange = addSendHandlers.onIsHideByDefaultToggle,
readOnly = policyDisablesSend,
)
}
}
Spacer(modifier = Modifier.height(16.dp))
AddSendOptions(
state = state,
sendRestrictionPolicy = policyDisablesSend,
isAddMode = isAddMode,
addSendHandlers = addSendHandlers,
)
Spacer(modifier = Modifier.height(24.dp))
Spacer(modifier = Modifier.navigationBarsPadding())
}
}

View file

@ -3,8 +3,8 @@ package com.x8bit.bitwarden.ui.tools.feature.send.addsend
import android.widget.Toast
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
@ -23,6 +23,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
import com.x8bit.bitwarden.ui.platform.base.util.scrolledContainerBottomDivider
import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
import com.x8bit.bitwarden.ui.platform.components.appbar.NavigationIcon
import com.x8bit.bitwarden.ui.platform.components.appbar.action.BitwardenOverflowActionItem
@ -37,6 +38,8 @@ import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialo
import com.x8bit.bitwarden.ui.platform.components.dialog.LoadingDialogState
import com.x8bit.bitwarden.ui.platform.components.model.TopAppBarDividerStyle
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
import com.x8bit.bitwarden.ui.platform.components.segment.BitwardenSegmentedButton
import com.x8bit.bitwarden.ui.platform.components.segment.SegmentedButtonState
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
import com.x8bit.bitwarden.ui.platform.composition.LocalExitManager
import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
@ -46,11 +49,12 @@ import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import com.x8bit.bitwarden.ui.platform.manager.permissions.PermissionsManager
import com.x8bit.bitwarden.ui.platform.util.persistentListOfNotNull
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.handlers.AddSendHandlers
import kotlinx.collections.immutable.persistentListOf
/**
* Displays new send UX.
*/
@Suppress("LongMethod")
@Suppress("LongMethod", "CyclomaticComplexMethod")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AddSendScreen(
@ -187,16 +191,41 @@ fun AddSendScreen(
},
)
},
) { innerPadding ->
utilityBar = {
val viewState = state.viewState
if (state.isAddMode &&
!state.isShared &&
viewState is AddSendState.ViewState.Content
) {
BitwardenSegmentedButton(
modifier = Modifier
.scrolledContainerBottomDivider(topAppBarScrollBehavior = scrollBehavior)
.fillMaxWidth(),
options = persistentListOf(
SegmentedButtonState(
text = stringResource(id = R.string.file),
onClick = addSendHandlers.onFileTypeSelect,
isChecked = viewState.isFileType,
testTag = "SendFileButton",
),
SegmentedButtonState(
text = stringResource(id = R.string.text),
onClick = addSendHandlers.onTextTypeSelect,
isChecked = viewState.isTextType,
testTag = "SendTextButton",
),
),
)
}
},
) {
val modifier = Modifier
.imePadding()
.fillMaxSize()
.padding(paddingValues = innerPadding)
when (val viewState = state.viewState) {
is AddSendState.ViewState.Content -> AddSendContent(
state = viewState,
scrollBehavior = scrollBehavior,
policyDisablesSend = state.policyDisablesSend,
policySendOptionsInEffect = state.shouldDisplayPolicyWarning,
isAddMode = state.isAddMode,

View file

@ -3,7 +3,6 @@ package com.x8bit.bitwarden.ui.vault.feature.addedit
import android.widget.Toast
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
@ -312,7 +311,7 @@ fun VaultAddEditScreen(
.takeUnless {
state.isAddItemMode ||
!state.isCipherInCollection ||
!state.canAssociateToCollections
!state.canAssociateToCollections
},
OverflowMenuItemData(
text = stringResource(id = R.string.delete),
@ -324,7 +323,7 @@ fun VaultAddEditScreen(
},
)
},
) { innerPadding ->
) {
when (val viewState = state.viewState) {
is VaultAddEditState.ViewState.Content -> {
VaultAddEditContent(
@ -342,7 +341,6 @@ fun VaultAddEditScreen(
sshKeyItemTypeHandlers = sshKeyItemTypeHandlers,
modifier = Modifier
.imePadding()
.padding(innerPadding)
.fillMaxSize(),
)
}
@ -350,17 +348,13 @@ fun VaultAddEditScreen(
is VaultAddEditState.ViewState.Error -> {
BitwardenErrorContent(
message = viewState.message(),
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
)
}
VaultAddEditState.ViewState.Loading -> {
BitwardenLoadingContent(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
)
}
}

View file

@ -2,7 +2,6 @@ package com.x8bit.bitwarden.ui.vault.feature.attachments
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
@ -98,24 +97,21 @@ fun AttachmentsScreen(
},
)
},
) { innerPadding ->
val modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
) {
when (val viewState = state.viewState) {
is AttachmentsState.ViewState.Content -> AttachmentsContent(
viewState = viewState,
attachmentsHandlers = attachmentsHandlers,
modifier = modifier,
modifier = Modifier.fillMaxSize(),
)
is AttachmentsState.ViewState.Error -> BitwardenErrorContent(
message = viewState.message(),
modifier = modifier,
modifier = Modifier.fillMaxSize(),
)
AttachmentsState.ViewState.Loading -> BitwardenLoadingContent(
modifier = modifier,
modifier = Modifier.fillMaxSize(),
)
}
}

View file

@ -104,10 +104,9 @@ fun ImportLoginsScreen(
onDismiss = handler.onSuccessfulSyncAcknowledged,
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
modifier = Modifier.statusBarsPadding(),
) { paddingValues, animatedOnDismiss ->
) { animatedOnDismiss ->
ImportLoginsSuccessBottomSheetContent(
onCompleteImportLogins = animatedOnDismiss,
modifier = Modifier.padding(paddingValues),
)
}
@ -132,13 +131,11 @@ fun ImportLoginsScreen(
scrollBehavior = scrollBehavior,
)
},
) { innerPadding ->
) {
Crossfade(
targetState = state.viewState,
label = "CrossfadeBetweenViewStates",
modifier = Modifier
.fillMaxSize()
.padding(paddingValues = innerPadding),
modifier = Modifier.fillMaxSize(),
) { viewState ->
when (viewState) {
ImportLoginsState.ViewState.InitialContent -> {

View file

@ -254,13 +254,12 @@ fun VaultItemScreen(
)
}
},
) { innerPadding ->
) {
VaultItemContent(
viewState = state.viewState,
modifier = Modifier
.imePadding()
.fillMaxSize()
.padding(innerPadding),
.fillMaxSize(),
vaultCommonItemTypeHandlers = remember(viewModel) {
VaultCommonItemTypeHandlers.create(viewModel = viewModel)
},

View file

@ -3,7 +3,6 @@ package com.x8bit.bitwarden.ui.vault.feature.itemlisting
import android.widget.Toast
import androidx.activity.compose.BackHandler
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
@ -456,12 +455,24 @@ private fun VaultItemListingScaffold(
)
}
},
overlay = {
BitwardenAccountSwitcher(
isVisible = isAccountMenuVisible,
accountSummaries = state.accountSummaries.toImmutableList(),
onSwitchAccountClick = vaultItemListingHandlers.switchAccountClick,
onLockAccountClick = vaultItemListingHandlers.lockAccountClick,
onLogoutAccountClick = vaultItemListingHandlers.logoutAccountClick,
onAddAccountClick = {
// Not available
},
onDismissRequest = { isAccountMenuVisible = false },
isAddAccountAvailable = false,
topAppBarScrollBehavior = scrollBehavior,
modifier = Modifier.fillMaxSize(),
)
},
pullToRefreshState = pullToRefreshState,
) { paddingValues ->
val modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
) {
when (state.viewState) {
is VaultItemListingState.ViewState.Content -> {
VaultItemListingContent(
@ -475,7 +486,7 @@ private fun VaultItemListingScaffold(
masterPasswordRepromptSubmit =
vaultItemListingHandlers.masterPasswordRepromptSubmit,
onOverflowItemClick = vaultItemListingHandlers.overflowItemClick,
modifier = modifier,
modifier = Modifier.fillMaxSize(),
)
}
@ -485,7 +496,7 @@ private fun VaultItemListingScaffold(
policyDisablesSend = state.policyDisablesSend &&
state.itemListingType is VaultItemListingState.ItemListingType.Send,
addItemClickAction = vaultItemListingHandlers.addVaultItemClick,
modifier = modifier,
modifier = Modifier.fillMaxSize(),
)
}
@ -493,28 +504,13 @@ private fun VaultItemListingScaffold(
BitwardenErrorContent(
message = state.viewState.message(),
onTryAgainClick = vaultItemListingHandlers.refreshClick,
modifier = modifier,
modifier = Modifier.fillMaxSize(),
)
}
is VaultItemListingState.ViewState.Loading -> {
BitwardenLoadingContent(modifier = modifier)
BitwardenLoadingContent(modifier = Modifier.fillMaxSize())
}
}
BitwardenAccountSwitcher(
isVisible = isAccountMenuVisible,
accountSummaries = state.accountSummaries.toImmutableList(),
onSwitchAccountClick = vaultItemListingHandlers.switchAccountClick,
onLockAccountClick = vaultItemListingHandlers.lockAccountClick,
onLogoutAccountClick = vaultItemListingHandlers.logoutAccountClick,
onAddAccountClick = {
// Not available
},
onDismissRequest = { isAccountMenuVisible = false },
isAddAccountAvailable = false,
topAppBarScrollBehavior = scrollBehavior,
modifier = modifier,
)
}
}

View file

@ -121,9 +121,8 @@ fun ManualCodeEntryScreen(
scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()),
)
},
) { paddingValues ->
Column(modifier = Modifier.padding(paddingValues)) {
) {
Column {
Text(
text = stringResource(id = R.string.enter_key_manually),
style = BitwardenTheme.typography.titleMedium,

View file

@ -3,7 +3,6 @@ package com.x8bit.bitwarden.ui.vault.feature.movetoorganization
import android.widget.Toast
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
@ -126,12 +125,10 @@ private fun VaultMoveToOrganizationScaffold(
},
)
},
) { innerPadding ->
) {
val modifier = Modifier
.imePadding()
.fillMaxSize()
.padding(innerPadding)
when (state.viewState) {
is VaultMoveToOrganizationState.ViewState.Content -> {
VaultMoveToOrganizationContent(

View file

@ -125,24 +125,21 @@ fun QrCodeScanScreen(
),
)
},
) { 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(innerPadding),
)
} else {
LandscapeQRCodeContent(
onEnterCodeManuallyClick = onEnterCodeManuallyClick,
modifier = Modifier.padding(innerPadding),
)
}
}

View file

@ -2,8 +2,15 @@ package com.x8bit.bitwarden.ui.vault.feature.vault
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.union
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarScrollBehavior
@ -37,6 +44,7 @@ import kotlinx.collections.immutable.ImmutableList
* @param topAppBarScrollBehavior Used to derive the background color of the content and keep it in
* sync with the associated app bar.
* @param modifier A [Modifier] for the composable.
* @param windowInsets The insets to be applied to this composable.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ -46,6 +54,9 @@ fun VaultFilter(
onVaultFilterTypeSelect: (VaultFilterType) -> Unit,
topAppBarScrollBehavior: TopAppBarScrollBehavior,
modifier: Modifier = Modifier,
windowInsets: WindowInsets = WindowInsets.displayCutout
.union(WindowInsets.navigationBars)
.only(WindowInsetsSides.Horizontal),
) {
var shouldShowSelectionDialog by remember { mutableStateOf(false) }
@ -73,7 +84,8 @@ fun VaultFilter(
.scrolledContainerBottomDivider(topAppBarScrollBehavior = topAppBarScrollBehavior)
.padding(vertical = 8.dp)
.testTag("ActiveFilterRow")
.then(modifier),
.then(modifier)
.windowInsetsPadding(insets = windowInsets),
verticalAlignment = Alignment.CenterVertically,
) {
Text(

View file

@ -4,7 +4,6 @@ import android.widget.Toast
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@ -240,6 +239,24 @@ private fun VaultScreenScaffold(
},
)
},
utilityBar = {
state.vaultFilterDataWithFilter?.let {
VaultFilter(
selectedVaultFilterType = it.selectedVaultFilterType,
vaultFilterTypes = it.vaultFilterTypes.toImmutableList(),
onVaultFilterTypeSelect = vaultHandlers.vaultFilterTypeSelect,
topAppBarScrollBehavior = scrollBehavior,
modifier = Modifier
.padding(
start = 16.dp,
// There is some built-in padding to the menu button that makes up
// the visual difference here.
end = 12.dp,
)
.fillMaxWidth(),
)
}
},
snackbarHost = {
BitwardenSnackbarHost(
bitwardenHostState = snackbarHostState,
@ -259,81 +276,7 @@ private fun VaultScreenScaffold(
)
}
},
pullToRefreshState = pullToRefreshState,
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
) { paddingValues ->
Box {
val innerModifier = Modifier
.fillMaxSize()
val outerModifier = Modifier
.fillMaxSize()
.padding(paddingValues)
Column(modifier = outerModifier) {
state.vaultFilterDataWithFilter?.let {
VaultFilter(
selectedVaultFilterType = it.selectedVaultFilterType,
vaultFilterTypes = it.vaultFilterTypes.toImmutableList(),
onVaultFilterTypeSelect = vaultHandlers.vaultFilterTypeSelect,
topAppBarScrollBehavior = scrollBehavior,
modifier = Modifier
.padding(
start = 16.dp,
// There is some built-in padding to the menu button that makes up
// the visual difference here.
end = 12.dp,
)
.fillMaxWidth(),
)
}
when (val viewState = state.viewState) {
is VaultState.ViewState.Content -> VaultContent(
state = viewState,
showSshKeys = state.showSshKeys,
vaultHandlers = vaultHandlers,
onOverflowOptionClick = { masterPasswordRepromptAction = it },
modifier = innerModifier,
)
is VaultState.ViewState.Loading -> BitwardenLoadingContent(
modifier = innerModifier,
)
is VaultState.ViewState.NoItems -> {
AnimatedVisibility(
visible = state.showImportActionCard,
exit = actionCardExitAnimation(),
label = "VaultNoItemsActionCard",
) {
BitwardenActionCard(
cardTitle = stringResource(R.string.import_saved_logins),
cardSubtitle = stringResource(
R.string.use_a_computer_to_import_logins,
),
actionText = stringResource(R.string.get_started),
onActionClick = vaultHandlers.importActionCardClick,
onDismissClick = vaultHandlers.dismissImportActionCard,
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin()
.padding(top = 12.dp),
)
}
VaultNoItems(
modifier = innerModifier,
policyDisablesSend = false,
addItemClickAction = vaultHandlers.addItemClickAction,
)
}
is VaultState.ViewState.Error -> BitwardenErrorContent(
message = viewState.message(),
onTryAgainClick = vaultHandlers.tryAgainClick,
modifier = innerModifier,
)
}
}
overlay = {
BitwardenAccountSwitcher(
isVisible = accountMenuVisible,
accountSummaries = state.accountSummaries.toImmutableList(),
@ -343,8 +286,61 @@ private fun VaultScreenScaffold(
onAddAccountClick = vaultHandlers.addAccountClickAction,
onDismissRequest = { updateAccountMenuVisibility(false) },
topAppBarScrollBehavior = scrollBehavior,
modifier = outerModifier,
modifier = Modifier.fillMaxSize(),
)
},
pullToRefreshState = pullToRefreshState,
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
) {
Column(
modifier = Modifier.fillMaxSize(),
) {
when (val viewState = state.viewState) {
is VaultState.ViewState.Content -> VaultContent(
state = viewState,
showSshKeys = state.showSshKeys,
vaultHandlers = vaultHandlers,
onOverflowOptionClick = { masterPasswordRepromptAction = it },
modifier = Modifier.fillMaxSize(),
)
is VaultState.ViewState.Loading -> BitwardenLoadingContent(
modifier = Modifier.fillMaxSize(),
)
is VaultState.ViewState.NoItems -> {
AnimatedVisibility(
visible = state.showImportActionCard,
exit = actionCardExitAnimation(),
label = "VaultNoItemsActionCard",
) {
BitwardenActionCard(
cardTitle = stringResource(R.string.import_saved_logins),
cardSubtitle = stringResource(
R.string.use_a_computer_to_import_logins,
),
actionText = stringResource(R.string.get_started),
onActionClick = vaultHandlers.importActionCardClick,
onDismissClick = vaultHandlers.dismissImportActionCard,
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin()
.padding(top = 12.dp),
)
}
VaultNoItems(
policyDisablesSend = false,
addItemClickAction = vaultHandlers.addItemClickAction,
modifier = Modifier.fillMaxSize(),
)
}
is VaultState.ViewState.Error -> BitwardenErrorContent(
message = viewState.message(),
onTryAgainClick = vaultHandlers.tryAgainClick,
modifier = Modifier.fillMaxSize(),
)
}
}
}
}

View file

@ -106,18 +106,14 @@ fun VerificationCodeScreen(
)
},
pullToRefreshState = pullToRefreshState,
) { paddingValues ->
val modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
) {
when (val viewState = state.viewState) {
is VerificationCodeState.ViewState.Content -> {
VerificationCodeContent(
items = viewState.verificationCodeDisplayItems.toImmutableList(),
onCopyClick = verificationCodeHandler.copyClick,
itemClick = verificationCodeHandler.itemClick,
modifier = modifier,
modifier = Modifier.fillMaxSize(),
)
}
@ -125,12 +121,12 @@ fun VerificationCodeScreen(
BitwardenErrorContent(
message = viewState.message.invoke(),
onTryAgainClick = verificationCodeHandler.refreshClick,
modifier = modifier,
modifier = Modifier.fillMaxSize(),
)
}
is VerificationCodeState.ViewState.Loading -> {
BitwardenLoadingContent(modifier = modifier)
BitwardenLoadingContent(modifier = Modifier.fillMaxSize())
}
}
}

View file

@ -409,7 +409,8 @@ class AddSendScreenTest : BaseComposeTest() {
@Test
fun `Text segmented button click should send TextTypeClick`() {
composeTestRule
.onAllNodesWithText("Text")[0]
.onAllNodesWithText("Text")
.filterToOne(!isEditableText)
// A bug prevents performClick from working here so we
// have to perform the semantic action instead.
.performSemanticsAction(SemanticsActions.OnClick)
@ -469,9 +470,13 @@ class AddSendScreenTest : BaseComposeTest() {
@Test
fun `text input change should send TextChange`() {
composeTestRule
.onAllNodesWithText("Text")[1]
.onAllNodesWithText("Text")
.filterToOne(isEditableText)
.performScrollTo()
.performTextInput("input")
viewModel.trySendAction(AddSendAction.TextChange("input"))
verify(exactly = 1) {
viewModel.trySendAction(AddSendAction.TextChange("input"))
}
}
@Test

View file

@ -2,8 +2,8 @@
[versions]
# SDK Versions
compileSdk = "34"
targetSdk = "34"
compileSdk = "35"
targetSdk = "35"
minSdk = "29"
# Dependency Versions
@ -13,17 +13,17 @@ androidXBiometrics = "1.2.0-alpha05"
androidxBrowser = "1.8.0"
androidxCamera = "1.4.0"
androidxComposeBom = "2024.10.01"
androidxCore = "1.13.1"
androidxCore = "1.15.0"
androidxCredentials = "1.3.0"
androidxHiltNavigationCompose = "1.2.0"
androidxLifecycle = "2.8.6"
androidxLifecycle = "2.8.7"
androidxNavigation = "2.8.0"
androidxRoom = "2.6.1"
androidXSecurityCrypto = "1.1.0-alpha06"
androidxSplash = "1.1.0-rc01"
androidXAppCompat = "1.7.0"
androdixAutofill = "1.1.0"
androidxWork = "2.9.1"
androidxWork = "2.10.0"
bitwardenSdk = "1.0.0-20241030.101847-8"
crashlytics = "3.0.2"
detekt = "1.23.7"