小优化
This commit is contained in:
1 parent
391797d377
commit
dd1c62f0f0
7 files changed
+296
-104
No files matched your search
@@ -316,7 +316,7 @@ fun MainApp() {
|
||||
},
|
||||
) {
|
||||
Box(
|
||||
Modifier.fillMaxWidth().padding(bottom = 12.dp),
|
||||
Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.BottomCenter,
|
||||
) {
|
||||
LiquidAppBottomBar(
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) },
|
||||
)
|
||||
|
||||
@@ -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) },
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
+13
-1
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user