Remove haze
This commit is contained in:
@@ -1,9 +1,5 @@
|
||||
package dev.ulfrx.recipe.di
|
||||
|
||||
import com.russhwolf.settings.Settings
|
||||
import dev.ulfrx.recipe.ui.components.glass.GlassBackend
|
||||
import dev.ulfrx.recipe.ui.components.glass.isDebugBuild
|
||||
import dev.ulfrx.recipe.ui.components.glass.resolveGlassBackend
|
||||
import dev.ulfrx.recipe.ui.screens.pantry.PantrySearchViewModel
|
||||
import dev.ulfrx.recipe.ui.screens.pantry.PantryViewModel
|
||||
import dev.ulfrx.recipe.ui.screens.planner.PlannerViewModel
|
||||
@@ -14,37 +10,8 @@ import dev.ulfrx.recipe.ui.screens.shopping.ShoppingViewModel
|
||||
import org.koin.dsl.module
|
||||
import org.koin.plugin.module.dsl.viewModel
|
||||
|
||||
/**
|
||||
* Phase 2.1 (UI-03 / UI-04 / UI-09 / UI-10) — DI module for the app-shell layer.
|
||||
*
|
||||
* Registers:
|
||||
* - 4 tab ViewModels (Planner / Recipes / Pantry / Shopping) — pure StateFlow,
|
||||
* no dependencies this phase. Phase 5+ extends each to inject repositories.
|
||||
* - 2 Search ViewModels (Recipes + Pantry) — pure StateFlow with nullable
|
||||
* `searchSource: SearchSource? = null` per RESEARCH § Pattern 4.
|
||||
* - 1 ShellViewModel — active-tab + search-open state machine.
|
||||
* - 1 GlassBackend single — resolved at composition root from
|
||||
* [resolveGlassBackend] (CONTEXT D-16 / D-17). Default backend is
|
||||
* [GlassBackend.Liquid] — the iOS+Android primary path; debug builds may
|
||||
* pick up a runtime override stored in `multiplatform-settings`.
|
||||
*
|
||||
* Settings binding: registered in platform-specific Koin modules
|
||||
* (`auth/IosAuthModule.kt`, `auth/AndroidAuthModule.kt`) for use by
|
||||
* SecureAuthStateStore — the same single<Settings> binding is reused here.
|
||||
*/
|
||||
val shellModule =
|
||||
module {
|
||||
// Glass backend — resolved once at startup. Production builds short-circuit
|
||||
// [resolveGlassBackend] via [isDebugBuild] = false; debug builds may pick up
|
||||
// a runtime override stored in `multiplatform-settings`.
|
||||
single<GlassBackend> {
|
||||
resolveGlassBackend(
|
||||
settings = get<Settings>(),
|
||||
isDebug = isDebugBuild,
|
||||
default = GlassBackend.Liquid,
|
||||
)
|
||||
}
|
||||
|
||||
// Shell-level state machine.
|
||||
viewModel<ShellViewModel>()
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ import recipe.composeapp.generated.resources.search_close_a11y
|
||||
* [animateContentSize] (size) + [AnimatedContent] (content swap) at 250ms with
|
||||
* [FastOutSlowInEasing] per UI-SPEC line 198.
|
||||
*
|
||||
* Substrate: [GlassSurface] from plan 02.1-03 — direct Liquid/Haze API calls are
|
||||
* Substrate: [GlassSurface] from plan 02.1-03 — direct Liquid API calls are
|
||||
* forbidden here per CLAUDE.md non-negotiable #10.
|
||||
*
|
||||
* Touch targets: each tab cell + collapsed toggle is ≥ 44dp (UI-SPEC line 52, 224).
|
||||
|
||||
@@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.composables.icons.lucide.Lucide
|
||||
import com.composables.icons.lucide.Search
|
||||
@@ -30,10 +31,9 @@ import recipe.composeapp.generated.resources.search_open_a11y
|
||||
*/
|
||||
@Composable
|
||||
fun FloatingSearchButton(
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit = {},
|
||||
) {
|
||||
val a11y = stringResource(Res.string.search_open_a11y)
|
||||
GlassSurface(
|
||||
modifier = modifier.size(56.dp),
|
||||
cornerRadius = 28.dp,
|
||||
@@ -49,7 +49,7 @@ fun FloatingSearchButton(
|
||||
) {
|
||||
UnstyledIcon(
|
||||
imageVector = Lucide.Search,
|
||||
contentDescription = a11y,
|
||||
contentDescription = stringResource(Res.string.search_open_a11y),
|
||||
tint = RecipeTheme.colors.content,
|
||||
modifier = Modifier.size(24.dp),
|
||||
)
|
||||
@@ -57,3 +57,4 @@ fun FloatingSearchButton(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
package dev.ulfrx.recipe.ui.components.glass
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.Dp
|
||||
|
||||
/**
|
||||
* Flat translucent fallback with no blur. Geometry matches Liquid/Haze so
|
||||
* chrome call sites never branch on the active backend.
|
||||
*/
|
||||
@Composable
|
||||
internal fun FlatGlassSurface(
|
||||
modifier: Modifier,
|
||||
tint: Color,
|
||||
cornerRadius: Dp,
|
||||
border: BorderStroke?,
|
||||
content: @Composable BoxScope.() -> Unit,
|
||||
) {
|
||||
val shape = RoundedCornerShape(cornerRadius)
|
||||
Box(
|
||||
modifier =
|
||||
modifier
|
||||
.clip(shape)
|
||||
.background(tint, shape)
|
||||
.let { if (border != null) it.border(border, shape) else it },
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
@@ -13,13 +13,12 @@ import androidx.compose.ui.Modifier
|
||||
* Shared source/sampling state for glass chrome.
|
||||
*
|
||||
* AppShell wraps the screen body in [GlassBackdropSource]. GlassSurface backends
|
||||
* consume [LocalGlassBackdropState] so Liquid/Haze sample the same layer behind
|
||||
* consume [LocalGlassBackdropState] so Liquid sample the same layer behind
|
||||
* the dock/search chrome.
|
||||
*/
|
||||
@Stable
|
||||
class GlassBackdropState internal constructor(
|
||||
internal val liquidState: Any,
|
||||
internal val hazeState: Any,
|
||||
)
|
||||
|
||||
val LocalGlassBackdropState = compositionLocalOf<GlassBackdropState?> { null }
|
||||
@@ -27,11 +26,9 @@ val LocalGlassBackdropState = compositionLocalOf<GlassBackdropState?> { null }
|
||||
@Composable
|
||||
fun rememberGlassBackdropState(): GlassBackdropState {
|
||||
val liquidState = rememberLiquidBackdropHandle()
|
||||
val hazeState = rememberHazeBackdropHandle()
|
||||
return remember(liquidState, hazeState) {
|
||||
return remember(liquidState) {
|
||||
GlassBackdropState(
|
||||
liquidState = liquidState,
|
||||
hazeState = hazeState,
|
||||
liquidState = liquidState
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -46,8 +43,7 @@ fun GlassBackdropSource(
|
||||
Box(
|
||||
modifier =
|
||||
modifier
|
||||
.liquidBackdropSource(state)
|
||||
.hazeBackdropSource(state),
|
||||
.liquidBackdropSource(state),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
package dev.ulfrx.recipe.ui.components.glass
|
||||
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import com.russhwolf.settings.Settings
|
||||
|
||||
/**
|
||||
* Three glass-effect backends per CONTEXT D-16. All three consume the same
|
||||
* token API so chrome call sites never branch on the active backend.
|
||||
*/
|
||||
enum class GlassBackend {
|
||||
Liquid,
|
||||
Haze,
|
||||
Flat,
|
||||
}
|
||||
|
||||
/**
|
||||
* Composition root sets this to the resolved backend for the running build.
|
||||
* Consumers outside a provider fail safe to the simplest visible substrate.
|
||||
*/
|
||||
val LocalGlassBackend = compositionLocalOf { GlassBackend.Flat }
|
||||
|
||||
/**
|
||||
* Debug-only runtime override key (D-17). Values: "liquid", "haze", "flat".
|
||||
*/
|
||||
const val DEBUG_GLASS_BACKEND_KEY: String = "debug.glass_backend"
|
||||
|
||||
/**
|
||||
* Pure backend resolver used by production code and common tests.
|
||||
*
|
||||
* Release builds return [default] without consulting settings, so production
|
||||
* binaries do not carry a runtime backend switch.
|
||||
*/
|
||||
fun resolveGlassBackend(
|
||||
settings: Settings,
|
||||
isDebug: Boolean,
|
||||
default: GlassBackend,
|
||||
): GlassBackend {
|
||||
if (!isDebug) return default
|
||||
val raw = settings.getStringOrNull(DEBUG_GLASS_BACKEND_KEY) ?: return default
|
||||
return when (raw.lowercase()) {
|
||||
"liquid" -> GlassBackend.Liquid
|
||||
"haze" -> GlassBackend.Haze
|
||||
"flat" -> GlassBackend.Flat
|
||||
else -> default
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,6 @@ import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.ulfrx.recipe.ui.theme.RecipeTheme
|
||||
|
||||
/**
|
||||
* Single public entry point for glass-effect chrome. Dispatches to one backend
|
||||
* through [LocalGlassBackend] and consumes the shared backdrop source when one
|
||||
* is present above it.
|
||||
*/
|
||||
@Composable
|
||||
fun GlassSurface(
|
||||
modifier: Modifier = Modifier,
|
||||
@@ -23,9 +18,5 @@ fun GlassSurface(
|
||||
content: @Composable BoxScope.() -> Unit,
|
||||
) {
|
||||
val backdropState = LocalGlassBackdropState.current
|
||||
when (LocalGlassBackend.current) {
|
||||
GlassBackend.Liquid -> LiquidGlassSurface(modifier, tint, cornerRadius, border, backdropState, content)
|
||||
GlassBackend.Haze -> HazeGlassSurface(modifier, tint, cornerRadius, border, backdropState, content)
|
||||
GlassBackend.Flat -> FlatGlassSurface(modifier, tint, cornerRadius, border, content)
|
||||
}
|
||||
LiquidGlassSurface(modifier, tint, cornerRadius, border, backdropState, content)
|
||||
}
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
package dev.ulfrx.recipe.ui.components.glass
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.chrisbanes.haze.HazeState
|
||||
import dev.chrisbanes.haze.HazeStyle
|
||||
import dev.chrisbanes.haze.HazeTint
|
||||
import dev.chrisbanes.haze.hazeEffect
|
||||
import dev.chrisbanes.haze.hazeSource
|
||||
import dev.chrisbanes.haze.rememberHazeState
|
||||
|
||||
/**
|
||||
* Haze 1.x backend per CONTEXT D-16. The actual 1.6.10 API takes a
|
||||
* HazeStyle/block instead of a shape parameter, so shape is enforced by the
|
||||
* surrounding clip while the effect consumes the shared [HazeState].
|
||||
*/
|
||||
@Composable
|
||||
internal fun HazeGlassSurface(
|
||||
modifier: Modifier,
|
||||
tint: Color,
|
||||
cornerRadius: Dp,
|
||||
border: BorderStroke?,
|
||||
backdropState: GlassBackdropState?,
|
||||
content: @Composable BoxScope.() -> Unit,
|
||||
) {
|
||||
val state = backdropState?.hazeState as? HazeState ?: rememberHazeState()
|
||||
val shape = RoundedCornerShape(cornerRadius)
|
||||
val style =
|
||||
HazeStyle(
|
||||
backgroundColor = tint.copy(alpha = 1f),
|
||||
tint = HazeTint(tint),
|
||||
blurRadius = 24.dp,
|
||||
)
|
||||
Box(
|
||||
modifier =
|
||||
modifier
|
||||
.clip(shape)
|
||||
.hazeEffect(state, style)
|
||||
.background(tint, shape)
|
||||
.let { if (border != null) it.border(border, shape) else it },
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun rememberHazeBackdropHandle(): Any = rememberHazeState()
|
||||
|
||||
internal fun Modifier.hazeBackdropSource(state: GlassBackdropState): Modifier = hazeSource(state.hazeState as HazeState)
|
||||
@@ -1,7 +0,0 @@
|
||||
package dev.ulfrx.recipe.ui.components.glass
|
||||
|
||||
/**
|
||||
* Compile-time gate for the [resolveGlassBackend] runtime override path
|
||||
* (CONTEXT D-17).
|
||||
*/
|
||||
expect val isDebugBuild: Boolean
|
||||
@@ -111,10 +111,6 @@ fun SearchPill(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal helper — placeholder text rendered when the BasicTextField is empty.
|
||||
* Plain text in [RecipeTheme.typography.body] tinted [RecipeTheme.colors.contentMuted].
|
||||
*/
|
||||
@Composable
|
||||
private fun PlaceholderText(
|
||||
text: String,
|
||||
|
||||
@@ -61,7 +61,7 @@ import recipe.composeapp.generated.resources.search_dismiss_keyboard_a11y
|
||||
* Layout responsibilities:
|
||||
* - Background: full-screen [RecipeTheme.colors.background] under the safe area.
|
||||
* - Body: [RootNavHost] consumes the full screen, wrapped in [GlassBackdropSource]
|
||||
* so Liquid/Haze chrome sample the screen body through [LocalGlassBackdropState].
|
||||
* so Liquid chrome samples the screen body through [LocalGlassBackdropState].
|
||||
* - Bottom chrome (overlay): bottom-anchored Column containing optional [SearchPill]
|
||||
* (when ui.searchOpen && active.hasSearch) and the [DockBar] (always visible).
|
||||
* Chrome consumes [WindowInsets.navigationBars] + [imePadding] explicitly per
|
||||
@@ -137,7 +137,7 @@ fun AppShell(modifier: Modifier = Modifier) {
|
||||
.background(RecipeTheme.colors.background),
|
||||
) {
|
||||
// Body — RootNavHost fills the available space and is the shared source layer
|
||||
// for Liquid/Haze chrome sampling via GlassBackdropSource (plan 02.1-03).
|
||||
// for Liquid chrome sampling via GlassBackdropSource (plan 02.1-03).
|
||||
GlassBackdropSource(modifier = Modifier.fillMaxSize()) {
|
||||
RootNavHost(
|
||||
navController = navController,
|
||||
|
||||
@@ -5,9 +5,6 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.ProvidableCompositionLocal
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import dev.ulfrx.recipe.ui.components.glass.GlassBackend
|
||||
import dev.ulfrx.recipe.ui.components.glass.LocalGlassBackend
|
||||
import org.koin.compose.koinInject
|
||||
|
||||
/**
|
||||
* Recipe theme entry point (CONTEXT D-14, D-15).
|
||||
@@ -35,7 +32,6 @@ public val LocalRecipeGlass: ProvidableCompositionLocal<RecipeGlass> =
|
||||
public fun RecipeTheme(content: @Composable () -> Unit) {
|
||||
val dark = isSystemInDarkTheme()
|
||||
val recipeColors = if (dark) DarkRecipeColors else LightRecipeColors
|
||||
val glassBackend = koinInject<GlassBackend>()
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalRecipeColors provides recipeColors,
|
||||
@@ -43,7 +39,6 @@ public fun RecipeTheme(content: @Composable () -> Unit) {
|
||||
LocalRecipeSpacing provides DefaultRecipeSpacing,
|
||||
LocalRecipeShapes provides DefaultRecipeShapes,
|
||||
LocalRecipeGlass provides DefaultRecipeGlass,
|
||||
LocalGlassBackend provides glassBackend,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user