Pin the segmented control to toolbar in AddSendScreen (#4093)

This commit is contained in:
David Perez 2024-10-15 16:13:29 -05:00 committed by GitHub
parent 970a1e14cd
commit 8eb408b140
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 190 additions and 161 deletions

View File

@ -21,10 +21,12 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
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.bottomDivider
import com.x8bit.bitwarden.ui.platform.base.util.mirrorIfRtl import com.x8bit.bitwarden.ui.platform.base.util.mirrorIfRtl
import com.x8bit.bitwarden.ui.platform.base.util.scrolledContainerBottomDivider import com.x8bit.bitwarden.ui.platform.base.util.scrolledContainerBottomDivider
import com.x8bit.bitwarden.ui.platform.components.appbar.color.bitwardenTopAppBarColors import com.x8bit.bitwarden.ui.platform.components.appbar.color.bitwardenTopAppBarColors
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenStandardIconButton import com.x8bit.bitwarden.ui.platform.components.button.BitwardenStandardIconButton
import com.x8bit.bitwarden.ui.platform.components.model.TopAppBarDividerStyle
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
@ -47,6 +49,7 @@ fun BitwardenTopAppBar(
navigationIconContentDescription: String, navigationIconContentDescription: String,
onNavigationIconClick: () -> Unit, onNavigationIconClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
dividerStyle: TopAppBarDividerStyle = TopAppBarDividerStyle.ON_SCROLL,
actions: @Composable RowScope.() -> Unit = { }, actions: @Composable RowScope.() -> Unit = { },
) { ) {
BitwardenTopAppBar( BitwardenTopAppBar(
@ -58,6 +61,7 @@ fun BitwardenTopAppBar(
onNavigationIconClick = onNavigationIconClick, onNavigationIconClick = onNavigationIconClick,
), ),
modifier = modifier, modifier = modifier,
dividerStyle = dividerStyle,
actions = actions, actions = actions,
) )
} }
@ -82,6 +86,7 @@ fun BitwardenTopAppBar(
scrollBehavior: TopAppBarScrollBehavior, scrollBehavior: TopAppBarScrollBehavior,
navigationIcon: NavigationIcon?, navigationIcon: NavigationIcon?,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
dividerStyle: TopAppBarDividerStyle = TopAppBarDividerStyle.ON_SCROLL,
actions: @Composable RowScope.() -> Unit = {}, actions: @Composable RowScope.() -> Unit = {},
) { ) {
var titleTextHasOverflow by remember { var titleTextHasOverflow by remember {
@ -102,6 +107,24 @@ fun BitwardenTopAppBar(
} }
} }
} }
val customModifier = modifier
.testTag(tag = "HeaderBarComponent")
.scrolledContainerBottomDivider(
topAppBarScrollBehavior = scrollBehavior,
enabled = when (dividerStyle) {
TopAppBarDividerStyle.NONE -> false
TopAppBarDividerStyle.STATIC -> false
TopAppBarDividerStyle.ON_SCROLL -> true
},
)
.bottomDivider(
enabled = when (dividerStyle) {
TopAppBarDividerStyle.NONE -> false
TopAppBarDividerStyle.STATIC -> true
TopAppBarDividerStyle.ON_SCROLL -> false
},
thickness = (0.5).dp,
)
if (titleTextHasOverflow) { if (titleTextHasOverflow) {
MediumTopAppBar( MediumTopAppBar(
@ -120,9 +143,7 @@ fun BitwardenTopAppBar(
modifier = Modifier.testTag(tag = "PageTitleLabel"), modifier = Modifier.testTag(tag = "PageTitleLabel"),
) )
}, },
modifier = modifier modifier = customModifier,
.testTag(tag = "HeaderBarComponent")
.scrolledContainerBottomDivider(topAppBarScrollBehavior = scrollBehavior),
actions = actions, actions = actions,
) )
} else { } else {
@ -144,9 +165,7 @@ fun BitwardenTopAppBar(
}, },
) )
}, },
modifier = modifier modifier = customModifier,
.testTag(tag = "HeaderBarComponent")
.scrolledContainerBottomDivider(topAppBarScrollBehavior = scrollBehavior),
actions = actions, actions = actions,
) )
} }

View File

