summaryrefslogtreecommitdiffhomepage
path: root/android
diff options
context:
space:
mode:
Diffstat (limited to 'android')
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/BaseCell.kt87
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/CollapsingTopBar.kt126
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/Scaffolding.kt60
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/Switch.kt99
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/textfield/CustomTextField.kt172
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/Color.kt19
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/Theme.kt33
7 files changed, 596 insertions, 0 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/BaseCell.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/BaseCell.kt
new file mode 100644
index 0000000000..cd63483d45
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/BaseCell.kt
@@ -0,0 +1,87 @@
+package net.mullvad.mullvadvpn.compose.cell
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.Dp
+import net.mullvad.mullvadvpn.R
+import net.mullvad.mullvadvpn.compose.theme.MullvadBlue
+import net.mullvad.mullvadvpn.compose.theme.MullvadDarkBlue
+
+@Composable
+fun BaseCell(
+ title: @Composable () -> Unit,
+ bodyView: @Composable () -> Unit,
+ modifier: Modifier = Modifier,
+ onCellClicked: () -> Unit = {},
+ subtitle: @Composable (() -> Unit)? = null,
+ subtitleModifier: Modifier = Modifier,
+ background: Color = MullvadBlue,
+ startPadding: Dp = dimensionResource(id = R.dimen.cell_left_padding),
+ endPadding: Dp = dimensionResource(id = R.dimen.cell_right_padding)
+) {
+ val cellHeight = dimensionResource(id = R.dimen.cell_height)
+ val cellVerticalSpacing = dimensionResource(id = R.dimen.cell_label_vertical_padding)
+ val subtitleVerticalSpacing = dimensionResource(id = R.dimen.cell_footer_top_padding)
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .wrapContentHeight()
+ .background(background)
+ ) {
+
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Start,
+ modifier = Modifier
+ .height(cellHeight)
+ .fillMaxWidth()
+ .clickable { onCellClicked.invoke() }
+ .padding(start = startPadding, end = endPadding)
+
+ ) {
+ title()
+
+ Spacer(modifier = Modifier.weight(1.0f))
+
+ Column(
+ modifier = modifier
+ .wrapContentWidth()
+ .wrapContentHeight()
+ ) {
+ bodyView()
+ }
+ }
+
+ if (subtitle != null) {
+ Row(
+ modifier = subtitleModifier
+ .background(MullvadDarkBlue)
+ .padding(
+ start = startPadding,
+ top = subtitleVerticalSpacing,
+ end = endPadding,
+ bottom = cellVerticalSpacing
+ )
+ .fillMaxWidth()
+ .wrapContentHeight()
+ ) {
+ subtitle()
+ }
+ }
+ }
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/CollapsingTopBar.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/CollapsingTopBar.kt
new file mode 100644
index 0000000000..b117f7cbf3
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/CollapsingTopBar.kt
@@ -0,0 +1,126 @@
+package net.mullvad.mullvadvpn.compose.component
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.material.Button
+import androidx.compose.material.ButtonDefaults
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import net.mullvad.mullvadvpn.R
+import net.mullvad.mullvadvpn.compose.theme.MullvadDarkBlue
+import net.mullvad.mullvadvpn.compose.theme.MullvadWhite60
+
+@Preview
+@Composable
+private fun PreviewTopBar() {
+ CollapsingTopBar(
+ backgroundColor = MullvadDarkBlue,
+ onBackClicked = {},
+ title = "View title",
+ progress = 1.0f,
+ backTitle = "Back",
+ modifier = Modifier.height(102.dp)
+ )
+}
+
+@Composable
+fun CollapsingTopBar(
+ backgroundColor: Color,
+ onBackClicked: () -> Unit,
+ title: String,
+ progress: Float,
+ backTitle: String,
+ modifier: Modifier
+) {
+ val expandedToolbarHeight = dimensionResource(id = R.dimen.expanded_toolbar_height)
+ val iconSize = dimensionResource(id = R.dimen.icon_size)
+ val iconPadding = dimensionResource(id = R.dimen.small_padding)
+ val sideMargin = dimensionResource(id = R.dimen.side_margin)
+ val verticalMargin = dimensionResource(id = R.dimen.cell_label_vertical_padding)
+ val textSize = dimensionResource(id = R.dimen.text_small).value.sp
+ val maxTopPadding = 48
+ val minTopPadding = 14
+ val maxTitleSize = 30
+ val minTitleSize = 20
+
+ Spacer(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(expandedToolbarHeight)
+ .background(backgroundColor)
+ )
+
+ Button(
+ modifier = Modifier
+ .wrapContentWidth()
+ .wrapContentHeight(),
+ onClick = onBackClicked,
+ colors = ButtonDefaults.buttonColors(
+ contentColor = Color.White,
+ backgroundColor = MullvadDarkBlue
+ )
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.icon_back),
+ contentDescription = stringResource(id = R.string.back),
+ modifier = Modifier
+ .width(iconSize)
+ .height(iconSize)
+ )
+ Spacer(
+ modifier = Modifier
+ .width(iconPadding)
+ .fillMaxHeight()
+ )
+ Text(
+ text = backTitle,
+ color = MullvadWhite60,
+ fontWeight = FontWeight.Bold,
+ fontSize = textSize
+ )
+ }
+
+ Text(
+ text = title,
+ style = TextStyle(
+ color = Color.White,
+ fontWeight = FontWeight.Bold,
+ textAlign = TextAlign.End
+ ),
+ modifier = modifier
+ .padding(
+ start = sideMargin,
+ end = sideMargin,
+ top = (minTopPadding + (maxTopPadding - minTopPadding) * progress).dp,
+ bottom = verticalMargin
+ ),
+ fontSize = topBarSize(
+ progress = progress,
+ minTitleSize = minTitleSize,
+ maxTitleSize = maxTitleSize
+ ).sp
+ )
+}
+
+private fun topBarSize(progress: Float, minTitleSize: Int, maxTitleSize: Int): Float {
+ return (minTitleSize + ((maxTitleSize - minTitleSize) * progress))
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/Scaffolding.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/Scaffolding.kt
index 05cd60cba1..c6d32a4df9 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/Scaffolding.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/Scaffolding.kt
@@ -1,10 +1,25 @@
package net.mullvad.mullvadvpn.compose.component
+import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.material.Scaffold
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.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 com.google.accompanist.systemuicontroller.rememberSystemUiController
+import me.onebone.toolbar.CollapsingToolbarScaffold
+import me.onebone.toolbar.CollapsingToolbarScaffoldScope
+import me.onebone.toolbar.CollapsingToolbarScaffoldState
+import me.onebone.toolbar.CollapsingToolbarScope
+import me.onebone.toolbar.ExperimentalToolbarApi
+import me.onebone.toolbar.ScrollStrategy
@Composable
fun ScaffoldWithTopBar(
@@ -30,3 +45,48 @@ fun ScaffoldWithTopBar(
content = content
)
}
+
+@Composable
+@OptIn(ExperimentalToolbarApi::class)
+fun CollapsableAwareToolbarScaffold(
+ modifier: Modifier,
+ state: CollapsingToolbarScaffoldState,
+ scrollStrategy: ScrollStrategy,
+ isEnabledWhenCollapsable: Boolean = true,
+ toolbarModifier: Modifier = Modifier,
+ toolbar: @Composable CollapsingToolbarScope.() -> Unit,
+ body: @Composable CollapsingToolbarScaffoldScope.() -> Unit
+) {
+ var isCollapsable by remember { mutableStateOf(false) }
+
+ LaunchedEffect(isCollapsable) {
+ if (!isCollapsable) {
+ state.toolbarState.expand()
+ }
+ }
+
+ CollapsingToolbarScaffold(
+ modifier = modifier,
+ state = state,
+ scrollStrategy = scrollStrategy,
+ enabled = isEnabledWhenCollapsable && isCollapsable,
+ toolbarModifier = toolbarModifier,
+ toolbar = toolbar,
+ body = {
+ var bodyHeight by remember { mutableStateOf(0) }
+
+ BoxWithConstraints(
+ modifier = Modifier.onGloballyPositioned { bodyHeight = it.size.height }
+ ) {
+ val minMaxToolbarHeightDiff = with(state) {
+ toolbarState.maxHeight - toolbarState.minHeight
+ }
+ val isContentHigherThanCollapseThreshold = with(LocalDensity.current) {
+ bodyHeight > maxHeight.toPx() - minMaxToolbarHeightDiff
+ }
+ isCollapsable = isContentHigherThanCollapseThreshold
+ body()
+ }
+ }
+ )
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/Switch.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/Switch.kt
new file mode 100644
index 0000000000..b30ca26f22
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/Switch.kt
@@ -0,0 +1,99 @@
+package net.mullvad.mullvadvpn.compose.component
+
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.scale
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.StrokeCap
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import net.mullvad.mullvadvpn.compose.theme.MullvadGreen
+import net.mullvad.mullvadvpn.compose.theme.MullvadRed
+import net.mullvad.mullvadvpn.compose.theme.MullvadWhite
+
+@Preview
+@Composable
+private fun PreviewSwitch() {
+ CellSwitch(
+ isChecked = false,
+ onCheckedChange = null
+ )
+}
+
+@Composable
+fun CellSwitch(
+ isChecked: Boolean,
+ onCheckedChange: ((Boolean) -> Unit)?,
+ modifier: Modifier = Modifier,
+ scale: Float = 1f,
+ thumbCheckedTrackColor: Color = MullvadGreen,
+ thumbUncheckedTrackColor: Color = MullvadRed,
+ thumbColor: Color = MullvadWhite
+) {
+ val gapBetweenThumbAndTrackEdge: Dp = 2.dp
+ val width: Dp = 46.dp
+ val height: Dp = 28.dp
+ val thumbRadius = 11.dp
+
+ // To move the thumb, we need to calculate the position (along x axis)
+ val animatePosition = animateFloatAsState(
+ targetValue = if (isChecked)
+ with(LocalDensity.current) {
+ (width - thumbRadius - gapBetweenThumbAndTrackEdge - 1.dp).toPx()
+ }
+ else
+ with(LocalDensity.current) { (thumbRadius + gapBetweenThumbAndTrackEdge + 1.dp).toPx() }
+ )
+
+ Canvas(
+ modifier = modifier
+ .padding(1.dp)
+ .size(width = width, height = height)
+ .scale(scale = scale)
+ .pointerInput(Unit) {
+ if (onCheckedChange != null) {
+ detectTapGestures(
+ onTap = {
+ onCheckedChange(!isChecked)
+ }
+ )
+ }
+ }
+ ) {
+ // Track
+ drawRoundRect(
+ color = thumbColor,
+ cornerRadius = CornerRadius(x = 15.dp.toPx(), y = 15.dp.toPx()),
+ style = Stroke(
+ width = 2.dp.toPx(),
+ miter = 6.dp.toPx(),
+ cap = StrokeCap.Square,
+ ),
+ )
+
+ // Thumb
+ drawCircle(
+ color = if (isChecked) thumbCheckedTrackColor else thumbUncheckedTrackColor,
+ radius = thumbRadius.toPx(),
+ center = Offset(
+ x = animatePosition.value,
+ y = size.height / 2
+ )
+ )
+ }
+
+ Spacer(modifier = Modifier.height(18.dp))
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/textfield/CustomTextField.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/textfield/CustomTextField.kt
new file mode 100644
index 0000000000..3a263a6886
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/textfield/CustomTextField.kt
@@ -0,0 +1,172 @@
+package net.mullvad.mullvadvpn.compose.textfield
+
+import android.text.TextUtils
+import android.view.KeyEvent
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.foundation.text.KeyboardActions
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.focus.FocusDirection
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.input.key.onKeyEvent
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.platform.LocalSoftwareKeyboardController
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import net.mullvad.mullvadvpn.R
+import net.mullvad.mullvadvpn.compose.theme.MullvadBlue
+import net.mullvad.mullvadvpn.compose.theme.MullvadWhite10
+
+private const val EMPTY_STRING = ""
+private const val NEWLINE_STRING = "\n"
+
+@Composable
+@OptIn(ExperimentalComposeUiApi::class)
+fun CustomTextField(
+ value: String,
+ modifier: Modifier = Modifier,
+ onValueChanged: (String) -> Unit,
+ onFocusChange: (Boolean) -> Unit,
+ onSubmit: (String) -> Unit,
+ isEnabled: Boolean = true,
+ placeholderText: String = "",
+ placeHolderColor: Color = MullvadBlue,
+ maxCharLength: Int = Int.MAX_VALUE,
+ isValidValue: Boolean,
+ isDigitsOnlyAllowed: Boolean,
+ defaultTextColor: Color = Color.White,
+ textAlign: TextAlign = TextAlign.Start
+) {
+ val fontSize = dimensionResource(id = R.dimen.text_medium_plus).value.sp
+ val shape = RoundedCornerShape(4.dp)
+ val textFieldHeight = 44.dp
+
+ val focusManager = LocalFocusManager.current
+ val keyboardController = LocalSoftwareKeyboardController.current
+
+ var isFocused by remember { mutableStateOf(false) }
+
+ val textColor = when {
+ isValidValue.not() -> Color.Red
+ isFocused -> MullvadBlue
+ else -> defaultTextColor
+ }
+
+ val placeholderTextColor = if (isFocused) {
+ placeHolderColor
+ } else {
+ Color.White
+ }
+
+ val backgroundColor = if (isFocused) {
+ Color.White
+ } else {
+ MullvadWhite10
+ }
+
+ fun triggerSubmit() {
+ keyboardController?.hide()
+ focusManager.moveFocus(FocusDirection.Previous)
+ onSubmit(value)
+ }
+
+ BasicTextField(
+ value = value,
+ onValueChange = { input ->
+ val isValidInput = if (isDigitsOnlyAllowed) TextUtils.isDigitsOnly(input) else true
+ if (input.length <= maxCharLength && isValidInput) {
+ // Remove any newline chars added by enter key clicks
+ onValueChanged(input.replace(NEWLINE_STRING, EMPTY_STRING))
+ }
+ },
+ textStyle = TextStyle(
+ color = textColor,
+ fontSize = fontSize,
+ textAlign = textAlign
+ ),
+ enabled = isEnabled,
+ singleLine = true,
+ maxLines = 1,
+ keyboardOptions = KeyboardOptions(
+ keyboardType = KeyboardType.Number,
+ imeAction = ImeAction.Done,
+ autoCorrect = false,
+ ),
+ keyboardActions = KeyboardActions(
+ onDone = { triggerSubmit() }
+ ),
+ decorationBox = { decorationBox ->
+ Box(
+ modifier = Modifier
+ .padding(PaddingValues(12.dp, 10.dp))
+ .fillMaxWidth()
+ ) {
+ if (value.isBlank()) {
+ Text(
+ text = placeholderText,
+ color = placeholderTextColor,
+ fontSize = fontSize,
+ textAlign = textAlign,
+ modifier = Modifier.fillMaxWidth()
+ )
+ }
+ decorationBox()
+ }
+ },
+ cursorBrush = SolidColor(MullvadBlue),
+ modifier = modifier
+ .background(backgroundColor)
+ .clip(shape)
+ .onFocusChanged { focusState ->
+ isFocused = focusState.isFocused
+ onFocusChange(focusState.isFocused)
+ }
+ .height(textFieldHeight)
+ .onKeyEvent { keyEvent ->
+ return@onKeyEvent when (keyEvent.nativeKeyEvent.keyCode) {
+ KeyEvent.KEYCODE_ENTER -> {
+ triggerSubmit()
+ true
+ }
+ KeyEvent.KEYCODE_ESCAPE -> {
+ focusManager.clearFocus(force = true)
+ keyboardController?.hide()
+ true
+ }
+ KeyEvent.KEYCODE_DPAD_DOWN -> {
+ focusManager.moveFocus(FocusDirection.Down)
+ true
+ }
+ KeyEvent.KEYCODE_DPAD_UP -> {
+ focusManager.moveFocus(FocusDirection.Up)
+ true
+ }
+ else -> {
+ false
+ }
+ }
+ }
+ )
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/Color.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/Color.kt
new file mode 100644
index 0000000000..2c542951ab
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/Color.kt
@@ -0,0 +1,19 @@
+package net.mullvad.mullvadvpn.compose.theme
+
+import androidx.compose.ui.graphics.Color
+
+val MullvadBeige = Color(0xFFFFCD86)
+val MullvadBlue = Color(0xFF294D73)
+val MullvadBlue60 = Color(0x99294D73)
+val MullvadBlue20 = Color(0x33294D73)
+val MullvadBrown = Color(0xFFD2943B)
+val MullvadDarkBlue = Color(0xFF192E45)
+val MullvadGreen = Color(0xFF44AD4D)
+val MullvadRed = Color(0xFFE34039)
+val MullvadYellow = Color(0xFFFFD524)
+val MullvadHelmetYellow = Color(0xFFFFD524)
+val MullvadWhite = Color(0xFFFFFFFF)
+val MullvadWhite10 = Color(0x1AFFFFFF)
+val MullvadWhite20 = Color(0x33FFFFFF)
+val MullvadWhite40 = Color(0x66FFFFFF)
+val MullvadWhite60 = Color(0x99FFFFFF)
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/Theme.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/Theme.kt
new file mode 100644
index 0000000000..95e6d312a0
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/Theme.kt
@@ -0,0 +1,33 @@
+package net.mullvad.mullvadvpn.compose.theme
+
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Shapes
+import androidx.compose.material.lightColors
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.unit.dp
+
+private val MullvadColorPalette = lightColors(
+ primary = MullvadBlue,
+ primaryVariant = MullvadDarkBlue,
+ secondary = MullvadRed
+)
+
+val Shapes = Shapes(
+ small = RoundedCornerShape(4.dp),
+ medium = RoundedCornerShape(4.dp),
+ large = RoundedCornerShape(0.dp)
+)
+
+@Composable
+fun CollapsingToolbarTheme(
+ content: @Composable () -> Unit
+) {
+ val colors = MullvadColorPalette
+
+ MaterialTheme(
+ colors = colors,
+ shapes = Shapes,
+ content = content
+ )
+}