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 ## Compatibility
- **Minimum SDK**: 29 - **Minimum SDK**: 29
- **Target SDK**: 34 - **Target SDK**: 35
- **Device Types Supported**: Phone and Tablet - **Device Types Supported**: Phone and Tablet
- **Orientations Supported**: Portrait and Landscape - **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.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
@ -134,14 +133,13 @@ fun SetupAutoFillScreen(
}, },
) )
}, },
) { innerPadding -> ) {
SetupAutoFillContent( SetupAutoFillContent(
state = state, state = state,
onAutofillServiceChanged = { handler.onAutofillServiceChanged(it) }, onAutofillServiceChanged = { handler.onAutofillServiceChanged(it) },
onContinueClick = handler.onContinueClick, onContinueClick = handler.onContinueClick,
onTurnOnLaterClick = handler.onTurnOnLaterClick, onTurnOnLaterClick = handler.onTurnOnLaterClick,
modifier = Modifier modifier = Modifier
.padding(innerPadding)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.fillMaxSize(), .fillMaxSize(),
) )

View file

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

View file

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

View file

@ -91,10 +91,9 @@ fun CheckEmailScreen(
onNavigationIconClick = handler.onBackClick, onNavigationIconClick = handler.onBackClick,
) )
}, },
) { innerPadding -> ) {
Column( Column(
modifier = Modifier modifier = Modifier
.padding(innerPadding)
.imePadding() .imePadding()
.fillMaxSize() .fillMaxSize()
.verticalScroll(rememberScrollState()), .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.height
import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
@ -158,10 +157,9 @@ fun CompleteRegistrationScreen(
}, },
) )
}, },
) { innerPadding -> ) {
Column( Column(
modifier = Modifier modifier = Modifier
.padding(innerPadding)
.imePadding() .imePadding()
.fillMaxSize() .fillMaxSize()
.verticalScroll(rememberScrollState()), .verticalScroll(rememberScrollState()),

View file

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

View file

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

View file

@ -98,10 +98,9 @@ fun EnvironmentScreen(
}, },
) )
}, },
) { innerPadding -> ) {
Column( Column(
Modifier modifier = Modifier
.padding(innerPadding)
.fillMaxSize() .fillMaxSize()
.imePadding() .imePadding()
.verticalScroll(rememberScrollState()), .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.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
@ -72,7 +71,7 @@ fun ExpiredRegistrationLinkScreen(
), ),
) )
}, },
) { innerPadding -> ) {
ExpiredRegistrationLinkContent( ExpiredRegistrationLinkContent(
onNavigateToLogin = remember(viewModel) { onNavigateToLogin = remember(viewModel) {
{ {
@ -87,7 +86,6 @@ fun ExpiredRegistrationLinkScreen(
} }
}, },
modifier = Modifier modifier = Modifier
.padding(innerPadding)
.fillMaxSize() .fillMaxSize()
.verticalScroll(rememberScrollState()), .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( LandingScreenContent(
state = state, state = state,
isAppBarVisible = isAppBarVisible, isAppBarVisible = isAppBarVisible,
@ -167,32 +189,7 @@ fun LandingScreen(
onCreateAccountClick = remember(viewModel) { onCreateAccountClick = remember(viewModel) {
{ viewModel.trySendAction(LandingAction.CreateAccountClick) } { viewModel.trySendAction(LandingAction.CreateAccountClick) }
}, },
modifier = Modifier modifier = Modifier.fillMaxSize(),
.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(),
) )
} }
} }

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( LoginScreenContent(
state = state, state = state,
onPasswordInputChanged = remember(viewModel) { onPasswordInputChanged = remember(viewModel) {
@ -169,31 +190,7 @@ fun LoginScreen(
onNotYouButtonClick = remember(viewModel) { onNotYouButtonClick = remember(viewModel) {
{ viewModel.trySendAction(LoginAction.NotYouButtonClick) } { viewModel.trySendAction(LoginAction.NotYouButtonClick) }
}, },
modifier = Modifier modifier = Modifier.fillMaxSize(),
.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(),
) )
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -74,10 +74,9 @@ fun PreventAccountLockoutScreen(
}, },
) )
}, },
) { innerPadding -> ) {
Column( Column(
modifier = Modifier modifier = Modifier
.padding(innerPadding)
.fillMaxWidth() .fillMaxWidth()
.standardHorizontalMargin() .standardHorizontalMargin()
.verticalScroll(rememberScrollState()), .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.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
@ -65,7 +64,7 @@ fun RemovePasswordScreen(
navigationIcon = null, navigationIcon = null,
) )
}, },
) { innerPadding -> ) {
RemovePasswordScreenContent( RemovePasswordScreenContent(
state = state, state = state,
onContinueClick = remember(viewModel) { onContinueClick = remember(viewModel) {
@ -74,9 +73,7 @@ fun RemovePasswordScreen(
onInputChanged = remember(viewModel) { onInputChanged = remember(viewModel) {
{ viewModel.trySendAction(RemovePasswordAction.InputChanged(it)) } { viewModel.trySendAction(RemovePasswordAction.InputChanged(it)) }
}, },
modifier = Modifier modifier = Modifier.fillMaxSize(),
.padding(innerPadding)
.fillMaxSize(),
) )
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,7 +2,6 @@ package com.x8bit.bitwarden.ui.auth.feature.vaultunlock
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@ -205,99 +204,7 @@ fun VaultUnlockScreen(
}, },
) )
}, },
) { innerPadding -> overlay = {
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())
}
BitwardenAccountSwitcher( BitwardenAccountSwitcher(
isVisible = accountMenuVisible, isVisible = accountMenuVisible,
accountSummaries = state.accountSummaries.toImmutableList(), accountSummaries = state.accountSummaries.toImmutableList(),
@ -315,10 +222,98 @@ fun VaultUnlockScreen(
}, },
onDismissRequest = { accountMenuVisible = false }, onDismissRequest = { accountMenuVisible = false },
topAppBarScrollBehavior = scrollBehavior, topAppBarScrollBehavior = scrollBehavior,
modifier = Modifier modifier = Modifier.fillMaxSize(),
.fillMaxSize()
.padding(innerPadding),
) )
},
) {
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(), modifier = Modifier.fillMaxSize(),
containerColor = BitwardenTheme.colorScheme.background.secondary, containerColor = BitwardenTheme.colorScheme.background.secondary,
contentColor = BitwardenTheme.colorScheme.text.secondary, contentColor = BitwardenTheme.colorScheme.text.secondary,
) { innerPadding -> ) {
WelcomeScreenContent( WelcomeScreenContent(
state = state, state = state,
pagerState = pagerState, pagerState = pagerState,
@ -97,9 +97,7 @@ fun WelcomeScreen(
onLoginClick = remember(viewModel) { onLoginClick = remember(viewModel) {
{ viewModel.trySendAction(WelcomeAction.LoginClick) } { viewModel.trySendAction(WelcomeAction.LoginClick) }
}, },
modifier = Modifier modifier = Modifier.fillMaxSize(),
.padding(innerPadding)
.fillMaxSize(),
) )
} }
} }

