diff --git a/app/src/main/kotlin/com/example/myapplication/MainApp.kt b/app/src/main/kotlin/com/example/myapplication/MainApp.kt index 21a446e..84356a9 100644 --- a/app/src/main/kotlin/com/example/myapplication/MainApp.kt +++ b/app/src/main/kotlin/com/example/myapplication/MainApp.kt @@ -316,7 +316,7 @@ fun MainApp() { }, ) { Box( - Modifier.fillMaxWidth().padding(bottom = 12.dp), + Modifier.fillMaxWidth(), contentAlignment = Alignment.BottomCenter, ) { LiquidAppBottomBar( diff --git a/app/src/main/kotlin/com/example/myapplication/ui/AppBottomBar.kt b/app/src/main/kotlin/com/example/myapplication/ui/AppBottomBar.kt index 611872e..a672adb 100644 --- a/app/src/main/kotlin/com/example/myapplication/ui/AppBottomBar.kt +++ b/app/src/main/kotlin/com/example/myapplication/ui/AppBottomBar.kt @@ -1,8 +1,10 @@ package com.example.myapplication.ui +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box 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.navigationBarsPadding import androidx.compose.foundation.layout.padding @@ -11,6 +13,8 @@ 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.Brush +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp @@ -30,66 +34,89 @@ fun LiquidAppBottomBar( onSelect: (Int) -> Unit, backdrop: Backdrop?, modifier: Modifier = Modifier, + contentTint: Color = Color.Transparent, ) { val active = LumaColors.accent val inactive = LumaColors.textSecondary + // iOS 26: subtle content-aware color overlay for glass tinting + val tintOverlay = if (contentTint != Color.Transparent) { + Brush.verticalGradient( + listOf( + Color.Transparent, + contentTint.copy(alpha = 0.05f), + ) + ) + } else null - Row( - modifier = modifier - .navigationBarsPadding() - .padding(start = LumaSpacing.lg, end = LumaSpacing.lg, bottom = LumaSpacing.sm), - verticalAlignment = Alignment.CenterVertically, - ) { - Box(modifier = Modifier.weight(1f)) { - LiquidBottomTabs( - selectedTabIndex = { selectedTab.coerceIn(0, 3) }, - onTabSelected = onSelect, - backdrop = backdrop, - tabsCount = 4, - showSelection = { selectedTab < 4 }, - ) { - for (index in mainTabs.indices) { - val tab = mainTabs[index] - LiquidBottomTab(onClick = { onSelect(index) }) { - LumaAnimatedIcon( - icon = tab.icon, - selected = selectedTab == index, - contentDescription = stringResource(tab.labelRes), - tint = if (selectedTab == index) active else inactive, - modifier = Modifier.size(26.dp), - ) - Spacer(Modifier.height(2.dp)) - Text( - text = stringResource(tab.labelRes), - color = if (selectedTab == index) active else inactive, - fontSize = 10.sp, - fontWeight = if (selectedTab == index) FontWeight.SemiBold else FontWeight.Medium, - maxLines = 1, - ) + Box(modifier = modifier) { + Row( + modifier = Modifier + .navigationBarsPadding() + .padding(start = LumaSpacing.lg, end = LumaSpacing.lg, bottom = LumaSpacing.sm), + verticalAlignment = Alignment.CenterVertically, + ) { + Box(modifier = Modifier.weight(1f)) { + LiquidBottomTabs( + selectedTabIndex = { selectedTab.coerceIn(0, 3) }, + onTabSelected = onSelect, + backdrop = backdrop, + tabsCount = 4, + showSelection = { selectedTab < 4 }, + ) { + for (index in mainTabs.indices) { + val tab = mainTabs[index] + LiquidBottomTab(onClick = { onSelect(index) }) { + LumaAnimatedIcon( + icon = tab.icon, + selected = selectedTab == index, + contentDescription = stringResource(tab.labelRes), + tint = if (selectedTab == index) active else inactive, + modifier = Modifier.size(26.dp), + ) + Spacer(Modifier.height(2.dp)) + Text( + text = stringResource(tab.labelRes), + color = if (selectedTab == index) active else inactive, + fontSize = 10.sp, + fontWeight = if (selectedTab == index) FontWeight.SemiBold else FontWeight.Medium, + maxLines = 1, + ) + } } } } + + Spacer(Modifier.width(LumaSpacing.sm)) + + LiquidBottomTabs( + selectedTabIndex = { 0 }, + onTabSelected = { onSelect(4) }, + backdrop = backdrop, + tabsCount = 1, + showSelection = { selectedTab == 4 }, + modifier = Modifier.size(64.dp), + ) { + LiquidBottomTab(onClick = { onSelect(4) }) { + LumaAnimatedIcon( + icon = LumaIcon.Search, + selected = selectedTab == 4, + contentDescription = stringResource(com.example.myapplication.R.string.search), + tint = if (selectedTab == 4) active else inactive, + modifier = Modifier.size(27.dp), + ) + } + } } - Spacer(Modifier.width(LumaSpacing.sm)) - - LiquidBottomTabs( - selectedTabIndex = { 0 }, - onTabSelected = { onSelect(4) }, - backdrop = backdrop, - tabsCount = 1, - showSelection = { selectedTab == 4 }, - modifier = Modifier.size(64.dp), - ) { - LiquidBottomTab(onClick = { onSelect(4) }) { - LumaAnimatedIcon( - icon = LumaIcon.Search, - selected = selectedTab == 4, - contentDescription = stringResource(com.example.myapplication.R.string.search), - tint = if (selectedTab == 4) active else inactive, - modifier = Modifier.size(27.dp), - ) - } + // iOS 26: subtle content-aware tint overlay (visible only when contentTint is provided) + tintOverlay?.let { brush -> + Box( + modifier = Modifier + .fillMaxWidth() + .navigationBarsPadding() + .height(80.dp) + .background(brush), + ) } } } diff --git a/app/src/main/kotlin/com/example/myapplication/ui/LiquidGlass.kt b/app/src/main/kotlin/com/example/myapplication/ui/LiquidGlass.kt index 774a449..492f862 100644 --- a/app/src/main/kotlin/com/example/myapplication/ui/LiquidGlass.kt +++ b/app/src/main/kotlin/com/example/myapplication/ui/LiquidGlass.kt @@ -14,6 +14,7 @@ import com.kyant.backdrop.effects.colorControls import com.kyant.backdrop.effects.lens import com.kyant.backdrop.effects.vibrancy import com.kyant.backdrop.highlight.Highlight +import com.kyant.backdrop.highlight.HighlightStyle import com.kyant.backdrop.shadow.Shadow import com.kyant.shapes.RoundedRectangle import androidx.compose.foundation.border @@ -591,7 +592,12 @@ fun LiquidGlassPopupOverlay( ) } }, - highlight = { Highlight.Plain.copy(alpha = 0.58f) }, + highlight = { + Highlight( + alpha = 0.60f, + style = HighlightStyle.Default(angle = 180f, falloff = 1.1f), + ) + }, shadow = { Shadow(alpha = 0.18f) }, onDrawSurface = { drawRect(surface) }, ) diff --git a/app/src/main/kotlin/com/example/myapplication/ui/design/LumaDesignSystem.kt b/app/src/main/kotlin/com/example/myapplication/ui/design/LumaDesignSystem.kt index 87a8b10..df1b293 100644 --- a/app/src/main/kotlin/com/example/myapplication/ui/design/LumaDesignSystem.kt +++ b/app/src/main/kotlin/com/example/myapplication/ui/design/LumaDesignSystem.kt @@ -17,6 +17,9 @@ import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp @@ -33,6 +36,8 @@ import com.kyant.backdrop.effects.colorControls import com.kyant.backdrop.effects.lens import com.kyant.backdrop.effects.vibrancy import com.kyant.backdrop.highlight.Highlight +import com.kyant.backdrop.highlight.HighlightStyle +import com.kyant.shapes.Capsule import com.kyant.backdrop.shadow.Shadow import com.kyant.shapes.RoundedRectangle @@ -174,6 +179,25 @@ object LumaColors { get() = if (isDark) Color.White.copy(alpha = 0.07f) else Color.White.copy(alpha = 0.24f) val glassShadow: Color get() = if (isDark) Color.Black.copy(alpha = 0.32f) else Color(0xFF344054).copy(alpha = 0.14f) + /** Sweep-gradient border for iOS 26 liquid glass edge effect */ + val glassBorderGradient: Brush + get() = if (isDark) { + Brush.sweepGradient( + 0.00f to Color.White.copy(alpha = 0.35f), + 0.25f to Color.White.copy(alpha = 0.10f), + 0.50f to Color.White.copy(alpha = 0.20f), + 0.75f to Color.White.copy(alpha = 0.10f), + 1.00f to Color.White.copy(alpha = 0.35f), + ) + } else { + Brush.sweepGradient( + 0.00f to Color.White.copy(alpha = 0.65f), + 0.25f to Color.White.copy(alpha = 0.18f), + 0.50f to Color.White.copy(alpha = 0.35f), + 0.75f to Color.White.copy(alpha = 0.18f), + 1.00f to Color.White.copy(alpha = 0.65f), + ) + } // ── Settings popup colors ── val settingsPopupBg: Color get() = if (isDark) Color(0xFF111821) else Color(0xFFF4F7FA) @@ -192,28 +216,34 @@ fun Modifier.lumaFallbackGlass( val fill = when { liquidFallback && light && selected -> Brush.verticalGradient( listOf( - Color.White.copy(alpha = 0.46f), - Color(0xFFE5EFFA).copy(alpha = 0.38f), - Color.White.copy(alpha = 0.28f), + Color.White.copy(alpha = 0.50f), + Color(0xFFE0ECF8).copy(alpha = 0.42f), + Color(0xFFD4E2F0).copy(alpha = 0.36f), + Color(0xFFE8F0F8).copy(alpha = 0.32f), + Color.White.copy(alpha = 0.26f), ), ) liquidFallback && light -> Brush.verticalGradient( listOf( - Color.White.copy(alpha = 0.32f), - Color(0xFFE4EDF7).copy(alpha = 0.27f), - Color.White.copy(alpha = 0.20f), + Color.White.copy(alpha = 0.36f), + Color(0xFFE4EDF7).copy(alpha = 0.30f), + Color(0xFFD8E4EF).copy(alpha = 0.26f), + Color(0xFFE2EBF4).copy(alpha = 0.28f), + Color.White.copy(alpha = 0.18f), ), ) liquidFallback && selected -> Brush.verticalGradient( listOf( - Color(0xFF60758A).copy(alpha = 0.70f), - Color(0xFF304253).copy(alpha = 0.64f), + Color(0xFF657B92).copy(alpha = 0.74f), + Color(0xFF405870).copy(alpha = 0.68f), + Color(0xFF2D4258).copy(alpha = 0.62f), ), ) liquidFallback -> Brush.verticalGradient( listOf( - Color(0xFF44586C).copy(alpha = 0.60f), - Color(0xFF233140).copy(alpha = 0.66f), + Color(0xFF4A6278).copy(alpha = 0.64f), + Color(0xFF2E4254).copy(alpha = 0.58f), + Color(0xFF1E3040).copy(alpha = 0.52f), ), ) light && selected -> Brush.verticalGradient( @@ -262,25 +292,38 @@ fun Modifier.lumaFallbackGlass( }, ) } - val topHighlight = if (light) { - Color.White.copy( - alpha = when { - liquidFallback && selected -> 0.34f - liquidFallback -> 0.24f - selected -> 0.28f - else -> 0.18f - }, - ) + // Pre-compute highlight alphas (size not available outside DrawScope) + val highlightTop = if (light) { + when { + liquidFallback && selected -> 0.50f + liquidFallback -> 0.38f + selected -> 0.42f + else -> 0.30f + } } else { - Color.White.copy( - alpha = when { - liquidFallback && selected -> 0.14f - liquidFallback -> 0.08f - selected -> 0.10f - else -> 0.06f - }, - ) + when { + liquidFallback && selected -> 0.22f + liquidFallback -> 0.14f + selected -> 0.16f + else -> 0.10f + } } + val highlightMid = if (light) { + when { + liquidFallback && selected -> 0.12f + liquidFallback -> 0.08f + selected -> 0.10f + else -> 0.06f + } + } else { + when { + liquidFallback && selected -> 0.06f + liquidFallback -> 0.04f + selected -> 0.05f + else -> 0.02f + } + } + val highlightEndFraction = if (light) 0.55f else 0.50f return this .shadow( elevation = if (selected) 2.dp else 4.dp, @@ -294,16 +337,17 @@ fun Modifier.lumaFallbackGlass( neutralVeil?.let { Modifier.background(it, shape) } ?: Modifier, ) .drawWithCache { - val highlight = Brush.verticalGradient( - listOf(topHighlight, Color.Transparent), + val topHighlight = Brush.verticalGradient( + colors = listOf( + Color.White.copy(alpha = highlightTop), + Color.White.copy(alpha = highlightMid), + Color.Transparent, + ), startY = 0f, - endY = size.height * 0.72f, + endY = size.height * highlightEndFraction, ) onDrawWithContent { - // The fallback surface is a material layer. Its highlight must - // sit behind the content; drawing it after drawContent makes - // labels and icons look washed out on devices without Backdrop. - drawRect(highlight) + drawRect(topHighlight) drawContent() } } @@ -311,6 +355,68 @@ fun Modifier.lumaFallbackGlass( .clip(shape) } +/** + * iOS 26 liquid glass gradient border. + * Uses [LumaColors.glassBorderGradient] to draw a sweep-gradient stroke + * that simulates light refraction around the glass surface edge. + */ +fun Modifier.liquidBorder( + shape: Shape, + strokeWidth: Dp = 0.8.dp, + gradientBrush: Brush = LumaColors.glassBorderGradient, +): Modifier = this.then( + Modifier.drawWithCache { + val strokePx = strokeWidth.toPx() + // Compute corner radii by probing the shape at each corner + val cornerRadii = when (shape) { + is Capsule -> floatArrayOf(size.minDimension / 2f) + is androidx.compose.foundation.shape.RoundedCornerShape -> floatArrayOf( + shape.topStart.toPx(size, this@drawWithCache), + shape.topEnd.toPx(size, this@drawWithCache), + shape.bottomEnd.toPx(size, this@drawWithCache), + shape.bottomStart.toPx(size, this@drawWithCache), + ) + else -> floatArrayOf(0f) + } + val inset = strokePx / 2f + val roundedRect = when { + cornerRadii.size == 1 -> { + val r = cornerRadii[0] + androidx.compose.ui.geometry.RoundRect( + inset, inset, + size.width - inset, size.height - inset, + CornerRadius(r, r), + ) + } + cornerRadii.size == 4 -> { + androidx.compose.ui.geometry.RoundRect( + left = inset, top = inset, + right = size.width - inset, bottom = size.height - inset, + topLeftCornerRadius = CornerRadius(cornerRadii[0], cornerRadii[0]), + topRightCornerRadius = CornerRadius(cornerRadii[1], cornerRadii[1]), + bottomRightCornerRadius = CornerRadius(cornerRadii[2], cornerRadii[2]), + bottomLeftCornerRadius = CornerRadius(cornerRadii[3], cornerRadii[3]), + ) + } + else -> { + androidx.compose.ui.geometry.RoundRect( + inset, inset, + size.width - inset, size.height - inset, + ) + } + } + onDrawBehind { + drawRoundRect( + brush = gradientBrush, + topLeft = androidx.compose.ui.geometry.Offset(roundedRect.left, roundedRect.top), + size = androidx.compose.ui.geometry.Size(roundedRect.width, roundedRect.height), + cornerRadius = CornerRadius(cornerRadii.getOrElse(0) { 0f }), + style = Stroke(width = strokePx, cap = StrokeCap.Round), + ) + } + } +) + @Immutable enum class LumaGlassLevel { Clear, @@ -382,7 +488,13 @@ fun Modifier.lumaGlass( ) } }, - highlight = if (highlight) ({ Highlight.Plain.copy(alpha = 0.58f) }) else null, + highlight = if (highlight) ({ + // iOS 26: directional gradient highlight for consistent glass depth + Highlight( + alpha = 0.62f, + style = HighlightStyle.Default(angle = 180f, falloff = 1.1f), + ) + }) else null, shadow = { Shadow(alpha = spec.shadowAlpha) }, onDrawSurface = { drawRect(surfaceTint) }, ) diff --git a/app/src/main/kotlin/com/example/myapplication/ui/liquidglass/DampedDragAnimation.kt b/app/src/main/kotlin/com/example/myapplication/ui/liquidglass/DampedDragAnimation.kt index 9c012ae..5712b5a 100644 --- a/app/src/main/kotlin/com/example/myapplication/ui/liquidglass/DampedDragAnimation.kt +++ b/app/src/main/kotlin/com/example/myapplication/ui/liquidglass/DampedDragAnimation.kt @@ -28,16 +28,17 @@ class DampedDragAnimation( val onDrag: DampedDragAnimation.(size: IntSize, dragAmount: Offset) -> Unit, ) { + // iOS 26 liquid glass: more fluid, bouncy spring physics private val valueAnimationSpec = - spring(1f, 1000f, visibilityThreshold) + spring(0.65f, 260f, visibilityThreshold) private val velocityAnimationSpec = - spring(0.5f, 300f, visibilityThreshold * 10f) + spring(0.55f, 240f, visibilityThreshold * 10f) private val pressProgressAnimationSpec = - spring(1f, 1000f, 0.001f) + spring(0.7f, 280f, 0.001f) private val scaleXAnimationSpec = - spring(0.6f, 250f, 0.001f) + spring(0.55f, 200f, 0.001f) private val scaleYAnimationSpec = - spring(0.7f, 250f, 0.001f) + spring(0.6f, 200f, 0.001f) private val valueAnimation = Animatable(initialValue, visibilityThreshold) diff --git a/app/src/main/kotlin/com/example/myapplication/ui/liquidglass/InteractiveHighlight.kt b/app/src/main/kotlin/com/example/myapplication/ui/liquidglass/InteractiveHighlight.kt index 654e03a..f087370 100644 --- a/app/src/main/kotlin/com/example/myapplication/ui/liquidglass/InteractiveHighlight.kt +++ b/app/src/main/kotlin/com/example/myapplication/ui/liquidglass/InteractiveHighlight.kt @@ -39,7 +39,19 @@ class InteractiveHighlight( Modifier.drawWithContent { val progress = pressProgressAnimation.value if (progress > 0f) { + // iOS 26: subtle ambient glow + directional top gradient drawRect(Color.White.copy(0.08f * progress), blendMode = BlendMode.Plus) + // Top specular line for glass thickness illusion + val topGlow = Brush.verticalGradient( + listOf( + Color.White.copy(alpha = 0.12f * progress), + Color.Transparent, + ), + startY = 0f, + endY = size.height * 0.40f, + ) + drawRect(topGlow, blendMode = BlendMode.Plus) + // Touch-following radial highlight val center = position(size, positionAnimation.value).let { Offset( it.x.fastCoerceIn(0f, size.width), @@ -49,7 +61,7 @@ class InteractiveHighlight( drawCircle( brush = Brush.radialGradient( colors = listOf( - Color.White.copy(alpha = 0.15f * progress), + Color.White.copy(alpha = 0.18f * progress), Color.Transparent, ), center = center, diff --git a/app/src/main/kotlin/com/example/myapplication/ui/liquidglass/LiquidBottomTabs.kt b/app/src/main/kotlin/com/example/myapplication/ui/liquidglass/LiquidBottomTabs.kt index 23c0478..528a66e 100644 --- a/app/src/main/kotlin/com/example/myapplication/ui/liquidglass/LiquidBottomTabs.kt +++ b/app/src/main/kotlin/com/example/myapplication/ui/liquidglass/LiquidBottomTabs.kt @@ -28,6 +28,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalDensity @@ -45,6 +46,7 @@ import com.example.myapplication.ui.design.LumaColors import com.example.myapplication.ui.design.LumaGlassRuntime import com.example.myapplication.ui.design.LumaSpacing import com.example.myapplication.ui.design.lumaFallbackGlass +import com.example.myapplication.ui.design.liquidBorder import com.kyant.backdrop.Backdrop import com.kyant.backdrop.backdrops.layerBackdrop import com.kyant.backdrop.backdrops.rememberCombinedBackdrop @@ -54,6 +56,7 @@ import com.kyant.backdrop.effects.blur import com.kyant.backdrop.effects.lens import com.kyant.backdrop.effects.vibrancy import com.kyant.backdrop.highlight.Highlight +import com.kyant.backdrop.highlight.HighlightStyle import com.kyant.backdrop.shadow.InnerShadow import com.kyant.backdrop.shadow.Shadow import com.kyant.shapes.Capsule @@ -136,7 +139,8 @@ fun LiquidBottomTabs( currentIndex = targetIndex animateToValue(targetIndex.toFloat()) animationScope.launch { - offsetAnimation.animateTo(0f, spring(1f, 300f, 0.5f)) + // iOS 26: softer spring for smoother panel offset recovery + offsetAnimation.animateTo(0f, spring(0.6f, 250f, 0.5f)) } }, onDrag = { _, dragAmount -> @@ -234,6 +238,13 @@ fun LiquidBottomTabs( blur(8f.dp.toPx()) lens(24f.dp.toPx(), 24f.dp.toPx()) }, + highlight = { + // iOS 26: stronger top-down directional gradient highlight + Highlight( + alpha = 0.72f, + style = HighlightStyle.Default(angle = 180f, falloff = 1.2f), + ) + }, layerBlock = { val progress = dampedDragAnimation.pressProgress val scale = lerp(1f, 1f + 16f.dp.toPx() / size.width, progress) @@ -243,7 +254,7 @@ fun LiquidBottomTabs( onDrawSurface = { drawRect(containerColor) }, ) .then(if (pressHighlight) interactiveHighlight.modifier else Modifier) - .border(0.6.dp, LumaColors.glassBorder.copy(alpha = 0.72f), Capsule()) + .liquidBorder(Capsule(), strokeWidth = 0.8.dp) .height(height) .then(interactiveHighlight.gestureModifier) .then(dampedDragAnimation.modifier) @@ -278,7 +289,11 @@ fun LiquidBottomTabs( lens(24f.dp.toPx() * progress, 24f.dp.toPx() * progress) }, highlight = { - Highlight.Default.copy(alpha = dampedDragAnimation.pressProgress * pressHighlightAlpha) + // Ghost layer: subtle directional highlight for depth + Highlight( + alpha = dampedDragAnimation.pressProgress * pressHighlightAlpha * 0.5f, + style = HighlightStyle.Default(angle = 180f, falloff = 1.3f), + ) }, onDrawSurface = { drawRect(containerColor) }, ) @@ -307,37 +322,56 @@ fun LiquidBottomTabs( shape = { Capsule() }, effects = { val progress = dampedDragAnimation.pressProgress + // Static lens for persistent depth + press-enhanced chromatic aberration lens( - 10f.dp.toPx() * progress, - 14f.dp.toPx() * progress, + (6f + 10f * progress).dp.toPx(), + (8f + 8f * progress).dp.toPx(), chromaticAberration = true, ) }, highlight = { - Highlight.Default.copy(alpha = dampedDragAnimation.pressProgress * pressHighlightAlpha) + // iOS 26: directional gradient highlight with stronger top-down light + Highlight( + width = 0.8f.dp, + alpha = (0.15f + 0.55f * dampedDragAnimation.pressProgress) * pressHighlightAlpha, + style = HighlightStyle.Default(angle = 180f, falloff = 1.5f), + ) }, - shadow = { Shadow(alpha = dampedDragAnimation.pressProgress) }, + shadow = { Shadow(alpha = dampedDragAnimation.pressProgress * 0.9f) }, innerShadow = { val progress = dampedDragAnimation.pressProgress - InnerShadow(radius = 8.dp * progress, alpha = progress) + InnerShadow(radius = 8.dp * progress, alpha = progress * 0.85f) }, layerBlock = { scaleX = dampedDragAnimation.scaleX scaleY = dampedDragAnimation.scaleY + // iOS 26: enhanced velocity-based liquid deformation val velocity = dampedDragAnimation.velocity / 10f - scaleX /= 1f - (velocity * 0.75f).fastCoerceIn(-0.2f, 0.2f) - scaleY *= 1f - (velocity * 0.25f).fastCoerceIn(-0.2f, 0.2f) + scaleX /= 1f - (velocity * 0.85f).fastCoerceIn(-0.22f, 0.22f) + scaleY *= 1f - (velocity * 0.30f).fastCoerceIn(-0.22f, 0.22f) }, onDrawSurface = { val progress = dampedDragAnimation.pressProgress + // Base tint drawRect( if (isLightTheme) Color.Black.copy(alpha = 0.10f) else Color.White.copy(alpha = 0.10f), alpha = 1f - progress, ) drawRect(Color.Black.copy(alpha = 0.03f * progress)) + // iOS 26: top-edge specular line for glass thickness illusion + val specular = Brush.verticalGradient( + listOf( + Color.White.copy(alpha = 0.12f * (1f - progress)), + Color.Transparent, + ), + startY = 0f, + endY = size.height * 0.35f, + ) + drawRect(specular) }, ) + .liquidBorder(Capsule(), strokeWidth = 0.6.dp) .height(height - 8.dp) .fillMaxWidth(1f / tabsCount), )