From 7234747cae96006c275cc2de961933c74ed95c4a Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Tue, 4 Aug 2026 13:42:45 +0200 Subject: [PATCH 01/14] title font --- .../com/hedvig/android/feature/home/home/ui/HomeDestination.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt index 1b17bbbf20..09eb6d97af 100644 --- a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt +++ b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt @@ -1336,8 +1336,7 @@ private fun WelcomeMessage(firstName: String, modifier: Modifier = Modifier) { // todo custom style since new DS does not have this specification // https://hedviginsurance.slack.com/archives/C03U9C6Q7TP/p1727365167917719 val titleStyle = HedvigTheme.typography.headlineMedium.copy( - fontFamily = HedvigTheme.typography.serif, - fontSize = 28.0.sp, + fontSize = 24.0.sp, lineBreak = LineBreak.Heading, textAlign = TextAlign.Center, ) From 778590bcf0b22642bba295edc018bd0b5233cf10 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Tue, 4 Aug 2026 13:53:18 +0200 Subject: [PATCH 02/14] blur only for light mode --- .../design/system/hedvig/ColorScheme.kt | 7 ++ .../feature/home/home/ui/HomeDestination.kt | 105 ++++++++---------- 2 files changed, 52 insertions(+), 60 deletions(-) diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/ColorScheme.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/ColorScheme.kt index 209a444730..a410e1e34b 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/ColorScheme.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/ColorScheme.kt @@ -13,6 +13,11 @@ import com.hedvig.android.design.system.hedvig.tokens.ColorSchemeKeyTokens @Immutable data class ColorScheme( + /** + * Whether this is the light color scheme. Read it for UI that cannot be expressed as a color token, + * such as a background image that only exists in one theme. + */ + val isLight: Boolean, val textPrimary: Color, val textNegative: Color, val textSecondary: Color, @@ -121,6 +126,7 @@ data class ColorScheme( ) internal val lightColorScheme: ColorScheme = ColorScheme( + isLight = true, textPrimary = ColorLightTokens.TextPrimary, textNegative = ColorLightTokens.TextNegative, textSecondary = ColorLightTokens.TextSecondary, @@ -229,6 +235,7 @@ internal val lightColorScheme: ColorScheme = ColorScheme( ) internal val darkColorScheme: ColorScheme = ColorScheme( + isLight = false, textPrimary = ColorDarkTokens.TextPrimary, textNegative = ColorDarkTokens.TextNegative, textSecondary = ColorDarkTokens.TextSecondary, diff --git a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt index 09eb6d97af..0cce894bdb 100644 --- a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt +++ b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt @@ -429,37 +429,33 @@ private fun HomeScreen( if (currentState.firstVetAction != null) add(currentState.firstVetAction) if (currentState.chatAction != null) add(currentState.chatAction) } - OnHeroGradient { - actionsList.forEach { action -> - when (action) { - ChatAction -> { - // The FirstVet/cross-sell icons are colored images (theme-independent), but this one is a - // tinted glyph on a tonal surface, so it must use the light scheme on the light gradient. - ToolbarChatIcon( - onClick = onNavigateToInbox, - modifier = Modifier.notificationCircle(uiState.hasUnseenChatMessages), - ) - } + actionsList.forEach { action -> + when (action) { + ChatAction -> { + ToolbarChatIcon( + onClick = onNavigateToInbox, + modifier = Modifier.notificationCircle(uiState.hasUnseenChatMessages), + ) + } - is CrossSellsAction -> { - ToolbarCrossSellsIcon( - onClick = { - crossSellBottomSheetState.show( - action.crossSells, - ) - }, - modifier = Modifier.notificationCircle( - action.crossSellRecommendationNotification.hasUnreadRecommendation, - ), - ) - } + is CrossSellsAction -> { + ToolbarCrossSellsIcon( + onClick = { + crossSellBottomSheetState.show( + action.crossSells, + ) + }, + modifier = Modifier.notificationCircle( + action.crossSellRecommendationNotification.hasUnreadRecommendation, + ), + ) + } - is FirstVetAction -> { - val sections = action.sections - ToolbarFirstVetIcon( - onClick = { navigateToFirstVet(sections) }, - ) - } + is FirstVetAction -> { + val sections = action.sections + ToolbarFirstVetIcon( + onClick = { navigateToFirstVet(sections) }, + ) } } } @@ -595,14 +591,17 @@ private fun HomeScreenSuccess( .onConsumedWindowInsetsChanged { consumedWindowInsets.insets = it } .pullRefresh(pullRefreshState), ) { - // Full-screen blur gradient behind the whole home screen. Sections that need a solid surface draw - // their own background on top to "hide" it (the content cards already do; so do the pinned pills). - Image( - painter = painterResource(Res.drawable.blur_background), - contentDescription = null, - contentScale = ContentScale.Crop, - modifier = Modifier.matchParentSize(), - ) + // Full-screen blur gradient behind the whole home screen, in the light theme only; the dark theme + // keeps the regular background. Sections that need a solid surface draw their own background on top + // to "hide" it (the content cards already do; so do the pinned pills). + if (HedvigTheme.colorScheme.isLight) { + Image( + painter = painterResource(Res.drawable.blur_background), + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier.matchParentSize(), + ) + } NotificationPermissionDialog(notificationPermissionState, openAppSettings) val horizontalInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal).exclude(consumedWindowInsets).asPaddingValues() @@ -770,9 +769,7 @@ private fun HomeScreenSuccess( } }, ) { - OnHeroGradient { - WelcomeSection(uiState.firstName) - } + WelcomeSection(uiState.firstName) } } } @@ -791,16 +788,14 @@ private fun HomeScreenSuccess( .pointerInput(Unit) { detectTapGestures {} }, ) { Spacer(Modifier.height(pinnedTopOffset)) - OnHeroGradient { - QuickActionCarouselSection( - isHelpCenterEnabled = uiState.isHelpCenterEnabled, - onMakeClaim = openClaimFlowSheet, - onHelpAndSupport = navigateToHelpCenter, - onContactUs = onNavigateToInbox, - horizontalInsets = horizontalInsets, - modifier = Modifier.padding(bottom = 8.dp), - ) - } + QuickActionCarouselSection( + isHelpCenterEnabled = uiState.isHelpCenterEnabled, + onMakeClaim = openClaimFlowSheet, + onHelpAndSupport = navigateToHelpCenter, + onContactUs = onNavigateToInbox, + horizontalInsets = horizontalInsets, + modifier = Modifier.padding(bottom = 8.dp), + ) HomeSheetDragHandle( Modifier .fillMaxWidth() @@ -970,16 +965,6 @@ private fun gapAfter(section: HomeSection): Dp = when (section) { else -> homeSectionGap } -/** - * The hero gradient ([Res.drawable.blur_background]) is the same light image in both light and dark - * themes, so content drawn on it (greeting, pills, the tonal chat icon) must always render with the - * light color scheme. Otherwise dark-theme text and tints turn near-invisible against the light gradient. - */ -@Composable -private fun OnHeroGradient(content: @Composable () -> Unit) { - HedvigTheme(darkTheme = false, content = content) -} - @Composable private fun WelcomeSection(firstName: String) { Column( From b567347a6280abe572f5903a0903c8b0cf2ef669 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Tue, 4 Aug 2026 16:37:25 +0200 Subject: [PATCH 03/14] change toolbar icons --- .../design/system/hedvig/icon/ChatNoCircle.kt | 85 ++++++++++++++ .../icon/colored/ColoredFirstVetNoCircle.kt | 101 +++++++++++++++++ .../feature/home/home/ui/ToolbarIcons.kt | 107 ++++++++---------- 3 files changed, 235 insertions(+), 58 deletions(-) create mode 100644 app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/ChatNoCircle.kt create mode 100644 app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/colored/ColoredFirstVetNoCircle.kt diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/ChatNoCircle.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/ChatNoCircle.kt new file mode 100644 index 0000000000..55d02a050f --- /dev/null +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/ChatNoCircle.kt @@ -0,0 +1,85 @@ +package com.hedvig.android.design.system.hedvig.icon + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +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.graphics.PathFillType +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.hedvig.android.design.system.hedvig.HedvigTheme + +@Suppress("UnusedReceiverParameter") +val HedvigIcons.ChatNoCircle: ImageVector + get() { + if (_ChatNoCircle != null) { + return _ChatNoCircle!! + } + _ChatNoCircle = ImageVector.Builder( + name = "ChatNoCircle", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 24f, + viewportHeight = 24f, + ).apply { + path( + fill = SolidColor(Color(0xFF121212)), + pathFillType = PathFillType.EvenOdd, + ) { + moveTo(5.118f, 5f) + curveTo(3.948f, 5f, 3f, 5.964f, 3f, 7.154f) + verticalLineTo(16.846f) + curveTo(3f, 18.036f, 3.948f, 19f, 5.118f, 19f) + horizontalLineTo(18.882f) + curveTo(20.052f, 19f, 21f, 18.036f, 21f, 16.846f) + verticalLineTo(7.154f) + curveTo(21f, 5.964f, 20.052f, 5f, 18.882f, 5f) + horizontalLineTo(5.118f) + close() + moveTo(12.583f, 13.224f) + curveTo(12.229f, 13.462f, 11.771f, 13.462f, 11.417f, 13.224f) + lineTo(5.458f, 9.228f) + curveTo(5.246f, 9.086f, 5.118f, 8.844f, 5.118f, 8.585f) + curveTo(5.118f, 7.971f, 5.789f, 7.604f, 6.292f, 7.942f) + lineTo(11.417f, 11.378f) + curveTo(11.771f, 11.616f, 12.229f, 11.616f, 12.583f, 11.378f) + lineTo(17.708f, 7.942f) + curveTo(18.211f, 7.604f, 18.882f, 7.971f, 18.882f, 8.585f) + curveTo(18.882f, 8.844f, 18.754f, 9.086f, 18.542f, 9.228f) + lineTo(12.583f, 13.224f) + close() + } + }.build() + + return _ChatNoCircle!! + } + +@Suppress("ObjectPropertyName") +private var _ChatNoCircle: ImageVector? = null + +@Preview +@Composable +private fun IconPreview() { + HedvigTheme { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Image( + imageVector = HedvigIcons.ChatNoCircle, + contentDescription = com.hedvig.android.compose.ui.EmptyContentDescription, + modifier = Modifier + .width((40.0).dp) + .height((40.0).dp), + ) + } + } +} diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/colored/ColoredFirstVetNoCircle.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/colored/ColoredFirstVetNoCircle.kt new file mode 100644 index 0000000000..18f5a86c63 --- /dev/null +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/icon/colored/ColoredFirstVetNoCircle.kt @@ -0,0 +1,101 @@ +package com.hedvig.android.design.system.hedvig.icon.colored + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +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.graphics.PathFillType +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.hedvig.android.design.system.hedvig.HedvigTheme +import com.hedvig.android.design.system.hedvig.icon.HedvigIcons + +val HedvigIcons.ColoredFirstVetNoCircle: ImageVector + get() { + if (_FirstVetLogo != null) { + return _FirstVetLogo!! + } + _FirstVetLogo = ImageVector.Builder( + name = "FirstVetLogo", + defaultWidth = 24.dp, + defaultHeight = 19.dp, + viewportWidth = 24f, + viewportHeight = 19f, + ).apply { + path( + fill = SolidColor(Color(0xFF0062FF)), + pathFillType = PathFillType.EvenOdd, + ) { + moveTo(23.301f, 2.357f) + lineTo(19.411f, 4.422f) + curveTo(19.402f, 4.343f, 19.395f, 4.263f, 19.386f, 4.185f) + curveTo(19.146f, 2.127f, 17.488f, 0.518f, 15.434f, 0.29f) + curveTo(13.697f, 0.096f, 11.932f, 0f, 10.166f, 0f) + horizontalLineTo(9.532f) + curveTo(7.767f, 0f, 6.001f, 0.096f, 4.266f, 0.29f) + curveTo(2.212f, 0.518f, 0.553f, 2.127f, 0.311f, 4.182f) + curveTo(-0.104f, 7.715f, -0.104f, 11.283f, 0.311f, 14.815f) + curveTo(0.553f, 16.871f, 2.212f, 18.482f, 4.266f, 18.709f) + curveTo(6.001f, 18.902f, 7.767f, 19f, 9.532f, 19f) + horizontalLineTo(10.166f) + curveTo(11.93f, 19f, 13.697f, 18.904f, 15.432f, 18.709f) + curveTo(17.485f, 18.482f, 19.144f, 16.871f, 19.386f, 14.815f) + curveTo(19.395f, 14.735f, 19.402f, 14.657f, 19.411f, 14.576f) + lineTo(23.298f, 16.641f) + curveTo(23.617f, 16.808f, 24f, 16.58f, 24f, 16.221f) + verticalLineTo(2.775f) + curveTo(24f, 2.417f, 23.617f, 2.187f, 23.301f, 2.357f) + close() + moveTo(15.777f, 10.49f) + curveTo(15.64f, 11.265f, 14.898f, 11.783f, 14.12f, 11.645f) + lineTo(11.305f, 11.149f) + lineTo(10.561f, 15.358f) + lineTo(9.153f, 15.11f) + curveTo(8.375f, 14.974f, 7.855f, 14.234f, 7.994f, 13.459f) + lineTo(8.491f, 10.653f) + lineTo(4.268f, 9.911f) + lineTo(4.517f, 8.508f) + curveTo(4.653f, 7.733f, 5.395f, 7.214f, 6.173f, 7.353f) + lineTo(8.989f, 7.847f) + lineTo(9.733f, 3.637f) + lineTo(11.141f, 3.885f) + curveTo(11.919f, 4.022f, 12.439f, 4.761f, 12.3f, 5.536f) + lineTo(11.805f, 8.343f) + lineTo(16.028f, 9.084f) + lineTo(15.777f, 10.49f) + close() + } + }.build() + + return _FirstVetLogo!! + } + +@Suppress("ObjectPropertyName") +private var _FirstVetLogo: ImageVector? = null + +@Preview +@Composable +private fun IconPreview() { + HedvigTheme { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Image( + imageVector = HedvigIcons.ColoredFirstVetNoCircle, + contentDescription = com.hedvig.android.compose.ui.EmptyContentDescription, + modifier = Modifier + .width((40.0).dp) + .height((40.0).dp), + ) + } + } +} diff --git a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/ToolbarIcons.kt b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/ToolbarIcons.kt index 74d51b6c21..b9e4c375c3 100644 --- a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/ToolbarIcons.kt +++ b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/ToolbarIcons.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape @@ -27,12 +28,12 @@ import com.hedvig.android.design.system.hedvig.HedvigText import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.Icon import com.hedvig.android.design.system.hedvig.Surface -import com.hedvig.android.design.system.hedvig.icon.Chat +import com.hedvig.android.design.system.hedvig.icon.Campaign +import com.hedvig.android.design.system.hedvig.icon.ChatNoCircle import com.hedvig.android.design.system.hedvig.icon.Clock import com.hedvig.android.design.system.hedvig.icon.HedvigIcons -import com.hedvig.android.design.system.hedvig.icon.colored.ColoredCampaign import com.hedvig.android.design.system.hedvig.icon.colored.ColoredChat -import com.hedvig.android.design.system.hedvig.icon.colored.ColoredFirstVet +import com.hedvig.android.design.system.hedvig.icon.colored.ColoredFirstVetNoCircle import hedvig.resources.DASHBOARD_OPEN_CHAT import hedvig.resources.HC_QUICK_ACTIONS_FIRSTVET_SUBTITLE import hedvig.resources.Res @@ -40,33 +41,49 @@ import hedvig.resources.home_tab_claim_button_text import hedvig.resources.insurance_tab_cross_sells_title import org.jetbrains.compose.resources.stringResource +// Diameter shared by every circular button in the home top app bar, and the glyph centered in it. +private val toolbarButtonSize = 48.dp +private val toolbarGlyphSize = 24.dp + +/** + * The circular surface every home top app bar button sits on: a [toolbarButtonSize] circle holding a + * centered [toolbarGlyphSize] glyph. + */ @Composable -fun ToolbarChatIcon(onClick: () -> Unit, modifier: Modifier = Modifier) { - Icon( - imageVector = HedvigIcons.Chat, - contentDescription = stringResource(Res.string.DASHBOARD_OPEN_CHAT), - tint = HedvigTheme.colorScheme.signalGreyElement, +private fun ToolbarIconButton(onClick: () -> Unit, modifier: Modifier = Modifier, glyph: @Composable () -> Unit) { + Box( + contentAlignment = Alignment.Center, modifier = modifier - .size(40.dp) + .size(toolbarButtonSize) .shadow(4.dp, CircleShape) .clip(CircleShape) - .background(HedvigTheme.colorScheme.surfacePrimaryTransparent) + .background(HedvigTheme.colorScheme.surfacePrimary) .clickable(role = Role.Button, onClick = onClick), + content = { glyph() }, ) } +@Composable +fun ToolbarChatIcon(onClick: () -> Unit, modifier: Modifier = Modifier) { + ToolbarIconButton(onClick = onClick, modifier = modifier) { + Icon( + imageVector = HedvigIcons.ChatNoCircle, + contentDescription = stringResource(Res.string.DASHBOARD_OPEN_CHAT), + tint = HedvigTheme.colorScheme.fillPrimary, + modifier = Modifier.size(toolbarGlyphSize), + ) + } +} + @Composable fun ToolbarFirstVetIcon(onClick: () -> Unit, modifier: Modifier = Modifier) { - Image( - imageVector = HedvigIcons.ColoredFirstVet, - contentDescription = stringResource(Res.string.HC_QUICK_ACTIONS_FIRSTVET_SUBTITLE), - modifier = modifier - .size(40.dp) - .shadow(4.dp, CircleShape) - .clip(CircleShape) - .background(HedvigTheme.colorScheme.surfacePrimary) - .clickable(role = Role.Button, onClick = onClick), - ) + ToolbarIconButton(onClick = onClick, modifier = modifier) { + Image( + imageVector = HedvigIcons.ColoredFirstVetNoCircle, + contentDescription = stringResource(Res.string.HC_QUICK_ACTIONS_FIRSTVET_SUBTITLE), + modifier = Modifier.size(toolbarGlyphSize), + ) + } } @Composable @@ -76,41 +93,18 @@ fun ToolbarCrossSellsIcon(onClick: () -> Unit, modifier: Modifier = Modifier) { targetValue = if (isRotated) 360f else 0f, animationSpec = tween(1500, 50), ) - Image( - imageVector = HedvigIcons.ColoredCampaign, - contentDescription = stringResource(Res.string.insurance_tab_cross_sells_title), - modifier = modifier - // Spin the whole circular icon as one unit (outermost transform), so the rotation stays within - // the circular clip and shadow. - .graphicsLayer { rotationZ = fullRotation } - .size(40.dp) - .shadow(4.dp, CircleShape) - .clip(CircleShape) - .background(HedvigTheme.colorScheme.surfacePrimary) - .clickable(role = Role.Button, onClick = onClick), - ) -} - -@Composable -fun ToolbarClaimChatIcon(onClick: () -> Unit, modifier: Modifier = Modifier, isDev: Boolean = false) { - Box { + ToolbarIconButton( + onClick = onClick, + // Spin the whole circular icon as one unit (outermost transform), so the rotation stays within + // the circular clip and shadow. + modifier = modifier.graphicsLayer { rotationZ = fullRotation }, + ) { Icon( - imageVector = HedvigIcons.ColoredChat, - contentDescription = stringResource(Res.string.home_tab_claim_button_text), - tint = Color.Unspecified, - modifier = modifier - .size(40.dp) - .shadow(4.dp, CircleShape) - .clip(CircleShape) - .clickable(role = Role.Button, onClick = onClick), + imageVector = HedvigIcons.Campaign, + contentDescription = stringResource(Res.string.insurance_tab_cross_sells_title), + tint = HedvigTheme.colorScheme.signalGreenElement, + modifier = Modifier.size(toolbarGlyphSize), ) - if (isDev) { - HedvigText( - "dev", - style = HedvigTheme.typography.label, - modifier = Modifier.align(Alignment.Center), - ) - } } } @@ -120,14 +114,11 @@ private fun PreviewToolbarChatIcon() { HedvigTheme { Surface(color = HedvigTheme.colorScheme.backgroundPrimary) { Row { - ToolbarClaimChatIcon({}, isDev = true) - Spacer(modifier = Modifier.width(8.dp)) - ToolbarClaimChatIcon({}) + ToolbarFirstVetIcon(onClick = {}) Spacer(modifier = Modifier.width(8.dp)) ToolbarCrossSellsIcon({}) Spacer(modifier = Modifier.width(8.dp)) - ToolbarFirstVetIcon(onClick = {}) - Spacer(modifier = Modifier.width(8.dp)) + ToolbarChatIcon({}) } } From 3d186a3dcd98178cd7b51926844a6ca02d484f9d Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Tue, 4 Aug 2026 19:15:33 +0200 Subject: [PATCH 04/14] sheet border --- .../feature/home/home/ui/HomeDestination.kt | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt index 0cce894bdb..45302f0438 100644 --- a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt +++ b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt @@ -5,6 +5,7 @@ import androidx.compose.animation.core.updateTransition import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement @@ -54,7 +55,12 @@ import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.drawOutline +import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.clipRect +import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollSource @@ -689,10 +695,14 @@ private fun HomeScreenSuccess( } } } + // The sheet's surface is painted by three things that have to agree: this backdrop, the pinned lid, + // and every scrolling section's own background. Only the parts the sections don't cover are actually + // this backdrop, so changing one of the three alone leaves the rest at the old color. + val sheetColor = HedvigTheme.colorScheme.backgroundPrimary + val sheetShape = HedvigTheme.shapes.cornerXLargeTop // Opaque sheet backdrop from the pinned lid down to the bottom, so the sheet fills the screen even // when the content is short (e.g. landscape) — no blur gap below the last section. Drawn behind the // LazyColumn (on top of the blur); the hero + pills above the lid stay transparent on the gradient. - val sheetColor = HedvigTheme.colorScheme.backgroundPrimary val contentMaxWidth = 600.dp Box( Modifier @@ -799,10 +809,8 @@ private fun HomeScreenSuccess( HomeSheetDragHandle( Modifier .fillMaxWidth() - .background( - color = HedvigTheme.colorScheme.backgroundPrimary, - shape = HedvigTheme.shapes.cornerXLargeTop, - ), + .background(color = sheetColor, shape = sheetShape) + .topEdgeBorder(sheetShape, HedvigTheme.colorScheme.borderPrimary), ) } } @@ -824,7 +832,7 @@ private fun HomeScreenSuccess( val clipTop = (stickyHeaderBottomPx - itemTopPx).coerceIn(0f, size.height) clipRect(top = clipTop) { this@drawWithContent.drawContent() } } - .background(HedvigTheme.colorScheme.backgroundPrimary), + .background(sheetColor), ) { // The first scrolling section keeps a leading gap below the pinned lid; every section then carries // its gap to the NEXT one as trailing room, giving drop-shadows space within the section. @@ -904,6 +912,23 @@ private fun HomeScreenSuccess( } } +/** + * Strokes only the top of [shape]: its rounded corners and the edge between them. The stroke runs down + * the sides for the height of the element it is applied to and stops there, and the bottom edge is + * clipped away entirely, so the surface below continues borderless. + */ +private fun Modifier.topEdgeBorder(shape: Shape, color: Color, width: Dp = 1.dp): Modifier = drawWithContent { + drawContent() + val stroke = width.toPx() + // Outline sized and offset so the whole stroke lands inside the element rather than straddling its edge. + val outline = shape.createOutline(Size(size.width - stroke, size.height), layoutDirection, this) + clipRect(bottom = size.height - stroke) { + translate(left = stroke / 2f, top = stroke / 2f) { + drawOutline(outline, color = color, style = Stroke(stroke)) + } + } +} + @Composable private fun HomeSheetDragHandle(modifier: Modifier = Modifier) { Box( From 9fda8c48fd876234e87b4ad7ebc4eaa61681110e Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 5 Aug 2026 09:37:44 +0200 Subject: [PATCH 05/14] member reminders border --- .../com/hedvig/android/design/system/hedvig/HedvigCard.kt | 1 + .../com/hedvig/android/feature/home/home/ui/HomeDestination.kt | 2 +- .../android/memberreminders/ui/MemberReminderToDoList.kt | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/HedvigCard.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/HedvigCard.kt index fb95a35cd9..e81f7003ad 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/HedvigCard.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/HedvigCard.kt @@ -80,6 +80,7 @@ fun HedvigCard( shape = shape, color = color, modifier = modifier, + border = borderColor, ) { content() } diff --git a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt index 45302f0438..440a80d381 100644 --- a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt +++ b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt @@ -804,7 +804,7 @@ private fun HomeScreenSuccess( onHelpAndSupport = navigateToHelpCenter, onContactUs = onNavigateToInbox, horizontalInsets = horizontalInsets, - modifier = Modifier.padding(bottom = 8.dp), + modifier = Modifier.padding(bottom = 16.dp), ) HomeSheetDragHandle( Modifier diff --git a/app/member-reminders/member-reminders-ui/src/main/kotlin/com/hedvig/android/memberreminders/ui/MemberReminderToDoList.kt b/app/member-reminders/member-reminders-ui/src/main/kotlin/com/hedvig/android/memberreminders/ui/MemberReminderToDoList.kt index a82fb447ab..1ba0d11ffd 100644 --- a/app/member-reminders/member-reminders-ui/src/main/kotlin/com/hedvig/android/memberreminders/ui/MemberReminderToDoList.kt +++ b/app/member-reminders/member-reminders-ui/src/main/kotlin/com/hedvig/android/memberreminders/ui/MemberReminderToDoList.kt @@ -74,6 +74,9 @@ fun MemberReminderToDoList( if (rows.isEmpty()) return HedvigCard( color = HedvigTheme.colorScheme.fillNegative, + // fillNegative matches the surface this sits on in both themes, and the drop shadow that would + // otherwise separate them is light-only, so the outline is what gives the card an edge in dark mode. + borderColor = HedvigTheme.colorScheme.borderPrimary, modifier = modifier .fillMaxWidth() .hedvigDropShadow(HedvigTheme.shapes.cornerXLarge), From 84cd7690b5dc08153c979989f2a5e532ff35df11 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 5 Aug 2026 09:44:16 +0200 Subject: [PATCH 06/14] spacers --- .../feature/home/home/ui/HomeDestination.kt | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt index 440a80d381..d623808277 100644 --- a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt +++ b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt @@ -834,11 +834,8 @@ private fun HomeScreenSuccess( } .background(sheetColor), ) { - // The first scrolling section keeps a leading gap below the pinned lid; every section then carries - // its gap to the NEXT one as trailing room, giving drop-shadows space within the section. - if (index == 0) { - Spacer(Modifier.height(gapAfter(HomeSection.QuickActionCarousel))) - } + // Each section carries its gap to the NEXT one as trailing room, giving drop-shadows space + // within the section. The first one starts flush under the pinned lid. when (section) { HomeSection.Welcome, HomeSection.QuickActionCarousel -> Unit @@ -904,7 +901,7 @@ private fun HomeScreenSuccess( ) } if (next != null) { - Spacer(Modifier.height(gapAfter(section))) + Spacer(Modifier.height(homeSectionGap)) } } } @@ -976,20 +973,10 @@ private val homeSectionOrder: List = listOf( HomeSection.Addons, ) -// Gap below the pinned lid before the first scrolling section. -private val homeSectionLeadInGap = 24.dp - -// Uniform gap between two consecutive scrolling sections. +// Gap between two consecutive scrolling sections, carried as trailing room below a section's content so +// a card's drop-shadow renders inside the section's own bounds. private val homeSectionGap = 40.dp -// The gap that follows [section], carried as trailing room below its content so a card's drop-shadow -// renders inside the section's own bounds. The pinned lid (QuickActionCarousel) gets the smaller -// lead-in gap; every scrolling section is followed by the uniform section gap. -private fun gapAfter(section: HomeSection): Dp = when (section) { - HomeSection.QuickActionCarousel -> homeSectionLeadInGap - else -> homeSectionGap -} - @Composable private fun WelcomeSection(firstName: String) { Column( From 31d4ddf437a06f8c7c5a186f3ad74fce88e583b8 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 5 Aug 2026 11:14:06 +0200 Subject: [PATCH 07/14] liquid button v1 --- .../android/design/system/hedvig/Button.kt | 186 +++++++++++++++++- .../design/system/hedvig/ColorScheme.kt | 72 +++++++ .../android/design/system/hedvig/Shapes.kt | 2 + .../system/hedvig/tokens/ButtonTokens.kt | 34 ++++ .../system/hedvig/tokens/ColorDarkTokens.kt | 25 +++ .../system/hedvig/tokens/ColorLightTokens.kt | 25 +++ .../hedvig/tokens/ColorSchemeKeyTokens.kt | 18 ++ .../system/hedvig/tokens/PaletteTokens.kt | 8 + .../system/hedvig/tokens/ShapeKeyTokens.kt | 1 + .../feature/home/home/ui/HomeDestination.kt | 40 ++-- 10 files changed, 385 insertions(+), 26 deletions(-) diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Button.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Button.kt index 8336f8a8c3..8555ab4980 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Button.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Button.kt @@ -7,10 +7,12 @@ import androidx.compose.animation.core.updateTransition import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith +import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsHoveredAsState import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope @@ -24,14 +26,18 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.dropShadow +import androidx.compose.ui.draw.innerShadow import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.compositeOver +import androidx.compose.ui.graphics.shadow.Shadow import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import com.hedvig.android.compose.ui.LayoutWithoutPlacement import com.hedvig.android.compose.ui.withoutPlacement @@ -43,6 +49,9 @@ import com.hedvig.android.design.system.hedvig.tokens.MiniSizeButtonTokens import com.hedvig.android.design.system.hedvig.tokens.PrimaryAltStyleButtonTokens import com.hedvig.android.design.system.hedvig.tokens.PrimaryStyleButtonTokens import com.hedvig.android.design.system.hedvig.tokens.RedStyleButtonTokens +import com.hedvig.android.design.system.hedvig.tokens.RoundedLargeSizeButtonTokens +import com.hedvig.android.design.system.hedvig.tokens.RoundedLiquidGlassStyleButtonTokens +import com.hedvig.android.design.system.hedvig.tokens.RoundedPrimaryStyleButtonTokens import com.hedvig.android.design.system.hedvig.tokens.SecondaryAltStyleButtonTokens import com.hedvig.android.design.system.hedvig.tokens.SecondaryStyleButtonTokens import com.hedvig.android.design.system.hedvig.tokens.SmallSizeButtonTokens @@ -119,7 +128,9 @@ fun HedvigButton( ) { @Suppress("NAME_SHADOWING") val interactionSource = interactionSource ?: remember { MutableInteractionSource() } - val buttonColors = buttonStyle.style.buttonColors + val style = buttonStyle.style + val size = buttonSize.sizeIn(style) + val buttonColors = style.buttonColors val containerColor = buttonColors.containerColor(enabled) val contentColor = buttonColors.contentColor(enabled) val isHovered by interactionSource.collectIsHoveredAsState() @@ -131,21 +142,35 @@ fun HedvigButton( containerColor }, ) + + @Suppress("NAME_SHADOWING") + val shape = shape ?: size.shape + val glass = style.glassContainer.takeIf { enabled } Surface( onClick = onClick, - modifier = modifier, + modifier = if (glass == null) { + modifier + } else { + modifier + .dropShadow(shape, glass.dropShadow) + .background(color, shape) + .innerShadow(shape, glass.rimShade) + .innerShadow(shape, glass.rimSheen) + }, onClickLabel = onClickLabel, role = Role.Button, enabled = enabled, - shape = shape ?: buttonSize.size.shape, + shape = shape, border = border, - color = color, + // The rim shadows have to sit on top of the container fill, which is only reachable from outside + // this Surface, so the fill moves into the modifier above and the Surface itself stays see-through. + color = if (glass == null) color else Color.Transparent, contentColor = contentColor, interactionSource = interactionSource, ) { - ProvideTextStyle(buttonSize.size.textStyle) { + ProvideTextStyle(size.textStyle) { Row( - modifier = Modifier.padding(buttonSize.size.contentPadding), + modifier = Modifier.padding(size.contentPadding), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, content = content, @@ -246,6 +271,31 @@ fun HedvigButtonGhostWithBorder( ) } +@HedvigPreview +@Composable +private fun PreviewRoundedButtons() { + HedvigTheme { + // A tinted backdrop, so that the translucent liquid glass container is distinguishable from it. + Surface(color = HedvigTheme.colorScheme.surfaceSecondary) { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.padding(16.dp), + ) { + for (buttonStyle in listOf( + ButtonDefaults.ButtonStyle.RoundedPrimary, + ButtonDefaults.ButtonStyle.RoundedLiquidGlass, + )) { + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + HedvigButton("Make a claim", {}, enabled = true, buttonStyle = buttonStyle) + HedvigButton("Disabled", {}, enabled = false, buttonStyle = buttonStyle) + HedvigButton("Loading", {}, enabled = true, buttonStyle = buttonStyle, isLoading = true) + } + } + } + } + } +} + object ButtonDefaults { internal val buttonStyle: ButtonStyle = ButtonStyle.Primary internal val buttonSize: ButtonSize = ButtonSize.Large @@ -257,6 +307,12 @@ object ButtonDefaults { SecondaryAlt, Ghost, Red, + + /** A filled pill with the sheen and drop shadow of the iOS glass material. */ + RoundedPrimary, + + /** A translucent pill of the iOS glass material, letting the backdrop show through. */ + RoundedLiquidGlass, } enum class ButtonSize { @@ -275,6 +331,8 @@ private val ButtonDefaults.ButtonStyle.style: Style ButtonDefaults.ButtonStyle.SecondaryAlt -> Style.SecondaryAlt ButtonDefaults.ButtonStyle.Ghost -> Style.Ghost ButtonDefaults.ButtonStyle.Red -> Style.Red + ButtonDefaults.ButtonStyle.RoundedPrimary -> Style.RoundedPrimary + ButtonDefaults.ButtonStyle.RoundedLiquidGlass -> Style.RoundedLiquidGlass } private val ButtonSize.size: Size @@ -285,6 +343,48 @@ private val ButtonSize.size: Size ButtonSize.Mini -> Size.Mini } +/** + * The metrics this size takes on within [style]. The rounded styles get their own pill metrics at + * [ButtonSize.Large]; at every smaller size they fall back to the standard button metrics. + */ +@Composable +private fun ButtonSize.sizeIn(style: Style): Size = when { + style.glassContainer != null && this == ButtonSize.Large -> Size.LargeRounded + else -> size +} + +/** + * The container treatment of the iOS glass material: a [dropShadow] behind the container, and two + * inner shadows over its fill that shade the top-left rim and light up the bottom-right one, which is + * how the material reads as lit from below and to the right. + */ +@Immutable +private data class GlassContainer(val dropShadow: Shadow, val rimShade: Shadow, val rimSheen: Shadow) + +private val glassDropShadow = Shadow( + radius = 40.dp, + color = Color.Black, + offset = DpOffset(0.dp, 8.dp), + alpha = 0.12f, +) + +// TODO: the rim shadows below stand in for a Figma glass effect that cannot be exported, so their +// values are read off the Figma render rather than given by design. Revisit once design catches up. + +/** The rim as the glass material normally reads: a faint shade and a pronounced sheen. */ +private val regularGlassContainer = GlassContainer( + dropShadow = glassDropShadow, + rimShade = Shadow(radius = 10.dp, color = Color.Black, offset = DpOffset(4.dp, 4.dp), alpha = 0.06f), + rimSheen = Shadow(radius = 10.dp, color = Color.White, offset = DpOffset((-4).dp, (-4).dp), alpha = 0.80f), +) + +/** The rim weighted for an opaque near-black fill, which would read as a grey smudge under [regularGlassContainer]. */ +private val opaqueDarkGlassContainer = GlassContainer( + dropShadow = glassDropShadow, + rimShade = Shadow(radius = 10.dp, color = Color.Black, offset = DpOffset(4.dp, 4.dp), alpha = 0.40f), + rimSheen = Shadow(radius = 10.dp, color = Color.White, offset = DpOffset((-4).dp, (-4).dp), alpha = 0.04f), +) + @Immutable private data class ButtonColors( val containerColor: Color, @@ -343,6 +443,25 @@ private sealed interface Size { get() = LargeSizeButtonTokens.ContainerShape.value } + object LargeRounded : Size { + override val contentPadding: PaddingValues = PaddingValues( + top = RoundedLargeSizeButtonTokens.TopPadding, + bottom = RoundedLargeSizeButtonTokens.BottomPadding, + start = RoundedLargeSizeButtonTokens.HorizontalPadding, + end = RoundedLargeSizeButtonTokens.HorizontalPadding, + ) + + override val textStyle: TextStyle + @Composable + @ReadOnlyComposable + get() = RoundedLargeSizeButtonTokens.LabelTextFont.value + + override val shape: Shape + @Composable + @ReadOnlyComposable + get() = RoundedLargeSizeButtonTokens.ContainerShape.value + } + object Medium : Size { override val contentPadding: PaddingValues = PaddingValues( top = MediumSizeButtonTokens.TopPadding, @@ -405,6 +524,11 @@ private sealed interface Style { @get:Composable val buttonColors: ButtonColors + /** Non-null on the styles that render the iOS glass material. */ + val glassContainer: GlassContainer? + @Composable + get() = null + data object Primary : Style { override val buttonColors: ButtonColors @Composable @@ -505,6 +629,56 @@ private sealed interface Style { } } + data object RoundedPrimary : Style { + // The fill inverts between themes: near-black on light, opaque white on dark. + override val glassContainer: GlassContainer + @Composable + get() = if (HedvigTheme.colorScheme.isLight) opaqueDarkGlassContainer else regularGlassContainer + + override val buttonColors: ButtonColors + @Composable + get() = with(HedvigTheme.colorScheme) { + remember(this) { + ButtonColors( + containerColor = fromToken(RoundedPrimaryStyleButtonTokens.ContainerColor), + contentColor = fromToken(RoundedPrimaryStyleButtonTokens.ContentColor), + disabledContainerColor = fromToken(RoundedPrimaryStyleButtonTokens.DisabledContainerColor), + disabledContentColor = fromToken(RoundedPrimaryStyleButtonTokens.DisabledContentColor), + hoverContainerColor = fromToken(RoundedPrimaryStyleButtonTokens.HoverContainerColor), + hoverContentColor = fromToken(RoundedPrimaryStyleButtonTokens.HoverContentColor), + activeLoadingIndicatorColor = fromToken(RoundedPrimaryStyleButtonTokens.ActiveLoadingIndicatorColor), + inactiveLoadingIndicatorColor = fromToken(RoundedPrimaryStyleButtonTokens.InactiveLoadingIndicatorColor), + redTextColor = fromToken(RoundedPrimaryStyleButtonTokens.RedContentColor), + ) + } + } + } + + data object RoundedLiquidGlass : Style { + override val glassContainer: GlassContainer + @Composable + get() = regularGlassContainer + + override val buttonColors: ButtonColors + @Composable + get() = with(HedvigTheme.colorScheme) { + remember(this) { + ButtonColors( + containerColor = fromToken(RoundedLiquidGlassStyleButtonTokens.ContainerColor), + contentColor = fromToken(RoundedLiquidGlassStyleButtonTokens.ContentColor), + disabledContainerColor = fromToken(RoundedLiquidGlassStyleButtonTokens.DisabledContainerColor), + disabledContentColor = fromToken(RoundedLiquidGlassStyleButtonTokens.DisabledContentColor), + hoverContainerColor = fromToken(RoundedLiquidGlassStyleButtonTokens.HoverContainerColor), + hoverContentColor = fromToken(RoundedLiquidGlassStyleButtonTokens.HoverContentColor), + activeLoadingIndicatorColor = fromToken(RoundedLiquidGlassStyleButtonTokens.ActiveLoadingIndicatorColor), + inactiveLoadingIndicatorColor = + fromToken(RoundedLiquidGlassStyleButtonTokens.InactiveLoadingIndicatorColor), + redTextColor = fromToken(RoundedLiquidGlassStyleButtonTokens.RedContentColor), + ) + } + } + } + data object Red : Style { override val buttonColors: ButtonColors @Composable diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/ColorScheme.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/ColorScheme.kt index a410e1e34b..fadf70c8a7 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/ColorScheme.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/ColorScheme.kt @@ -51,6 +51,24 @@ data class ColorScheme( val buttonGhostResting: Color, val buttonGhostHover: Color, val buttonGhostDisabled: Color, + val buttonRoundedPrimaryResting: Color, + val buttonRoundedPrimaryHover: Color, + val buttonRoundedPrimaryDisabled: Color, + val buttonRoundedPrimaryContent: Color, + val buttonRoundedPrimaryHoverContent: Color, + val buttonRoundedPrimaryDisabledContent: Color, + val buttonRoundedPrimaryLoadingActive: Color, + val buttonRoundedPrimaryLoadingInactive: Color, + val buttonRoundedPrimaryRedContent: Color, + val buttonRoundedLiquidGlassResting: Color, + val buttonRoundedLiquidGlassHover: Color, + val buttonRoundedLiquidGlassDisabled: Color, + val buttonRoundedLiquidGlassContent: Color, + val buttonRoundedLiquidGlassHoverContent: Color, + val buttonRoundedLiquidGlassDisabledContent: Color, + val buttonRoundedLiquidGlassLoadingActive: Color, + val buttonRoundedLiquidGlassLoadingInactive: Color, + val buttonRoundedLiquidGlassRedContent: Color, val fillPrimary: Color, val fillSecondary: Color, val fillTertiary: Color, @@ -160,6 +178,24 @@ internal val lightColorScheme: ColorScheme = ColorScheme( buttonGhostResting = ColorLightTokens.ButtonGhostResting, buttonGhostHover = ColorLightTokens.ButtonGhostHover, buttonGhostDisabled = ColorLightTokens.ButtonGhostDisabled, + buttonRoundedPrimaryResting = ColorLightTokens.ButtonRoundedPrimaryResting, + buttonRoundedPrimaryHover = ColorLightTokens.ButtonRoundedPrimaryHover, + buttonRoundedPrimaryDisabled = ColorLightTokens.ButtonRoundedPrimaryDisabled, + buttonRoundedPrimaryContent = ColorLightTokens.ButtonRoundedPrimaryContent, + buttonRoundedPrimaryHoverContent = ColorLightTokens.ButtonRoundedPrimaryHoverContent, + buttonRoundedPrimaryDisabledContent = ColorLightTokens.ButtonRoundedPrimaryDisabledContent, + buttonRoundedPrimaryLoadingActive = ColorLightTokens.ButtonRoundedPrimaryLoadingActive, + buttonRoundedPrimaryLoadingInactive = ColorLightTokens.ButtonRoundedPrimaryLoadingInactive, + buttonRoundedPrimaryRedContent = ColorLightTokens.ButtonRoundedPrimaryRedContent, + buttonRoundedLiquidGlassResting = ColorLightTokens.ButtonRoundedLiquidGlassResting, + buttonRoundedLiquidGlassHover = ColorLightTokens.ButtonRoundedLiquidGlassHover, + buttonRoundedLiquidGlassDisabled = ColorLightTokens.ButtonRoundedLiquidGlassDisabled, + buttonRoundedLiquidGlassContent = ColorLightTokens.ButtonRoundedLiquidGlassContent, + buttonRoundedLiquidGlassHoverContent = ColorLightTokens.ButtonRoundedLiquidGlassHoverContent, + buttonRoundedLiquidGlassDisabledContent = ColorLightTokens.ButtonRoundedLiquidGlassDisabledContent, + buttonRoundedLiquidGlassLoadingActive = ColorLightTokens.ButtonRoundedLiquidGlassLoadingActive, + buttonRoundedLiquidGlassLoadingInactive = ColorLightTokens.ButtonRoundedLiquidGlassLoadingInactive, + buttonRoundedLiquidGlassRedContent = ColorLightTokens.ButtonRoundedLiquidGlassRedContent, fillPrimary = ColorLightTokens.FillPrimary, fillSecondary = ColorLightTokens.FillSecondary, fillTertiary = ColorLightTokens.FillTertiary, @@ -269,6 +305,24 @@ internal val darkColorScheme: ColorScheme = ColorScheme( buttonGhostResting = ColorDarkTokens.ButtonGhostResting, buttonGhostHover = ColorDarkTokens.ButtonGhostHover, buttonGhostDisabled = ColorDarkTokens.ButtonGhostDisabled, + buttonRoundedPrimaryResting = ColorDarkTokens.ButtonRoundedPrimaryResting, + buttonRoundedPrimaryHover = ColorDarkTokens.ButtonRoundedPrimaryHover, + buttonRoundedPrimaryDisabled = ColorDarkTokens.ButtonRoundedPrimaryDisabled, + buttonRoundedPrimaryContent = ColorDarkTokens.ButtonRoundedPrimaryContent, + buttonRoundedPrimaryHoverContent = ColorDarkTokens.ButtonRoundedPrimaryHoverContent, + buttonRoundedPrimaryDisabledContent = ColorDarkTokens.ButtonRoundedPrimaryDisabledContent, + buttonRoundedPrimaryLoadingActive = ColorDarkTokens.ButtonRoundedPrimaryLoadingActive, + buttonRoundedPrimaryLoadingInactive = ColorDarkTokens.ButtonRoundedPrimaryLoadingInactive, + buttonRoundedPrimaryRedContent = ColorDarkTokens.ButtonRoundedPrimaryRedContent, + buttonRoundedLiquidGlassResting = ColorDarkTokens.ButtonRoundedLiquidGlassResting, + buttonRoundedLiquidGlassHover = ColorDarkTokens.ButtonRoundedLiquidGlassHover, + buttonRoundedLiquidGlassDisabled = ColorDarkTokens.ButtonRoundedLiquidGlassDisabled, + buttonRoundedLiquidGlassContent = ColorDarkTokens.ButtonRoundedLiquidGlassContent, + buttonRoundedLiquidGlassHoverContent = ColorDarkTokens.ButtonRoundedLiquidGlassHoverContent, + buttonRoundedLiquidGlassDisabledContent = ColorDarkTokens.ButtonRoundedLiquidGlassDisabledContent, + buttonRoundedLiquidGlassLoadingActive = ColorDarkTokens.ButtonRoundedLiquidGlassLoadingActive, + buttonRoundedLiquidGlassLoadingInactive = ColorDarkTokens.ButtonRoundedLiquidGlassLoadingInactive, + buttonRoundedLiquidGlassRedContent = ColorDarkTokens.ButtonRoundedLiquidGlassRedContent, fillPrimary = ColorDarkTokens.FillPrimary, fillSecondary = ColorDarkTokens.FillSecondary, fillTertiary = ColorDarkTokens.FillTertiary, @@ -403,6 +457,24 @@ internal fun ColorScheme.fromToken(value: ColorSchemeKeyTokens): Color { ColorSchemeKeyTokens.ButtonGhostResting -> buttonGhostResting ColorSchemeKeyTokens.ButtonGhostHover -> buttonGhostHover ColorSchemeKeyTokens.ButtonGhostDisabled -> buttonGhostDisabled + ColorSchemeKeyTokens.ButtonRoundedPrimaryResting -> buttonRoundedPrimaryResting + ColorSchemeKeyTokens.ButtonRoundedPrimaryHover -> buttonRoundedPrimaryHover + ColorSchemeKeyTokens.ButtonRoundedPrimaryDisabled -> buttonRoundedPrimaryDisabled + ColorSchemeKeyTokens.ButtonRoundedPrimaryContent -> buttonRoundedPrimaryContent + ColorSchemeKeyTokens.ButtonRoundedPrimaryHoverContent -> buttonRoundedPrimaryHoverContent + ColorSchemeKeyTokens.ButtonRoundedPrimaryDisabledContent -> buttonRoundedPrimaryDisabledContent + ColorSchemeKeyTokens.ButtonRoundedPrimaryLoadingActive -> buttonRoundedPrimaryLoadingActive + ColorSchemeKeyTokens.ButtonRoundedPrimaryLoadingInactive -> buttonRoundedPrimaryLoadingInactive + ColorSchemeKeyTokens.ButtonRoundedPrimaryRedContent -> buttonRoundedPrimaryRedContent + ColorSchemeKeyTokens.ButtonRoundedLiquidGlassResting -> buttonRoundedLiquidGlassResting + ColorSchemeKeyTokens.ButtonRoundedLiquidGlassHover -> buttonRoundedLiquidGlassHover + ColorSchemeKeyTokens.ButtonRoundedLiquidGlassDisabled -> buttonRoundedLiquidGlassDisabled + ColorSchemeKeyTokens.ButtonRoundedLiquidGlassContent -> buttonRoundedLiquidGlassContent + ColorSchemeKeyTokens.ButtonRoundedLiquidGlassHoverContent -> buttonRoundedLiquidGlassHoverContent + ColorSchemeKeyTokens.ButtonRoundedLiquidGlassDisabledContent -> buttonRoundedLiquidGlassDisabledContent + ColorSchemeKeyTokens.ButtonRoundedLiquidGlassLoadingActive -> buttonRoundedLiquidGlassLoadingActive + ColorSchemeKeyTokens.ButtonRoundedLiquidGlassLoadingInactive -> buttonRoundedLiquidGlassLoadingInactive + ColorSchemeKeyTokens.ButtonRoundedLiquidGlassRedContent -> buttonRoundedLiquidGlassRedContent ColorSchemeKeyTokens.FillPrimary -> fillPrimary ColorSchemeKeyTokens.FillSecondary -> fillSecondary ColorSchemeKeyTokens.FillTertiary -> fillTertiary diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Shapes.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Shapes.kt index a20dbc9b94..c900899b1f 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Shapes.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Shapes.kt @@ -23,6 +23,7 @@ import com.hedvig.android.design.system.hedvig.FigmaShapeDirection.All import com.hedvig.android.design.system.hedvig.FigmaShapeDirection.BottomOnly import com.hedvig.android.design.system.hedvig.FigmaShapeDirection.TopOnly import com.hedvig.android.design.system.hedvig.tokens.ShapeKeyTokens +import com.hedvig.android.design.system.hedvig.tokens.ShapeKeyTokens.CornerFull import com.hedvig.android.design.system.hedvig.tokens.ShapeKeyTokens.CornerLarge import com.hedvig.android.design.system.hedvig.tokens.ShapeKeyTokens.CornerMedium import com.hedvig.android.design.system.hedvig.tokens.ShapeKeyTokens.CornerNone @@ -60,6 +61,7 @@ internal val ShapeKeyTokens.value: Shape internal fun Shapes.fromToken(token: ShapeKeyTokens): Shape { return when (token) { + CornerFull -> cornerFull CornerXXLarge -> cornerXXLarge CornerXLarge -> cornerXLarge CornerLarge -> cornerLarge diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ButtonTokens.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ButtonTokens.kt index a665012f79..bc6595345f 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ButtonTokens.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ButtonTokens.kt @@ -70,6 +70,30 @@ internal object RedStyleButtonTokens { val RedContentColor = ColorSchemeKeyTokens.SignalRedElement // should not be used ever } +internal object RoundedPrimaryStyleButtonTokens { + val ContainerColor = ColorSchemeKeyTokens.ButtonRoundedPrimaryResting + val ContentColor = ColorSchemeKeyTokens.ButtonRoundedPrimaryContent + val HoverContainerColor = ColorSchemeKeyTokens.ButtonRoundedPrimaryHover + val HoverContentColor = ColorSchemeKeyTokens.ButtonRoundedPrimaryHoverContent + val DisabledContainerColor = ColorSchemeKeyTokens.ButtonRoundedPrimaryDisabled + val DisabledContentColor = ColorSchemeKeyTokens.ButtonRoundedPrimaryDisabledContent + val ActiveLoadingIndicatorColor = ColorSchemeKeyTokens.ButtonRoundedPrimaryLoadingActive + val InactiveLoadingIndicatorColor = ColorSchemeKeyTokens.ButtonRoundedPrimaryLoadingInactive + val RedContentColor = ColorSchemeKeyTokens.ButtonRoundedPrimaryRedContent +} + +internal object RoundedLiquidGlassStyleButtonTokens { + val ContainerColor = ColorSchemeKeyTokens.ButtonRoundedLiquidGlassResting + val ContentColor = ColorSchemeKeyTokens.ButtonRoundedLiquidGlassContent + val HoverContainerColor = ColorSchemeKeyTokens.ButtonRoundedLiquidGlassHover + val HoverContentColor = ColorSchemeKeyTokens.ButtonRoundedLiquidGlassHoverContent + val DisabledContainerColor = ColorSchemeKeyTokens.ButtonRoundedLiquidGlassDisabled + val DisabledContentColor = ColorSchemeKeyTokens.ButtonRoundedLiquidGlassDisabledContent + val ActiveLoadingIndicatorColor = ColorSchemeKeyTokens.ButtonRoundedLiquidGlassLoadingActive + val InactiveLoadingIndicatorColor = ColorSchemeKeyTokens.ButtonRoundedLiquidGlassLoadingInactive + val RedContentColor = ColorSchemeKeyTokens.ButtonRoundedLiquidGlassRedContent +} + internal object LargeSizeButtonTokens { val HorizontalPadding = 32.dp val TopPadding = 15.dp @@ -78,6 +102,16 @@ internal object LargeSizeButtonTokens { val LabelTextFont = TypographyKeyTokens.BodySmall } +// TODO: the rounded button styles are only designed at the large size. Every smaller size falls back to +// the standard button metrics above until design provides them. +internal object RoundedLargeSizeButtonTokens { + val HorizontalPadding = 20.dp + val TopPadding = 12.dp + val BottomPadding = 12.dp + val ContainerShape = ShapeKeyTokens.CornerFull + val LabelTextFont = TypographyKeyTokens.BodySmall +} + internal object MediumSizeButtonTokens { val HorizontalPadding = 14.dp val TopPadding = 7.dp // todo look into if we use the right line heights to justify the offset introduced here diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ColorDarkTokens.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ColorDarkTokens.kt index 4861801346..f12b8cc12d 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ColorDarkTokens.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ColorDarkTokens.kt @@ -36,6 +36,31 @@ internal object ColorDarkTokens { val ButtonGhostResting: Color = PaletteTokens.Transparent val ButtonGhostHover: Color = PaletteTokens.G900TDark val ButtonGhostDisabled: Color = PaletteTokens.Transparent + + // TODO: the rounded buttons are only designed for light mode. In dark mode the primary one inverts to + // an opaque white fill and the remaining states mirror the primary button. Review once design catches up. + val ButtonRoundedPrimaryResting: Color = PaletteTokens.White + val ButtonRoundedPrimaryHover: Color = PaletteTokens.G200TLight + val ButtonRoundedPrimaryDisabled: Color = PaletteTokens.G900 + val ButtonRoundedPrimaryContent: Color = PaletteTokens.Black + val ButtonRoundedPrimaryHoverContent: Color = PaletteTokens.Black + val ButtonRoundedPrimaryDisabledContent: Color = PaletteTokens.G700 + val ButtonRoundedPrimaryLoadingActive: Color = PaletteTokens.Black + val ButtonRoundedPrimaryLoadingInactive: Color = PaletteTokens.G800TDark + val ButtonRoundedPrimaryRedContent: Color = PaletteTokens.Red650 + + // TODO: the rounded buttons are only designed for light mode. In dark mode the liquid glass one is a + // copy of the secondary button. Review once design catches up. + val ButtonRoundedLiquidGlassResting: Color = PaletteTokens.G900TDark + val ButtonRoundedLiquidGlassHover: Color = PaletteTokens.G900TDark + val ButtonRoundedLiquidGlassDisabled: Color = PaletteTokens.G900 + val ButtonRoundedLiquidGlassContent: Color = PaletteTokens.White + val ButtonRoundedLiquidGlassHoverContent: Color = PaletteTokens.White + val ButtonRoundedLiquidGlassDisabledContent: Color = PaletteTokens.G700 + val ButtonRoundedLiquidGlassLoadingActive: Color = PaletteTokens.White + val ButtonRoundedLiquidGlassLoadingInactive: Color = PaletteTokens.G800TDark + val ButtonRoundedLiquidGlassRedContent: Color = PaletteTokens.Red650 + val FillPrimary: Color = PaletteTokens.White val FillSecondary: Color = PaletteTokens.G500 val FillTertiary: Color = PaletteTokens.G700 diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ColorLightTokens.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ColorLightTokens.kt index 63978c52bb..72c02be07a 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ColorLightTokens.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ColorLightTokens.kt @@ -36,6 +36,31 @@ internal object ColorLightTokens { val ButtonGhostResting: Color = PaletteTokens.Transparent val ButtonGhostHover: Color = PaletteTokens.G100TLight val ButtonGhostDisabled: Color = PaletteTokens.Transparent + + // TODO: only the resting colors of the rounded buttons come from design. Every other state below + // mirrors the primary button and needs reviewing once the design catches up. + val ButtonRoundedPrimaryResting: Color = PaletteTokens.Black + val ButtonRoundedPrimaryHover: Color = PaletteTokens.G900TDark + val ButtonRoundedPrimaryDisabled: Color = PaletteTokens.G100 + val ButtonRoundedPrimaryContent: Color = PaletteTokens.White + val ButtonRoundedPrimaryHoverContent: Color = PaletteTokens.White + val ButtonRoundedPrimaryDisabledContent: Color = PaletteTokens.G500 + val ButtonRoundedPrimaryLoadingActive: Color = PaletteTokens.White + val ButtonRoundedPrimaryLoadingInactive: Color = PaletteTokens.G200TLight + val ButtonRoundedPrimaryRedContent: Color = PaletteTokens.Red600 + + // TODO: only the resting color comes from design. Every other state below mirrors the ghost button + // and needs reviewing once the design catches up. + val ButtonRoundedLiquidGlassResting: Color = PaletteTokens.LiquidGlassFrost + val ButtonRoundedLiquidGlassHover: Color = PaletteTokens.G100TLight + val ButtonRoundedLiquidGlassDisabled: Color = PaletteTokens.Transparent + val ButtonRoundedLiquidGlassContent: Color = PaletteTokens.Black + val ButtonRoundedLiquidGlassHoverContent: Color = PaletteTokens.Black + val ButtonRoundedLiquidGlassDisabledContent: Color = PaletteTokens.G500 + val ButtonRoundedLiquidGlassLoadingActive: Color = PaletteTokens.Black + val ButtonRoundedLiquidGlassLoadingInactive: Color = PaletteTokens.G200TLight + val ButtonRoundedLiquidGlassRedContent: Color = PaletteTokens.Red600 + val FillPrimary: Color = PaletteTokens.Black val FillSecondary: Color = PaletteTokens.G700 val FillTertiary: Color = PaletteTokens.G500 diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ColorSchemeKeyTokens.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ColorSchemeKeyTokens.kt index d138282c7b..d6ce52b9d3 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ColorSchemeKeyTokens.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ColorSchemeKeyTokens.kt @@ -35,6 +35,24 @@ internal enum class ColorSchemeKeyTokens { ButtonGhostResting, ButtonGhostHover, ButtonGhostDisabled, + ButtonRoundedPrimaryResting, + ButtonRoundedPrimaryHover, + ButtonRoundedPrimaryDisabled, + ButtonRoundedPrimaryContent, + ButtonRoundedPrimaryHoverContent, + ButtonRoundedPrimaryDisabledContent, + ButtonRoundedPrimaryLoadingActive, + ButtonRoundedPrimaryLoadingInactive, + ButtonRoundedPrimaryRedContent, + ButtonRoundedLiquidGlassResting, + ButtonRoundedLiquidGlassHover, + ButtonRoundedLiquidGlassDisabled, + ButtonRoundedLiquidGlassContent, + ButtonRoundedLiquidGlassHoverContent, + ButtonRoundedLiquidGlassDisabledContent, + ButtonRoundedLiquidGlassLoadingActive, + ButtonRoundedLiquidGlassLoadingInactive, + ButtonRoundedLiquidGlassRedContent, FillPrimary, FillSecondary, FillTertiary, diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/PaletteTokens.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/PaletteTokens.kt index a08009c7df..f4a344fbb0 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/PaletteTokens.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/PaletteTokens.kt @@ -23,6 +23,14 @@ internal object PaletteTokens { val G800 = Color(0xFF505050) val G900 = Color(0xFF303030) + /** + * The translucent fill of the iOS-style glass material. Composited over any backdrop it lands within + * ~1.5% of the frosted white Figma renders for the rounded liquid glass button. + * + * TODO: replace with a real palette entry once the design system defines the glass material. + */ + val LiquidGlassFrost = Color(0xA1F0F0F0) + val G50TLight = Color(0x05121212) val G100TLight = Color(0x0B121212) val G150TLight = Color(0x0D121212) diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ShapeKeyTokens.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ShapeKeyTokens.kt index 9d426a7cb7..633b15ea5b 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ShapeKeyTokens.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ShapeKeyTokens.kt @@ -1,6 +1,7 @@ package com.hedvig.android.design.system.hedvig.tokens internal enum class ShapeKeyTokens { + CornerFull, CornerXXLarge, CornerXLarge, CornerLarge, diff --git a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt index d623808277..cb7f953256 100644 --- a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt +++ b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/HomeDestination.kt @@ -72,7 +72,6 @@ import androidx.compose.ui.layout.onPlaced import androidx.compose.ui.layout.positionInParent import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.testTag -import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.heading import androidx.compose.ui.semantics.hideFromAccessibility import androidx.compose.ui.semantics.semantics @@ -105,6 +104,8 @@ import com.hedvig.android.data.coinsured.CoInsuredFlowType import com.hedvig.android.data.contract.CrossSell import com.hedvig.android.data.contract.ImageAsset import com.hedvig.android.design.system.hedvig.ButtonDefaults.ButtonSize +import com.hedvig.android.design.system.hedvig.ButtonDefaults.ButtonStyle.RoundedLiquidGlass +import com.hedvig.android.design.system.hedvig.ButtonDefaults.ButtonStyle.RoundedPrimary import com.hedvig.android.design.system.hedvig.ButtonDefaults.ButtonStyle.Secondary import com.hedvig.android.design.system.hedvig.DraftClaimDialog import com.hedvig.android.design.system.hedvig.ErrorDialog @@ -1233,22 +1234,6 @@ private fun HomeActionTile(icon: ImageVector, text: String, onClick: () -> Unit, } } -@Composable -private fun HomeActionChip(text: String, onClick: () -> Unit) { - Surface( - onClick = onClick, - shape = HedvigTheme.shapes.cornerXLarge, - color = HedvigTheme.colorScheme.surfacePrimaryTransparent, - role = Role.Button, - ) { - HedvigText( - text = text, - style = HedvigTheme.typography.label, - modifier = Modifier.padding(horizontal = 16.dp, vertical = 10.dp), - ) - } -} - @Composable private fun QuickActionCarouselSection( isHelpCenterEnabled: Boolean, @@ -1266,11 +1251,26 @@ private fun QuickActionCarouselSection( .padding(horizontal = 16.dp) .padding(horizontalInsets), ) { - HomeActionChip(stringResource(Res.string.home_tab_claim_button_text), onMakeClaim) + HedvigButton( + text = stringResource(Res.string.home_tab_claim_button_text), + onClick = onMakeClaim, + enabled = true, + buttonStyle = RoundedPrimary, + ) if (isHelpCenterEnabled) { - HomeActionChip(stringResource(Res.string.home_tab_get_help), onHelpAndSupport) + HedvigButton( + text = stringResource(Res.string.home_tab_get_help), + onClick = onHelpAndSupport, + enabled = true, + buttonStyle = RoundedLiquidGlass, + ) } - HomeActionChip(stringResource(Res.string.DASHBOARD_OPEN_CHAT), onContactUs) + HedvigButton( + text = stringResource(Res.string.DASHBOARD_OPEN_CHAT), + onClick = onContactUs, + enabled = true, + buttonStyle = RoundedLiquidGlass, + ) } } From 0d9000d28ddc413f5ebed90af9382f9eb5b785c5 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 5 Aug 2026 11:20:36 +0200 Subject: [PATCH 08/14] add white rim --- .../android/design/system/hedvig/Button.kt | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Button.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Button.kt index 8555ab4980..98721decf9 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Button.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Button.kt @@ -7,6 +7,7 @@ import androidx.compose.animation.core.updateTransition import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.interaction.MutableInteractionSource @@ -28,6 +29,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.dropShadow import androidx.compose.ui.draw.innerShadow +import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.compositeOver @@ -156,6 +158,7 @@ fun HedvigButton( .background(color, shape) .innerShadow(shape, glass.rimShade) .innerShadow(shape, glass.rimSheen) + .then(glass.rimHighlight?.let { Modifier.border(it, shape) } ?: Modifier) }, onClickLabel = onClickLabel, role = Role.Button, @@ -354,12 +357,17 @@ private fun ButtonSize.sizeIn(style: Style): Size = when { } /** - * The container treatment of the iOS glass material: a [dropShadow] behind the container, and two - * inner shadows over its fill that shade the top-left rim and light up the bottom-right one, which is - * how the material reads as lit from below and to the right. + * The container treatment of the iOS glass material: a [dropShadow] behind the container, two inner + * shadows over its fill that shade the top-left rim and light up the bottom-right one, and an optional + * hairline [rimHighlight] where the material catches the light along its top edge. */ @Immutable -private data class GlassContainer(val dropShadow: Shadow, val rimShade: Shadow, val rimSheen: Shadow) +private data class GlassContainer( + val dropShadow: Shadow, + val rimShade: Shadow, + val rimSheen: Shadow, + val rimHighlight: BorderStroke?, +) private val glassDropShadow = Shadow( radius = 40.dp, @@ -376,6 +384,16 @@ private val regularGlassContainer = GlassContainer( dropShadow = glassDropShadow, rimShade = Shadow(radius = 10.dp, color = Color.Black, offset = DpOffset(4.dp, 4.dp), alpha = 0.06f), rimSheen = Shadow(radius = 10.dp, color = Color.White, offset = DpOffset((-4).dp, (-4).dp), alpha = 0.80f), + // Near-opaque along the top edge and down the upper half of the sides, gone by the bottom, where the + // broader rimSheen takes over. + rimHighlight = BorderStroke( + width = 1.dp, + brush = Brush.verticalGradient( + 0f to Color.White.copy(alpha = 0.85f), + 0.6f to Color.White.copy(alpha = 0.70f), + 1f to Color.White.copy(alpha = 0f), + ), + ), ) /** The rim weighted for an opaque near-black fill, which would read as a grey smudge under [regularGlassContainer]. */ @@ -383,6 +401,7 @@ private val opaqueDarkGlassContainer = GlassContainer( dropShadow = glassDropShadow, rimShade = Shadow(radius = 10.dp, color = Color.Black, offset = DpOffset(4.dp, 4.dp), alpha = 0.40f), rimSheen = Shadow(radius = 10.dp, color = Color.White, offset = DpOffset((-4).dp, (-4).dp), alpha = 0.04f), + rimHighlight = null, ) @Immutable From 5599c8c91e0b12d5cff9b4ba1448cd951892c6df Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 5 Aug 2026 11:29:16 +0200 Subject: [PATCH 09/14] separate liquid glass, apply to toolbar icons --- .../android/design/system/hedvig/Button.kt | 69 ++------------- .../design/system/hedvig/ColorScheme.kt | 4 + .../design/system/hedvig/LiquidGlass.kt | 83 +++++++++++++++++++ .../system/hedvig/tokens/ColorDarkTokens.kt | 1 + .../system/hedvig/tokens/ColorLightTokens.kt | 1 + .../hedvig/tokens/ColorSchemeKeyTokens.kt | 1 + .../feature/home/home/ui/ToolbarIcons.kt | 6 +- 7 files changed, 100 insertions(+), 65 deletions(-) create mode 100644 app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/LiquidGlass.kt diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Button.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Button.kt index 98721decf9..be54f8239d 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Button.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Button.kt @@ -147,18 +147,13 @@ fun HedvigButton( @Suppress("NAME_SHADOWING") val shape = shape ?: size.shape - val glass = style.glassContainer.takeIf { enabled } + val glass = style.glassMaterial.takeIf { enabled } Surface( onClick = onClick, modifier = if (glass == null) { modifier } else { - modifier - .dropShadow(shape, glass.dropShadow) - .background(color, shape) - .innerShadow(shape, glass.rimShade) - .innerShadow(shape, glass.rimSheen) - .then(glass.rimHighlight?.let { Modifier.border(it, shape) } ?: Modifier) + modifier.glassMaterial(glass, color, shape) }, onClickLabel = onClickLabel, role = Role.Button, @@ -352,58 +347,10 @@ private val ButtonSize.size: Size */ @Composable private fun ButtonSize.sizeIn(style: Style): Size = when { - style.glassContainer != null && this == ButtonSize.Large -> Size.LargeRounded + style.glassMaterial != null && this == ButtonSize.Large -> Size.LargeRounded else -> size } -/** - * The container treatment of the iOS glass material: a [dropShadow] behind the container, two inner - * shadows over its fill that shade the top-left rim and light up the bottom-right one, and an optional - * hairline [rimHighlight] where the material catches the light along its top edge. - */ -@Immutable -private data class GlassContainer( - val dropShadow: Shadow, - val rimShade: Shadow, - val rimSheen: Shadow, - val rimHighlight: BorderStroke?, -) - -private val glassDropShadow = Shadow( - radius = 40.dp, - color = Color.Black, - offset = DpOffset(0.dp, 8.dp), - alpha = 0.12f, -) - -// TODO: the rim shadows below stand in for a Figma glass effect that cannot be exported, so their -// values are read off the Figma render rather than given by design. Revisit once design catches up. - -/** The rim as the glass material normally reads: a faint shade and a pronounced sheen. */ -private val regularGlassContainer = GlassContainer( - dropShadow = glassDropShadow, - rimShade = Shadow(radius = 10.dp, color = Color.Black, offset = DpOffset(4.dp, 4.dp), alpha = 0.06f), - rimSheen = Shadow(radius = 10.dp, color = Color.White, offset = DpOffset((-4).dp, (-4).dp), alpha = 0.80f), - // Near-opaque along the top edge and down the upper half of the sides, gone by the bottom, where the - // broader rimSheen takes over. - rimHighlight = BorderStroke( - width = 1.dp, - brush = Brush.verticalGradient( - 0f to Color.White.copy(alpha = 0.85f), - 0.6f to Color.White.copy(alpha = 0.70f), - 1f to Color.White.copy(alpha = 0f), - ), - ), -) - -/** The rim weighted for an opaque near-black fill, which would read as a grey smudge under [regularGlassContainer]. */ -private val opaqueDarkGlassContainer = GlassContainer( - dropShadow = glassDropShadow, - rimShade = Shadow(radius = 10.dp, color = Color.Black, offset = DpOffset(4.dp, 4.dp), alpha = 0.40f), - rimSheen = Shadow(radius = 10.dp, color = Color.White, offset = DpOffset((-4).dp, (-4).dp), alpha = 0.04f), - rimHighlight = null, -) - @Immutable private data class ButtonColors( val containerColor: Color, @@ -544,7 +491,7 @@ private sealed interface Style { val buttonColors: ButtonColors /** Non-null on the styles that render the iOS glass material. */ - val glassContainer: GlassContainer? + val glassMaterial: GlassMaterial? @Composable get() = null @@ -650,9 +597,9 @@ private sealed interface Style { data object RoundedPrimary : Style { // The fill inverts between themes: near-black on light, opaque white on dark. - override val glassContainer: GlassContainer + override val glassMaterial: GlassMaterial @Composable - get() = if (HedvigTheme.colorScheme.isLight) opaqueDarkGlassContainer else regularGlassContainer + get() = if (HedvigTheme.colorScheme.isLight) opaqueDarkGlassMaterial else regularGlassMaterial override val buttonColors: ButtonColors @Composable @@ -674,9 +621,9 @@ private sealed interface Style { } data object RoundedLiquidGlass : Style { - override val glassContainer: GlassContainer + override val glassMaterial: GlassMaterial @Composable - get() = regularGlassContainer + get() = regularGlassMaterial override val buttonColors: ButtonColors @Composable diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/ColorScheme.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/ColorScheme.kt index fadf70c8a7..7284d053ee 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/ColorScheme.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/ColorScheme.kt @@ -88,6 +88,7 @@ data class ColorScheme( val surfacePrimaryTransparent: Color, val surfaceSecondaryTransparent: Color, val surfaceHighlightTransparent: Color, + val surfaceLiquidGlass: Color, val backgroundPrimary: Color, val backgroundNegative: Color, val backgroundBlack: Color, @@ -215,6 +216,7 @@ internal val lightColorScheme: ColorScheme = ColorScheme( surfacePrimaryTransparent = ColorLightTokens.SurfacePrimaryTransparent, surfaceSecondaryTransparent = ColorLightTokens.SurfaceSecondaryTransparent, surfaceHighlightTransparent = ColorLightTokens.SurfaceHighlightTransparent, + surfaceLiquidGlass = ColorLightTokens.SurfaceLiquidGlass, backgroundPrimary = ColorLightTokens.BackgroundPrimary, backgroundNegative = ColorLightTokens.BackgroundNegative, backgroundBlack = ColorLightTokens.BackgroundBlack, @@ -342,6 +344,7 @@ internal val darkColorScheme: ColorScheme = ColorScheme( surfacePrimaryTransparent = ColorDarkTokens.SurfacePrimaryTransparent, surfaceSecondaryTransparent = ColorDarkTokens.SurfaceSecondaryTransparent, surfaceHighlightTransparent = ColorDarkTokens.SurfaceHighlightTransparent, + surfaceLiquidGlass = ColorDarkTokens.SurfaceLiquidGlass, backgroundPrimary = ColorDarkTokens.BackgroundPrimary, backgroundNegative = ColorDarkTokens.BackgroundNegative, backgroundBlack = ColorDarkTokens.BackgroundBlack, @@ -494,6 +497,7 @@ internal fun ColorScheme.fromToken(value: ColorSchemeKeyTokens): Color { ColorSchemeKeyTokens.SurfacePrimaryTransparent -> surfacePrimaryTransparent ColorSchemeKeyTokens.SurfaceSecondaryTransparent -> surfaceSecondaryTransparent ColorSchemeKeyTokens.SurfaceHighlightTransparent -> surfaceHighlightTransparent + ColorSchemeKeyTokens.SurfaceLiquidGlass -> surfaceLiquidGlass ColorSchemeKeyTokens.BackgroundPrimary -> backgroundPrimary ColorSchemeKeyTokens.BackgroundNegative -> backgroundNegative ColorSchemeKeyTokens.BackgroundBlack -> backgroundBlack diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/LiquidGlass.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/LiquidGlass.kt new file mode 100644 index 0000000000..93b666f9e3 --- /dev/null +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/LiquidGlass.kt @@ -0,0 +1,83 @@ +package com.hedvig.android.design.system.hedvig + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.dropShadow +import androidx.compose.ui.draw.innerShadow +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.shadow.Shadow +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp + +/** + * Fills [shape] with the iOS-style glass material: a translucent fill that lets the backdrop through, a + * drop shadow, rim shading, and a hairline highlight along the top edge. + * + * Draws behind the content it is applied to, so put it before any `clickable` that should ripple on top + * of it. + */ +@Composable +fun Modifier.liquidGlass(shape: Shape): Modifier = + glassMaterial(regularGlassMaterial, HedvigTheme.colorScheme.surfaceLiquidGlass, shape) + +/** + * The rim and shadow treatment of the glass material: a [dropShadow] behind the container, two inner + * shadows over its fill that shade the top-left rim and light up the bottom-right one, and an optional + * hairline [rimHighlight] where the material catches the light along its top edge. + */ +@Immutable +internal data class GlassMaterial( + val dropShadow: Shadow, + val rimShade: Shadow, + val rimSheen: Shadow, + val rimHighlight: BorderStroke?, +) + +/** Paints [material] over a [fill] container, both clipped to [shape]. */ +internal fun Modifier.glassMaterial(material: GlassMaterial, fill: Color, shape: Shape): Modifier = this + .dropShadow(shape, material.dropShadow) + .background(fill, shape) + .innerShadow(shape, material.rimShade) + .innerShadow(shape, material.rimSheen) + .then(material.rimHighlight?.let { Modifier.border(it, shape) } ?: Modifier) + +private val glassDropShadow = Shadow( + radius = 40.dp, + color = Color.Black, + offset = DpOffset(0.dp, 8.dp), + alpha = 0.12f, +) + +// TODO: the rim shadows below stand in for a Figma glass effect that cannot be exported, so their +// values are read off the Figma render rather than given by design. Revisit once design catches up. + +/** The rim as the glass material normally reads: a faint shade and a pronounced sheen. */ +internal val regularGlassMaterial = GlassMaterial( + dropShadow = glassDropShadow, + rimShade = Shadow(radius = 10.dp, color = Color.Black, offset = DpOffset(4.dp, 4.dp), alpha = 0.06f), + rimSheen = Shadow(radius = 10.dp, color = Color.White, offset = DpOffset((-4).dp, (-4).dp), alpha = 0.80f), + // Near-opaque along the top edge and down the upper half of the sides, gone by the bottom, where the + // broader rimSheen takes over. + rimHighlight = BorderStroke( + width = 1.dp, + brush = Brush.verticalGradient( + 0f to Color.White.copy(alpha = 0.85f), + 0.6f to Color.White.copy(alpha = 0.70f), + 1f to Color.White.copy(alpha = 0f), + ), + ), +) + +/** The rim weighted for an opaque near-black fill, which would read as a grey smudge under [regularGlassMaterial]. */ +internal val opaqueDarkGlassMaterial = GlassMaterial( + dropShadow = glassDropShadow, + rimShade = Shadow(radius = 10.dp, color = Color.Black, offset = DpOffset(4.dp, 4.dp), alpha = 0.40f), + rimSheen = Shadow(radius = 10.dp, color = Color.White, offset = DpOffset((-4).dp, (-4).dp), alpha = 0.04f), + rimHighlight = null, +) diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ColorDarkTokens.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ColorDarkTokens.kt index f12b8cc12d..ae49ba9577 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ColorDarkTokens.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ColorDarkTokens.kt @@ -80,6 +80,7 @@ internal object ColorDarkTokens { val SurfacePrimaryTransparent: Color = PaletteTokens.G900TDark val SurfaceSecondaryTransparent: Color = PaletteTokens.G800TDark val SurfaceHighlightTransparent: Color = PaletteTokens.G900TDark + val SurfaceLiquidGlass: Color = PaletteTokens.G900TDark val BackgroundPrimary: Color = PaletteTokens.Black val BackgroundNegative: Color = PaletteTokens.White val BackgroundBlack: Color = PaletteTokens.Black diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ColorLightTokens.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ColorLightTokens.kt index 72c02be07a..fb5dc03711 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ColorLightTokens.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ColorLightTokens.kt @@ -80,6 +80,7 @@ internal object ColorLightTokens { val SurfacePrimaryTransparent: Color = PaletteTokens.G100TLight val SurfaceSecondaryTransparent: Color = PaletteTokens.G200TLight val SurfaceHighlightTransparent: Color = PaletteTokens.G50TLight + val SurfaceLiquidGlass: Color = PaletteTokens.LiquidGlassFrost val BackgroundPrimary: Color = PaletteTokens.White val BackgroundNegative: Color = PaletteTokens.Black val BackgroundBlack: Color = PaletteTokens.Black diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ColorSchemeKeyTokens.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ColorSchemeKeyTokens.kt index d6ce52b9d3..792329ec62 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ColorSchemeKeyTokens.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/tokens/ColorSchemeKeyTokens.kt @@ -72,6 +72,7 @@ internal enum class ColorSchemeKeyTokens { SurfacePrimaryTransparent, SurfaceSecondaryTransparent, SurfaceHighlightTransparent, + SurfaceLiquidGlass, BackgroundPrimary, BackgroundNegative, BackgroundBlack, diff --git a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/ToolbarIcons.kt b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/ToolbarIcons.kt index b9e4c375c3..34a3bc7a34 100644 --- a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/ToolbarIcons.kt +++ b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/ui/ToolbarIcons.kt @@ -3,7 +3,6 @@ package com.hedvig.android.feature.home.home.ui import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row @@ -18,7 +17,6 @@ import androidx.compose.runtime.produceState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.semantics.Role @@ -34,6 +32,7 @@ import com.hedvig.android.design.system.hedvig.icon.Clock import com.hedvig.android.design.system.hedvig.icon.HedvigIcons import com.hedvig.android.design.system.hedvig.icon.colored.ColoredChat import com.hedvig.android.design.system.hedvig.icon.colored.ColoredFirstVetNoCircle +import com.hedvig.android.design.system.hedvig.liquidGlass import hedvig.resources.DASHBOARD_OPEN_CHAT import hedvig.resources.HC_QUICK_ACTIONS_FIRSTVET_SUBTITLE import hedvig.resources.Res @@ -55,9 +54,8 @@ private fun ToolbarIconButton(onClick: () -> Unit, modifier: Modifier = Modifier contentAlignment = Alignment.Center, modifier = modifier .size(toolbarButtonSize) - .shadow(4.dp, CircleShape) + .liquidGlass(CircleShape) .clip(CircleShape) - .background(HedvigTheme.colorScheme.surfacePrimary) .clickable(role = Role.Button, onClick = onClick), content = { glyph() }, ) From 9d37968d8f65e9546e10c00c17558e067851a2e1 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 5 Aug 2026 11:53:50 +0200 Subject: [PATCH 10/14] change rimSheen a little bit --- .../com/hedvig/android/design/system/hedvig/LiquidGlass.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/LiquidGlass.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/LiquidGlass.kt index 93b666f9e3..f739b2728f 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/LiquidGlass.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/LiquidGlass.kt @@ -61,7 +61,9 @@ private val glassDropShadow = Shadow( internal val regularGlassMaterial = GlassMaterial( dropShadow = glassDropShadow, rimShade = Shadow(radius = 10.dp, color = Color.Black, offset = DpOffset(4.dp, 4.dp), alpha = 0.06f), - rimSheen = Shadow(radius = 10.dp, color = Color.White, offset = DpOffset((-4).dp, (-4).dp), alpha = 0.80f), + // Cast straight up from the bottom edge: the material lifts along the bottom roughly four times as + // much as along the sides, which a diagonal offset would spread too far around a narrow container. + rimSheen = Shadow(radius = 10.dp, color = Color.White, offset = DpOffset(0.dp, (-4).dp), alpha = 0.80f), // Near-opaque along the top edge and down the upper half of the sides, gone by the bottom, where the // broader rimSheen takes over. rimHighlight = BorderStroke( From 5ebd69df8191b2fefe650df69f743dc5d494bc91 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Wed, 5 Aug 2026 15:10:14 +0200 Subject: [PATCH 11/14] dark mode less dramatic --- .../android/design/system/hedvig/Button.kt | 2 +- .../design/system/hedvig/LiquidGlass.kt | 29 +++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Button.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Button.kt index be54f8239d..4797753193 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Button.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/Button.kt @@ -623,7 +623,7 @@ private sealed interface Style { data object RoundedLiquidGlass : Style { override val glassMaterial: GlassMaterial @Composable - get() = regularGlassMaterial + get() = liquidGlassMaterial override val buttonColors: ButtonColors @Composable diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/LiquidGlass.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/LiquidGlass.kt index f739b2728f..92ac3c289e 100644 --- a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/LiquidGlass.kt +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/LiquidGlass.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.shadow.Shadow import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp +import com.hedvig.android.design.system.hedvig.tokens.PaletteTokens /** * Fills [shape] with the iOS-style glass material: a translucent fill that lets the backdrop through, a @@ -24,7 +25,12 @@ import androidx.compose.ui.unit.dp */ @Composable fun Modifier.liquidGlass(shape: Shape): Modifier = - glassMaterial(regularGlassMaterial, HedvigTheme.colorScheme.surfaceLiquidGlass, shape) + glassMaterial(liquidGlassMaterial, HedvigTheme.colorScheme.surfaceLiquidGlass, shape) + +/** The material for a translucent glass fill, weighted for the active color scheme. */ +internal val liquidGlassMaterial: GlassMaterial + @Composable + get() = if (HedvigTheme.colorScheme.isLight) regularGlassMaterial else translucentDarkGlassMaterial /** * The rim and shadow treatment of the glass material: a [dropShadow] behind the container, two inner @@ -57,7 +63,7 @@ private val glassDropShadow = Shadow( // TODO: the rim shadows below stand in for a Figma glass effect that cannot be exported, so their // values are read off the Figma render rather than given by design. Revisit once design catches up. -/** The rim as the glass material normally reads: a faint shade and a pronounced sheen. */ +/** The rim weighted for a light fill: a faint shade and a pronounced sheen. */ internal val regularGlassMaterial = GlassMaterial( dropShadow = glassDropShadow, rimShade = Shadow(radius = 10.dp, color = Color.Black, offset = DpOffset(4.dp, 4.dp), alpha = 0.06f), @@ -76,6 +82,25 @@ internal val regularGlassMaterial = GlassMaterial( ), ) +/** + * The rim weighted for a translucent dark fill, which only sits a little above its backdrop: the sheen + * is drawn in grey and tucked close to the edge, so the rim stays within reach of the fill's own + * brightness. + */ +internal val translucentDarkGlassMaterial = GlassMaterial( + dropShadow = glassDropShadow, + rimShade = Shadow(radius = 10.dp, color = Color.Black, offset = DpOffset(4.dp, 4.dp), alpha = 0.20f), + rimSheen = Shadow(radius = 6.dp, color = PaletteTokens.G500, offset = DpOffset(0.dp, (-3).dp), alpha = 0.45f), + rimHighlight = BorderStroke( + width = 1.dp, + brush = Brush.verticalGradient( + 0f to PaletteTokens.G500.copy(alpha = 0.55f), + 0.6f to PaletteTokens.G500.copy(alpha = 0.35f), + 1f to PaletteTokens.G500.copy(alpha = 0f), + ), + ), +) + /** The rim weighted for an opaque near-black fill, which would read as a grey smudge under [regularGlassMaterial]. */ internal val opaqueDarkGlassMaterial = GlassMaterial( dropShadow = glassDropShadow, From 66cf6f49f8c7772f0e666a868cfa0e2bd538f23a Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Thu, 6 Aug 2026 13:36:16 +0200 Subject: [PATCH 12/14] todo with dialog --- .../ui/MemberReminderToDoList.kt | 100 +++++++++++++++++- 1 file changed, 95 insertions(+), 5 deletions(-) diff --git a/app/member-reminders/member-reminders-ui/src/main/kotlin/com/hedvig/android/memberreminders/ui/MemberReminderToDoList.kt b/app/member-reminders/member-reminders-ui/src/main/kotlin/com/hedvig/android/memberreminders/ui/MemberReminderToDoList.kt index 1ba0d11ffd..bbaf4535f5 100644 --- a/app/member-reminders/member-reminders-ui/src/main/kotlin/com/hedvig/android/memberreminders/ui/MemberReminderToDoList.kt +++ b/app/member-reminders/member-reminders-ui/src/main/kotlin/com/hedvig/android/memberreminders/ui/MemberReminderToDoList.kt @@ -4,18 +4,30 @@ 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.size import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.hedvig.android.data.coinsured.CoInsuredFlowType +import com.hedvig.android.design.system.hedvig.ButtonDefaults +import com.hedvig.android.design.system.hedvig.DialogDefaults.DialogStyle.NoButtons import com.hedvig.android.design.system.hedvig.DividerPosition +import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigCard +import com.hedvig.android.design.system.hedvig.HedvigDialog import com.hedvig.android.design.system.hedvig.HedvigText import com.hedvig.android.design.system.hedvig.HedvigTheme import com.hedvig.android.design.system.hedvig.Icon @@ -38,6 +50,7 @@ import hedvig.resources.HOME_TODO_PAYMENT_OVERDUE_TITLE import hedvig.resources.HOME_TODO_REQUIRES_ACTION_SUBTITLE import hedvig.resources.HOME_TODO_UPDATE_CONTACT_DETAILS_TITLE import hedvig.resources.Res +import hedvig.resources.open_chat import kotlinx.datetime.LocalDate import org.jetbrains.compose.resources.StringResource import org.jetbrains.compose.resources.stringResource @@ -61,12 +74,23 @@ fun MemberReminderToDoList( navigateToChipId: () -> Unit, modifier: Modifier = Modifier, ) { + var showMissedPaymentsDialog by rememberSaveable { mutableStateOf(false) } + val missedPaymentsReminder = memberReminders + .filterIsInstance() + .firstOrNull() + if (showMissedPaymentsDialog && missedPaymentsReminder != null) { + MissedPaymentsDialog( + reminder = missedPaymentsReminder, + onNavigateToNewConversation = onNavigateToNewConversation, + onDismissRequest = { showMissedPaymentsDialog = false }, + ) + } val rows = memberReminders.mapNotNull { reminder -> reminder.toToDoRowOrNull( navigateToConnectPayment = navigateToConnectPayment, navigateToConnectPayout = navigateToConnectPayout, navigateToAddMissingInfo = navigateToAddMissingInfo, - onNavigateToNewConversation = onNavigateToNewConversation, + onShowMissedPaymentsDialog = { showMissedPaymentsDialog = true }, navigateToContactInfo = navigateToContactInfo, navigateToChipId = navigateToChipId, ) @@ -85,6 +109,7 @@ fun MemberReminderToDoList( rows.forEachIndexed { index, row -> ToDoRow( icon = row.icon, + iconTint = row.iconTint, title = row.title, onClick = row.onClick, modifier = Modifier.horizontalDivider(DividerPosition.Top, show = index != 0), @@ -124,50 +149,74 @@ private fun MemberReminder.homeToDoOrder(): Int? = when (this) { private data class ToDoRowData( val icon: ImageVector, + val iconTint: ToDoRowIconTint, val title: StringResource, val onClick: () -> Unit, ) +/** + * The tint of a To do row's leading icon, resolved against the theme in [ToDoRow]. [Attention] marks the rows whose + * action is time critical, so the icon carries the same urgency as the row's red subtitle. + */ +private enum class ToDoRowIconTint { + Primary, + Attention, + ; + + val color: Color + @Composable + get() = when (this) { + Primary -> HedvigTheme.colorScheme.fillPrimary + Attention -> HedvigTheme.colorScheme.signalRedElement + } +} + private fun MemberReminder.toToDoRowOrNull( navigateToConnectPayment: () -> Unit, navigateToConnectPayout: () -> Unit, navigateToAddMissingInfo: (String, CoInsuredFlowType) -> Unit, - onNavigateToNewConversation: () -> Unit, + onShowMissedPaymentsDialog: () -> Unit, navigateToContactInfo: () -> Unit, navigateToChipId: () -> Unit, ): ToDoRowData? = when (this) { is MemberReminder.PaymentReminder.TerminationDueToMissedPayments -> ToDoRowData( icon = HedvigIcons.WarningOutline, + iconTint = ToDoRowIconTint.Attention, title = Res.string.HOME_TODO_PAYMENT_OVERDUE_TITLE, - onClick = onNavigateToNewConversation, + onClick = onShowMissedPaymentsDialog, ) is MemberReminder.PaymentReminder.ConnectPayment -> ToDoRowData( icon = HedvigIcons.Card, + iconTint = ToDoRowIconTint.Primary, title = Res.string.HOME_TODO_MISSING_PAYMENT_METHOD_TITLE, onClick = navigateToConnectPayment, ) is MemberReminder.PaymentReminder.ConnectPayout -> ToDoRowData( icon = HedvigIcons.Card, + iconTint = ToDoRowIconTint.Primary, title = Res.string.HOME_TODO_MISSING_PAYOUT_METHOD_TITLE, onClick = navigateToConnectPayout, ) is MemberReminder.MissingChipId -> ToDoRowData( icon = HedvigIcons.ID, + iconTint = ToDoRowIconTint.Primary, title = Res.string.HOME_TODO_MISSING_CHIP_ID_TITLE, onClick = navigateToChipId, ) is MemberReminder.CoInsuredInfo -> ToDoRowData( icon = HedvigIcons.ProfileOutline, + iconTint = ToDoRowIconTint.Primary, title = Res.string.HOME_TODO_ADD_COINSURED_TITLE, onClick = { navigateToAddMissingInfo(contractId, coInsuredType) }, ) is MemberReminder.ContactInfoUpdateNeeded -> ToDoRowData( icon = HedvigIcons.InfoOutline, + iconTint = ToDoRowIconTint.Primary, title = Res.string.HOME_TODO_UPDATE_CONTACT_DETAILS_TITLE, onClick = navigateToContactInfo, ) @@ -177,8 +226,49 @@ private fun MemberReminder.toToDoRowOrNull( is MemberReminder.EnableNotifications -> null } +/** + * Explains the pending termination and offers chat as the way out, since the member can't resolve missed payments in + * the app themselves. + */ @Composable -private fun ToDoRow(icon: ImageVector, title: StringResource, onClick: () -> Unit, modifier: Modifier = Modifier) { +private fun MissedPaymentsDialog( + reminder: MemberReminder.PaymentReminder.TerminationDueToMissedPayments, + onNavigateToNewConversation: () -> Unit, + onDismissRequest: () -> Unit, +) { + HedvigDialog( + onDismissRequest = onDismissRequest, + style = NoButtons, + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + HedvigText( + text = getMemberReminderMessage(reminder), + textAlign = TextAlign.Center, + ) + Spacer(Modifier.height(24.dp)) + HedvigButton( + text = stringResource(Res.string.open_chat), + onClick = { + onDismissRequest() + onNavigateToNewConversation() + }, + enabled = true, + buttonStyle = ButtonDefaults.ButtonStyle.Primary, + buttonSize = ButtonDefaults.ButtonSize.Large, + modifier = Modifier.fillMaxWidth(), + ) + } + } +} + +@Composable +private fun ToDoRow( + icon: ImageVector, + iconTint: ToDoRowIconTint, + title: StringResource, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp), @@ -190,7 +280,7 @@ private fun ToDoRow(icon: ImageVector, title: StringResource, onClick: () -> Uni Icon( imageVector = icon, contentDescription = null, - tint = HedvigTheme.colorScheme.fillPrimary, + tint = iconTint.color, modifier = Modifier.size(24.dp), ) Column(Modifier.weight(1f)) { From 51991b9ba188e83c0f7dc46408f4d1486e8c01a8 Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Thu, 6 Aug 2026 14:11:00 +0200 Subject: [PATCH 13/14] dialog paddings --- .../android/memberreminders/ui/MemberReminderToDoList.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/member-reminders/member-reminders-ui/src/main/kotlin/com/hedvig/android/memberreminders/ui/MemberReminderToDoList.kt b/app/member-reminders/member-reminders-ui/src/main/kotlin/com/hedvig/android/memberreminders/ui/MemberReminderToDoList.kt index bbaf4535f5..06bbbf4790 100644 --- a/app/member-reminders/member-reminders-ui/src/main/kotlin/com/hedvig/android/memberreminders/ui/MemberReminderToDoList.kt +++ b/app/member-reminders/member-reminders-ui/src/main/kotlin/com/hedvig/android/memberreminders/ui/MemberReminderToDoList.kt @@ -244,6 +244,7 @@ private fun MissedPaymentsDialog( HedvigText( text = getMemberReminderMessage(reminder), textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 20.dp), ) Spacer(Modifier.height(24.dp)) HedvigButton( @@ -254,9 +255,10 @@ private fun MissedPaymentsDialog( }, enabled = true, buttonStyle = ButtonDefaults.ButtonStyle.Primary, - buttonSize = ButtonDefaults.ButtonSize.Large, + buttonSize = ButtonDefaults.ButtonSize.Medium, modifier = Modifier.fillMaxWidth(), ) + Spacer(Modifier.height(16.dp)) } } } From 3f71fe4a4dcd13fb2e88f2381219cdd7c284828e Mon Sep 17 00:00:00 2001 From: mariiapanasetskaia Date: Thu, 6 Aug 2026 14:14:54 +0200 Subject: [PATCH 14/14] fix startup race crash --- .../android/feature/home/home/data/GetHomeDataUseCase.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/data/GetHomeDataUseCase.kt b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/data/GetHomeDataUseCase.kt index e884924629..37de1b6bdb 100644 --- a/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/data/GetHomeDataUseCase.kt +++ b/app/feature/feature-home/src/main/kotlin/com/hedvig/android/feature/home/home/data/GetHomeDataUseCase.kt @@ -35,6 +35,7 @@ import kotlin.time.Clock import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.seconds import kotlin.time.Instant +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.delay @@ -43,6 +44,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.isActive import kotlinx.datetime.LocalDate import kotlinx.datetime.TimeZone @@ -218,6 +220,11 @@ internal class GetHomeDataUseCaseImpl( } } } + // Keeps the whole chain — including flatMapLatest's cancellation of the previous inner flow — off the + // collector's dispatcher. Collected from a Molecule presenter, that dispatcher is the main thread, and + // cancelling an in-flight query makes Ktor close the response body inline, tripping StrictMode's + // NetworkOnMainThreadException. + .flowOn(Dispatchers.IO) } private fun shouldShowChatButton(isInboxEnabledFromKillSwitch: Boolean, hasActiveConversations: Boolean): Boolean {