@ -18,8 +18,10 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.layout.wrapContentWidth
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.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
@ -33,6 +35,7 @@ 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.BitwardenFilledTonalButton import com.x8bit.bitwarden.ui.platform.components.button.BitwardenFilledTonalButton
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
@ -52,10 +55,12 @@ import kotlinx.collections.immutable.persistentListOf
/** /**
* Content view for the [AddSendScreen]. * Content view for the [AddSendScreen].
*/ */
@OptIn(ExperimentalMaterial3Api::class)
@Suppress("LongMethod") @Suppress("LongMethod")
@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,
@ -67,14 +72,12 @@ fun AddSendContent(
val chooseFileCameraPermissionLauncher = permissionsManager.getLauncher { isGranted -> val chooseFileCameraPermissionLauncher = permissionsManager.getLauncher { isGranted ->
addSendHandlers.onChooseFileClick(isGranted) addSendHandlers.onChooseFileClick(isGranted)
} }
Column(modifier = modifier) {
Column(
modifier = modifier
.verticalScroll(rememberScrollState()),
) {
if (isAddMode && !isShared) { if (isAddMode && !isShared) {
BitwardenSegmentedButton( BitwardenSegmentedButton(
modifier = Modifier.fillMaxWidth(), modifier = Modifier
.scrolledContainerBottomDivider(topAppBarScrollBehavior = scrollBehavior)
.fillMaxWidth(),
options = persistentListOf( options = persistentListOf(
SegmentedButtonState( SegmentedButtonState(
text = stringResource(id = R.string.file), text = stringResource(id = R.string.file),
@ -92,171 +95,177 @@ fun AddSendContent(
) )
} }
if (policyDisablesSend) { Column(
BitwardenInfoCalloutCard( modifier = Modifier.verticalScroll(rememberScrollState()),
text = stringResource(id = R.string.send_disabled_warning), ) {
modifier = Modifier if (policyDisablesSend) {
.padding(horizontal = 16.dp) Spacer(modifier = Modifier.height(8.dp))
.fillMaxWidth(), BitwardenInfoCalloutCard(
) text = stringResource(id = R.string.send_disabled_warning),
Spacer(modifier = Modifier.height(16.dp))
}
if (policySendOptionsInEffect) {
BitwardenInfoCalloutCard(
text = stringResource(id = R.string.send_options_policy_in_effect),
modifier = Modifier
.testTag("SendOptionsPolicyInEffectLabel")
.padding(horizontal = 16.dp)
.fillMaxWidth(),
)
Spacer(modifier = Modifier.height(16.dp))
}
BitwardenTextField(
modifier = Modifier
.testTag("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 modifier = Modifier
.fillMaxWidth() .padding(horizontal = 16.dp)
.padding(horizontal = 16.dp), .fillMaxWidth(),
) )
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
if (isShared) { }
Text(
text = type.name.orEmpty(), if (policySendOptionsInEffect) {
color = BitwardenTheme.colorScheme.text.primary, BitwardenInfoCalloutCard(
style = BitwardenTheme.typography.bodyMedium, text = stringResource(id = R.string.send_options_policy_in_effect),
modifier = Modifier
.testTag(tag = "SendOptionsPolicyInEffectLabel")
.padding(horizontal = 16.dp)
.fillMaxWidth(),
)
Spacer(modifier = Modifier.height(16.dp))
}
BitwardenTextField(
modifier = Modifier
.testTag(tag = "SendNameEntry")
.fillMaxWidth()
.padding(horizontal = 16.dp),
label = stringResource(id = R.string.name),
hint = stringResource(id = R.string.name_info),
readOnly = policyDisablesSend,
value = state.common.name,
onValueChange = addSendHandlers.onNamChange,
)
Spacer(modifier = Modifier.height(8.dp))
when (val type = state.selectedType) {
is AddSendState.ViewState.Content.SendType.File -> {
BitwardenListHeaderText(
label = stringResource(id = R.string.file),
modifier = Modifier 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("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))
BitwardenFilledTonalButton(
label = stringResource(id = R.string.choose_file),
onClick = {
if (permissionsManager.checkPermission(Manifest.permission.CAMERA)) {
addSendHandlers.onChooseFileClick(true)
} else {
chooseFileCameraPermissionLauncher.launch(
Manifest.permission.CAMERA,
)
}
},
modifier = Modifier
.testTag("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))
Text( if (isShared) {
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))
BitwardenFilledTonalButton(
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))
BitwardenWideSwitch(
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,
)
}
} }
is AddSendState.ViewState.Content.SendType.Text -> { Spacer(modifier = Modifier.height(16.dp))
BitwardenTextField( AddSendOptions(
modifier = Modifier state = state,
.testTag("SendTextContentEntry") sendRestrictionPolicy = policyDisablesSend,
.fillMaxWidth() isAddMode = isAddMode,
.padding(horizontal = 16.dp), addSendHandlers = addSendHandlers,
label = stringResource(id = R.string.text), )
hint = stringResource(id = R.string.type_text_info),
readOnly = policyDisablesSend, Spacer(modifier = Modifier.height(24.dp))
value = type.input, Spacer(modifier = Modifier.navigationBarsPadding())
singleLine = false,
onValueChange = addSendHandlers.onTextChange,
)
Spacer(modifier = Modifier.height(16.dp))
BitwardenWideSwitch(
modifier = Modifier
.testTag("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

@ -35,6 +35,7 @@ import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialog import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialog
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.scaffold.BitwardenScaffold import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
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
@ -137,6 +138,7 @@ fun AddSendScreen(
}, },
) )
.takeUnless { state.isShared }, .takeUnless { state.isShared },
dividerStyle = TopAppBarDividerStyle.NONE,
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
actions = { actions = {
BitwardenTextButton( BitwardenTextButton(
@ -194,6 +196,7 @@ fun AddSendScreen(
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

@ -376,12 +376,10 @@ class AddSendScreenTest : BaseComposeTest() {
composeTestRule composeTestRule
.onAllNodesWithText("File") .onAllNodesWithText("File")
.filterToOne(!isEditableText) .filterToOne(!isEditableText)
.performScrollTo()
.assertIsDisplayed() .assertIsDisplayed()
composeTestRule composeTestRule
.onAllNodesWithText("Text") .onAllNodesWithText("Text")
.filterToOne(!isEditableText) .filterToOne(!isEditableText)
.performScrollTo()
.assertIsDisplayed() .assertIsDisplayed()
mutableStateFlow.update { mutableStateFlow.update {