Skip to content

Commit

Permalink
Unify button appearance
Browse files Browse the repository at this point in the history
- Make almost all button depend on 3 standard buttons
- Replace surface and onSurface with our own custom theme color
- Set button standard height to material design default
- Support bigger font sizes for buttons
  • Loading branch information
Pururun committed Oct 17, 2023
1 parent 391268b commit 0a81e45
Show file tree
Hide file tree
Showing 29 changed files with 499 additions and 547 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,33 +1,43 @@
package net.mullvad.mullvadvpn.compose.button

import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilledIconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.LocalMinimumInteractiveComponentEnforcement
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.lib.theme.AppTheme
import net.mullvad.mullvadvpn.lib.theme.Dimens
import net.mullvad.mullvadvpn.lib.theme.color.AlphaDisconnectButton
import net.mullvad.mullvadvpn.lib.theme.color.onVariant
import net.mullvad.mullvadvpn.lib.theme.color.variant
import net.mullvad.mullvadvpn.model.TunnelState

@Composable
Expand All @@ -42,14 +52,14 @@ fun ConnectionButton(
) {
val containerColor =
if (state is TunnelState.Disconnected) {
MaterialTheme.colorScheme.surface
MaterialTheme.colorScheme.variant
} else {
MaterialTheme.colorScheme.error.copy(alpha = AlphaDisconnectButton)
}

val contentColor =
if (state is TunnelState.Disconnected) {
MaterialTheme.colorScheme.onSurface
MaterialTheme.colorScheme.onVariant
} else {
MaterialTheme.colorScheme.onError
}
Expand Down Expand Up @@ -108,11 +118,12 @@ private fun PreviewConnectionButton() {
containerColor = MaterialTheme.colorScheme.error.copy(alpha = AlphaDisconnectButton),
contentColor = MaterialTheme.colorScheme.onError,
reconnectClick = {},
isReconnectButtonEnabled = false
isReconnectButtonEnabled = true
)
}
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ConnectionButton(
text: String,
Expand All @@ -122,62 +133,98 @@ private fun ConnectionButton(
containerColor: Color,
contentColor: Color,
modifier: Modifier = Modifier,
height: Dp = Dimens.connectButtonHeight,
reconnectButtonTestTag: String = ""
) {
Row(modifier = modifier.height(height)) {
Button(
onClick = mainClick,
shape =
MaterialTheme.shapes.small.copy(
topEnd = CornerSize(percent = 0),
bottomEnd = CornerSize(percent = 0)
),
colors =
ButtonDefaults.buttonColors(
containerColor = containerColor,
contentColor = contentColor
),
modifier = Modifier.weight(1f).height(height)
) {
// Offset to compensate for the reconnect button.
val paddingOffset =
if (isReconnectButtonEnabled) {
height + Dimens.listItemDivider
} else {
0.dp
}
Text(
text = text,
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(start = paddingOffset)
)
}
ConstraintLayout(modifier = modifier.fillMaxWidth()) {
// initial height set at 0.dp
var componentHeight by remember { mutableStateOf(0.dp) }

if (isReconnectButtonEnabled) {
Spacer(modifier = Modifier.width(Dimens.listItemDivider))
// get local density from composable
val density = LocalDensity.current

FilledIconButton(
val (connectionButton, reconnectButton) = createRefs()
CompositionLocalProvider(
LocalMinimumInteractiveComponentEnforcement provides false,
) {
val dividerSize = Dimens.listItemDivider

Button(
onClick = mainClick,
shape =
MaterialTheme.shapes.small.copy(
topStart = CornerSize(percent = 0),
bottomStart = CornerSize(percent = 0)
),
if (isReconnectButtonEnabled) {
MaterialTheme.shapes.small.copy(
topEnd = CornerSize(percent = 0),
bottomEnd = CornerSize(percent = 0)
)
} else {
MaterialTheme.shapes.small
},
colors =
IconButtonDefaults.filledIconButtonColors(
ButtonDefaults.buttonColors(
containerColor = containerColor,
contentColor = contentColor
),
onClick = reconnectClick,
modifier =
Modifier.height(height).aspectRatio(1f, true).testTag(reconnectButtonTestTag)
Modifier.constrainAs(connectionButton) {
start.linkTo(parent.start)
if (isReconnectButtonEnabled) {
end.linkTo(reconnectButton.start)
} else {
end.linkTo(parent.end)
}
width = Dimension.fillToConstraints
height = Dimension.wrapContent
}
.onGloballyPositioned {
componentHeight = with(density) { it.size.height.toDp() }
}
) {
Icon(
painter = painterResource(id = R.drawable.icon_reload),
contentDescription = null
// Offset to compensate for the reconnect button.
Text(
text = text,
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier =
if (isReconnectButtonEnabled) {
Modifier.padding(start = componentHeight + Dimens.listItemDivider)
} else {
Modifier
}
)
}

if (isReconnectButtonEnabled) {
FilledIconButton(
shape =
MaterialTheme.shapes.small.copy(
topStart = CornerSize(percent = 0),
bottomStart = CornerSize(percent = 0)
),
colors =
IconButtonDefaults.filledIconButtonColors(
containerColor = containerColor,
contentColor = contentColor
),
onClick = reconnectClick,
modifier =
Modifier.testTag(reconnectButtonTestTag)
.constrainAs(reconnectButton) {
start.linkTo(connectionButton.end, margin = dividerSize)
top.linkTo(connectionButton.top)
bottom.linkTo(connectionButton.bottom)
end.linkTo(parent.end)
height = Dimension.fillToConstraints
}
.aspectRatio(1f, true)
) {
Icon(
painter = painterResource(id = R.drawable.icon_reload),
contentDescription = null
)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package net.mullvad.mullvadvpn.compose.button

import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.state.DeviceRevokedUiState

@Composable
fun DeviceRevokedLoginButton(onClick: () -> Unit, state: DeviceRevokedUiState) {
if (state == DeviceRevokedUiState.SECURED) {
NegativeButton(text = stringResource(id = R.string.go_to_login), onClick = onClick)
} else {
VariantButton(text = stringResource(id = R.string.go_to_login), onClick = onClick)
}
}
Original file line number Diff line number Diff line change
@@ -1,70 +1,51 @@
package net.mullvad.mullvadvpn.compose.button

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.constraintlayout.compose.ConstraintLayout
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.lib.theme.AppTheme
import net.mullvad.mullvadvpn.lib.theme.Dimens
import net.mullvad.mullvadvpn.lib.theme.color.AlphaDisabled
import net.mullvad.mullvadvpn.lib.theme.color.AlphaVisible

@Preview
@Composable
private fun PreviewExternalActionButton() {
private fun PreviewExternalButtonEnabled() {
AppTheme { ExternalButton(onClick = {}, text = "Button", isEnabled = true) }
}

@Preview
@Composable
private fun PreviewExternalButtonDisabled() {
AppTheme { ExternalButton(onClick = {}, text = "Button", isEnabled = false) }
}

@Preview
@Composable
private fun PreviewExternalButtonLongText() {
AppTheme {
ExternalActionButton(onClick = {}, colors = ButtonDefaults.buttonColors(), text = "Button")
ExternalButton(
onClick = {},
text = "Button text is long and is trying to take up space that is large",
isEnabled = true
)
}
}

@Composable
fun ExternalActionButton(
fun ExternalButton(
onClick: () -> Unit,
colors: ButtonColors,
text: String,
modifier: Modifier = Modifier,
isEnabled: Boolean = true,
) {
ActionButton(
VariantButton(
text = text,
onClick = onClick,
colors = colors,
modifier = modifier,
isEnabled = isEnabled,
) {
ConstraintLayout(modifier = Modifier.fillMaxSize()) {
val (title, logo) = createRefs()
Text(
text = text,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodyMedium,
modifier =
Modifier.constrainAs(title) {
end.linkTo(logo.start)
centerTo(parent)
}
)
Image(
painter = painterResource(id = R.drawable.icon_extlink),
contentDescription = null,
modifier =
Modifier.constrainAs(logo) {
centerVerticallyTo(parent)
end.linkTo(parent.end)
}
.padding(horizontal = Dimens.smallPadding)
.alpha(if (isEnabled) AlphaVisible else AlphaDisabled)
)
}
}
icon = {
Icon(painter = painterResource(id = R.drawable.icon_extlink), contentDescription = null)
},
)
}
Loading

0 comments on commit 0a81e45

Please sign in to comment.