mirror of
https://github.com/KRTirtho/spotube.git
synced 2026-11-04 10:13:59 +00:00
feat(ui): replace Scaffold with BaseScaffold for consistent surface theming across screens
This commit is contained in:
parent
4da5b25a5c
commit
9f96b07ebd
@ -44,6 +44,14 @@
|
|||||||
<string name="settings_theme_dark">Dark</string>
|
<string name="settings_theme_dark">Dark</string>
|
||||||
<string name="settings_theme_system">System</string>
|
<string name="settings_theme_system">System</string>
|
||||||
|
|
||||||
|
<string name="settings_surface_theme_title">Surface</string>
|
||||||
|
<string name="settings_surface_theme_subtitle_current">Background material of the app. Current: %1$s</string>
|
||||||
|
<string name="settings_surface_watery">Watery</string>
|
||||||
|
<string name="settings_surface_icy">Icy</string>
|
||||||
|
<string name="settings_surface_chrome">Chrome</string>
|
||||||
|
<string name="settings_surface_acrylic">Acrylic</string>
|
||||||
|
<string name="settings_surface_glass">Glass</string>
|
||||||
|
|
||||||
<string name="settings_accent_title">Accent color</string>
|
<string name="settings_accent_title">Accent color</string>
|
||||||
<string name="settings_accent_subtitle_current">Current palette: %1$s</string>
|
<string name="settings_accent_subtitle_current">Current palette: %1$s</string>
|
||||||
<string name="settings_accent_dialog_title">Accent color</string>
|
<string name="settings_accent_dialog_title">Accent color</string>
|
||||||
|
|||||||
@ -35,6 +35,7 @@ import dev.krtirtho.spotube.core.navigation.Routes
|
|||||||
import dev.krtirtho.spotube.core.navigation.TOP_LEVEL_ROUTES
|
import dev.krtirtho.spotube.core.navigation.TOP_LEVEL_ROUTES
|
||||||
import dev.krtirtho.spotube.core.navigation.rememberNavigationState
|
import dev.krtirtho.spotube.core.navigation.rememberNavigationState
|
||||||
import dev.krtirtho.spotube.core.navigation.toEntries
|
import dev.krtirtho.spotube.core.navigation.toEntries
|
||||||
|
import dev.krtirtho.spotube.core.ui.base.LocalBaseUIColors
|
||||||
import dev.krtirtho.spotube.core.ui.base.LocalBaseUITheme
|
import dev.krtirtho.spotube.core.ui.base.LocalBaseUITheme
|
||||||
import dev.krtirtho.spotube.core.ui.base.rememberBaseUITheme
|
import dev.krtirtho.spotube.core.ui.base.rememberBaseUITheme
|
||||||
import dev.krtirtho.spotube.core.ui.component.LocalSharedTransitionScope
|
import dev.krtirtho.spotube.core.ui.component.LocalSharedTransitionScope
|
||||||
@ -156,8 +157,11 @@ fun App(
|
|||||||
}
|
}
|
||||||
|
|
||||||
SpotubeTheme(settings = userSettings) {
|
SpotubeTheme(settings = userSettings) {
|
||||||
val baseUITheme = rememberBaseUITheme()
|
val baseUITheme = rememberBaseUITheme(surfaceTheme = userSettings.surfaceTheme)
|
||||||
CompositionLocalProvider(LocalBaseUITheme provides baseUITheme) {
|
CompositionLocalProvider(
|
||||||
|
LocalBaseUITheme provides baseUITheme,
|
||||||
|
LocalBaseUIColors provides baseUITheme.colors,
|
||||||
|
) {
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
val currentRoute =
|
val currentRoute =
|
||||||
navigationState.backStacks[navigationState.topLevelRoute]?.last()
|
navigationState.backStacks[navigationState.topLevelRoute]?.last()
|
||||||
|
|||||||
@ -0,0 +1,302 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2026 Kingkor Roy Tirtho and Spotube Contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.krtirtho.spotube.core.ui.base
|
||||||
|
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.lerp
|
||||||
|
import androidx.compose.ui.graphics.luminance
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The app's own colour system, independent of [MaterialTheme].
|
||||||
|
*
|
||||||
|
* Each [SurfaceStyle] is a complete visual theme with its own palette (backgrounds,
|
||||||
|
* surfaces, text and outlines) rather than Material's role colours. The user's chosen
|
||||||
|
* accent is threaded through as `primary`, but everything else comes from the surface
|
||||||
|
* theme, so the app never has to rely on Material's colour scheme.
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
data class BaseUIColorScheme(
|
||||||
|
val surfaceTheme: SurfaceStyle,
|
||||||
|
val isLight: Boolean,
|
||||||
|
/** Full-bleed page background for the current surface theme. */
|
||||||
|
val background: Brush,
|
||||||
|
val surface: Color,
|
||||||
|
val surfaceVariant: Color,
|
||||||
|
val surfaceContainer: Color,
|
||||||
|
val surfaceContainerHigh: Color,
|
||||||
|
val surfaceContainerHighest: Color,
|
||||||
|
val onBackground: Color,
|
||||||
|
val onSurface: Color,
|
||||||
|
val onSurfaceVariant: Color,
|
||||||
|
val primary: Color,
|
||||||
|
val onPrimary: Color,
|
||||||
|
val primaryContainer: Color,
|
||||||
|
val onPrimaryContainer: Color,
|
||||||
|
val secondaryContainer: Color,
|
||||||
|
val onSecondaryContainer: Color,
|
||||||
|
val outline: Color,
|
||||||
|
val outlineVariant: Color,
|
||||||
|
val error: Color,
|
||||||
|
val onError: Color,
|
||||||
|
val scrim: Color,
|
||||||
|
)
|
||||||
|
|
||||||
|
val LocalBaseUIColors = staticCompositionLocalOf<BaseUIColorScheme> {
|
||||||
|
error("No BaseUIColorScheme provided. Wrap your UI in a BaseUITheme provider.")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the [BaseUIColorScheme] for the selected [surfaceTheme] and accent.
|
||||||
|
*
|
||||||
|
* Light/dark is read from the surrounding [MaterialTheme] (which the app already drives
|
||||||
|
* from the user's theme setting), and the accent comes from the user's chosen accent
|
||||||
|
* colour. Everything else is derived from the surface theme palette.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun rememberBaseUIColorScheme(
|
||||||
|
surfaceTheme: SurfaceStyle = SurfaceStyle.Glass,
|
||||||
|
accentColor: Color? = null,
|
||||||
|
): BaseUIColorScheme {
|
||||||
|
val isLight = MaterialTheme.colorScheme.surface.luminance() > 0.5f
|
||||||
|
val accent = accentColor ?: MaterialTheme.colorScheme.primary
|
||||||
|
return remember(surfaceTheme, isLight, accent) {
|
||||||
|
baseUIColorScheme(surfaceTheme, isLight, accent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun baseUIColorScheme(
|
||||||
|
surfaceTheme: SurfaceStyle,
|
||||||
|
isLight: Boolean,
|
||||||
|
accent: Color,
|
||||||
|
): BaseUIColorScheme {
|
||||||
|
val palette = surfacePalette(surfaceTheme, isLight)
|
||||||
|
val onPrimary = if (accent.luminance() > 0.5f) Color(0xFF10161C) else Color.White
|
||||||
|
val primaryContainer = lerp(palette.surface, accent, if (isLight) 0.16f else 0.32f)
|
||||||
|
val onPrimaryContainer = if (isLight) {
|
||||||
|
lerp(accent, Color.Black, 0.55f)
|
||||||
|
} else {
|
||||||
|
lerp(accent, Color.White, 0.7f)
|
||||||
|
}
|
||||||
|
val secondaryContainer = lerp(palette.surface, accent, if (isLight) 0.22f else 0.34f)
|
||||||
|
|
||||||
|
return BaseUIColorScheme(
|
||||||
|
surfaceTheme = surfaceTheme,
|
||||||
|
isLight = isLight,
|
||||||
|
background = Brush.verticalGradient(
|
||||||
|
listOf(palette.backgroundTop, palette.backgroundBottom),
|
||||||
|
),
|
||||||
|
surface = palette.surface,
|
||||||
|
surfaceVariant = palette.surfaceVariant,
|
||||||
|
surfaceContainer = palette.surfaceContainer,
|
||||||
|
surfaceContainerHigh = palette.surfaceContainerHigh,
|
||||||
|
surfaceContainerHighest = palette.surfaceContainerHighest,
|
||||||
|
onBackground = palette.onBackground,
|
||||||
|
onSurface = palette.onSurface,
|
||||||
|
onSurfaceVariant = palette.onSurfaceVariant,
|
||||||
|
primary = accent,
|
||||||
|
onPrimary = onPrimary,
|
||||||
|
primaryContainer = primaryContainer,
|
||||||
|
onPrimaryContainer = onPrimaryContainer,
|
||||||
|
secondaryContainer = secondaryContainer,
|
||||||
|
onSecondaryContainer = palette.onSurface,
|
||||||
|
outline = palette.outline,
|
||||||
|
outlineVariant = palette.outlineVariant,
|
||||||
|
error = if (isLight) Color(0xFFC0392B) else Color(0xFFE57373),
|
||||||
|
onError = Color.White,
|
||||||
|
scrim = Color.Black.copy(alpha = 0.42f),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SurfacePalette(
|
||||||
|
val backgroundTop: Color,
|
||||||
|
val backgroundBottom: Color,
|
||||||
|
val surface: Color,
|
||||||
|
val surfaceVariant: Color,
|
||||||
|
val surfaceContainer: Color,
|
||||||
|
val surfaceContainerHigh: Color,
|
||||||
|
val surfaceContainerHighest: Color,
|
||||||
|
val onBackground: Color,
|
||||||
|
val onSurface: Color,
|
||||||
|
val onSurfaceVariant: Color,
|
||||||
|
val outline: Color,
|
||||||
|
val outlineVariant: Color,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun surfacePalette(style: SurfaceStyle, isLight: Boolean): SurfacePalette {
|
||||||
|
return if (isLight) {
|
||||||
|
when (style) {
|
||||||
|
SurfaceStyle.Glass -> SurfacePalette(
|
||||||
|
backgroundTop = Color(0xFFEAF0F7),
|
||||||
|
backgroundBottom = Color(0xFFD8E3F1),
|
||||||
|
surface = Color(0xFFF8FBFF),
|
||||||
|
surfaceVariant = Color(0xFFE4ECF5),
|
||||||
|
surfaceContainer = Color(0xFFEEF3F9),
|
||||||
|
surfaceContainerHigh = Color(0xFFF3F7FC),
|
||||||
|
surfaceContainerHighest = Color(0xFFE1E9F3),
|
||||||
|
onBackground = Color(0xFF1B2733),
|
||||||
|
onSurface = Color(0xFF1B2733),
|
||||||
|
onSurfaceVariant = Color(0xFF56677A),
|
||||||
|
outline = Color(0xFFA9B8C8),
|
||||||
|
outlineVariant = Color(0xFFD3DDE8),
|
||||||
|
)
|
||||||
|
|
||||||
|
SurfaceStyle.Acrylic -> SurfacePalette(
|
||||||
|
backgroundTop = Color(0xFFE6EAF0),
|
||||||
|
backgroundBottom = Color(0xFFD6DCE5),
|
||||||
|
surface = Color(0xFFF4F7FA),
|
||||||
|
surfaceVariant = Color(0xFFE1E7EE),
|
||||||
|
surfaceContainer = Color(0xFFEAEEF4),
|
||||||
|
surfaceContainerHigh = Color(0xFFF1F4F8),
|
||||||
|
surfaceContainerHighest = Color(0xFFDCE2E9),
|
||||||
|
onBackground = Color(0xFF1C222B),
|
||||||
|
onSurface = Color(0xFF1C222B),
|
||||||
|
onSurfaceVariant = Color(0xFF545E6C),
|
||||||
|
outline = Color(0xFFA6AEBC),
|
||||||
|
outlineVariant = Color(0xFFD2D8E0),
|
||||||
|
)
|
||||||
|
|
||||||
|
SurfaceStyle.Watery -> SurfacePalette(
|
||||||
|
backgroundTop = Color(0xFFD6F0FF),
|
||||||
|
backgroundBottom = Color(0xFFB4DFFA),
|
||||||
|
surface = Color(0xFFF0FAFF),
|
||||||
|
surfaceVariant = Color(0xFFDCF0FC),
|
||||||
|
surfaceContainer = Color(0xFFE7F5FE),
|
||||||
|
surfaceContainerHigh = Color(0xFFEFF9FF),
|
||||||
|
surfaceContainerHighest = Color(0xFFCFE9F8),
|
||||||
|
onBackground = Color(0xFF08324A),
|
||||||
|
onSurface = Color(0xFF08324A),
|
||||||
|
onSurfaceVariant = Color(0xFF3A6884),
|
||||||
|
outline = Color(0xFF8FBBD4),
|
||||||
|
outlineVariant = Color(0xFFC6E2F2),
|
||||||
|
)
|
||||||
|
|
||||||
|
SurfaceStyle.Icy -> SurfacePalette(
|
||||||
|
backgroundTop = Color(0xFFE8F7FF),
|
||||||
|
backgroundBottom = Color(0xFFC9E8FB),
|
||||||
|
surface = Color(0xFFF6FCFF),
|
||||||
|
surfaceVariant = Color(0xFFE0F1FC),
|
||||||
|
surfaceContainer = Color(0xFFEBF7FE),
|
||||||
|
surfaceContainerHigh = Color(0xFFF3FBFF),
|
||||||
|
surfaceContainerHighest = Color(0xFFD4EBFA),
|
||||||
|
onBackground = Color(0xFF0B3A55),
|
||||||
|
onSurface = Color(0xFF0B3A55),
|
||||||
|
onSurfaceVariant = Color(0xFF3D6C88),
|
||||||
|
outline = Color(0xFF93C0D9),
|
||||||
|
outlineVariant = Color(0xFFC9E5F4),
|
||||||
|
)
|
||||||
|
|
||||||
|
SurfaceStyle.Chrome -> SurfacePalette(
|
||||||
|
backgroundTop = Color(0xFFE4E8ED),
|
||||||
|
backgroundBottom = Color(0xFFD0D6DE),
|
||||||
|
surface = Color(0xFFF4F7FA),
|
||||||
|
surfaceVariant = Color(0xFFE0E5EB),
|
||||||
|
surfaceContainer = Color(0xFFE9EDF2),
|
||||||
|
surfaceContainerHigh = Color(0xFFF1F4F7),
|
||||||
|
surfaceContainerHighest = Color(0xFFDBE0E7),
|
||||||
|
onBackground = Color(0xFF1B2733),
|
||||||
|
onSurface = Color(0xFF1B2733),
|
||||||
|
onSurfaceVariant = Color(0xFF525C6A),
|
||||||
|
outline = Color(0xFFA2AAB6),
|
||||||
|
outlineVariant = Color(0xFFD0D5DC),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
when (style) {
|
||||||
|
SurfaceStyle.Glass -> SurfacePalette(
|
||||||
|
backgroundTop = Color(0xFF0E1217),
|
||||||
|
backgroundBottom = Color(0xFF161C24),
|
||||||
|
surface = Color(0xFF1E242C),
|
||||||
|
surfaceVariant = Color(0xFF262E38),
|
||||||
|
surfaceContainer = Color(0xFF1A2027),
|
||||||
|
surfaceContainerHigh = Color(0xFF232A33),
|
||||||
|
surfaceContainerHighest = Color(0xFF2C343E),
|
||||||
|
onBackground = Color(0xFFE6EDF5),
|
||||||
|
onSurface = Color(0xFFE6EDF5),
|
||||||
|
onSurfaceVariant = Color(0xFF9AA7B6),
|
||||||
|
outline = Color(0xFF46525F),
|
||||||
|
outlineVariant = Color(0xFF2C343E),
|
||||||
|
)
|
||||||
|
|
||||||
|
SurfaceStyle.Acrylic -> SurfacePalette(
|
||||||
|
backgroundTop = Color(0xFF101317),
|
||||||
|
backgroundBottom = Color(0xFF181C22),
|
||||||
|
surface = Color(0xFF20262E),
|
||||||
|
surfaceVariant = Color(0xFF282F39),
|
||||||
|
surfaceContainer = Color(0xFF1C2229),
|
||||||
|
surfaceContainerHigh = Color(0xFF252C35),
|
||||||
|
surfaceContainerHighest = Color(0xFF2E3640),
|
||||||
|
onBackground = Color(0xFFE4EAF1),
|
||||||
|
onSurface = Color(0xFFE4EAF1),
|
||||||
|
onSurfaceVariant = Color(0xFF98A2B0),
|
||||||
|
outline = Color(0xFF48525E),
|
||||||
|
outlineVariant = Color(0xFF2E3640),
|
||||||
|
)
|
||||||
|
|
||||||
|
SurfaceStyle.Watery -> SurfacePalette(
|
||||||
|
backgroundTop = Color(0xFF06121E),
|
||||||
|
backgroundBottom = Color(0xFF0B1F31),
|
||||||
|
surface = Color(0xFF0E2233),
|
||||||
|
surfaceVariant = Color(0xFF153148),
|
||||||
|
surfaceContainer = Color(0xFF0B1D2C),
|
||||||
|
surfaceContainerHigh = Color(0xFF12293C),
|
||||||
|
surfaceContainerHighest = Color(0xFF183449),
|
||||||
|
onBackground = Color(0xFFD6EAF7),
|
||||||
|
onSurface = Color(0xFFD6EAF7),
|
||||||
|
onSurfaceVariant = Color(0xFF8FB0C6),
|
||||||
|
outline = Color(0xFF3C5A72),
|
||||||
|
outlineVariant = Color(0xFF183449),
|
||||||
|
)
|
||||||
|
|
||||||
|
SurfaceStyle.Icy -> SurfacePalette(
|
||||||
|
backgroundTop = Color(0xFF0C1620),
|
||||||
|
backgroundBottom = Color(0xFF132331),
|
||||||
|
surface = Color(0xFF16283A),
|
||||||
|
surfaceVariant = Color(0xFF1E3448),
|
||||||
|
surfaceContainer = Color(0xFF122130),
|
||||||
|
surfaceContainerHigh = Color(0xFF1A2E42),
|
||||||
|
surfaceContainerHighest = Color(0xFF213A50),
|
||||||
|
onBackground = Color(0xFFDCEBF7),
|
||||||
|
onSurface = Color(0xFFDCEBF7),
|
||||||
|
onSurfaceVariant = Color(0xFF92B3CA),
|
||||||
|
outline = Color(0xFF40607A),
|
||||||
|
outlineVariant = Color(0xFF213A50),
|
||||||
|
)
|
||||||
|
|
||||||
|
SurfaceStyle.Chrome -> SurfacePalette(
|
||||||
|
backgroundTop = Color(0xFF101317),
|
||||||
|
backgroundBottom = Color(0xFF181C22),
|
||||||
|
surface = Color(0xFF232830),
|
||||||
|
surfaceVariant = Color(0xFF2B313A),
|
||||||
|
surfaceContainer = Color(0xFF1E232A),
|
||||||
|
surfaceContainerHigh = Color(0xFF272D36),
|
||||||
|
surfaceContainerHighest = Color(0xFF303742),
|
||||||
|
onBackground = Color(0xFFE7ECF2),
|
||||||
|
onSurface = Color(0xFFE7ECF2),
|
||||||
|
onSurfaceVariant = Color(0xFF9BA5B2),
|
||||||
|
outline = Color(0xFF4B5561),
|
||||||
|
outlineVariant = Color(0xFF303742),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -48,15 +48,20 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||||||
@Composable
|
@Composable
|
||||||
internal fun BaseUIPreview(
|
internal fun BaseUIPreview(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
surfaceTheme: SurfaceStyle = SurfaceStyle.Glass,
|
||||||
content: @Composable BoxScope.() -> Unit,
|
content: @Composable BoxScope.() -> Unit,
|
||||||
) {
|
) {
|
||||||
MaterialTheme(
|
MaterialTheme(
|
||||||
colorScheme = if (isSystemInDarkTheme()) darkColorScheme() else lightColorScheme(),
|
colorScheme = if (isSystemInDarkTheme()) darkColorScheme() else lightColorScheme(),
|
||||||
) {
|
) {
|
||||||
CompositionLocalProvider(LocalBaseUITheme provides rememberBaseUITheme()) {
|
val theme = rememberBaseUITheme(surfaceTheme = surfaceTheme)
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalBaseUITheme provides theme,
|
||||||
|
LocalBaseUIColors provides theme.colors,
|
||||||
|
) {
|
||||||
Surface(
|
Surface(
|
||||||
modifier = modifier.fillMaxSize(),
|
modifier = modifier.fillMaxSize(),
|
||||||
color = MaterialTheme.colorScheme.background,
|
color = theme.colors.surface,
|
||||||
) {
|
) {
|
||||||
Box(modifier = Modifier.fillMaxSize(), content = content)
|
Box(modifier = Modifier.fillMaxSize(), content = content)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2026 Kingkor Roy Tirtho and Spotube Contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package dev.krtirtho.spotube.core.ui.base
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.ScaffoldDefaults
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A theme-aware replacement for [Scaffold].
|
||||||
|
*
|
||||||
|
* Material's [Scaffold] paints Material's own surface colour, which hides the selected
|
||||||
|
* [SurfaceStyle] (Watery/Icy/Chrome/Acrylic/Glass). [BaseScaffold] instead:
|
||||||
|
*
|
||||||
|
* - is **transparent by default**, so the app-wide themed backdrop shows through, or
|
||||||
|
* - paints a chosen [surface] material locally, or
|
||||||
|
* - uses an explicit [containerColor] when one is given.
|
||||||
|
*
|
||||||
|
* It keeps the same slot structure as [Scaffold] (top/bottom bars, snackbar host,
|
||||||
|
* floating action button) and hands the resolved [PaddingValues] to [content].
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun BaseScaffold(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
topBar: @Composable () -> Unit = {},
|
||||||
|
bottomBar: @Composable () -> Unit = {},
|
||||||
|
snackbarHost: @Composable () -> Unit = {},
|
||||||
|
floatingActionButton: @Composable () -> Unit = {},
|
||||||
|
surface: SurfaceStyle? = null,
|
||||||
|
containerColor: Color? = null,
|
||||||
|
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
|
||||||
|
content: @Composable (PaddingValues) -> Unit,
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
modifier = modifier,
|
||||||
|
topBar = topBar,
|
||||||
|
bottomBar = bottomBar,
|
||||||
|
snackbarHost = snackbarHost,
|
||||||
|
floatingActionButton = floatingActionButton,
|
||||||
|
containerColor = Color.Transparent,
|
||||||
|
contentWindowInsets = contentWindowInsets,
|
||||||
|
) { innerPadding ->
|
||||||
|
val backgroundModifier = when {
|
||||||
|
containerColor != null -> Modifier.background(containerColor)
|
||||||
|
surface != null -> {
|
||||||
|
val isLight = LocalBaseUIColors.current.isLight
|
||||||
|
Modifier.baseSurfaceBackdrop(surface, isLight)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> Modifier
|
||||||
|
}
|
||||||
|
Box(modifier = Modifier.fillMaxSize().then(backgroundModifier)) {
|
||||||
|
content(innerPadding)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@BaseUIPhonePreview
|
||||||
|
@BaseUIDesktopPreview
|
||||||
|
@Composable
|
||||||
|
private fun BaseScaffoldPreview() {
|
||||||
|
BaseUIPreview {
|
||||||
|
val style = LocalBaseUITheme.current.surfaceTheme
|
||||||
|
BaseScaffold(
|
||||||
|
surface = style,
|
||||||
|
topBar = {
|
||||||
|
Text(
|
||||||
|
text = "Title",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
bottomBar = {
|
||||||
|
Text(
|
||||||
|
text = "Bottom bar",
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { innerPadding ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(innerPadding)
|
||||||
|
.padding(16.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
Text("Content")
|
||||||
|
Text("Sits on the selected surface theme.", style = MaterialTheme.typography.bodySmall)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -29,6 +29,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@ -52,6 +53,7 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Frutiger-Aero material families available to [BaseSurface].
|
* The Frutiger-Aero material families available to [BaseSurface].
|
||||||
@ -61,7 +63,10 @@ import kotlin.random.Random
|
|||||||
* - [Chrome]: brushed metal with a hard specular reflection.
|
* - [Chrome]: brushed metal with a hard specular reflection.
|
||||||
* - [Acrylic]: translucent tinted panel with fine grain.
|
* - [Acrylic]: translucent tinted panel with fine grain.
|
||||||
* - [Glass]: clear, glossy glass with a strong sheen.
|
* - [Glass]: clear, glossy glass with a strong sheen.
|
||||||
|
*
|
||||||
|
* Each style doubles as a full app theme (see [BaseUIColorScheme]).
|
||||||
*/
|
*/
|
||||||
|
@Serializable
|
||||||
enum class SurfaceStyle {
|
enum class SurfaceStyle {
|
||||||
Watery,
|
Watery,
|
||||||
Icy,
|
Icy,
|
||||||
@ -101,7 +106,7 @@ fun BaseSurface(
|
|||||||
val resolvedContentColor = contentColor ?: when (style) {
|
val resolvedContentColor = contentColor ?: when (style) {
|
||||||
SurfaceStyle.Watery -> Color.White
|
SurfaceStyle.Watery -> Color.White
|
||||||
SurfaceStyle.Icy -> if (isLight) Color(0xFF0B3A55) else Color(0xFFDCEBF7)
|
SurfaceStyle.Icy -> if (isLight) Color(0xFF0B3A55) else Color(0xFFDCEBF7)
|
||||||
SurfaceStyle.Chrome -> Color(0xFF1B2733)
|
SurfaceStyle.Chrome -> if (isLight) Color(0xFF1B2733) else Color(0xFFE7ECF2)
|
||||||
SurfaceStyle.Acrylic, SurfaceStyle.Glass -> onSurface
|
SurfaceStyle.Acrylic, SurfaceStyle.Glass -> onSurface
|
||||||
}
|
}
|
||||||
val ambient = when (style) {
|
val ambient = when (style) {
|
||||||
@ -167,11 +172,25 @@ fun BaseSurface(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paints the full-bleed material of [style] as a page background: the material body plus
|
||||||
|
* its own sheen and reflections, but no panel bevel (a backdrop has no edges).
|
||||||
|
*/
|
||||||
|
fun Modifier.baseSurfaceBackdrop(style: SurfaceStyle, isLight: Boolean): Modifier =
|
||||||
|
drawWithCache {
|
||||||
|
onDrawBehind { drawSurfaceBase(style, isLight) }
|
||||||
|
}
|
||||||
|
|
||||||
private fun DrawScope.drawSurfaceMaterial(style: SurfaceStyle, isLight: Boolean) {
|
private fun DrawScope.drawSurfaceMaterial(style: SurfaceStyle, isLight: Boolean) {
|
||||||
|
drawSurfaceBase(style, isLight)
|
||||||
|
drawInnerBevel(isLight)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DrawScope.drawSurfaceBase(style: SurfaceStyle, isLight: Boolean) {
|
||||||
when (style) {
|
when (style) {
|
||||||
SurfaceStyle.Watery -> drawWatery(isLight)
|
SurfaceStyle.Watery -> drawWatery(isLight)
|
||||||
SurfaceStyle.Icy -> drawIcy(isLight)
|
SurfaceStyle.Icy -> drawIcy(isLight)
|
||||||
SurfaceStyle.Chrome -> drawChrome()
|
SurfaceStyle.Chrome -> drawChrome(isLight)
|
||||||
SurfaceStyle.Acrylic -> drawAcrylic(isLight)
|
SurfaceStyle.Acrylic -> drawAcrylic(isLight)
|
||||||
SurfaceStyle.Glass -> drawGlass(isLight)
|
SurfaceStyle.Glass -> drawGlass(isLight)
|
||||||
}
|
}
|
||||||
@ -191,7 +210,6 @@ private fun DrawScope.drawGlass(isLight: Boolean) {
|
|||||||
)
|
)
|
||||||
drawGloss(if (isLight) 0.55f else 0.16f)
|
drawGloss(if (isLight) 0.55f else 0.16f)
|
||||||
drawBottomReflection(if (isLight) 0.4f else 0.1f)
|
drawBottomReflection(if (isLight) 0.4f else 0.1f)
|
||||||
drawInnerBevel(isLight)
|
|
||||||
drawBubbles(seed = 11, count = 3, alpha = if (isLight) 0.4f else 0.14f)
|
drawBubbles(seed = 11, count = 3, alpha = if (isLight) 0.4f else 0.14f)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,10 +240,10 @@ private fun DrawScope.drawAcrylic(isLight: Boolean) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
drawGloss(if (isLight) 0.4f else 0.12f, heightFraction = 0.45f)
|
drawGloss(if (isLight) 0.4f else 0.12f, heightFraction = 0.45f)
|
||||||
drawInnerBevel(isLight)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun DrawScope.drawChrome() {
|
private fun DrawScope.drawChrome(isLight: Boolean) {
|
||||||
|
if (isLight) {
|
||||||
drawRect(
|
drawRect(
|
||||||
brush = Brush.verticalGradient(
|
brush = Brush.verticalGradient(
|
||||||
0f to Color(0xFFF8FBFF),
|
0f to Color(0xFFF8FBFF),
|
||||||
@ -244,6 +262,26 @@ private fun DrawScope.drawChrome() {
|
|||||||
endY = size.height * 0.22f,
|
endY = size.height * 0.22f,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
// Dark gunmetal for the night variant.
|
||||||
|
drawRect(
|
||||||
|
brush = Brush.verticalGradient(
|
||||||
|
0f to Color(0xFF3B424C),
|
||||||
|
0.16f to Color(0xFF2A303A),
|
||||||
|
0.4f to Color(0xFF1B2028),
|
||||||
|
0.5f to Color(0xFF343B45),
|
||||||
|
0.62f to Color(0xFF232830),
|
||||||
|
1f to Color(0xFF141820),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
drawRect(
|
||||||
|
brush = Brush.verticalGradient(
|
||||||
|
colors = listOf(Color.White.copy(alpha = 0.18f), Color.Transparent),
|
||||||
|
startY = size.height * 0.05f,
|
||||||
|
endY = size.height * 0.22f,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
// Darkened lower edge for the metal bevel.
|
// Darkened lower edge for the metal bevel.
|
||||||
drawRect(
|
drawRect(
|
||||||
brush = Brush.verticalGradient(
|
brush = Brush.verticalGradient(
|
||||||
@ -463,3 +501,36 @@ private fun BaseSurfaceGridPreview() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@BaseUIPhonePreview
|
||||||
|
@BaseUIDesktopPreview
|
||||||
|
@Composable
|
||||||
|
private fun SurfaceThemeBackdropPreview() {
|
||||||
|
val isLight = LocalBaseUIColors.current.isLight
|
||||||
|
val onSurface = LocalBaseUIColors.current.onSurface
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(20.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
) {
|
||||||
|
SurfaceStyle.entries.forEach { style ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(66.dp)
|
||||||
|
.clip(RoundedCornerShape(14.dp))
|
||||||
|
.baseSurfaceBackdrop(style, isLight),
|
||||||
|
contentAlignment = Alignment.CenterStart,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = style.name,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
color = if (style == SurfaceStyle.Watery) Color.White else onSurface,
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -42,6 +42,8 @@ import dev.krtirtho.spotube.core.ui.base.BaseUITheme.TextFieldTheme
|
|||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
data class BaseUITheme(
|
data class BaseUITheme(
|
||||||
|
val colors: BaseUIColorScheme,
|
||||||
|
val surfaceTheme: SurfaceStyle,
|
||||||
val buttons: ButtonVariants,
|
val buttons: ButtonVariants,
|
||||||
val iconButtons: ButtonVariants,
|
val iconButtons: ButtonVariants,
|
||||||
val textField: TextFieldTheme,
|
val textField: TextFieldTheme,
|
||||||
@ -216,9 +218,12 @@ val LocalBaseUITheme = staticCompositionLocalOf<BaseUITheme> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun rememberBaseUITheme(): BaseUITheme {
|
fun rememberBaseUITheme(
|
||||||
val scheme = MaterialTheme.colorScheme
|
surfaceTheme: SurfaceStyle = SurfaceStyle.Glass,
|
||||||
val isLight = scheme.surface.luminance() > 0.5f
|
accentColor: Color? = null,
|
||||||
|
): BaseUITheme {
|
||||||
|
val colors = rememberBaseUIColorScheme(surfaceTheme, accentColor)
|
||||||
|
val isLight = colors.isLight
|
||||||
|
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
// Frutiger-Aero glass palette.
|
// Frutiger-Aero glass palette.
|
||||||
@ -230,28 +235,28 @@ fun rememberBaseUITheme(): BaseUITheme {
|
|||||||
val glassTop = if (isLight) {
|
val glassTop = if (isLight) {
|
||||||
Color.White
|
Color.White
|
||||||
} else {
|
} else {
|
||||||
lerp(scheme.surfaceContainerHigh, Color.White, 0.10f)
|
lerp(colors.surfaceContainerHigh, Color.White, 0.10f)
|
||||||
}
|
}
|
||||||
val glassBottom = if (isLight) {
|
val glassBottom = if (isLight) {
|
||||||
lerp(scheme.surface, Color.White, 0.04f)
|
lerp(colors.surface, Color.White, 0.04f)
|
||||||
} else {
|
} else {
|
||||||
scheme.surfaceContainer
|
colors.surfaceContainer
|
||||||
}
|
}
|
||||||
val glassPressed = if (isLight) {
|
val glassPressed = if (isLight) {
|
||||||
lerp(scheme.surfaceContainerHighest, Color.White, 0.06f)
|
lerp(colors.surfaceContainerHighest, Color.White, 0.06f)
|
||||||
} else {
|
} else {
|
||||||
lerp(scheme.surfaceContainerHighest, Color.Black, 0.08f)
|
lerp(colors.surfaceContainerHighest, Color.Black, 0.08f)
|
||||||
}
|
}
|
||||||
|
|
||||||
val gloss = if (isLight) Color.White.copy(alpha = 0.75f) else Color.White.copy(alpha = 0.18f)
|
val gloss = if (isLight) Color.White.copy(alpha = 0.75f) else Color.White.copy(alpha = 0.18f)
|
||||||
val glossSoft = if (isLight) Color.White.copy(alpha = 0.5f) else Color.White.copy(alpha = 0.12f)
|
val glossSoft = if (isLight) Color.White.copy(alpha = 0.5f) else Color.White.copy(alpha = 0.12f)
|
||||||
val glassEdge = if (isLight) Color.White.copy(alpha = 0.95f) else Color.White.copy(alpha = 0.22f)
|
val glassEdge = if (isLight) Color.White.copy(alpha = 0.95f) else Color.White.copy(alpha = 0.22f)
|
||||||
val glassBorder = if (isLight) {
|
val glassBorder = if (isLight) {
|
||||||
lerp(scheme.outlineVariant, Color.White, 0.55f)
|
lerp(colors.outlineVariant, Color.White, 0.55f)
|
||||||
} else {
|
} else {
|
||||||
scheme.outlineVariant.copy(alpha = 0.5f)
|
colors.outlineVariant.copy(alpha = 0.5f)
|
||||||
}
|
}
|
||||||
val shadowColor = scheme.onSurface
|
val shadowColor = colors.onSurface
|
||||||
|
|
||||||
fun glassBrush(top: Color, bottom: Color): Brush =
|
fun glassBrush(top: Color, bottom: Color): Brush =
|
||||||
Brush.verticalGradient(listOf(top, bottom))
|
Brush.verticalGradient(listOf(top, bottom))
|
||||||
@ -288,22 +293,22 @@ fun rememberBaseUITheme(): BaseUITheme {
|
|||||||
val outlineColors = BaseUITheme.InteractionState(
|
val outlineColors = BaseUITheme.InteractionState(
|
||||||
normal = BaseUITheme.ButtonColors(
|
normal = BaseUITheme.ButtonColors(
|
||||||
background = glassBrush(glassTop, glassBottom),
|
background = glassBrush(glassTop, glassBottom),
|
||||||
foreground = scheme.onSurface,
|
foreground = colors.onSurface,
|
||||||
highlight = gloss,
|
highlight = gloss,
|
||||||
),
|
),
|
||||||
hovered = BaseUITheme.ButtonColors(
|
hovered = BaseUITheme.ButtonColors(
|
||||||
background = glassBrush(glassTop, glassBottom),
|
background = glassBrush(glassTop, glassBottom),
|
||||||
foreground = scheme.onSurface,
|
foreground = colors.onSurface,
|
||||||
highlight = Color.White.copy(alpha = if (isLight) 0.9f else 0.26f),
|
highlight = Color.White.copy(alpha = if (isLight) 0.9f else 0.26f),
|
||||||
),
|
),
|
||||||
pressed = BaseUITheme.ButtonColors(
|
pressed = BaseUITheme.ButtonColors(
|
||||||
background = glassBrush(glassPressed, glassPressed),
|
background = glassBrush(glassPressed, glassPressed),
|
||||||
foreground = scheme.onSurface,
|
foreground = colors.onSurface,
|
||||||
highlight = glossSoft,
|
highlight = glossSoft,
|
||||||
),
|
),
|
||||||
focused = BaseUITheme.ButtonColors(
|
focused = BaseUITheme.ButtonColors(
|
||||||
background = glassBrush(glassTop, glassBottom),
|
background = glassBrush(glassTop, glassBottom),
|
||||||
foreground = scheme.onSurface,
|
foreground = colors.onSurface,
|
||||||
highlight = gloss,
|
highlight = gloss,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -341,48 +346,48 @@ fun rememberBaseUITheme(): BaseUITheme {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// --- Aqua accent (primary) -------------------------------------------
|
// --- Aqua accent (primary) -------------------------------------------
|
||||||
val primaryEdge = lerp(scheme.primary, Color.White, 0.5f)
|
val primaryEdge = lerp(colors.primary, Color.White, 0.5f)
|
||||||
val primaryColors = BaseUITheme.InteractionState(
|
val primaryColors = BaseUITheme.InteractionState(
|
||||||
normal = BaseUITheme.ButtonColors(aqua(scheme.primary), scheme.onPrimary, gloss),
|
normal = BaseUITheme.ButtonColors(aqua(colors.primary), colors.onPrimary, gloss),
|
||||||
hovered = BaseUITheme.ButtonColors(
|
hovered = BaseUITheme.ButtonColors(
|
||||||
aqua(scheme.primary),
|
aqua(colors.primary),
|
||||||
scheme.onPrimary,
|
colors.onPrimary,
|
||||||
Color.White.copy(alpha = if (isLight) 0.9f else 0.3f),
|
Color.White.copy(alpha = if (isLight) 0.9f else 0.3f),
|
||||||
),
|
),
|
||||||
pressed = BaseUITheme.ButtonColors(
|
pressed = BaseUITheme.ButtonColors(
|
||||||
glassBrush(
|
glassBrush(
|
||||||
lerp(scheme.primary, Color.Black, 0.16f),
|
lerp(colors.primary, Color.Black, 0.16f),
|
||||||
lerp(scheme.primary, Color.Black, 0.24f),
|
lerp(colors.primary, Color.Black, 0.24f),
|
||||||
),
|
),
|
||||||
scheme.onPrimary,
|
colors.onPrimary,
|
||||||
glossSoft,
|
glossSoft,
|
||||||
),
|
),
|
||||||
focused = BaseUITheme.ButtonColors(aqua(scheme.primary), scheme.onPrimary, gloss),
|
focused = BaseUITheme.ButtonColors(aqua(colors.primary), colors.onPrimary, gloss),
|
||||||
)
|
)
|
||||||
val primaryShadow = BaseUITheme.InteractionState(
|
val primaryShadow = BaseUITheme.InteractionState(
|
||||||
normal = BaseUITheme.Shadow(
|
normal = BaseUITheme.Shadow(
|
||||||
10.dp,
|
10.dp,
|
||||||
true,
|
true,
|
||||||
scheme.primary.copy(alpha = 0.35f),
|
colors.primary.copy(alpha = 0.35f),
|
||||||
scheme.primary.copy(alpha = 0.42f),
|
colors.primary.copy(alpha = 0.42f),
|
||||||
),
|
),
|
||||||
hovered = BaseUITheme.Shadow(
|
hovered = BaseUITheme.Shadow(
|
||||||
16.dp,
|
16.dp,
|
||||||
true,
|
true,
|
||||||
scheme.primary.copy(alpha = 0.45f),
|
colors.primary.copy(alpha = 0.45f),
|
||||||
scheme.primary.copy(alpha = 0.5f),
|
colors.primary.copy(alpha = 0.5f),
|
||||||
),
|
),
|
||||||
pressed = BaseUITheme.Shadow(
|
pressed = BaseUITheme.Shadow(
|
||||||
3.dp,
|
3.dp,
|
||||||
true,
|
true,
|
||||||
scheme.primary.copy(alpha = 0.22f),
|
colors.primary.copy(alpha = 0.22f),
|
||||||
scheme.primary.copy(alpha = 0.28f),
|
colors.primary.copy(alpha = 0.28f),
|
||||||
),
|
),
|
||||||
focused = BaseUITheme.Shadow(
|
focused = BaseUITheme.Shadow(
|
||||||
10.dp,
|
10.dp,
|
||||||
true,
|
true,
|
||||||
scheme.primary.copy(alpha = 0.35f),
|
colors.primary.copy(alpha = 0.35f),
|
||||||
scheme.primary.copy(alpha = 0.42f),
|
colors.primary.copy(alpha = 0.42f),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
val primaryBorder = BaseUITheme.InteractionState(
|
val primaryBorder = BaseUITheme.InteractionState(
|
||||||
@ -393,16 +398,16 @@ fun rememberBaseUITheme(): BaseUITheme {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// --- Secondary (soft aqua glass) -------------------------------------
|
// --- Secondary (soft aqua glass) -------------------------------------
|
||||||
val secondaryBase = scheme.secondaryContainer
|
val secondaryBase = colors.secondaryContainer
|
||||||
val secondaryColors = BaseUITheme.InteractionState(
|
val secondaryColors = BaseUITheme.InteractionState(
|
||||||
normal = BaseUITheme.ButtonColors(
|
normal = BaseUITheme.ButtonColors(
|
||||||
glassBrush(lerp(secondaryBase, Color.White, 0.5f), secondaryBase),
|
glassBrush(lerp(secondaryBase, Color.White, 0.5f), secondaryBase),
|
||||||
scheme.onSecondaryContainer,
|
colors.onSecondaryContainer,
|
||||||
gloss,
|
gloss,
|
||||||
),
|
),
|
||||||
hovered = BaseUITheme.ButtonColors(
|
hovered = BaseUITheme.ButtonColors(
|
||||||
glassBrush(lerp(secondaryBase, Color.White, 0.55f), secondaryBase),
|
glassBrush(lerp(secondaryBase, Color.White, 0.55f), secondaryBase),
|
||||||
scheme.onSecondaryContainer,
|
colors.onSecondaryContainer,
|
||||||
Color.White.copy(alpha = if (isLight) 0.85f else 0.24f),
|
Color.White.copy(alpha = if (isLight) 0.85f else 0.24f),
|
||||||
),
|
),
|
||||||
pressed = BaseUITheme.ButtonColors(
|
pressed = BaseUITheme.ButtonColors(
|
||||||
@ -410,12 +415,12 @@ fun rememberBaseUITheme(): BaseUITheme {
|
|||||||
lerp(secondaryBase, Color.Black, 0.08f),
|
lerp(secondaryBase, Color.Black, 0.08f),
|
||||||
lerp(secondaryBase, Color.Black, 0.14f),
|
lerp(secondaryBase, Color.Black, 0.14f),
|
||||||
),
|
),
|
||||||
scheme.onSecondaryContainer,
|
colors.onSecondaryContainer,
|
||||||
glossSoft,
|
glossSoft,
|
||||||
),
|
),
|
||||||
focused = BaseUITheme.ButtonColors(
|
focused = BaseUITheme.ButtonColors(
|
||||||
glassBrush(lerp(secondaryBase, Color.White, 0.5f), secondaryBase),
|
glassBrush(lerp(secondaryBase, Color.White, 0.5f), secondaryBase),
|
||||||
scheme.onSecondaryContainer,
|
colors.onSecondaryContainer,
|
||||||
gloss,
|
gloss,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -456,25 +461,25 @@ fun rememberBaseUITheme(): BaseUITheme {
|
|||||||
val ghostColors = BaseUITheme.InteractionState(
|
val ghostColors = BaseUITheme.InteractionState(
|
||||||
normal = BaseUITheme.ButtonColors(
|
normal = BaseUITheme.ButtonColors(
|
||||||
background = glassBrush(Color.Transparent, Color.Transparent),
|
background = glassBrush(Color.Transparent, Color.Transparent),
|
||||||
foreground = scheme.onSurface,
|
foreground = colors.onSurface,
|
||||||
highlight = Color.Transparent,
|
highlight = Color.Transparent,
|
||||||
),
|
),
|
||||||
hovered = BaseUITheme.ButtonColors(
|
hovered = BaseUITheme.ButtonColors(
|
||||||
background = glassBrush(
|
background = glassBrush(
|
||||||
lerp(scheme.surfaceContainerHigh, Color.White, 0.1f),
|
lerp(colors.surfaceContainerHigh, Color.White, 0.1f),
|
||||||
scheme.surfaceContainer,
|
colors.surfaceContainer,
|
||||||
),
|
),
|
||||||
foreground = scheme.onSurface,
|
foreground = colors.onSurface,
|
||||||
highlight = glossSoft,
|
highlight = glossSoft,
|
||||||
),
|
),
|
||||||
pressed = BaseUITheme.ButtonColors(
|
pressed = BaseUITheme.ButtonColors(
|
||||||
background = glassBrush(glassPressed, glassPressed),
|
background = glassBrush(glassPressed, glassPressed),
|
||||||
foreground = scheme.onSurface,
|
foreground = colors.onSurface,
|
||||||
highlight = Color.Transparent,
|
highlight = Color.Transparent,
|
||||||
),
|
),
|
||||||
focused = BaseUITheme.ButtonColors(
|
focused = BaseUITheme.ButtonColors(
|
||||||
background = glassBrush(Color.Transparent, Color.Transparent),
|
background = glassBrush(Color.Transparent, Color.Transparent),
|
||||||
foreground = scheme.onSurface,
|
foreground = colors.onSurface,
|
||||||
highlight = Color.Transparent,
|
highlight = Color.Transparent,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -538,10 +543,10 @@ fun rememberBaseUITheme(): BaseUITheme {
|
|||||||
)
|
)
|
||||||
|
|
||||||
val textFieldForeground = BaseUITheme.InteractionState(
|
val textFieldForeground = BaseUITheme.InteractionState(
|
||||||
normal = scheme.onSurface,
|
normal = colors.onSurface,
|
||||||
hovered = scheme.onSurface,
|
hovered = colors.onSurface,
|
||||||
pressed = scheme.onSurface,
|
pressed = colors.onSurface,
|
||||||
focused = scheme.onSurface,
|
focused = colors.onSurface,
|
||||||
)
|
)
|
||||||
|
|
||||||
val checkBoxShape = RoundedCornerShape(7.dp)
|
val checkBoxShape = RoundedCornerShape(7.dp)
|
||||||
@ -571,6 +576,8 @@ fun rememberBaseUITheme(): BaseUITheme {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return BaseUITheme(
|
return BaseUITheme(
|
||||||
|
colors = colors,
|
||||||
|
surfaceTheme = surfaceTheme,
|
||||||
buttons = BaseUITheme.ButtonVariants(
|
buttons = BaseUITheme.ButtonVariants(
|
||||||
primary = primaryStyle,
|
primary = primaryStyle,
|
||||||
secondary = secondaryStyle,
|
secondary = secondaryStyle,
|
||||||
@ -630,13 +637,13 @@ fun rememberBaseUITheme(): BaseUITheme {
|
|||||||
focused = BaseUITheme.Shadow(
|
focused = BaseUITheme.Shadow(
|
||||||
10.dp,
|
10.dp,
|
||||||
true,
|
true,
|
||||||
scheme.primary.copy(alpha = 0.22f),
|
colors.primary.copy(alpha = 0.22f),
|
||||||
scheme.primary.copy(alpha = 0.28f)
|
colors.primary.copy(alpha = 0.28f)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
padding = PaddingValues(horizontal = 16.dp, vertical = 12.dp),
|
padding = PaddingValues(horizontal = 16.dp, vertical = 12.dp),
|
||||||
textStyle = TextStyle.Default,
|
textStyle = TextStyle.Default,
|
||||||
cursor = SolidColor(scheme.primary),
|
cursor = SolidColor(colors.primary),
|
||||||
foreground = textFieldForeground,
|
foreground = textFieldForeground,
|
||||||
),
|
),
|
||||||
autoCompleteMenuTheme = AutoCompleteMenuTheme(
|
autoCompleteMenuTheme = AutoCompleteMenuTheme(
|
||||||
@ -648,10 +655,10 @@ fun rememberBaseUITheme(): BaseUITheme {
|
|||||||
),
|
),
|
||||||
slider = SliderTheme(
|
slider = SliderTheme(
|
||||||
trackActiveColor = BaseUITheme.InteractionState(
|
trackActiveColor = BaseUITheme.InteractionState(
|
||||||
normal = scheme.primary,
|
normal = colors.primary,
|
||||||
hovered = scheme.primary,
|
hovered = colors.primary,
|
||||||
pressed = scheme.primary,
|
pressed = colors.primary,
|
||||||
focused = scheme.primary,
|
focused = colors.primary,
|
||||||
),
|
),
|
||||||
trackInactiveColor = BaseUITheme.InteractionState(
|
trackInactiveColor = BaseUITheme.InteractionState(
|
||||||
normal = glassBorder,
|
normal = glassBorder,
|
||||||
@ -660,39 +667,39 @@ fun rememberBaseUITheme(): BaseUITheme {
|
|||||||
focused = glassBorder,
|
focused = glassBorder,
|
||||||
),
|
),
|
||||||
thumbColor = BaseUITheme.InteractionState(
|
thumbColor = BaseUITheme.InteractionState(
|
||||||
normal = scheme.primary,
|
normal = colors.primary,
|
||||||
hovered = scheme.primary,
|
hovered = colors.primary,
|
||||||
pressed = scheme.primary,
|
pressed = colors.primary,
|
||||||
focused = scheme.primary,
|
focused = colors.primary,
|
||||||
),
|
),
|
||||||
thumbShadow = BaseUITheme.InteractionState(
|
thumbShadow = BaseUITheme.InteractionState(
|
||||||
normal = BaseUITheme.Shadow(
|
normal = BaseUITheme.Shadow(
|
||||||
5.dp,
|
5.dp,
|
||||||
true,
|
true,
|
||||||
scheme.primary.copy(alpha = 0.4f),
|
colors.primary.copy(alpha = 0.4f),
|
||||||
scheme.primary.copy(alpha = 0.45f)
|
colors.primary.copy(alpha = 0.45f)
|
||||||
),
|
),
|
||||||
hovered = BaseUITheme.Shadow(
|
hovered = BaseUITheme.Shadow(
|
||||||
8.dp,
|
8.dp,
|
||||||
true,
|
true,
|
||||||
scheme.primary.copy(alpha = 0.45f),
|
colors.primary.copy(alpha = 0.45f),
|
||||||
scheme.primary.copy(alpha = 0.5f)
|
colors.primary.copy(alpha = 0.5f)
|
||||||
),
|
),
|
||||||
pressed = BaseUITheme.Shadow(
|
pressed = BaseUITheme.Shadow(
|
||||||
2.dp,
|
2.dp,
|
||||||
true,
|
true,
|
||||||
scheme.primary.copy(alpha = 0.25f),
|
colors.primary.copy(alpha = 0.25f),
|
||||||
scheme.primary.copy(alpha = 0.3f)
|
colors.primary.copy(alpha = 0.3f)
|
||||||
),
|
),
|
||||||
focused = BaseUITheme.Shadow(
|
focused = BaseUITheme.Shadow(
|
||||||
5.dp,
|
5.dp,
|
||||||
true,
|
true,
|
||||||
scheme.primary.copy(alpha = 0.4f),
|
colors.primary.copy(alpha = 0.4f),
|
||||||
scheme.primary.copy(alpha = 0.45f)
|
colors.primary.copy(alpha = 0.45f)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
stepActiveColor = Color.White.copy(alpha = 0.7f),
|
stepActiveColor = Color.White.copy(alpha = 0.7f),
|
||||||
stepInactiveColor = scheme.onSurface.copy(alpha = 0.25f),
|
stepInactiveColor = colors.onSurface.copy(alpha = 0.25f),
|
||||||
),
|
),
|
||||||
checkBox = CheckBoxTheme(
|
checkBox = CheckBoxTheme(
|
||||||
selected = BaseUITheme.ButtonStyle(
|
selected = BaseUITheme.ButtonStyle(
|
||||||
@ -709,7 +716,7 @@ fun rememberBaseUITheme(): BaseUITheme {
|
|||||||
border = glassBorderState,
|
border = glassBorderState,
|
||||||
padding = noPadding,
|
padding = noPadding,
|
||||||
),
|
),
|
||||||
checkmarkColor = scheme.onPrimary,
|
checkmarkColor = colors.onPrimary,
|
||||||
),
|
),
|
||||||
chipTab = ChipTabTheme(
|
chipTab = ChipTabTheme(
|
||||||
selected = BaseUITheme.ButtonStyle(
|
selected = BaseUITheme.ButtonStyle(
|
||||||
@ -794,8 +801,8 @@ fun rememberBaseUITheme(): BaseUITheme {
|
|||||||
pressed = glassBrush(glassPressed, glassPressed),
|
pressed = glassBrush(glassPressed, glassPressed),
|
||||||
focused = glassBrush(glassTop, glassBottom),
|
focused = glassBrush(glassTop, glassBottom),
|
||||||
selected = glassBrush(
|
selected = glassBrush(
|
||||||
lerp(scheme.primaryContainer, Color.White, 0.4f),
|
lerp(colors.primaryContainer, Color.White, 0.4f),
|
||||||
scheme.primaryContainer,
|
colors.primaryContainer,
|
||||||
),
|
),
|
||||||
disabled = glassBrush(
|
disabled = glassBrush(
|
||||||
glassTop.copy(alpha = 0.5f),
|
glassTop.copy(alpha = 0.5f),
|
||||||
@ -841,12 +848,12 @@ fun rememberBaseUITheme(): BaseUITheme {
|
|||||||
disabled = TextStyle.Default,
|
disabled = TextStyle.Default,
|
||||||
),
|
),
|
||||||
foreground = BaseUITheme.AdvancedInteractionState(
|
foreground = BaseUITheme.AdvancedInteractionState(
|
||||||
normal = scheme.onSurface,
|
normal = colors.onSurface,
|
||||||
hovered = scheme.onSurface,
|
hovered = colors.onSurface,
|
||||||
pressed = scheme.onSurface,
|
pressed = colors.onSurface,
|
||||||
focused = scheme.onSurface,
|
focused = colors.onSurface,
|
||||||
selected = scheme.onSurface,
|
selected = colors.onSurface,
|
||||||
disabled = scheme.onSurface.copy(alpha = 0.38f),
|
disabled = colors.onSurface.copy(alpha = 0.38f),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
dialog = BaseUITheme.DialogTheme(
|
dialog = BaseUITheme.DialogTheme(
|
||||||
@ -873,7 +880,7 @@ fun rememberBaseUITheme(): BaseUITheme {
|
|||||||
shadowColor.copy(alpha = 0.28f)
|
shadowColor.copy(alpha = 0.28f)
|
||||||
),
|
),
|
||||||
itemHoverBackground = glassBrush(glassTop, glassBottom),
|
itemHoverBackground = glassBrush(glassTop, glassBottom),
|
||||||
itemForeground = scheme.onSurface,
|
itemForeground = colors.onSurface,
|
||||||
itemHighlight = gloss,
|
itemHighlight = gloss,
|
||||||
padding = PaddingValues(6.dp),
|
padding = PaddingValues(6.dp),
|
||||||
itemPadding = PaddingValues(horizontal = 14.dp, vertical = 11.dp),
|
itemPadding = PaddingValues(horizontal = 14.dp, vertical = 11.dp),
|
||||||
|
|||||||
@ -44,7 +44,7 @@ import androidx.compose.material3.Icon
|
|||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
import androidx.compose.material3.Scaffold
|
import dev.krtirtho.spotube.core.ui.base.BaseScaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
|
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@ -327,7 +327,7 @@ private fun AdaptiveBottomSheetContent(
|
|||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
private fun AdaptiveDropdownBottomSheetPreview() {
|
private fun AdaptiveDropdownBottomSheetPreview() {
|
||||||
Scaffold {
|
BaseScaffold {
|
||||||
AdaptiveDropdownBottomSheet(
|
AdaptiveDropdownBottomSheet(
|
||||||
items = listOf(
|
items = listOf(
|
||||||
AdaptiveMenuItem(
|
AdaptiveMenuItem(
|
||||||
|
|||||||
@ -27,7 +27,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import dev.krtirtho.spotube.core.ui.base.BaseScaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
@ -103,7 +103,7 @@ fun CollectionView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
BaseScaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
ApplicationMainBar(
|
ApplicationMainBar(
|
||||||
backButton = true,
|
backButton = true,
|
||||||
|
|||||||
@ -37,7 +37,7 @@ import androidx.compose.material3.CardDefaults
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import dev.krtirtho.spotube.core.ui.base.BaseScaffold
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
@ -278,7 +278,7 @@ private fun ErrorDisplayPreview() {
|
|||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
private fun ErrorLogDialogPreview() {
|
private fun ErrorLogDialogPreview() {
|
||||||
Scaffold {
|
BaseScaffold {
|
||||||
ErrorLogDialog(
|
ErrorLogDialog(
|
||||||
errorMessage = "This is an error message",
|
errorMessage = "This is an error message",
|
||||||
onDismiss = {},
|
onDismiss = {},
|
||||||
|
|||||||
@ -48,7 +48,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
|||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import dev.krtirtho.spotube.core.ui.base.BaseScaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@ -1035,7 +1035,7 @@ fun previewTracks(): List<MetadataTrack> {
|
|||||||
@Composable
|
@Composable
|
||||||
@Preview
|
@Preview
|
||||||
private fun TrackListPreview() {
|
private fun TrackListPreview() {
|
||||||
Scaffold {
|
BaseScaffold {
|
||||||
TrackList(
|
TrackList(
|
||||||
tracks = previewTracks(),
|
tracks = previewTracks(),
|
||||||
hasMore = true,
|
hasMore = true,
|
||||||
|
|||||||
@ -38,7 +38,7 @@ import androidx.compose.foundation.shape.CircleShape
|
|||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import dev.krtirtho.spotube.core.ui.base.BaseScaffold
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@ -189,7 +189,7 @@ fun PlayableCard(
|
|||||||
@PreviewLightDark
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
private fun PlayableCardPreview() {
|
private fun PlayableCardPreview() {
|
||||||
Scaffold {
|
BaseScaffold {
|
||||||
PlayableCard(
|
PlayableCard(
|
||||||
title = "Sample Title",
|
title = "Sample Title",
|
||||||
subtitle = "Sample Subtitle",
|
subtitle = "Sample Subtitle",
|
||||||
|
|||||||
@ -34,7 +34,7 @@ import androidx.compose.foundation.text.BasicTextField
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import dev.krtirtho.spotube.core.ui.base.BaseScaffold
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@ -148,7 +148,7 @@ fun PlatformWebViewScreen(webViewController: WebViewController) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
BaseScaffold(
|
||||||
contentWindowInsets = WindowInsets.statusBars,
|
contentWindowInsets = WindowInsets.statusBars,
|
||||||
topBar = {
|
topBar = {
|
||||||
ApplicationMainBar(
|
ApplicationMainBar(
|
||||||
|
|||||||
@ -42,7 +42,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.PlainTooltip
|
import androidx.compose.material3.PlainTooltip
|
||||||
import androidx.compose.material3.Scaffold
|
import dev.krtirtho.spotube.core.ui.base.BaseScaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TooltipAnchorPosition
|
import androidx.compose.material3.TooltipAnchorPosition
|
||||||
import androidx.compose.material3.TooltipBox
|
import androidx.compose.material3.TooltipBox
|
||||||
@ -118,7 +118,7 @@ fun ArtistScreen(
|
|||||||
val trackOptionsContext by viewModel.trackOptionsContext.collectAsStateWithLifecycle()
|
val trackOptionsContext by viewModel.trackOptionsContext.collectAsStateWithLifecycle()
|
||||||
val showAddToPlaylistPicker by viewModel.showAddToPlaylistPicker.collectAsStateWithLifecycle()
|
val showAddToPlaylistPicker by viewModel.showAddToPlaylistPicker.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
Scaffold(
|
BaseScaffold(
|
||||||
topBar = { ApplicationMainBar() }
|
topBar = { ApplicationMainBar() }
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
when (val currentState = state) {
|
when (val currentState = state) {
|
||||||
|
|||||||
@ -33,7 +33,6 @@ import androidx.compose.foundation.lazy.items
|
|||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@ -51,6 +50,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import dev.krtirtho.plugin_interfaces.plugin_apis.metadata.artist.MetadataArtist
|
import dev.krtirtho.plugin_interfaces.plugin_apis.metadata.artist.MetadataArtist
|
||||||
import dev.krtirtho.plugin_interfaces.plugin_apis.metadata.track.MetadataTrack
|
import dev.krtirtho.plugin_interfaces.plugin_apis.metadata.track.MetadataTrack
|
||||||
|
import dev.krtirtho.spotube.core.ui.base.BaseScaffold
|
||||||
import dev.krtirtho.spotube.core.ui.base.GhostIconButton
|
import dev.krtirtho.spotube.core.ui.base.GhostIconButton
|
||||||
import dev.krtirtho.spotube.core.ui.base.TextField
|
import dev.krtirtho.spotube.core.ui.base.TextField
|
||||||
import dev.krtirtho.spotube.core.ui.component.ApplicationMainBar
|
import dev.krtirtho.spotube.core.ui.component.ApplicationMainBar
|
||||||
@ -67,7 +67,7 @@ import org.koin.compose.viewmodel.koinViewModel
|
|||||||
@Composable
|
@Composable
|
||||||
fun BlacklistScreen() {
|
fun BlacklistScreen() {
|
||||||
val viewModel = koinViewModel<BlacklistViewModel>()
|
val viewModel = koinViewModel<BlacklistViewModel>()
|
||||||
Scaffold(
|
BaseScaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
ApplicationMainBar(
|
ApplicationMainBar(
|
||||||
backButton = true,
|
backButton = true,
|
||||||
@ -83,7 +83,7 @@ fun BlacklistScreen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The blacklist UI without its own [Scaffold] / app bar, so it can be embedded
|
* The blacklist UI without its own [BaseScaffold] / app bar, so it can be embedded
|
||||||
* as a tab in the large-screen settings layout as well as hosted by [BlacklistScreen].
|
* as a tab in the large-screen settings layout as well as hosted by [BlacklistScreen].
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@ -33,7 +33,7 @@ import androidx.compose.material3.CircularProgressIndicator
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.Scaffold
|
import dev.krtirtho.spotube.core.ui.base.BaseScaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
@ -77,7 +77,7 @@ fun DevicesScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
BaseScaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
ApplicationMainBar(
|
ApplicationMainBar(
|
||||||
backButton = true,
|
backButton = true,
|
||||||
|
|||||||
@ -45,7 +45,7 @@ import androidx.compose.material3.DropdownMenuItem
|
|||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import dev.krtirtho.spotube.core.ui.base.BaseScaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@ -107,7 +107,7 @@ fun RemoteControlScreen(
|
|||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
contentAlignment = Alignment.TopCenter,
|
contentAlignment = Alignment.TopCenter,
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
BaseScaffold(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxHeight(),
|
.fillMaxHeight(),
|
||||||
topBar = {
|
topBar = {
|
||||||
|
|||||||
@ -35,7 +35,7 @@ import androidx.compose.material3.Icon
|
|||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LocalTextStyle
|
import androidx.compose.material3.LocalTextStyle
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import dev.krtirtho.spotube.core.ui.base.BaseScaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@ -120,7 +120,7 @@ fun HomeScreen(viewModel: HomeScreenViewModel) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
BaseScaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
ApplicationMainBar(
|
ApplicationMainBar(
|
||||||
backButton = false,
|
backButton = false,
|
||||||
|
|||||||
@ -32,7 +32,7 @@ import androidx.compose.material3.HorizontalDivider
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Scaffold
|
import dev.krtirtho.spotube.core.ui.base.BaseScaffold
|
||||||
import androidx.compose.material3.SegmentedButton
|
import androidx.compose.material3.SegmentedButton
|
||||||
import androidx.compose.material3.SegmentedButtonDefaults
|
import androidx.compose.material3.SegmentedButtonDefaults
|
||||||
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
||||||
@ -71,7 +71,7 @@ fun JamScreen(
|
|||||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
val shellBottomInset = LocalAppShellBottomInset.current
|
val shellBottomInset = LocalAppShellBottomInset.current
|
||||||
|
|
||||||
Scaffold(
|
BaseScaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
ApplicationMainBar(
|
ApplicationMainBar(
|
||||||
backButton = true,
|
backButton = true,
|
||||||
|
|||||||
@ -27,7 +27,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
|||||||
import androidx.compose.material3.FilterChip
|
import androidx.compose.material3.FilterChip
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.Scaffold
|
import dev.krtirtho.spotube.core.ui.base.BaseScaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
@ -57,7 +57,7 @@ fun LibraryScreen(
|
|||||||
val searchMode by libraryState.searchMode.collectAsState()
|
val searchMode by libraryState.searchMode.collectAsState()
|
||||||
val isLargeScreen = appShellViewModel.useSidebar()
|
val isLargeScreen = appShellViewModel.useSidebar()
|
||||||
|
|
||||||
Scaffold(
|
BaseScaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
Column {
|
Column {
|
||||||
ApplicationMainBar(
|
ApplicationMainBar(
|
||||||
|
|||||||
@ -36,7 +36,7 @@ import androidx.compose.material3.CircularProgressIndicator
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import dev.krtirtho.spotube.core.ui.base.BaseScaffold
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@ -71,7 +71,7 @@ fun LyricsScreen(
|
|||||||
val positionMillis by viewModel.positionMillisFlow.collectAsState()
|
val positionMillis by viewModel.positionMillisFlow.collectAsState()
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
Scaffold(
|
BaseScaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
ApplicationMainBar(
|
ApplicationMainBar(
|
||||||
backButton = onClose == null,
|
backButton = onClose == null,
|
||||||
|
|||||||
@ -39,7 +39,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
|||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@ -62,6 +61,7 @@ import coil3.request.ImageRequest
|
|||||||
import coil3.request.crossfade
|
import coil3.request.crossfade
|
||||||
import dev.krtirtho.spotube.PlatformType
|
import dev.krtirtho.spotube.PlatformType
|
||||||
import dev.krtirtho.spotube.core.extras.kebabToTitleCase
|
import dev.krtirtho.spotube.core.extras.kebabToTitleCase
|
||||||
|
import dev.krtirtho.spotube.core.ui.base.BaseScaffold
|
||||||
import dev.krtirtho.spotube.core.ui.base.Card
|
import dev.krtirtho.spotube.core.ui.base.Card
|
||||||
import dev.krtirtho.spotube.core.ui.base.OutlineButton
|
import dev.krtirtho.spotube.core.ui.base.OutlineButton
|
||||||
import dev.krtirtho.spotube.core.ui.base.PrimaryButton
|
import dev.krtirtho.spotube.core.ui.base.PrimaryButton
|
||||||
@ -145,13 +145,12 @@ fun PluginScreen(
|
|||||||
viewModel: PluginViewModel = koinViewModel(),
|
viewModel: PluginViewModel = koinViewModel(),
|
||||||
onboarding: Boolean = false,
|
onboarding: Boolean = false,
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
BaseScaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
if (!onboarding) {
|
if (!onboarding) {
|
||||||
ApplicationMainBar(title = { Text(stringResource(Res.string.plugin_screen_title)) })
|
ApplicationMainBar(title = { Text(stringResource(Res.string.plugin_screen_title)) })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
containerColor = if (onboarding) Color.Transparent else MaterialTheme.colorScheme.surface,
|
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
PluginScreenContent(
|
PluginScreenContent(
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
@ -162,7 +161,7 @@ fun PluginScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The plugin management UI without its own [Scaffold] / app bar, so it can be embedded
|
* The plugin management UI without its own [BaseScaffold] / app bar, so it can be embedded
|
||||||
* as a tab in the large-screen settings layout as well as hosted by [PluginScreen].
|
* as a tab in the large-screen settings layout as well as hosted by [PluginScreen].
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@ -53,7 +53,7 @@ import androidx.compose.material3.Icon
|
|||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LinearProgressIndicator
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import dev.krtirtho.spotube.core.ui.base.BaseScaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@ -268,7 +268,7 @@ fun SearchScreen(viewModel: SearchScreenViewModel = koinViewModel()) {
|
|||||||
remotePlaybackController.requestTracksPlayNext(tracks, "Search results")
|
remotePlaybackController.requestTracksPlayNext(tracks, "Search results")
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
BaseScaffold(
|
||||||
topBar = { ApplicationMainBar(backButton = false) }
|
topBar = { ApplicationMainBar(backButton = false) }
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
Column(
|
Column(
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.widthIn
|
import androidx.compose.foundation.layout.widthIn
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@ -16,6 +15,7 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import dev.krtirtho.spotube.core.ui.base.BaseScaffold
|
||||||
import dev.krtirtho.spotube.core.ui.base.PrimaryButton
|
import dev.krtirtho.spotube.core.ui.base.PrimaryButton
|
||||||
import dev.krtirtho.spotube.core.ui.component.ApplicationMainBar
|
import dev.krtirtho.spotube.core.ui.component.ApplicationMainBar
|
||||||
import dev.krtirtho.spotube.modules.settings.components.SwitchSettingCard
|
import dev.krtirtho.spotube.modules.settings.components.SwitchSettingCard
|
||||||
@ -59,7 +59,7 @@ import spotube.composeapp.generated.resources.settings_section_jam
|
|||||||
fun JamSettingsScreen(
|
fun JamSettingsScreen(
|
||||||
viewModel: JamSettingsViewModel = koinViewModel(),
|
viewModel: JamSettingsViewModel = koinViewModel(),
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
BaseScaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
ApplicationMainBar(
|
ApplicationMainBar(
|
||||||
title = { Text(stringResource(Res.string.settings_section_jam)) },
|
title = { Text(stringResource(Res.string.settings_section_jam)) },
|
||||||
@ -74,7 +74,7 @@ fun JamSettingsScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Group Jam settings UI without its own [Scaffold] / app bar, so it can be embedded
|
* The Group Jam settings UI without its own [BaseScaffold] / app bar, so it can be embedded
|
||||||
* as a tab in the large-screen settings layout as well as hosted by [JamSettingsScreen].
|
* as a tab in the large-screen settings layout as well as hosted by [JamSettingsScreen].
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@ -19,6 +19,7 @@ package dev.krtirtho.spotube.modules.settings
|
|||||||
|
|
||||||
import dev.krtirtho.plugin_interfaces.plugin_apis.audio.AudioFormat
|
import dev.krtirtho.plugin_interfaces.plugin_apis.audio.AudioFormat
|
||||||
import dev.krtirtho.plugin_interfaces.plugin_apis.audio.AudioQuality
|
import dev.krtirtho.plugin_interfaces.plugin_apis.audio.AudioQuality
|
||||||
|
import dev.krtirtho.spotube.core.ui.base.SurfaceStyle
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -35,6 +36,7 @@ data class UserSettings(
|
|||||||
// Appearance
|
// Appearance
|
||||||
val theme: Theme = Theme.SYSTEM,
|
val theme: Theme = Theme.SYSTEM,
|
||||||
val accentColor: AccentColors = AccentColors.GREEN_GOBLIN,
|
val accentColor: AccentColors = AccentColors.GREEN_GOBLIN,
|
||||||
|
val surfaceTheme: SurfaceStyle = SurfaceStyle.Glass,
|
||||||
|
|
||||||
// Playback
|
// Playback
|
||||||
val streamingMusicFormat: AudioFormat = AudioFormat(
|
val streamingMusicFormat: AudioFormat = AudioFormat(
|
||||||
|
|||||||
@ -25,7 +25,6 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.layout.widthIn
|
import androidx.compose.foundation.layout.widthIn
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@ -39,6 +38,7 @@ import dev.krtirtho.spotube.getPlatform
|
|||||||
import dev.krtirtho.spotube.core.discovery.rememberLocalNetworkPermissionRequester
|
import dev.krtirtho.spotube.core.discovery.rememberLocalNetworkPermissionRequester
|
||||||
import dev.krtirtho.spotube.core.navigation.NavigationCommands
|
import dev.krtirtho.spotube.core.navigation.NavigationCommands
|
||||||
import dev.krtirtho.spotube.core.navigation.Routes
|
import dev.krtirtho.spotube.core.navigation.Routes
|
||||||
|
import dev.krtirtho.spotube.core.ui.base.BaseScaffold
|
||||||
import dev.krtirtho.spotube.core.ui.component.ApplicationMainBar
|
import dev.krtirtho.spotube.core.ui.component.ApplicationMainBar
|
||||||
import spotube.composeapp.generated.resources.*
|
import spotube.composeapp.generated.resources.*
|
||||||
import dev.krtirtho.spotube.modules.settings.sections.appearanceSection
|
import dev.krtirtho.spotube.modules.settings.sections.appearanceSection
|
||||||
@ -91,7 +91,7 @@ private fun CompactSettingsScreen(settingsViewModel: SettingsViewModel) {
|
|||||||
PaddingValues(top = 16.dp, bottom = 16.dp + shellBottomInset)
|
PaddingValues(top = 16.dp, bottom = 16.dp + shellBottomInset)
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
BaseScaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
ApplicationMainBar(
|
ApplicationMainBar(
|
||||||
title = {
|
title = {
|
||||||
|
|||||||
@ -52,6 +52,7 @@ import dev.krtirtho.spotube.core.ui.base.ThemedDialog
|
|||||||
import dev.krtirtho.spotube.core.ui.base.OutlineButton
|
import dev.krtirtho.spotube.core.ui.base.OutlineButton
|
||||||
import dev.krtirtho.spotube.core.ui.base.PrimaryButton
|
import dev.krtirtho.spotube.core.ui.base.PrimaryButton
|
||||||
import dev.krtirtho.spotube.core.ui.base.Radio
|
import dev.krtirtho.spotube.core.ui.base.Radio
|
||||||
|
import dev.krtirtho.spotube.core.ui.base.SurfaceStyle
|
||||||
import dev.krtirtho.spotube.modules.settings.AccentColors
|
import dev.krtirtho.spotube.modules.settings.AccentColors
|
||||||
import dev.krtirtho.spotube.modules.settings.SettingsViewModel
|
import dev.krtirtho.spotube.modules.settings.SettingsViewModel
|
||||||
import dev.krtirtho.spotube.modules.settings.Theme
|
import dev.krtirtho.spotube.modules.settings.Theme
|
||||||
@ -61,6 +62,7 @@ import dev.krtirtho.spotube.modules.settings.components.SettingCardItem
|
|||||||
import dev.krtirtho.spotube.resources.iconsax.Iconsax
|
import dev.krtirtho.spotube.resources.iconsax.Iconsax
|
||||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxColorSwatch
|
import dev.krtirtho.spotube.resources.iconsax.IconsaxColorSwatch
|
||||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxColorsSquare
|
import dev.krtirtho.spotube.resources.iconsax.IconsaxColorsSquare
|
||||||
|
import dev.krtirtho.spotube.resources.iconsax.IconsaxMagic
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
import spotube.composeapp.generated.resources.*
|
import spotube.composeapp.generated.resources.*
|
||||||
|
|
||||||
@ -94,6 +96,29 @@ internal fun LazyListScope.appearanceSection(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
SelectionSettingCard(
|
||||||
|
title = stringResource(Res.string.settings_surface_theme_title),
|
||||||
|
subtitle = stringResource(
|
||||||
|
Res.string.settings_surface_theme_subtitle_current,
|
||||||
|
settings.surfaceTheme.displayLabel()
|
||||||
|
),
|
||||||
|
icon = {
|
||||||
|
SettingsItemIcon(
|
||||||
|
Iconsax.IconsaxMagic,
|
||||||
|
stringResource(Res.string.settings_surface_theme_title)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
selectedOption = settings.surfaceTheme,
|
||||||
|
options = SurfaceStyle.entries,
|
||||||
|
optionLabel = { it.displayLabel() },
|
||||||
|
onOptionSelected = { surfaceTheme ->
|
||||||
|
settingsViewModel.updateSettings {
|
||||||
|
copy(surfaceTheme = surfaceTheme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
AccentColorSettingCard(
|
AccentColorSettingCard(
|
||||||
selectedAccent = settings.accentColor,
|
selectedAccent = settings.accentColor,
|
||||||
@ -120,6 +145,17 @@ private fun Theme.displayLabel(): String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SurfaceStyle.displayLabel(): String {
|
||||||
|
return when (this) {
|
||||||
|
SurfaceStyle.Watery -> stringResource(Res.string.settings_surface_watery)
|
||||||
|
SurfaceStyle.Icy -> stringResource(Res.string.settings_surface_icy)
|
||||||
|
SurfaceStyle.Chrome -> stringResource(Res.string.settings_surface_chrome)
|
||||||
|
SurfaceStyle.Acrylic -> stringResource(Res.string.settings_surface_acrylic)
|
||||||
|
SurfaceStyle.Glass -> stringResource(Res.string.settings_surface_glass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun AccentColors.displayLabel(): String {
|
private fun AccentColors.displayLabel(): String {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
|
|||||||
@ -47,7 +47,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
import androidx.compose.material3.Scaffold
|
import dev.krtirtho.spotube.core.ui.base.BaseScaffold
|
||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
@ -235,7 +235,7 @@ fun AppExpandedPlayer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
BaseScaffold(
|
||||||
modifier = modifier.fillMaxSize(),
|
modifier = modifier.fillMaxSize(),
|
||||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
|
|||||||
@ -74,6 +74,8 @@ import dev.krtirtho.spotube.core.navigation.Navigator
|
|||||||
import dev.krtirtho.spotube.core.navigation.Routes
|
import dev.krtirtho.spotube.core.navigation.Routes
|
||||||
import dev.krtirtho.spotube.core.remote.ConnectionRequestDialogHost
|
import dev.krtirtho.spotube.core.remote.ConnectionRequestDialogHost
|
||||||
import dev.krtirtho.spotube.core.remote.RemotePlaybackController
|
import dev.krtirtho.spotube.core.remote.RemotePlaybackController
|
||||||
|
import dev.krtirtho.spotube.core.ui.base.LocalBaseUIColors
|
||||||
|
import dev.krtirtho.spotube.core.ui.base.baseSurfaceBackdrop
|
||||||
import dev.krtirtho.spotube.modules.devices.PlayDestinationPickerHost
|
import dev.krtirtho.spotube.modules.devices.PlayDestinationPickerHost
|
||||||
import dev.krtirtho.spotube.modules.lyrics.LyricsScreen
|
import dev.krtirtho.spotube.modules.lyrics.LyricsScreen
|
||||||
import dev.krtirtho.spotube.modules.shell.alternative_track.AlternativeTrackContent
|
import dev.krtirtho.spotube.modules.shell.alternative_track.AlternativeTrackContent
|
||||||
@ -100,6 +102,7 @@ fun AppShell(
|
|||||||
) {
|
) {
|
||||||
val navigatorCommands: NavigationCommands = koinInject()
|
val navigatorCommands: NavigationCommands = koinInject()
|
||||||
val remotePlaybackController: RemotePlaybackController = koinInject()
|
val remotePlaybackController: RemotePlaybackController = koinInject()
|
||||||
|
val baseUIColors = LocalBaseUIColors.current
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
LaunchedEffect(remotePlaybackController) {
|
LaunchedEffect(remotePlaybackController) {
|
||||||
remotePlaybackController.events.collect { message ->
|
remotePlaybackController.events.collect { message ->
|
||||||
@ -156,7 +159,7 @@ fun AppShell(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(MaterialTheme.colorScheme.surface)
|
.baseSurfaceBackdrop(baseUIColors.surfaceTheme, baseUIColors.isLight)
|
||||||
.hazeSource(state = bgHazeState) // <-- Put it here!
|
.hazeSource(state = bgHazeState) // <-- Put it here!
|
||||||
)
|
)
|
||||||
Row(modifier = Modifier.fillMaxSize()) {
|
Row(modifier = Modifier.fillMaxSize()) {
|
||||||
@ -229,6 +232,11 @@ fun AppShell(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
|
BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.baseSurfaceBackdrop(baseUIColors.surfaceTheme, baseUIColors.isLight),
|
||||||
|
)
|
||||||
CompactPlayerOverlay(
|
CompactPlayerOverlay(
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
navigationState = navigationState,
|
navigationState = navigationState,
|
||||||
|
|||||||
@ -41,7 +41,7 @@ import androidx.compose.foundation.shape.CircleShape
|
|||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import dev.krtirtho.spotube.core.ui.base.BaseScaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@ -79,7 +79,7 @@ fun WelcomeScreen(viewModel: WelcomeViewModel) {
|
|||||||
WelcomeTheme {
|
WelcomeTheme {
|
||||||
Box(Modifier.fillMaxSize()) {
|
Box(Modifier.fillMaxSize()) {
|
||||||
WelcomeBackground()
|
WelcomeBackground()
|
||||||
Scaffold(
|
BaseScaffold(
|
||||||
containerColor = Color.Transparent,
|
containerColor = Color.Transparent,
|
||||||
topBar = {
|
topBar = {
|
||||||
ApplicationMainBar(
|
ApplicationMainBar(
|
||||||
|
|||||||
@ -29,6 +29,7 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.graphics.luminance
|
import androidx.compose.ui.graphics.luminance
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.unit.TextUnit
|
import androidx.compose.ui.unit.TextUnit
|
||||||
|
import dev.krtirtho.spotube.core.ui.base.LocalBaseUIColors
|
||||||
import dev.krtirtho.spotube.core.ui.base.LocalBaseUITheme
|
import dev.krtirtho.spotube.core.ui.base.LocalBaseUITheme
|
||||||
import dev.krtirtho.spotube.core.ui.base.rememberBaseUITheme
|
import dev.krtirtho.spotube.core.ui.base.rememberBaseUITheme
|
||||||
import org.jetbrains.compose.resources.Font
|
import org.jetbrains.compose.resources.Font
|
||||||
@ -60,7 +61,11 @@ fun WelcomeTheme(content: @Composable () -> Unit) {
|
|||||||
outlineVariant = Color(0xFFD2D0CC),
|
outlineVariant = Color(0xFFD2D0CC),
|
||||||
)
|
)
|
||||||
MaterialTheme(colorScheme = scheme) {
|
MaterialTheme(colorScheme = scheme) {
|
||||||
CompositionLocalProvider(LocalBaseUITheme provides rememberBaseUITheme()) {
|
val baseTheme = rememberBaseUITheme()
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalBaseUITheme provides baseTheme,
|
||||||
|
LocalBaseUIColors provides baseTheme.colors,
|
||||||
|
) {
|
||||||
content()
|
content()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,6 +41,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.WindowPlacement
|
import androidx.compose.ui.window.WindowPlacement
|
||||||
import dev.krtirtho.spotube.core.systemtray.SystemTrayService
|
import dev.krtirtho.spotube.core.systemtray.SystemTrayService
|
||||||
|
import dev.krtirtho.spotube.core.ui.base.LocalBaseUIColors
|
||||||
import dev.krtirtho.spotube.modules.settings.SettingsProvider
|
import dev.krtirtho.spotube.modules.settings.SettingsProvider
|
||||||
import dev.krtirtho.spotube.resources.iconsax.FluentDismiss
|
import dev.krtirtho.spotube.resources.iconsax.FluentDismiss
|
||||||
import dev.krtirtho.spotube.resources.iconsax.FluentMaximize
|
import dev.krtirtho.spotube.resources.iconsax.FluentMaximize
|
||||||
@ -63,6 +64,7 @@ actual fun ApplicationMainBar(
|
|||||||
backButton: Boolean,
|
backButton: Boolean,
|
||||||
transparent: Boolean,
|
transparent: Boolean,
|
||||||
) {
|
) {
|
||||||
|
val colors = LocalBaseUIColors.current
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
.windowDragArea(doubleClickAction = WindowDoubleClickAction.ToggleMaximize)
|
.windowDragArea(doubleClickAction = WindowDoubleClickAction.ToggleMaximize)
|
||||||
@ -82,9 +84,19 @@ actual fun ApplicationMainBar(
|
|||||||
TopAppBarDefaults.topAppBarColors(
|
TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = Color.Transparent,
|
containerColor = Color.Transparent,
|
||||||
scrolledContainerColor = Color.Transparent,
|
scrolledContainerColor = Color.Transparent,
|
||||||
|
titleContentColor = colors.onSurface,
|
||||||
|
actionIconContentColor = colors.onSurfaceVariant,
|
||||||
|
navigationIconContentColor = colors.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
TopAppBarDefaults.topAppBarColors()
|
// The app bar follows the selected surface theme rather than Material's.
|
||||||
|
TopAppBarDefaults.topAppBarColors(
|
||||||
|
containerColor = colors.surface,
|
||||||
|
scrolledContainerColor = colors.surfaceContainer,
|
||||||
|
titleContentColor = colors.onSurface,
|
||||||
|
actionIconContentColor = colors.onSurfaceVariant,
|
||||||
|
navigationIconContentColor = colors.onSurfaceVariant,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
modifier = Modifier.padding(end = 125.dp) // To avoid overlap with window buttons
|
modifier = Modifier.padding(end = 125.dp) // To avoid overlap with window buttons
|
||||||
)
|
)
|
||||||
|
|||||||
@ -17,20 +17,36 @@
|
|||||||
|
|
||||||
package dev.krtirtho.spotube.core.ui.component
|
package dev.krtirtho.spotube.core.ui.component
|
||||||
|
|
||||||
|
import androidx.compose.foundation.LocalScrollbarStyle
|
||||||
|
import androidx.compose.foundation.ScrollbarStyle
|
||||||
import androidx.compose.foundation.VerticalScrollbar
|
import androidx.compose.foundation.VerticalScrollbar
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.rememberScrollbarAdapter
|
import androidx.compose.foundation.rememberScrollbarAdapter
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import dev.krtirtho.spotube.core.ui.base.LocalBaseUIColors
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
actual fun VerticalScrollbar(
|
actual fun VerticalScrollbar(
|
||||||
listState: LazyListState,
|
listState: LazyListState,
|
||||||
modifier: Modifier
|
modifier: Modifier
|
||||||
|
) {
|
||||||
|
val colors = LocalBaseUIColors.current
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalScrollbarStyle provides ScrollbarStyle(
|
||||||
|
minimalHeight = 16.dp,
|
||||||
|
thickness = 8.dp,
|
||||||
|
shape = RoundedCornerShape(4.dp),
|
||||||
|
hoverDurationMillis = 300,
|
||||||
|
unhoverColor = colors.onSurface.copy(alpha = 0.18f),
|
||||||
|
hoverColor = colors.onSurface.copy(alpha = 0.4f),
|
||||||
|
),
|
||||||
) {
|
) {
|
||||||
VerticalScrollbar(
|
VerticalScrollbar(
|
||||||
adapter = rememberScrollbarAdapter(listState),
|
adapter = rememberScrollbarAdapter(listState),
|
||||||
@ -40,3 +56,4 @@ actual fun VerticalScrollbar(
|
|||||||
.width(12.dp),
|
.width(12.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user