View file

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

View file

@ -1,6 +1,11 @@
package com.x8bit.bitwarden.ui.platform.components.appbar package com.x8bit.bitwarden.ui.platform.components.appbar
import androidx.compose.foundation.layout.RowScope 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.ExperimentalMaterial3Api
import androidx.compose.material3.MediumTopAppBar import androidx.compose.material3.MediumTopAppBar
import androidx.compose.material3.Text 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 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 * @param scrollBehavior Defines the scrolling behavior of the app bar. It controls how the app bar
* behaves in conjunction with scrolling content. * 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 dividerStyle Determines how the bottom divider should be displayed.
* @param actions A lambda containing the set of actions (usually icons or similar) to display * @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 * in the app bar's trailing side. This lambda extends [RowScope], allowing flexibility in
@ -43,10 +49,13 @@ fun BitwardenMediumTopAppBar(
title: String, title: String,
scrollBehavior: TopAppBarScrollBehavior, scrollBehavior: TopAppBarScrollBehavior,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
windowInsets: WindowInsets = TopAppBarDefaults.windowInsets
.union(WindowInsets.displayCutout.only(WindowInsetsSides.Horizontal)),
dividerStyle: TopAppBarDividerStyle = TopAppBarDividerStyle.ON_SCROLL, dividerStyle: TopAppBarDividerStyle = TopAppBarDividerStyle.ON_SCROLL,
actions: @Composable RowScope.() -> Unit = {}, actions: @Composable RowScope.() -> Unit = {},
) { ) {
TopAppBar( TopAppBar(
windowInsets = windowInsets,
colors = bitwardenTopAppBarColors(), colors = bitwardenTopAppBarColors(),
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
expandedHeight = 56.dp, expandedHeight = 56.dp,

View file

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

View file

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

View file

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

View file

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

View file

@ -1,12 +1,14 @@
package com.x8bit.bitwarden.ui.platform.components.scaffold package com.x8bit.bitwarden.ui.platform.components.scaffold
import androidx.compose.foundation.layout.Box 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.WindowInsets
import androidx.compose.foundation.layout.exclude import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding 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.ExperimentalMaterial3Api
import androidx.compose.material3.FabPosition import androidx.compose.material3.FabPosition
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
@ -22,17 +24,25 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.compose.ui.unit.dp
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
/** /**
* Direct passthrough to [Scaffold] but contains a few specific override values. Everything is * Direct passthrough to [Scaffold] but contains a few specific override values. Everything is
* still overridable if necessary. * 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) @OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
@Composable @Composable
fun BitwardenScaffold( fun BitwardenScaffold(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
topBar: @Composable () -> Unit = { }, topBar: @Composable () -> Unit = { },
utilityBar: @Composable () -> Unit = { },
overlay: @Composable () -> Unit = { },
bottomBar: @Composable () -> Unit = { }, bottomBar: @Composable () -> Unit = { },
snackbarHost: @Composable () -> Unit = { }, snackbarHost: @Composable () -> Unit = { },
floatingActionButton: @Composable () -> Unit = { }, floatingActionButton: @Composable () -> Unit = { },
@ -42,8 +52,9 @@ fun BitwardenScaffold(
contentColor: Color = BitwardenTheme.colorScheme.text.primary, contentColor: Color = BitwardenTheme.colorScheme.text.primary,
contentWindowInsets: WindowInsets = ScaffoldDefaults contentWindowInsets: WindowInsets = ScaffoldDefaults
.contentWindowInsets .contentWindowInsets
.exclude(WindowInsets.navigationBars), .union(WindowInsets.displayCutout)
content: @Composable (PaddingValues) -> Unit, .only(WindowInsetsSides.Horizontal),
content: @Composable () -> Unit,
) { ) {
Scaffold( Scaffold(
modifier = Modifier modifier = Modifier
@ -52,36 +63,38 @@ fun BitwardenScaffold(
topBar = topBar, topBar = topBar,
bottomBar = bottomBar, bottomBar = bottomBar,
snackbarHost = snackbarHost, snackbarHost = snackbarHost,
floatingActionButton = { floatingActionButton = floatingActionButton,
Box(modifier = Modifier.navigationBarsPadding()) {
floatingActionButton()
}
},
floatingActionButtonPosition = floatingActionButtonPosition, floatingActionButtonPosition = floatingActionButtonPosition,
containerColor = containerColor, containerColor = containerColor,
contentColor = contentColor, contentColor = contentColor,
contentWindowInsets = contentWindowInsets, contentWindowInsets = WindowInsets(0.dp),
content = { paddingValues -> content = { paddingValues ->
val internalPullToRefreshState = rememberPullToRefreshState() Column(modifier = Modifier.padding(paddingValues = paddingValues)) {
Box( utilityBar()
modifier = Modifier.pullToRefresh( val internalPullToRefreshState = rememberPullToRefreshState()
state = internalPullToRefreshState, Box(
isRefreshing = pullToRefreshState.isRefreshing,
onRefresh = pullToRefreshState.onRefresh,
enabled = pullToRefreshState.isEnabled,
),
) {
content(paddingValues)
PullToRefreshDefaults.Indicator(
modifier = Modifier modifier = Modifier
.padding(paddingValues) .windowInsetsPadding(insets = contentWindowInsets)
.align(Alignment.TopCenter), .pullToRefresh(
isRefreshing = pullToRefreshState.isRefreshing, state = internalPullToRefreshState,
state = internalPullToRefreshState, isRefreshing = pullToRefreshState.isRefreshing,
containerColor = BitwardenTheme.colorScheme.background.secondary, onRefresh = pullToRefreshState.onRefresh,
color = BitwardenTheme.colorScheme.icon.secondary, 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.BorderStroke
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box 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.fillMaxWidth
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding 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.SegmentedButton
import androidx.compose.material3.SingleChoiceSegmentedButtonRow import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.Text import androidx.compose.material3.Text
@ -23,17 +30,22 @@ import kotlinx.collections.immutable.ImmutableList
* *
* @param options List of options to display. * @param options List of options to display.
* @param modifier Modifier. * @param modifier Modifier.
* @param windowInsets The insets to be applied to this composable.
*/ */
@Composable @Composable
fun BitwardenSegmentedButton( fun BitwardenSegmentedButton(
options: ImmutableList<SegmentedButtonState>, options: ImmutableList<SegmentedButtonState>,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
windowInsets: WindowInsets = WindowInsets.displayCutout
.union(WindowInsets.navigationBars)
.only(WindowInsetsSides.Horizontal),
) { ) {
if (options.isEmpty()) return if (options.isEmpty()) return
Box( Box(
modifier = modifier modifier = modifier
.background(color = BitwardenTheme.colorScheme.background.secondary) .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( SingleChoiceSegmentedButtonRow(
modifier = Modifier modifier = Modifier

View file

@ -1,5 +1,10 @@
package com.x8bit.bitwarden.ui.platform.components.snackbar 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.material3.SnackbarHost
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -9,15 +14,17 @@ import androidx.compose.ui.Modifier
* *
* @param bitwardenHostState The state of this snackbar. * @param bitwardenHostState The state of this snackbar.
* @param modifier The [Modifier] to be applied to the [SnackbarHost]. * @param modifier The [Modifier] to be applied to the [SnackbarHost].
* @param windowInsets The insets to be applied to this composable.
*/ */
@Composable @Composable
fun BitwardenSnackbarHost( fun BitwardenSnackbarHost(
bitwardenHostState: BitwardenSnackbarHostState, bitwardenHostState: BitwardenSnackbarHostState,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
windowInsets: WindowInsets = WindowInsets.displayCutout.union(WindowInsets.navigationBars),
) { ) {
SnackbarHost( SnackbarHost(
hostState = bitwardenHostState.snackbarHostState, hostState = bitwardenHostState.snackbarHostState,
modifier = modifier, modifier = modifier.windowInsetsPadding(insets = windowInsets),
) { snackbarData -> ) { snackbarData ->
val message = snackbarData.visuals.message val message = snackbarData.visuals.message
val currentCustomSnackbarData = bitwardenHostState.currentSnackbarData 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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
@ -74,11 +73,9 @@ fun DebugMenuScreen(
), ),
) )
}, },
) { innerPadding -> ) {
Column( Column(
modifier = Modifier modifier = Modifier.verticalScroll(rememberScrollState()),
.verticalScroll(rememberScrollState())
.padding(innerPadding),
) { ) {
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
FeatureFlagContent( FeatureFlagContent(

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -189,12 +189,7 @@ fun GeneratorScreen(
} }
} }
}, },
snackbarHost = { utilityBar = {
BitwardenSnackbarHost(bitwardenHostState = snackbarHostState)
},
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
) { innerPadding ->
Column(modifier = Modifier.padding(innerPadding)) {
MainStateOptionsItem( MainStateOptionsItem(
selectedType = state.selectedType, selectedType = state.selectedType,
passcodePolicyOverride = state.passcodePolicyOverride, passcodePolicyOverride = state.passcodePolicyOverride,
@ -203,20 +198,25 @@ fun GeneratorScreen(
modifier = Modifier modifier = Modifier
.scrolledContainerBottomDivider(topAppBarScrollBehavior = scrollBehavior), .scrolledContainerBottomDivider(topAppBarScrollBehavior = scrollBehavior),
) )
ScrollContent( },
state = state, snackbarHost = {
onRegenerateClick = onRegenerateClick, BitwardenSnackbarHost(bitwardenHostState = snackbarHostState)
onCopyClick = onCopyClick, },
onUsernameSubStateOptionClicked = onUsernameOptionClicked, modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
passwordHandlers = passwordHandlers, ) {
passphraseHandlers = passphraseHandlers, ScrollContent(
usernameTypeHandlers = usernameTypeHandlers, state = state,
forwardedEmailAliasHandlers = forwardedEmailAliasHandlers, onRegenerateClick = onRegenerateClick,
plusAddressedEmailHandlers = plusAddressedEmailHandlers, onCopyClick = onCopyClick,
catchAllEmailHandlers = catchAllEmailHandlers, onUsernameSubStateOptionClicked = onUsernameOptionClicked,
randomWordHandlers = randomWordHandlers, 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) { when (val viewState = state.viewState) {
is PasswordHistoryState.ViewState.Loading -> { is PasswordHistoryState.ViewState.Loading -> {
PasswordHistoryLoading( PasswordHistoryLoading(
modifier = Modifier modifier = Modifier.fillMaxSize(),
.padding(innerPadding)
.fillMaxSize(),
) )
} }
is PasswordHistoryState.ViewState.Error -> { is PasswordHistoryState.ViewState.Error -> {
PasswordHistoryError( PasswordHistoryError(
state = viewState, state = viewState,
modifier = Modifier modifier = Modifier.fillMaxSize(),
.padding(innerPadding)
.fillMaxSize(),
) )
} }
is PasswordHistoryState.ViewState.Empty -> { is PasswordHistoryState.ViewState.Empty -> {
PasswordHistoryEmpty( PasswordHistoryEmpty(
modifier = Modifier modifier = Modifier.fillMaxSize(),
.padding(innerPadding)
.fillMaxSize(),
) )
} }
@ -129,8 +123,7 @@ fun PasswordHistoryScreen(
state = viewState, state = viewState,
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.imePadding() .imePadding(),
.padding(innerPadding),
onPasswordCopyClick = { password -> onPasswordCopyClick = { password ->
viewModel.trySendAction( viewModel.trySendAction(
PasswordHistoryAction.PasswordCopyClick(password), PasswordHistoryAction.PasswordCopyClick(password),

View file

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

View file

@ -21,7 +21,6 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf 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.semantics.semantics
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.x8bit.bitwarden.R 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.BitwardenOutlinedButton
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenTextButton 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.card.BitwardenInfoCalloutCard
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenPasswordField 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.field.BitwardenTextField
import com.x8bit.bitwarden.ui.platform.components.header.BitwardenListHeaderText 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.stepper.BitwardenStepper
import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenSwitch import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenSwitch
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
import com.x8bit.bitwarden.ui.platform.manager.permissions.PermissionsManager import com.x8bit.bitwarden.ui.platform.manager.permissions.PermissionsManager
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.handlers.AddSendHandlers import com.x8bit.bitwarden.ui.tools.feature.send.addsend.handlers.AddSendHandlers
import kotlinx.collections.immutable.persistentListOf
/** /**
* Content view for the [AddSendScreen]. * Content view for the [AddSendScreen].
@ -60,7 +55,6 @@ import kotlinx.collections.immutable.persistentListOf
@Composable @Composable
fun AddSendContent( fun AddSendContent(
state: AddSendState.ViewState.Content, state: AddSendState.ViewState.Content,
scrollBehavior: TopAppBarScrollBehavior,
policyDisablesSend: Boolean, policyDisablesSend: Boolean,
policySendOptionsInEffect: Boolean, policySendOptionsInEffect: Boolean,
isAddMode: Boolean, isAddMode: Boolean,
@ -72,201 +66,177 @@ fun AddSendContent(
val chooseFileCameraPermissionLauncher = permissionsManager.getLauncher { isGranted -> val chooseFileCameraPermissionLauncher = permissionsManager.getLauncher { isGranted ->
addSendHandlers.onChooseFileClick(isGranted) addSendHandlers.onChooseFileClick(isGranted)
} }
Column(modifier = modifier) { Column(
if (isAddMode && !isShared) { modifier = modifier.verticalScroll(rememberScrollState()),
BitwardenSegmentedButton( ) {
if (policyDisablesSend) {
Spacer(modifier = Modifier.height(8.dp))
BitwardenInfoCalloutCard(
text = stringResource(id = R.string.send_disabled_warning),
modifier = Modifier modifier = Modifier
.scrolledContainerBottomDivider(topAppBarScrollBehavior = scrollBehavior) .padding(horizontal = 16.dp)
.fillMaxWidth(), .fillMaxWidth()
options = persistentListOf( .testTag("SendPolicyInEffectLabel"),
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",
),
),
) )
Spacer(modifier = Modifier.height(16.dp))
} }
Column( if (policySendOptionsInEffect) {
modifier = Modifier.verticalScroll(rememberScrollState()), BitwardenInfoCalloutCard(
) { text = stringResource(id = R.string.send_options_policy_in_effect),
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(
modifier = Modifier modifier = Modifier
.testTag(tag = "SendNameEntry") .testTag(tag = "SendPolicyInEffectLabel")
.fillMaxWidth() .padding(horizontal = 16.dp)
.padding(horizontal = 16.dp), .fillMaxWidth(),
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)) Spacer(modifier = Modifier.height(16.dp))
when (val type = state.selectedType) { }
is AddSendState.ViewState.Content.SendType.File -> {
BitwardenListHeaderText( BitwardenTextField(
label = stringResource(id = R.string.file), 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 modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp), .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)) 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(
text = type.name.orEmpty(), text = type.name.orEmpty(),
color = BitwardenTheme.colorScheme.text.primary, 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, 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)) is AddSendState.ViewState.Content.SendType.Text -> {
AddSendOptions( BitwardenTextField(
state = state, modifier = Modifier
sendRestrictionPolicy = policyDisablesSend, .testTag(tag = "SendTextContentEntry")
isAddMode = isAddMode, .fillMaxWidth()
addSendHandlers = addSendHandlers, .padding(horizontal = 16.dp),
) label = stringResource(id = R.string.text),
hint = stringResource(id = R.string.type_text_info),
Spacer(modifier = Modifier.height(24.dp)) readOnly = policyDisablesSend,
Spacer(modifier = Modifier.navigationBarsPadding()) 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 android.widget.Toast
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState import androidx.compose.material3.rememberTopAppBarState
@ -23,6 +23,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.x8bit.bitwarden.R import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect 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.BitwardenTopAppBar
import com.x8bit.bitwarden.ui.platform.components.appbar.NavigationIcon import com.x8bit.bitwarden.ui.platform.components.appbar.NavigationIcon
import com.x8bit.bitwarden.ui.platform.components.appbar.action.BitwardenOverflowActionItem 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.dialog.LoadingDialogState
import com.x8bit.bitwarden.ui.platform.components.model.TopAppBarDividerStyle 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.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.components.util.rememberVectorPainter
import com.x8bit.bitwarden.ui.platform.composition.LocalExitManager import com.x8bit.bitwarden.ui.platform.composition.LocalExitManager
import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager 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.manager.permissions.PermissionsManager
import com.x8bit.bitwarden.ui.platform.util.persistentListOfNotNull import com.x8bit.bitwarden.ui.platform.util.persistentListOfNotNull
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.handlers.AddSendHandlers import com.x8bit.bitwarden.ui.tools.feature.send.addsend.handlers.AddSendHandlers
import kotlinx.collections.immutable.persistentListOf
/** /**
* Displays new send UX. * Displays new send UX.
*/ */
@Suppress("LongMethod") @Suppress("LongMethod", "CyclomaticComplexMethod")
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun AddSendScreen( 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 val modifier = Modifier
.imePadding() .imePadding()
.fillMaxSize() .fillMaxSize()
.padding(paddingValues = innerPadding)
when (val viewState = state.viewState) { when (val viewState = state.viewState) {
is AddSendState.ViewState.Content -> AddSendContent( is AddSendState.ViewState.Content -> AddSendContent(
state = viewState, state = viewState,
scrollBehavior = scrollBehavior,
policyDisablesSend = state.policyDisablesSend, policyDisablesSend = state.policyDisablesSend,
policySendOptionsInEffect = state.shouldDisplayPolicyWarning, policySendOptionsInEffect = state.shouldDisplayPolicyWarning,
isAddMode = state.isAddMode, isAddMode = state.isAddMode,

View file

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

View file

@ -2,7 +2,6 @@ package com.x8bit.bitwarden.ui.vault.feature.attachments
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState import androidx.compose.material3.rememberTopAppBarState
@ -98,24 +97,21 @@ fun AttachmentsScreen(
}, },
) )
}, },
) { innerPadding -> ) {
val modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
when (val viewState = state.viewState) { when (val viewState = state.viewState) {
is AttachmentsState.ViewState.Content -> AttachmentsContent( is AttachmentsState.ViewState.Content -> AttachmentsContent(
viewState = viewState, viewState = viewState,
attachmentsHandlers = attachmentsHandlers, attachmentsHandlers = attachmentsHandlers,
modifier = modifier, modifier = Modifier.fillMaxSize(),
) )
is AttachmentsState.ViewState.Error -> BitwardenErrorContent( is AttachmentsState.ViewState.Error -> BitwardenErrorContent(
message = viewState.message(), message = viewState.message(),
modifier = modifier, modifier = Modifier.fillMaxSize(),
) )
AttachmentsState.ViewState.Loading -> BitwardenLoadingContent( AttachmentsState.ViewState.Loading -> BitwardenLoadingContent(
modifier = modifier, modifier = Modifier.fillMaxSize(),
) )
} }
} }

View file

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

View file

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

View file

@ -3,7 +3,6 @@ package com.x8bit.bitwarden.ui.vault.feature.itemlisting
import android.widget.Toast import android.widget.Toast
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState 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, pullToRefreshState = pullToRefreshState,
) { paddingValues -> ) {
val modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
when (state.viewState) { when (state.viewState) {
is VaultItemListingState.ViewState.Content -> { is VaultItemListingState.ViewState.Content -> {
VaultItemListingContent( VaultItemListingContent(
@ -475,7 +486,7 @@ private fun VaultItemListingScaffold(
masterPasswordRepromptSubmit = masterPasswordRepromptSubmit =
vaultItemListingHandlers.masterPasswordRepromptSubmit, vaultItemListingHandlers.masterPasswordRepromptSubmit,
onOverflowItemClick = vaultItemListingHandlers.overflowItemClick, onOverflowItemClick = vaultItemListingHandlers.overflowItemClick,
modifier = modifier, modifier = Modifier.fillMaxSize(),
) )
} }
@ -485,7 +496,7 @@ private fun VaultItemListingScaffold(
policyDisablesSend = state.policyDisablesSend && policyDisablesSend = state.policyDisablesSend &&
state.itemListingType is VaultItemListingState.ItemListingType.Send, state.itemListingType is VaultItemListingState.ItemListingType.Send,
addItemClickAction = vaultItemListingHandlers.addVaultItemClick, addItemClickAction = vaultItemListingHandlers.addVaultItemClick,
modifier = modifier, modifier = Modifier.fillMaxSize(),
) )
} }
@ -493,28 +504,13 @@ private fun VaultItemListingScaffold(
BitwardenErrorContent( BitwardenErrorContent(
message = state.viewState.message(), message = state.viewState.message(),
onTryAgainClick = vaultItemListingHandlers.refreshClick, onTryAgainClick = vaultItemListingHandlers.refreshClick,
modifier = modifier, modifier = Modifier.fillMaxSize(),
) )
} }
is VaultItemListingState.ViewState.Loading -> { 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()), scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()),
) )
}, },
) { paddingValues -> ) {
Column(modifier = Modifier.padding(paddingValues)) { Column {
Text( Text(
text = stringResource(id = R.string.enter_key_manually), text = stringResource(id = R.string.enter_key_manually),
style = BitwardenTheme.typography.titleMedium, style = BitwardenTheme.typography.titleMedium,

View file

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

View file

@ -125,24 +125,21 @@ fun QrCodeScanScreen(
), ),
) )
}, },
) { innerPadding -> ) {
CameraPreview( CameraPreview(
cameraErrorReceive = remember(viewModel) { cameraErrorReceive = remember(viewModel) {
{ viewModel.trySendAction(QrCodeScanAction.CameraSetupErrorReceive) } { viewModel.trySendAction(QrCodeScanAction.CameraSetupErrorReceive) }
}, },
qrCodeAnalyzer = qrCodeAnalyzer, qrCodeAnalyzer = qrCodeAnalyzer,
modifier = Modifier.padding(innerPadding),
) )
if (LocalConfiguration.current.isPortrait) { if (LocalConfiguration.current.isPortrait) {
PortraitQRCodeContent( PortraitQRCodeContent(
onEnterCodeManuallyClick = onEnterCodeManuallyClick, onEnterCodeManuallyClick = onEnterCodeManuallyClick,
modifier = Modifier.padding(innerPadding),
) )
} else { } else {
LandscapeQRCodeContent( LandscapeQRCodeContent(
onEnterCodeManuallyClick = onEnterCodeManuallyClick, 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.Row
import androidx.compose.foundation.layout.Spacer 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.padding
import androidx.compose.foundation.layout.union
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarScrollBehavior 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 * @param topAppBarScrollBehavior Used to derive the background color of the content and keep it in
* sync with the associated app bar. * sync with the associated app bar.
* @param modifier A [Modifier] for the composable. * @param modifier A [Modifier] for the composable.
* @param windowInsets The insets to be applied to this composable.
*/ */
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -46,6 +54,9 @@ fun VaultFilter(
onVaultFilterTypeSelect: (VaultFilterType) -> Unit, onVaultFilterTypeSelect: (VaultFilterType) -> Unit,
topAppBarScrollBehavior: TopAppBarScrollBehavior, topAppBarScrollBehavior: TopAppBarScrollBehavior,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
windowInsets: WindowInsets = WindowInsets.displayCutout
.union(WindowInsets.navigationBars)
.only(WindowInsetsSides.Horizontal),
) { ) {
var shouldShowSelectionDialog by remember { mutableStateOf(false) } var shouldShowSelectionDialog by remember { mutableStateOf(false) }
@ -73,7 +84,8 @@ fun VaultFilter(
.scrolledContainerBottomDivider(topAppBarScrollBehavior = topAppBarScrollBehavior) .scrolledContainerBottomDivider(topAppBarScrollBehavior = topAppBarScrollBehavior)
.padding(vertical = 8.dp) .padding(vertical = 8.dp)
.testTag("ActiveFilterRow") .testTag("ActiveFilterRow")
.then(modifier), .then(modifier)
.windowInsetsPadding(insets = windowInsets),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
Text( Text(

View file

@ -4,7 +4,6 @@ import android.widget.Toast
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut import androidx.compose.animation.scaleOut
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth 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 = { snackbarHost = {
BitwardenSnackbarHost( BitwardenSnackbarHost(
bitwardenHostState = snackbarHostState, bitwardenHostState = snackbarHostState,
@ -259,81 +276,7 @@ private fun VaultScreenScaffold(
) )
} }
}, },
pullToRefreshState = pullToRefreshState, overlay = {
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,
)
}
}
BitwardenAccountSwitcher( BitwardenAccountSwitcher(
isVisible = accountMenuVisible, isVisible = accountMenuVisible,
accountSummaries = state.accountSummaries.toImmutableList(), accountSummaries = state.accountSummaries.toImmutableList(),
@ -343,8 +286,61 @@ private fun VaultScreenScaffold(
onAddAccountClick = vaultHandlers.addAccountClickAction, onAddAccountClick = vaultHandlers.addAccountClickAction,
onDismissRequest = { updateAccountMenuVisibility(false) }, onDismissRequest = { updateAccountMenuVisibility(false) },
topAppBarScrollBehavior = scrollBehavior, 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, pullToRefreshState = pullToRefreshState,
) { paddingValues -> ) {
val modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
when (val viewState = state.viewState) { when (val viewState = state.viewState) {
is VerificationCodeState.ViewState.Content -> { is VerificationCodeState.ViewState.Content -> {
VerificationCodeContent( VerificationCodeContent(
items = viewState.verificationCodeDisplayItems.toImmutableList(), items = viewState.verificationCodeDisplayItems.toImmutableList(),
onCopyClick = verificationCodeHandler.copyClick, onCopyClick = verificationCodeHandler.copyClick,
itemClick = verificationCodeHandler.itemClick, itemClick = verificationCodeHandler.itemClick,
modifier = modifier, modifier = Modifier.fillMaxSize(),
) )
} }
@ -125,12 +121,12 @@ fun VerificationCodeScreen(
BitwardenErrorContent( BitwardenErrorContent(
message = viewState.message.invoke(), message = viewState.message.invoke(),
onTryAgainClick = verificationCodeHandler.refreshClick, onTryAgainClick = verificationCodeHandler.refreshClick,
modifier = modifier, modifier = Modifier.fillMaxSize(),
) )
} }
is VerificationCodeState.ViewState.Loading -> { is VerificationCodeState.ViewState.Loading -> {
BitwardenLoadingContent(modifier = modifier) BitwardenLoadingContent(modifier = Modifier.fillMaxSize())
} }
} }
} }

View file

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

View file

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