From 01aa6d9e784f67cb7461eba84cd4a1946da51885 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 03:03:53 +0530 Subject: [PATCH 001/194] Update build.gradle.kts --- app/build.gradle.kts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e12dd32fc..492f176c5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -220,6 +220,17 @@ dependencies { implementation(libs.nanohttpd) implementation(libs.lazycolumnscrollbar) implementation(libs.reorderable) + + // LIQUID GLASS DEPENDENCIES + // AndroidLiquidGlass 2.0.0-alpha03 (Contains the advanced lens effects) + implementation("io.github.kyant0:backdrop:2.0.0-alpha03") + + // SHAPES LIBRARY 1.2.0 (MINIMUM REQUIRED for G2 continuous curves) + implementation("io.github.kyant0:shapes:1.2.0") + + // DataStore for preferences persistence (To toggle the UI on/off) + implementation("androidx.datastore:datastore-preferences:1.0.0") + } /* ---------------- Git helpers ---------------- */ From b75752cd3ac5e39a794c7c6125bb3848b53269ab Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 03:14:45 +0530 Subject: [PATCH 002/194] Create LiquidUIPreferences.kt --- .../mpvex/preferences/LiquidUIPreferences.kt | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt diff --git a/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt b/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt new file mode 100644 index 000000000..07b2cc8dd --- /dev/null +++ b/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt @@ -0,0 +1,54 @@ +package app.marlboroadvance.mpvex.preferences + +import android.content.Context +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.preferencesDataStore +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +private val Context.liquidUIDataStore by preferencesDataStore(name = "liquid_ui_prefs") + +class LiquidUIPreferences(context: Context) { + private val dataStore = context.liquidUIDataStore + + companion object { + val LIQUID_UI_ENABLED = booleanPreferencesKey("liquid_ui_enabled") + val LIQUID_BLUR_ENABLED = booleanPreferencesKey("liquid_blur_enabled") + val LIQUID_LENS_ENABLED = booleanPreferencesKey("liquid_lens_enabled") + val LIQUID_VIBRANCY_ENABLED = booleanPreferencesKey("liquid_vibrancy_enabled") + } + + val liquidUIEnabledFlow: Flow = dataStore.data.map { prefs -> + prefs[LIQUID_UI_ENABLED] ?: false + } + + val liquidBlurEnabledFlow: Flow = dataStore.data.map { prefs -> + prefs[LIQUID_BLUR_ENABLED] ?: true + } + + val liquidLensEnabledFlow: Flow = dataStore.data.map { prefs -> + prefs[LIQUID_LENS_ENABLED] ?: true + } + + val liquidVibrancyEnabledFlow: Flow = dataStore.data.map { prefs -> + prefs[LIQUID_VIBRANCY_ENABLED] ?: true + } + + suspend fun setLiquidUIEnabled(enabled: Boolean) { + dataStore.edit { it[LIQUID_UI_ENABLED] = enabled } + } + + suspend fun setBlurEnabled(enabled: Boolean) { + dataStore.edit { it[LIQUID_BLUR_ENABLED] = enabled } + } + + suspend fun setLensEnabled(enabled: Boolean) { + dataStore.edit { it[LIQUID_LENS_ENABLED] = enabled } + } + + suspend fun setVibrancyEnabled(enabled: Boolean) { + dataStore.edit { it[LIQUID_VIBRANCY_ENABLED] = enabled } + } +} + From 0d87afad7f764167adb996d20e75adbcae7510ae Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 03:16:55 +0530 Subject: [PATCH 003/194] Create LiquidUIEffects.kt --- .../mpvex/ui/theme/LiquidUIEffects.kt | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 app/src/main/java/app/marlboroadvance/mpvex/ui/theme/LiquidUIEffects.kt diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/theme/LiquidUIEffects.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/theme/LiquidUIEffects.kt new file mode 100644 index 000000000..064923e2a --- /dev/null +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/theme/LiquidUIEffects.kt @@ -0,0 +1,87 @@ +package app.marlboroadvance.mpvex.ui.theme + +import android.os.Build +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.kyant.backdrop.effects.blur +import com.kyant.backdrop.effects.lens +import com.kyant.backdrop.effects.vibrancy + +object LiquidUIEffects { + + /** + * Glass Bottom Bar Effect - Used for media player controls + * Combines blur, lens, and vibrancy for a classic liquid glass look + */ + fun glassBottomBarEffects( + enableBlur: Boolean = true, + enableLens: Boolean = true, + enableVibrancy: Boolean = true + ) = buildList { + if (enableVibrancy) add(vibrancy()) + if (enableBlur) add(blur(4f.dp.toPx())) + if (enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + add(lens(16f.dp.toPx(), 32f.dp.toPx())) + } + } + + /** + * Interactive Glass Button Effect - For playback control buttons + * Lighter blur with enhanced color + */ + fun glassButtonEffects( + enableBlur: Boolean = true, + enableLens: Boolean = true + ) = buildList { + if (enableBlur) add(blur(3f.dp.toPx())) + if (enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + add(lens(12f.dp.toPx(), 24f.dp.toPx())) + } + } + + /** + * Glass Dialog Effect - For modal dialogs and bottom sheets + * Stronger blur for better readability + */ + fun glassDialogEffects( + enableBlur: Boolean = true, + enableLens: Boolean = true + ) = buildList { + add(vibrancy()) + if (enableBlur) add(blur(8f.dp.toPx())) + if (enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + add(lens(20f.dp.toPx(), 40f.dp.toPx())) + } + } + + /** + * Glass Card Effect - For video cards, playlist items + * Subtle effect to avoid overwhelming content + */ + fun glassCardEffects( + enableBlur: Boolean = true + ) = buildList { + if (enableBlur) add(blur(2.5f.dp.toPx())) + } + + /** + * Video Player Overlay Effect - For player controls overlay + * Balanced blur and lens for interactive controls + */ + fun playerOverlayEffects( + enableBlur: Boolean = true, + enableLens: Boolean = true, + enableVibrancy: Boolean = true + ) = buildList { + if (enableVibrancy) add(vibrancy()) + if (enableBlur) add(blur(5f.dp.toPx())) + if (enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + add(lens(18f.dp.toPx(), 36f.dp.toPx())) + } + } + + // Surface color for readability (semi-transparent white) + val glassSurfaceColor = Color.White.copy(alpha = 0.5f) + val glassDarkSurfaceColor = Color.Black.copy(alpha = 0.3f) +} + From 43c1f6609ef5186b446d850316f6519211bc5923 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 03:32:19 +0530 Subject: [PATCH 004/194] Update LiquidUIEffects.kt --- .../mpvex/ui/theme/LiquidUIEffects.kt | 66 ++++++++++++------- 1 file changed, 44 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/theme/LiquidUIEffects.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/theme/LiquidUIEffects.kt index 064923e2a..95989d09e 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/theme/LiquidUIEffects.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/theme/LiquidUIEffects.kt @@ -1,8 +1,11 @@ package app.marlboroadvance.mpvex.ui.theme import android.os.Build +import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp +import com.kyant.backdrop.effects.BackdropEffectScope import com.kyant.backdrop.effects.blur import com.kyant.backdrop.effects.lens import com.kyant.backdrop.effects.vibrancy @@ -13,15 +16,19 @@ object LiquidUIEffects { * Glass Bottom Bar Effect - Used for media player controls * Combines blur, lens, and vibrancy for a classic liquid glass look */ + @Composable fun glassBottomBarEffects( enableBlur: Boolean = true, enableLens: Boolean = true, enableVibrancy: Boolean = true - ) = buildList { - if (enableVibrancy) add(vibrancy()) - if (enableBlur) add(blur(4f.dp.toPx())) - if (enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - add(lens(16f.dp.toPx(), 32f.dp.toPx())) + ): BackdropEffectScope.() -> Unit { + val density = LocalDensity.current + return { + if (enableVibrancy) vibrancy() + if (enableBlur) blur(with(density) { 4f.dp.toPx() }) + if (enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + lens(with(density) { 16f.dp.toPx() }, with(density) { 32f.dp.toPx() }) + } } } @@ -29,13 +36,17 @@ object LiquidUIEffects { * Interactive Glass Button Effect - For playback control buttons * Lighter blur with enhanced color */ + @Composable fun glassButtonEffects( enableBlur: Boolean = true, enableLens: Boolean = true - ) = buildList { - if (enableBlur) add(blur(3f.dp.toPx())) - if (enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - add(lens(12f.dp.toPx(), 24f.dp.toPx())) + ): BackdropEffectScope.() -> Unit { + val density = LocalDensity.current + return { + if (enableBlur) blur(with(density) { 3f.dp.toPx() }) + if (enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + lens(with(density) { 12f.dp.toPx() }, with(density) { 24f.dp.toPx() }) + } } } @@ -43,14 +54,18 @@ object LiquidUIEffects { * Glass Dialog Effect - For modal dialogs and bottom sheets * Stronger blur for better readability */ + @Composable fun glassDialogEffects( enableBlur: Boolean = true, enableLens: Boolean = true - ) = buildList { - add(vibrancy()) - if (enableBlur) add(blur(8f.dp.toPx())) - if (enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - add(lens(20f.dp.toPx(), 40f.dp.toPx())) + ): BackdropEffectScope.() -> Unit { + val density = LocalDensity.current + return { + vibrancy() + if (enableBlur) blur(with(density) { 8f.dp.toPx() }) + if (enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + lens(with(density) { 20f.dp.toPx() }, with(density) { 40f.dp.toPx() }) + } } } @@ -58,25 +73,33 @@ object LiquidUIEffects { * Glass Card Effect - For video cards, playlist items * Subtle effect to avoid overwhelming content */ + @Composable fun glassCardEffects( enableBlur: Boolean = true - ) = buildList { - if (enableBlur) add(blur(2.5f.dp.toPx())) + ): BackdropEffectScope.() -> Unit { + val density = LocalDensity.current + return { + if (enableBlur) blur(with(density) { 2.5f.dp.toPx() }) + } } /** * Video Player Overlay Effect - For player controls overlay * Balanced blur and lens for interactive controls */ + @Composable fun playerOverlayEffects( enableBlur: Boolean = true, enableLens: Boolean = true, enableVibrancy: Boolean = true - ) = buildList { - if (enableVibrancy) add(vibrancy()) - if (enableBlur) add(blur(5f.dp.toPx())) - if (enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - add(lens(18f.dp.toPx(), 36f.dp.toPx())) + ): BackdropEffectScope.() -> Unit { + val density = LocalDensity.current + return { + if (enableVibrancy) vibrancy() + if (enableBlur) blur(with(density) { 5f.dp.toPx() }) + if (enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + lens(with(density) { 18f.dp.toPx() }, with(density) { 36f.dp.toPx() }) + } } } @@ -84,4 +107,3 @@ object LiquidUIEffects { val glassSurfaceColor = Color.White.copy(alpha = 0.5f) val glassDarkSurfaceColor = Color.Black.copy(alpha = 0.3f) } - From 814a384410d6fa54d2f6b4fd2bfab6781eaf6b18 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 03:35:54 +0530 Subject: [PATCH 005/194] Update build.yml --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aca592335..8692f3081 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,6 +2,7 @@ name: CI/CD Build on: pull_request: + workflow_dispatch concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number }} @@ -61,4 +62,4 @@ jobs: with: name: app-standard-x86_64-release-unsigned-${{ env.SHORT_SHA }} path: app/build/outputs/apk/standard/release/app-standard-x86_64-release-unsigned.apk - if-no-files-found: warn \ No newline at end of file + if-no-files-found: warn From 829451866db8ec1fc92eb24837c24d00fb59cef4 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 03:36:21 +0530 Subject: [PATCH 006/194] Update build.yml --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8692f3081..681c12788 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,7 @@ name: CI/CD Build on: pull_request: - workflow_dispatch + workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number }} From 706e3766f89951cad543165bf772d59785479c29 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 03:54:12 +0530 Subject: [PATCH 007/194] Update LiquidUIPreferences.kt --- .../mpvex/preferences/LiquidUIPreferences.kt | 128 +++++++++++------- 1 file changed, 80 insertions(+), 48 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt b/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt index 07b2cc8dd..2431e199a 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt @@ -1,54 +1,86 @@ -package app.marlboroadvance.mpvex.preferences +package app.marlboroadvance.mpvex.ui.theme -import android.content.Context -import androidx.datastore.preferences.core.booleanPreferencesKey -import androidx.datastore.preferences.core.edit -import androidx.datastore.preferences.preferencesDataStore -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map +import android.os.Build +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.dp +import com.kyant.backdrop.Backdrop +import com.kyant.backdrop.drawBackdrop +import com.kyant.backdrop.effects.blur +import com.kyant.backdrop.effects.lens +import com.kyant.backdrop.effects.vibrancy -private val Context.liquidUIDataStore by preferencesDataStore(name = "liquid_ui_prefs") - -class LiquidUIPreferences(context: Context) { - private val dataStore = context.liquidUIDataStore - - companion object { - val LIQUID_UI_ENABLED = booleanPreferencesKey("liquid_ui_enabled") - val LIQUID_BLUR_ENABLED = booleanPreferencesKey("liquid_blur_enabled") - val LIQUID_LENS_ENABLED = booleanPreferencesKey("liquid_lens_enabled") - val LIQUID_VIBRANCY_ENABLED = booleanPreferencesKey("liquid_vibrancy_enabled") - } - - val liquidUIEnabledFlow: Flow = dataStore.data.map { prefs -> - prefs[LIQUID_UI_ENABLED] ?: false - } - - val liquidBlurEnabledFlow: Flow = dataStore.data.map { prefs -> - prefs[LIQUID_BLUR_ENABLED] ?: true - } - - val liquidLensEnabledFlow: Flow = dataStore.data.map { prefs -> - prefs[LIQUID_LENS_ENABLED] ?: true - } - - val liquidVibrancyEnabledFlow: Flow = dataStore.data.map { prefs -> - prefs[LIQUID_VIBRANCY_ENABLED] ?: true - } - - suspend fun setLiquidUIEnabled(enabled: Boolean) { - dataStore.edit { it[LIQUID_UI_ENABLED] = enabled } - } - - suspend fun setBlurEnabled(enabled: Boolean) { - dataStore.edit { it[LIQUID_BLUR_ENABLED] = enabled } - } +// 1. Define an enum for all effect presets +enum class LiquidEffectType { + BOTTOM_BAR, BUTTON, DIALOG, CARD, PLAYER_OVERLAY +} - suspend fun setLensEnabled(enabled: Boolean) { - dataStore.edit { it[LIQUID_LENS_ENABLED] = enabled } - } +// 2. Configuration class to handle user preferences +data class LiquidEffectConfig( + val type: LiquidEffectType, + val enableBlur: Boolean = true, + val enableLens: Boolean = true, + val enableVibrancy: Boolean = true +) - suspend fun setVibrancyEnabled(enabled: Boolean) { - dataStore.edit { it[LIQUID_VIBRANCY_ENABLED] = enabled } - } +object LiquidUIEffects { + // Surface colors for readability over video + val glassSurfaceColor = Color.White.copy(alpha = 0.5f) + val glassDarkSurfaceColor = Color.Black.copy(alpha = 0.3f) } +/** + * 3. The Magic Wrapper! + * This uses type inference to apply the DSL safely, completely + * avoiding the unresolved 'BackdropEffectScope' error. + */ +fun Modifier.applyLiquidEffects( + backdrop: Backdrop, + shape: Shape, + config: LiquidEffectConfig, + surfaceColor: Color = LiquidUIEffects.glassSurfaceColor +): Modifier = composed { + val density = LocalDensity.current + this.drawBackdrop( + backdrop = backdrop, + shape = { shape }, + effects = { // Kotlin automatically infers the hidden scope here! + when (config.type) { + LiquidEffectType.BOTTOM_BAR -> { + if (config.enableVibrancy) vibrancy() + if (config.enableBlur) blur(with(density) { 4f.dp.toPx() }) + if (config.enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + lens(with(density) { 16f.dp.toPx() }, with(density) { 32f.dp.toPx() }) + } + } + LiquidEffectType.BUTTON -> { + if (config.enableBlur) blur(with(density) { 3f.dp.toPx() }) + if (config.enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + lens(with(density) { 12f.dp.toPx() }, with(density) { 24f.dp.toPx() }) + } + } + LiquidEffectType.DIALOG -> { + vibrancy() + if (config.enableBlur) blur(with(density) { 8f.dp.toPx() }) + if (config.enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + lens(with(density) { 20f.dp.toPx() }, with(density) { 40f.dp.toPx() }) + } + } + LiquidEffectType.CARD -> { + if (config.enableBlur) blur(with(density) { 2.5f.dp.toPx() }) + } + LiquidEffectType.PLAYER_OVERLAY -> { + if (config.enableVibrancy) vibrancy() + if (config.enableBlur) blur(with(density) { 5f.dp.toPx() }) + if (config.enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + lens(with(density) { 18f.dp.toPx() }, with(density) { 36f.dp.toPx() }) + } + } + } + }, + onDrawSurface = { drawRect(surfaceColor) } + ) +} From 0a3d051038ec6d2f9562d58acfa0f53019971f27 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 03:55:19 +0530 Subject: [PATCH 008/194] Create LiquidComponents.kt --- .../ui/components/liquid/LiquidComponents.kt | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt new file mode 100644 index 000000000..461914b5b --- /dev/null +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt @@ -0,0 +1,118 @@ +package app.marlboroadvance.mpvex.ui.components.liquid + +import androidx.compose.foundation.background +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.unit.dp +import com.kyant.backdrop.Backdrop +import app.marlboroadvance.mpvex.ui.theme.LiquidEffectConfig +import app.marlboroadvance.mpvex.ui.theme.LiquidEffectType +import app.marlboroadvance.mpvex.ui.theme.applyLiquidEffects + +@Composable +fun LiquidGlassContainer( + modifier: Modifier = Modifier, + shape: Shape = RoundedCornerShape(16.dp), + backdrop: Backdrop?, + effectConfig: LiquidEffectConfig = LiquidEffectConfig(LiquidEffectType.PLAYER_OVERLAY), + content: @Composable () -> Unit +) { + if (backdrop == null) { + androidx.compose.material3.Surface( + modifier = modifier.background(Color.White.copy(alpha = 0.1f), shape), + shape = shape, + color = Color.White.copy(alpha = 0.1f) + ) { + content() + } + } else { + androidx.compose.material3.Surface( + modifier = modifier.applyLiquidEffects(backdrop, shape, effectConfig), + shape = shape, + color = Color.Transparent + ) { + content() + } + } +} + +@Composable +fun LiquidGlassButton( + modifier: Modifier = Modifier, + backdrop: Backdrop?, + effectConfig: LiquidEffectConfig = LiquidEffectConfig(LiquidEffectType.BUTTON), + isLensButton: Boolean = false, + onClick: () -> Unit, + content: @Composable () -> Unit +) { + androidx.compose.material3.Button( + onClick = onClick, + modifier = if (backdrop != null) { + modifier.applyLiquidEffects( + backdrop = backdrop, + shape = if (isLensButton) CircleShape else RoundedCornerShape(12.dp), + config = effectConfig + ) + } else modifier, + colors = androidx.compose.material3.ButtonDefaults.buttonColors( + containerColor = Color.Transparent + ) + ) { + content() + } +} + +@Composable +fun LiquidGlassBottomSheet( + modifier: Modifier = Modifier, + backdrop: Backdrop?, + onDismiss: () -> Unit, + content: @Composable () -> Unit +) { + if (backdrop == null) { + androidx.compose.material3.BasicAlertDialog( + onDismissRequest = onDismiss, + modifier = modifier + ) { + content() + } + } else { + androidx.compose.material3.BasicAlertDialog( + onDismissRequest = onDismiss, + modifier = modifier.applyLiquidEffects( + backdrop = backdrop, + shape = RoundedCornerShape(24.dp), + config = LiquidEffectConfig(LiquidEffectType.DIALOG) + ) + ) { + content() + } + } +} + +@Composable +fun LiquidGlassCard( + modifier: Modifier = Modifier, + backdrop: Backdrop?, + onClick: () -> Unit = {}, + content: @Composable () -> Unit +) { + androidx.compose.material3.Card( + modifier = if (backdrop != null) { + modifier.applyLiquidEffects( + backdrop = backdrop, + shape = RoundedCornerShape(12.dp), + config = LiquidEffectConfig(LiquidEffectType.CARD) + ) + } else modifier, + shape = RoundedCornerShape(12.dp), + onClick = onClick + ) { + content() + } +} + From 18553ec603d5e318bb16f93d83c7bcf4684ba87c Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 08:45:29 +0530 Subject: [PATCH 009/194] Update LiquidUIEffects.kt --- .../mpvex/ui/theme/LiquidUIEffects.kt | 65 ++++++------------- 1 file changed, 21 insertions(+), 44 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/theme/LiquidUIEffects.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/theme/LiquidUIEffects.kt index 95989d09e..9ffb416a3 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/theme/LiquidUIEffects.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/theme/LiquidUIEffects.kt @@ -1,11 +1,8 @@ package app.marlboroadvance.mpvex.ui.theme import android.os.Build -import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp -import com.kyant.backdrop.effects.BackdropEffectScope import com.kyant.backdrop.effects.blur import com.kyant.backdrop.effects.lens import com.kyant.backdrop.effects.vibrancy @@ -16,19 +13,15 @@ object LiquidUIEffects { * Glass Bottom Bar Effect - Used for media player controls * Combines blur, lens, and vibrancy for a classic liquid glass look */ - @Composable fun glassBottomBarEffects( enableBlur: Boolean = true, enableLens: Boolean = true, enableVibrancy: Boolean = true - ): BackdropEffectScope.() -> Unit { - val density = LocalDensity.current - return { - if (enableVibrancy) vibrancy() - if (enableBlur) blur(with(density) { 4f.dp.toPx() }) - if (enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - lens(with(density) { 16f.dp.toPx() }, with(density) { 32f.dp.toPx() }) - } + ) = buildList { + if (enableVibrancy) add(vibrancy()) + if (enableBlur) add(blur(4f.dp.toPx())) + if (enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + add(lens(16f.dp.toPx(), 32f.dp.toPx())) } } @@ -36,17 +29,13 @@ object LiquidUIEffects { * Interactive Glass Button Effect - For playback control buttons * Lighter blur with enhanced color */ - @Composable fun glassButtonEffects( enableBlur: Boolean = true, enableLens: Boolean = true - ): BackdropEffectScope.() -> Unit { - val density = LocalDensity.current - return { - if (enableBlur) blur(with(density) { 3f.dp.toPx() }) - if (enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - lens(with(density) { 12f.dp.toPx() }, with(density) { 24f.dp.toPx() }) - } + ) = buildList { + if (enableBlur) add(blur(3f.dp.toPx())) + if (enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + add(lens(12f.dp.toPx(), 24f.dp.toPx())) } } @@ -54,18 +43,14 @@ object LiquidUIEffects { * Glass Dialog Effect - For modal dialogs and bottom sheets * Stronger blur for better readability */ - @Composable fun glassDialogEffects( enableBlur: Boolean = true, enableLens: Boolean = true - ): BackdropEffectScope.() -> Unit { - val density = LocalDensity.current - return { - vibrancy() - if (enableBlur) blur(with(density) { 8f.dp.toPx() }) - if (enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - lens(with(density) { 20f.dp.toPx() }, with(density) { 40f.dp.toPx() }) - } + ) = buildList { + add(vibrancy()) + if (enableBlur) add(blur(8f.dp.toPx())) + if (enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + add(lens(20f.dp.toPx(), 40f.dp.toPx())) } } @@ -73,33 +58,25 @@ object LiquidUIEffects { * Glass Card Effect - For video cards, playlist items * Subtle effect to avoid overwhelming content */ - @Composable fun glassCardEffects( enableBlur: Boolean = true - ): BackdropEffectScope.() -> Unit { - val density = LocalDensity.current - return { - if (enableBlur) blur(with(density) { 2.5f.dp.toPx() }) - } + ) = buildList { + if (enableBlur) add(blur(2.5f.dp.toPx())) } /** * Video Player Overlay Effect - For player controls overlay * Balanced blur and lens for interactive controls */ - @Composable fun playerOverlayEffects( enableBlur: Boolean = true, enableLens: Boolean = true, enableVibrancy: Boolean = true - ): BackdropEffectScope.() -> Unit { - val density = LocalDensity.current - return { - if (enableVibrancy) vibrancy() - if (enableBlur) blur(with(density) { 5f.dp.toPx() }) - if (enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - lens(with(density) { 18f.dp.toPx() }, with(density) { 36f.dp.toPx() }) - } + ) = buildList { + if (enableVibrancy) add(vibrancy()) + if (enableBlur) add(blur(5f.dp.toPx())) + if (enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + add(lens(18f.dp.toPx(), 36f.dp.toPx())) } } From 64b529988c7437965480737408b3285933eb33f5 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 08:48:06 +0530 Subject: [PATCH 010/194] Update LiquidUIPreferences.kt --- .../mpvex/preferences/LiquidUIPreferences.kt | 127 +++++++----------- 1 file changed, 47 insertions(+), 80 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt b/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt index 2431e199a..3cc4da026 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt @@ -1,86 +1,53 @@ -package app.marlboroadvance.mpvex.ui.theme +package app.marlboroadvance.mpvex.preferences -import android.os.Build -import androidx.compose.ui.Modifier -import androidx.compose.ui.composed -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.unit.dp -import com.kyant.backdrop.Backdrop -import com.kyant.backdrop.drawBackdrop -import com.kyant.backdrop.effects.blur -import com.kyant.backdrop.effects.lens -import com.kyant.backdrop.effects.vibrancy +import android.content.Context +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.preferencesDataStore +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map -// 1. Define an enum for all effect presets -enum class LiquidEffectType { - BOTTOM_BAR, BUTTON, DIALOG, CARD, PLAYER_OVERLAY -} +private val Context.liquidUIDataStore by preferencesDataStore(name = "liquid_ui_prefs") -// 2. Configuration class to handle user preferences -data class LiquidEffectConfig( - val type: LiquidEffectType, - val enableBlur: Boolean = true, - val enableLens: Boolean = true, - val enableVibrancy: Boolean = true -) +class LiquidUIPreferences(context: Context) { + private val dataStore = context.liquidUIDataStore -object LiquidUIEffects { - // Surface colors for readability over video - val glassSurfaceColor = Color.White.copy(alpha = 0.5f) - val glassDarkSurfaceColor = Color.Black.copy(alpha = 0.3f) -} + companion object { + val LIQUID_UI_ENABLED = booleanPreferencesKey("liquid_ui_enabled") + val LIQUID_BLUR_ENABLED = booleanPreferencesKey("liquid_blur_enabled") + val LIQUID_LENS_ENABLED = booleanPreferencesKey("liquid_lens_enabled") + val LIQUID_VIBRANCY_ENABLED = booleanPreferencesKey("liquid_vibrancy_enabled") + } + + val liquidUIEnabledFlow: Flow = dataStore.data.map { prefs -> + prefs[LIQUID_UI_ENABLED] ?: false + } + + val liquidBlurEnabledFlow: Flow = dataStore.data.map { prefs -> + prefs[LIQUID_BLUR_ENABLED] ?: true + } + + val liquidLensEnabledFlow: Flow = dataStore.data.map { prefs -> + prefs[LIQUID_LENS_ENABLED] ?: true + } + + val liquidVibrancyEnabledFlow: Flow = dataStore.data.map { prefs -> + prefs[LIQUID_VIBRANCY_ENABLED] ?: true + } + + suspend fun setLiquidUIEnabled(enabled: Boolean) { + dataStore.edit { it[LIQUID_UI_ENABLED] = enabled } + } + + suspend fun setBlurEnabled(enabled: Boolean) { + dataStore.edit { it[LIQUID_BLUR_ENABLED] = enabled } + } + + suspend fun setLensEnabled(enabled: Boolean) { + dataStore.edit { it[LIQUID_LENS_ENABLED] = enabled } + } -/** - * 3. The Magic Wrapper! - * This uses type inference to apply the DSL safely, completely - * avoiding the unresolved 'BackdropEffectScope' error. - */ -fun Modifier.applyLiquidEffects( - backdrop: Backdrop, - shape: Shape, - config: LiquidEffectConfig, - surfaceColor: Color = LiquidUIEffects.glassSurfaceColor -): Modifier = composed { - val density = LocalDensity.current - this.drawBackdrop( - backdrop = backdrop, - shape = { shape }, - effects = { // Kotlin automatically infers the hidden scope here! - when (config.type) { - LiquidEffectType.BOTTOM_BAR -> { - if (config.enableVibrancy) vibrancy() - if (config.enableBlur) blur(with(density) { 4f.dp.toPx() }) - if (config.enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - lens(with(density) { 16f.dp.toPx() }, with(density) { 32f.dp.toPx() }) - } - } - LiquidEffectType.BUTTON -> { - if (config.enableBlur) blur(with(density) { 3f.dp.toPx() }) - if (config.enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - lens(with(density) { 12f.dp.toPx() }, with(density) { 24f.dp.toPx() }) - } - } - LiquidEffectType.DIALOG -> { - vibrancy() - if (config.enableBlur) blur(with(density) { 8f.dp.toPx() }) - if (config.enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - lens(with(density) { 20f.dp.toPx() }, with(density) { 40f.dp.toPx() }) - } - } - LiquidEffectType.CARD -> { - if (config.enableBlur) blur(with(density) { 2.5f.dp.toPx() }) - } - LiquidEffectType.PLAYER_OVERLAY -> { - if (config.enableVibrancy) vibrancy() - if (config.enableBlur) blur(with(density) { 5f.dp.toPx() }) - if (config.enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - lens(with(density) { 18f.dp.toPx() }, with(density) { 36f.dp.toPx() }) - } - } - } - }, - onDrawSurface = { drawRect(surfaceColor) } - ) + suspend fun setVibrancyEnabled(enabled: Boolean) { + dataStore.edit { it[LIQUID_VIBRANCY_ENABLED] = enabled } + } } From 84ee0c45699577348babc20429071c364d29bbc7 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 08:53:01 +0530 Subject: [PATCH 011/194] Delete app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt --- .../ui/components/liquid/LiquidComponents.kt | 118 ------------------ 1 file changed, 118 deletions(-) delete mode 100644 app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt deleted file mode 100644 index 461914b5b..000000000 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt +++ /dev/null @@ -1,118 +0,0 @@ -package app.marlboroadvance.mpvex.ui.components.liquid - -import androidx.compose.foundation.background -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.unit.dp -import com.kyant.backdrop.Backdrop -import app.marlboroadvance.mpvex.ui.theme.LiquidEffectConfig -import app.marlboroadvance.mpvex.ui.theme.LiquidEffectType -import app.marlboroadvance.mpvex.ui.theme.applyLiquidEffects - -@Composable -fun LiquidGlassContainer( - modifier: Modifier = Modifier, - shape: Shape = RoundedCornerShape(16.dp), - backdrop: Backdrop?, - effectConfig: LiquidEffectConfig = LiquidEffectConfig(LiquidEffectType.PLAYER_OVERLAY), - content: @Composable () -> Unit -) { - if (backdrop == null) { - androidx.compose.material3.Surface( - modifier = modifier.background(Color.White.copy(alpha = 0.1f), shape), - shape = shape, - color = Color.White.copy(alpha = 0.1f) - ) { - content() - } - } else { - androidx.compose.material3.Surface( - modifier = modifier.applyLiquidEffects(backdrop, shape, effectConfig), - shape = shape, - color = Color.Transparent - ) { - content() - } - } -} - -@Composable -fun LiquidGlassButton( - modifier: Modifier = Modifier, - backdrop: Backdrop?, - effectConfig: LiquidEffectConfig = LiquidEffectConfig(LiquidEffectType.BUTTON), - isLensButton: Boolean = false, - onClick: () -> Unit, - content: @Composable () -> Unit -) { - androidx.compose.material3.Button( - onClick = onClick, - modifier = if (backdrop != null) { - modifier.applyLiquidEffects( - backdrop = backdrop, - shape = if (isLensButton) CircleShape else RoundedCornerShape(12.dp), - config = effectConfig - ) - } else modifier, - colors = androidx.compose.material3.ButtonDefaults.buttonColors( - containerColor = Color.Transparent - ) - ) { - content() - } -} - -@Composable -fun LiquidGlassBottomSheet( - modifier: Modifier = Modifier, - backdrop: Backdrop?, - onDismiss: () -> Unit, - content: @Composable () -> Unit -) { - if (backdrop == null) { - androidx.compose.material3.BasicAlertDialog( - onDismissRequest = onDismiss, - modifier = modifier - ) { - content() - } - } else { - androidx.compose.material3.BasicAlertDialog( - onDismissRequest = onDismiss, - modifier = modifier.applyLiquidEffects( - backdrop = backdrop, - shape = RoundedCornerShape(24.dp), - config = LiquidEffectConfig(LiquidEffectType.DIALOG) - ) - ) { - content() - } - } -} - -@Composable -fun LiquidGlassCard( - modifier: Modifier = Modifier, - backdrop: Backdrop?, - onClick: () -> Unit = {}, - content: @Composable () -> Unit -) { - androidx.compose.material3.Card( - modifier = if (backdrop != null) { - modifier.applyLiquidEffects( - backdrop = backdrop, - shape = RoundedCornerShape(12.dp), - config = LiquidEffectConfig(LiquidEffectType.CARD) - ) - } else modifier, - shape = RoundedCornerShape(12.dp), - onClick = onClick - ) { - content() - } -} - From ea66246704ce4eb0bfd87dfad00bbfaa98b08ee5 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 09:13:25 +0530 Subject: [PATCH 012/194] Update LiquidUIEffects.kt --- .../mpvex/ui/theme/LiquidUIEffects.kt | 56 +++++++++++++------ 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/theme/LiquidUIEffects.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/theme/LiquidUIEffects.kt index 9ffb416a3..62596b8d8 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/theme/LiquidUIEffects.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/theme/LiquidUIEffects.kt @@ -3,25 +3,46 @@ package app.marlboroadvance.mpvex.ui.theme import android.os.Build import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp +import com.kyant.backdrop.BackdropEffectScope import com.kyant.backdrop.effects.blur import com.kyant.backdrop.effects.lens import com.kyant.backdrop.effects.vibrancy +/** + * CORRECTED: Effects configuration for liquid glass UI + * + * IMPORTANT: These are lambdas to be used in the effects = { } block + * within drawBackdrop modifier, NOT to be used with buildList() + * + * Usage example: + * + * .drawBackdrop( + * backdrop = backdrop, + * shape = { RoundedCornerShape(16.dp) }, + * effects = LiquidUIEffects.glassBottomBarEffects( + * enableBlur = true, + * enableLens = true, + * enableVibrancy = true + * ) + * ) + */ object LiquidUIEffects { /** * Glass Bottom Bar Effect - Used for media player controls * Combines blur, lens, and vibrancy for a classic liquid glass look + * + * Returns a lambda to be used in effects = { } block */ fun glassBottomBarEffects( enableBlur: Boolean = true, enableLens: Boolean = true, enableVibrancy: Boolean = true - ) = buildList { - if (enableVibrancy) add(vibrancy()) - if (enableBlur) add(blur(4f.dp.toPx())) + ): BackdropEffectScope.() -> Unit = { + if (enableVibrancy) vibrancy() + if (enableBlur) blur(4f.dp.toPx()) if (enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - add(lens(16f.dp.toPx(), 32f.dp.toPx())) + lens(16f.dp.toPx(), 32f.dp.toPx()) } } @@ -32,10 +53,10 @@ object LiquidUIEffects { fun glassButtonEffects( enableBlur: Boolean = true, enableLens: Boolean = true - ) = buildList { - if (enableBlur) add(blur(3f.dp.toPx())) + ): BackdropEffectScope.() -> Unit = { + if (enableBlur) blur(3f.dp.toPx()) if (enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - add(lens(12f.dp.toPx(), 24f.dp.toPx())) + lens(12f.dp.toPx(), 24f.dp.toPx()) } } @@ -46,11 +67,11 @@ object LiquidUIEffects { fun glassDialogEffects( enableBlur: Boolean = true, enableLens: Boolean = true - ) = buildList { - add(vibrancy()) - if (enableBlur) add(blur(8f.dp.toPx())) + ): BackdropEffectScope.() -> Unit = { + vibrancy() + if (enableBlur) blur(8f.dp.toPx()) if (enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - add(lens(20f.dp.toPx(), 40f.dp.toPx())) + lens(20f.dp.toPx(), 40f.dp.toPx()) } } @@ -60,8 +81,8 @@ object LiquidUIEffects { */ fun glassCardEffects( enableBlur: Boolean = true - ) = buildList { - if (enableBlur) add(blur(2.5f.dp.toPx())) + ): BackdropEffectScope.() -> Unit = { + if (enableBlur) blur(2.5f.dp.toPx()) } /** @@ -72,15 +93,14 @@ object LiquidUIEffects { enableBlur: Boolean = true, enableLens: Boolean = true, enableVibrancy: Boolean = true - ) = buildList { - if (enableVibrancy) add(vibrancy()) - if (enableBlur) add(blur(5f.dp.toPx())) + ): BackdropEffectScope.() -> Unit = { + if (enableVibrancy) vibrancy() + if (enableBlur) blur(5f.dp.toPx()) if (enableLens && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - add(lens(18f.dp.toPx(), 36f.dp.toPx())) + lens(18f.dp.toPx(), 36f.dp.toPx()) } } // Surface color for readability (semi-transparent white) val glassSurfaceColor = Color.White.copy(alpha = 0.5f) val glassDarkSurfaceColor = Color.Black.copy(alpha = 0.3f) -} From 7ceee9adb3c4d96796d956b66fb6f33da44242fe Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 09:23:23 +0530 Subject: [PATCH 013/194] Update LiquidUIEffects.kt --- .../java/app/marlboroadvance/mpvex/ui/theme/LiquidUIEffects.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/theme/LiquidUIEffects.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/theme/LiquidUIEffects.kt index 62596b8d8..3023aab3c 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/theme/LiquidUIEffects.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/theme/LiquidUIEffects.kt @@ -104,3 +104,4 @@ object LiquidUIEffects { // Surface color for readability (semi-transparent white) val glassSurfaceColor = Color.White.copy(alpha = 0.5f) val glassDarkSurfaceColor = Color.Black.copy(alpha = 0.3f) +} From 493a0b0052c253efb42dd062a9447c9f1096f918 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 10:08:32 +0530 Subject: [PATCH 014/194] Create LiquidPlayerControls.kt --- .../ui/screens/player/LiquidPlayerControls.kt | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 app/src/main/java/app/marlboroadvance/mpvex/ui/screens/player/LiquidPlayerControls.kt diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/screens/player/LiquidPlayerControls.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/screens/player/LiquidPlayerControls.kt new file mode 100644 index 000000000..e022a719f --- /dev/null +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/screens/player/LiquidPlayerControls.kt @@ -0,0 +1,111 @@ +package app.marlboroadvance.mpvex.ui.screens.player + +import android.os.Build +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.kyant.backdrop.backdrops.rememberLayerBackdrop +import com.kyant.backdrop.drawBackdrop +import com.kyant.backdrop.Backdrop +import app.marlboroadvance.mpvex.preferences.LiquidUIPreferences +import app.marlboroadvance.mpvex.ui.theme.LiquidUIEffects + +@Composable +fun PlayerScreenWithLiquidUI( + liquidUIPreferences: LiquidUIPreferences +) { + // Collect the user's preference to toggle the UI on/off + val liquidUIEnabled = liquidUIPreferences.liquidUIEnabledFlow.collectAsState(initial = false).value + val backgroundColor = Color.Black + + // 1. Create the Backdrop State (CRITICAL) + val backdrop = rememberLayerBackdrop { + drawRect(backgroundColor) + drawContent() + } + + Box( + modifier = Modifier + .fillMaxSize() + .background(backgroundColor) + ) { + // Video content (Placeholder for the actual mpvEx video surface) + Box( + modifier = Modifier + .fillMaxSize() + .align(Alignment.Center), + contentAlignment = Alignment.Center + ) { + Text("Video Player Surface", color = Color.White) + } + + // 2. Apply the Controls + if (liquidUIEnabled) { + LiquidPlayerControls( + backdrop = backdrop, + modifier = Modifier.align(Alignment.BottomCenter) + ) + } else { + StandardPlayerControls( + modifier = Modifier.align(Alignment.BottomCenter) + ) + } + } +} + +@Composable +fun LiquidPlayerControls( + backdrop: Backdrop?, + modifier: Modifier = Modifier +) { + if (backdrop == null) { + StandardPlayerControls(modifier) + return + } + + Column(modifier = modifier.fillMaxWidth()) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(80.dp) + .padding(16.dp) + // 3. The Critical drawBackdrop Pattern + .drawBackdrop( + backdrop = backdrop, + shape = { RoundedCornerShape(24.dp) }, + effects = LiquidUIEffects.playerOverlayEffects( + enableBlur = true, + enableLens = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU, + enableVibrancy = true + ), + onDrawSurface = { drawRect(LiquidUIEffects.glassSurfaceColor) } + ), + contentAlignment = Alignment.Center + ) { + // Add your playback icons (Play/Pause/Seek) here + Text("Liquid Controls Active", color = Color.White) + } + } +} + +@Composable +fun StandardPlayerControls(modifier: Modifier = Modifier) { + // Fallback standard controls + Box( + modifier = modifier + .fillMaxWidth() + .height(80.dp) + .padding(16.dp) + .background(Color.DarkGray, RoundedCornerShape(24.dp)), + contentAlignment = Alignment.Center + ) { + Text("Standard Controls", color = Color.White) + } +} From 8aecc7061746cf2741e4a8505393c55af9dbe7a9 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 11:01:48 +0530 Subject: [PATCH 015/194] Create LiquidComponents.kt --- .../ui/components/liquid/LiquidComponents.kt | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt new file mode 100644 index 000000000..1121644d2 --- /dev/null +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt @@ -0,0 +1,86 @@ +package app.marlboroadvance.mpvex.ui.components.liquid + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.IconButton +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.kyant.backdrop.Backdrop +import com.kyant.backdrop.drawBackdrop +import app.marlboroadvance.mpvex.ui.theme.LiquidUIEffects + +/** + * Reusable Transparent Liquid Button + * Perfect for Play/Pause/Seek controls over the video player + */ +@Composable +fun TransparentLiquidButton( + modifier: Modifier = Modifier, + backdrop: Backdrop?, + onClick: () -> Unit, + content: @Composable () -> Unit +) { + // If liquid UI is disabled or backdrop is missing, show standard button + if (backdrop == null) { + IconButton(onClick = onClick, modifier = modifier) { content() } + return + } + + Surface( + modifier = modifier + .drawBackdrop( + backdrop = backdrop, + shape = { CircleShape }, + // Calls the lambda from your fixed LiquidUIEffects.kt + effects = LiquidUIEffects.glassButtonEffects(), + // Using an ultra-light overlay to ensure it remains a truly transparent liquid button + onDrawSurface = { drawRect(Color.White.copy(alpha = 0.1f)) } + ), + shape = CircleShape, + // Critical: The Surface itself must be transparent so the backdrop shows through + color = Color.Transparent + ) { + IconButton(onClick = onClick) { + content() + } + } +} + +/** + * Reusable Liquid Glass Card + * Perfect for the Browser Screen and video list items + */ +@Composable +fun LiquidGlassCard( + modifier: Modifier = Modifier, + backdrop: Backdrop?, + onClick: () -> Unit = {}, + content: @Composable () -> Unit +) { + if (backdrop == null) { + Card(modifier = modifier.clickable { onClick() }) { content() } + return + } + + Card( + modifier = modifier + .clickable { onClick() } + .drawBackdrop( + backdrop = backdrop, + shape = { RoundedCornerShape(12.dp) }, + effects = LiquidUIEffects.glassCardEffects(), + onDrawSurface = { drawRect(LiquidUIEffects.glassSurfaceColor) } + ), + shape = RoundedCornerShape(12.dp) + ) { + Box(modifier = Modifier.fillMaxSize()) { + content() + } + } +} From 34339f802a04384a277bae290b4bfdf794408191 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 13:30:50 +0530 Subject: [PATCH 016/194] add more liquid codes --- .../ui/components/liquid/LiquidUIToggle.kt | 258 ++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt new file mode 100644 index 000000000..357c6fa6e --- /dev/null +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt @@ -0,0 +1,258 @@ +package app.marlboroadvance.mpvex.ui.components.liquid + +import android.os.Build +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Surface +import androidx.compose.material3.Switch +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.kyant.backdrop.backdrops.rememberLayerBackdrop +import com.kyant.backdrop.drawBackdrop +import app.marlboroadvance.mpvex.ui.theme.LiquidUIEffects + +/** + * Liquid Glass Toggle Component + * + * A beautiful animated toggle with liquid glass effect. + * When liquid UI is disabled in preferences, falls back to standard Material Switch. + * + * Features: + * - Smooth animation between states + * - Liquid glass effect with customizable blur and lens + * - iOS-like appearance + * - Full accessibility support + * + * @param checked Whether the toggle is in the ON position + * @param onCheckedChange Callback when toggle state changes + * @param modifier Modifier for the toggle + * @param enabled Whether the toggle can be interacted with + * @param checkedColor Color when toggle is ON + * @param uncheckedColor Color when toggle is OFF + * @param thumbColor Color of the toggle thumb (circle) + * @param applyLiquidEffect Whether to apply liquid glass effect (set false for fallback) + */ +@Composable +fun LiquidToggle( + checked: Boolean, + onCheckedChange: (Boolean) -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + checkedColor: Color = Color(0xFF4CAF50), // Green when ON + uncheckedColor: Color = Color(0xFFBDBDBD), // Gray when OFF + thumbColor: Color = Color.White, + applyLiquidEffect: Boolean = true +) { + // Fallback to standard Material Switch if liquid effect not enabled + if (!applyLiquidEffect) { + Switch( + checked = checked, + onCheckedChange = onCheckedChange, + modifier = modifier, + enabled = enabled + ) + return + } + + val backgroundColor = if (checked) checkedColor else uncheckedColor + val thumbPadding = if (checked) 24.dp else 2.dp + + // Create backdrop for liquid glass effect + val backdrop = rememberLayerBackdrop { + drawRect(backgroundColor) + drawContent() + } + + // Animate thumb position + val animatedThumbPadding = animateDpAsState( + targetValue = thumbPadding, + label = "toggle_thumb_position" + ) + + Box( + modifier = modifier + .size(width = 52.dp, height = 32.dp) + .background( + color = backgroundColor, + shape = RoundedCornerShape(16.dp) + ) + .drawBackdrop( + backdrop = backdrop, + shape = { RoundedCornerShape(16.dp) }, + effects = LiquidUIEffects.glassCardEffects(enableBlur = true), + onDrawSurface = { drawRect(LiquidUIEffects.glassSurfaceColor) } + ) + .clickable( + enabled = enabled, + onClick = { onCheckedChange(!checked) }, + indication = null, // Remove ripple effect + interactionSource = remember { MutableInteractionSource() } + ), + contentAlignment = Alignment.CenterStart + ) { + // Toggle thumb (the circle that moves) + Box( + modifier = Modifier + .size(28.dp) + .padding(start = animatedThumbPadding.value) + .background( + color = thumbColor, + shape = RoundedCornerShape(14.dp) + ) + ) + } +} + +/** + * Liquid Glass Switch Component (with label) + * + * A toggle switch with optional label text. + * + * @param checked Whether the switch is ON + * @param onCheckedChange Callback when switch state changes + * @param modifier Modifier for the entire row + * @param enabled Whether the switch can be interacted with + * @param label Optional label text to display + * @param applyLiquidEffect Whether to apply liquid glass effect + */ +@Composable +fun LiquidSwitch( + checked: Boolean, + onCheckedChange: (Boolean) -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + label: String? = null, + applyLiquidEffect: Boolean = true +) { + if (label == null) { + // Just the toggle without label + LiquidToggle( + checked = checked, + onCheckedChange = onCheckedChange, + modifier = modifier, + enabled = enabled, + applyLiquidEffect = applyLiquidEffect + ) + return + } + + // Toggle with label + Row( + modifier = modifier.padding(vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + androidx.compose.material3.Text( + text = label, + modifier = Modifier.weight(1f) + ) + + Spacer(modifier = Modifier.size(8.dp)) + + LiquidToggle( + checked = checked, + onCheckedChange = onCheckedChange, + enabled = enabled, + applyLiquidEffect = applyLiquidEffect + ) + } +} + +/** + * Adaptive Toggle - Automatically uses liquid or standard based on settings + * + * This is the recommended component to use throughout the app. + * It automatically adapts to the liquid UI preference setting. + * + * @param checked Whether the toggle is ON + * @param onCheckedChange Callback when state changes + * @param modifier Modifier + * @param preferences LiquidUIPreferences to check liquid UI setting + * @param enabled Whether the toggle is interactive + * @param label Optional label text + */ +@Composable +fun AdaptiveToggle( + checked: Boolean, + onCheckedChange: (Boolean) -> Unit, + modifier: Modifier = Modifier, + preferences: app.marlboroadvance.mpvex.preferences.LiquidUIPreferences, + enabled: Boolean = true, + label: String? = null +) { + // Collect liquid UI enabled state + val isLiquidUIEnabled = preferences.liquidUIEnabledFlow + .androidx.compose.runtime.collectAsState(false).value + + if (label != null) { + LiquidSwitch( + checked = checked, + onCheckedChange = onCheckedChange, + modifier = modifier, + enabled = enabled, + label = label, + applyLiquidEffect = isLiquidUIEnabled + ) + } else { + LiquidToggle( + checked = checked, + onCheckedChange = onCheckedChange, + modifier = modifier, + enabled = enabled, + applyLiquidEffect = isLiquidUIEnabled + ) + } +} + +/** + * Simple Liquid Toggle (no adaptation) + * + * Use this when you're already checking liquid UI settings elsewhere + * and want to directly control whether liquid effect is shown. + * + * @param checked Toggle state + * @param onCheckedChange State change callback + * @param modifier Modifier + * @param enabled Interactive state + * @param isLiquidUI Whether to show liquid glass version (not adaptive) + * @param label Optional label + */ +@Composable +fun SimpleToggle( + checked: Boolean, + onCheckedChange: (Boolean) -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + isLiquidUI: Boolean = true, + label: String? = null +) { + if (label != null) { + LiquidSwitch( + checked = checked, + onCheckedChange = onCheckedChange, + modifier = modifier, + enabled = enabled, + label = label, + applyLiquidEffect = isLiquidUI + ) + } else { + LiquidToggle( + checked = checked, + onCheckedChange = onCheckedChange, + modifier = modifier, + enabled = enabled, + applyLiquidEffect = isLiquidUI + ) + } +} From afebe5acb8a419b329062bd886ddffba55244b0e Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 13:33:45 +0530 Subject: [PATCH 017/194] Create LiquidUISettingsScreen.kt --- .../settings/LiquidUISettingsScreen.kt | 314 ++++++++++++++++++ 1 file changed, 314 insertions(+) create mode 100644 app/src/main/java/app/marlboroadvance/mpvex/ui/screens/settings/LiquidUISettingsScreen.kt diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/screens/settings/LiquidUISettingsScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/screens/settings/LiquidUISettingsScreen.kt new file mode 100644 index 000000000..2020688cf --- /dev/null +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/screens/settings/LiquidUISettingsScreen.kt @@ -0,0 +1,314 @@ +package app.marlboroadvance.mpvex.ui.screens.settings + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Divider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import app.marlboroadvance.mpvex.preferences.LiquidUIPreferences +import app.marlboroadvance.mpvex.ui.components.liquid.AdaptiveToggle +import kotlinx.coroutines.launch + +/** + * Liquid UI Settings Screen + * + * Shows toggles for: + * - Master Liquid UI Enable/Disable + * - Individual Effect Settings (Blur, Lens, Vibrancy) + * - Preview of effects in real-time + */ +@Composable +fun LiquidUISettingsScreen( + preferences: LiquidUIPreferences, + modifier: Modifier = Modifier +) { + val scope = rememberCoroutineScope() + + // Collect all preference states + val liquidUIEnabled = preferences.liquidUIEnabledFlow.collectAsState(false).value + val blurEnabled = preferences.liquidBlurEnabledFlow.collectAsState(true).value + val lensEnabled = preferences.liquidLensEnabledFlow.collectAsState(true).value + val vibrancyEnabled = preferences.liquidVibrancyEnabledFlow.collectAsState(true).value + + Column( + modifier = modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(16.dp) + ) { + // ============================================================ + // SECTION 1: MASTER TOGGLE + // ============================================================ + + Text( + text = "Liquid Glass UI", + style = MaterialTheme.typography.headlineSmall, + modifier = Modifier.padding(bottom = 8.dp) + ) + + Text( + text = "Enable beautiful liquid glass effects throughout the app", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(bottom = 16.dp) + ) + + // Master toggle - Use standard switch for clarity on settings + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 12.dp), + horizontalArrangement = androidx.compose.foundation.layout.Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Enable Liquid UI", + style = MaterialTheme.typography.bodyLarge + ) + + // Use standard switch for the master toggle (meta-UI) + Switch( + checked = liquidUIEnabled, + onCheckedChange = { enabled -> + scope.launch { + preferences.setLiquidUIEnabled(enabled) + } + } + ) + } + + // ============================================================ + // SECTION 2: EFFECT TOGGLES + // ============================================================ + + Divider(modifier = Modifier.padding(vertical = 16.dp)) + + Text( + text = "Effect Settings", + style = MaterialTheme.typography.headlineSmall, + modifier = Modifier.padding(bottom = 8.dp) + ) + + Text( + text = "Customize which effects are applied (requires Liquid UI enabled)", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(bottom = 16.dp) + ) + + // Blur Effect Toggle - CAN be liquid to show the effect + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 12.dp), + horizontalArrangement = androidx.compose.foundation.layout.Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = "Blur Effect", + style = MaterialTheme.typography.bodyLarge + ) + Text( + text = "Blurs the background behind UI elements", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + + AdaptiveToggle( + checked = blurEnabled, + onCheckedChange = { enabled -> + scope.launch { + preferences.setBlurEnabled(enabled) + } + }, + preferences = preferences, + enabled = liquidUIEnabled + ) + } + + // Lens Effect Toggle + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 12.dp), + horizontalArrangement = androidx.compose.foundation.layout.Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = "Lens Effect", + style = MaterialTheme.typography.bodyLarge + ) + Text( + text = "Adds refraction lens effect (Android 13+ only)", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + + AdaptiveToggle( + checked = lensEnabled, + onCheckedChange = { enabled -> + scope.launch { + preferences.setLensEnabled(enabled) + } + }, + preferences = preferences, + enabled = liquidUIEnabled && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU + ) + } + + // Vibrancy Effect Toggle + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 12.dp), + horizontalArrangement = androidx.compose.foundation.layout.Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = "Vibrancy Effect", + style = MaterialTheme.typography.bodyLarge + ) + Text( + text = "Enhances color vibrancy of background content", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + + AdaptiveToggle( + checked = vibrancyEnabled, + onCheckedChange = { enabled -> + scope.launch { + preferences.setVibrancyEnabled(enabled) + } + }, + preferences = preferences, + enabled = liquidUIEnabled + ) + } + + // ============================================================ + // SECTION 3: INFORMATION + // ============================================================ + + Divider(modifier = Modifier.padding(vertical = 16.dp)) + + Text( + text = "Information", + style = MaterialTheme.typography.headlineSmall, + modifier = Modifier.padding(bottom = 8.dp) + ) + + InfoCard( + title = "Performance Note", + description = "Liquid glass effects work best on Android 12+. " + + "Lens effects require Android 13+. Effects are optimized " + + "to have minimal performance impact." + ) + + InfoCard( + title = "Supported Screens", + description = "Liquid UI is applied to: Player controls, " + + "Browser cards, Dialogs, Buttons, Toggles, Bottom bars, " + + "and more throughout the app." + ) + + InfoCard( + title = "Compatibility", + description = "All features work with your device. Some effects " + + "may be automatically disabled on older Android versions." + ) + } +} + +/** + * Info Card for displaying information + */ +@Composable +private fun InfoCard( + title: String, + description: String, + modifier: Modifier = Modifier +) { + androidx.compose.material3.Card( + modifier = modifier + .fillMaxWidth() + .padding(vertical = 8.dp) + ) { + Column(modifier = Modifier.padding(12.dp)) { + Text( + text = title, + style = MaterialTheme.typography.titleSmall + ) + Text( + text = description, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(top = 4.dp) + ) + } + } +} + +/** + * Preview of LiquidUISettingsScreen + */ +@Composable +fun LiquidUISettingsScreenPreview() { + // Create a mock preferences for preview + // In real app, this will be provided by DI + val mockPreferences = remember { + // This won't work in preview, but shows the structure + app.marlboroadvance.mpvex.preferences.LiquidUIPreferences( + androidx.compose.ui.platform.LocalContext.current + ) + } + + LiquidUISettingsScreen( + preferences = mockPreferences + ) +} + +// For preview +@androidx.compose.ui.tooling.preview.Preview( + showBackground = true, + backgroundColor = 0xFFFFFFFF +) +@Composable +private fun LiquidUISettingsScreenPreviewLight() { + // Note: Preview won't show real preferences in action + // This is just for UI structure visualization +} + +@androidx.compose.ui.tooling.preview.Preview( + showBackground = true, + backgroundColor = 0xFF1F1F1F +) +@Composable +private fun LiquidUISettingsScreenPreviewDark() { + // Dark mode preview +} + +// Remember helper for preview +@Composable +private fun remember(calculation: () -> T): T { + return androidx.compose.runtime.remember(calculation = calculation) +} From d346c12d162872327b42e4c380f8ec52a1b64d33 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 13:44:56 +0530 Subject: [PATCH 018/194] Update LiquidUISettingsScreen.kt --- .../settings/LiquidUISettingsScreen.kt | 58 +++---------------- 1 file changed, 7 insertions(+), 51 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/screens/settings/LiquidUISettingsScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/screens/settings/LiquidUISettingsScreen.kt index 2020688cf..a4d5ecd4e 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/screens/settings/LiquidUISettingsScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/screens/settings/LiquidUISettingsScreen.kt @@ -1,5 +1,6 @@ package app.marlboroadvance.mpvex.ui.screens.settings +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize @@ -7,6 +8,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Card import androidx.compose.material3.Divider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Switch @@ -17,7 +19,6 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel import app.marlboroadvance.mpvex.preferences.LiquidUIPreferences import app.marlboroadvance.mpvex.ui.components.liquid.AdaptiveToggle import kotlinx.coroutines.launch @@ -71,7 +72,7 @@ fun LiquidUISettingsScreen( modifier = Modifier .fillMaxWidth() .padding(vertical = 12.dp), - horizontalArrangement = androidx.compose.foundation.layout.Arrangement.SpaceBetween, + horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Text( @@ -114,7 +115,7 @@ fun LiquidUISettingsScreen( modifier = Modifier .fillMaxWidth() .padding(vertical = 12.dp), - horizontalArrangement = androidx.compose.foundation.layout.Arrangement.SpaceBetween, + horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Column(modifier = Modifier.weight(1f)) { @@ -146,7 +147,7 @@ fun LiquidUISettingsScreen( modifier = Modifier .fillMaxWidth() .padding(vertical = 12.dp), - horizontalArrangement = androidx.compose.foundation.layout.Arrangement.SpaceBetween, + horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Column(modifier = Modifier.weight(1f)) { @@ -178,7 +179,7 @@ fun LiquidUISettingsScreen( modifier = Modifier .fillMaxWidth() .padding(vertical = 12.dp), - horizontalArrangement = androidx.compose.foundation.layout.Arrangement.SpaceBetween, + horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Column(modifier = Modifier.weight(1f)) { @@ -248,7 +249,7 @@ private fun InfoCard( description: String, modifier: Modifier = Modifier ) { - androidx.compose.material3.Card( + Card( modifier = modifier .fillMaxWidth() .padding(vertical = 8.dp) @@ -267,48 +268,3 @@ private fun InfoCard( } } } - -/** - * Preview of LiquidUISettingsScreen - */ -@Composable -fun LiquidUISettingsScreenPreview() { - // Create a mock preferences for preview - // In real app, this will be provided by DI - val mockPreferences = remember { - // This won't work in preview, but shows the structure - app.marlboroadvance.mpvex.preferences.LiquidUIPreferences( - androidx.compose.ui.platform.LocalContext.current - ) - } - - LiquidUISettingsScreen( - preferences = mockPreferences - ) -} - -// For preview -@androidx.compose.ui.tooling.preview.Preview( - showBackground = true, - backgroundColor = 0xFFFFFFFF -) -@Composable -private fun LiquidUISettingsScreenPreviewLight() { - // Note: Preview won't show real preferences in action - // This is just for UI structure visualization -} - -@androidx.compose.ui.tooling.preview.Preview( - showBackground = true, - backgroundColor = 0xFF1F1F1F -) -@Composable -private fun LiquidUISettingsScreenPreviewDark() { - // Dark mode preview -} - -// Remember helper for preview -@Composable -private fun remember(calculation: () -> T): T { - return androidx.compose.runtime.remember(calculation = calculation) -} From 19d863bb8d61cfafe72e8fce821bae6d070ae457 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 13:46:12 +0530 Subject: [PATCH 019/194] Update LiquidUIToggle.kt --- .../mpvex/ui/components/liquid/LiquidUIToggle.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt index 357c6fa6e..0ad07c21f 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt @@ -13,7 +13,9 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Surface import androidx.compose.material3.Switch +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -22,6 +24,7 @@ import androidx.compose.ui.unit.dp import com.kyant.backdrop.backdrops.rememberLayerBackdrop import com.kyant.backdrop.drawBackdrop import app.marlboroadvance.mpvex.ui.theme.LiquidUIEffects +import app.marlboroadvance.mpvex.preferences.LiquidUIPreferences /** * Liquid Glass Toggle Component @@ -153,7 +156,7 @@ fun LiquidSwitch( modifier = modifier.padding(vertical = 8.dp), verticalAlignment = Alignment.CenterVertically ) { - androidx.compose.material3.Text( + Text( text = label, modifier = Modifier.weight(1f) ) @@ -187,13 +190,13 @@ fun AdaptiveToggle( checked: Boolean, onCheckedChange: (Boolean) -> Unit, modifier: Modifier = Modifier, - preferences: app.marlboroadvance.mpvex.preferences.LiquidUIPreferences, + preferences: LiquidUIPreferences, enabled: Boolean = true, label: String? = null ) { // Collect liquid UI enabled state val isLiquidUIEnabled = preferences.liquidUIEnabledFlow - .androidx.compose.runtime.collectAsState(false).value + .collectAsState(false).value if (label != null) { LiquidSwitch( From 3c3bb865d49c33a054ac437cbd31666440f65e77 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 14:17:31 +0530 Subject: [PATCH 020/194] Update PlayerControls.kt --- .../mpvex/ui/player/controls/PlayerControls.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt index ce24615e1..9c7bd1ead 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt @@ -1,5 +1,9 @@ package app.marlboroadvance.mpvex.ui.player.controls +import androidx.compose.ui.platform.LocalContext +import com.kyant.backdrop.backdrops.rememberLayerBackdrop +import app.marlboroadvance.mpvex.preferences.LiquidUIPreferences +import app.marlboroadvance.mpvex.ui.components.liquid.TransparentLiquidButton import androidx.activity.compose.LocalActivity import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.FastOutSlowInEasing @@ -157,6 +161,15 @@ fun PlayerControls( ) { val spacing = MaterialTheme.spacing val appearancePreferences = koinInject() + val context = LocalContext.current + val liquidUIPreferences = remember { LiquidUIPreferences(context) } + // Wired directly to the DataStore, defaults to false until toggled + val liquidUIEnabled by liquidUIPreferences.liquidUIEnabledFlow.collectAsState(initial = false) + + // The engine that captures the screen for the blur/lens effects + val backdrop = rememberLayerBackdrop { + drawContent() + } val hideBackground by appearancePreferences.hidePlayerButtonsBackground.collectAsState() val playerPreferences = koinInject() val audioPreferences = koinInject() From d913cd542ab5fcd5206cb62198946ca26b13230c Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 14:54:33 +0530 Subject: [PATCH 021/194] Update PlayerControls.kt --- .../ui/player/controls/PlayerControls.kt | 396 +++++++++++------- 1 file changed, 235 insertions(+), 161 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt index 9c7bd1ead..5413a5d09 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt @@ -823,74 +823,247 @@ fun PlayerControls( ) } - else -> { - val buttonShadow = - Brush.radialGradient( - 0.0f to Color.Black.copy(alpha = 0.3f), - 0.7f to Color.Transparent, - 1.0f to Color.Transparent, - ) + else -> { + if (liquidUIEnabled) { + // --- LIQUID GLASS UI --- + if (playlistMode && viewModel.hasPlaylistSupport()) { + androidx.compose.foundation.layout.Row( + horizontalArrangement = Arrangement.spacedBy(24.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + TransparentLiquidButton( + modifier = Modifier.size(56.dp), + backdrop = backdrop, + onClick = { + resetControlsTimestamp = System.currentTimeMillis() + if (viewModel.hasPrevious()) viewModel.playPrevious() + } + ) { + Icon( + imageVector = Icons.Default.SkipPrevious, + contentDescription = "Previous", + tint = if (viewModel.hasPrevious()) Color.White else Color.White.copy(alpha = 0.38f), + modifier = Modifier.fillMaxSize().padding(MaterialTheme.spacing.small) + ) + } - if (playlistMode && viewModel.hasPlaylistSupport()) { - androidx.compose.foundation.layout.Row( - horizontalArrangement = Arrangement.spacedBy(24.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Surface( - modifier = - Modifier - .size(56.dp) - .clip(CircleShape) - .clickable( - enabled = viewModel.hasPrevious(), - onClick = { - resetControlsTimestamp = System.currentTimeMillis() - if (viewModel.hasPrevious()) viewModel.playPrevious() - }, - ) - .then( - if (hideBackground) { - Modifier.background(brush = buttonShadow, shape = CircleShape) - } else { - Modifier - }, - ), - shape = CircleShape, - color = - if (!hideBackground) { - MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f) - } else { - Color.Transparent - }, - contentColor = MaterialTheme.colorScheme.onSurface, - tonalElevation = 0.dp, - shadowElevation = 0.dp, - border = - if (!hideBackground) { - BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)) - } else { - null - }, + TransparentLiquidButton( + modifier = Modifier.size(64.dp), + backdrop = backdrop, + onClick = { + resetControlsTimestamp = System.currentTimeMillis() + viewModel.pauseUnpause() + } + ) { + Image( + painter = rememberAnimatedVectorPainter(icon, paused == false), + modifier = Modifier.fillMaxSize().padding(MaterialTheme.spacing.medium), + contentDescription = "Play/Pause", + colorFilter = ColorFilter.tint(Color.White) + ) + } + + TransparentLiquidButton( + modifier = Modifier.size(56.dp), + backdrop = backdrop, + onClick = { + resetControlsTimestamp = System.currentTimeMillis() + if (viewModel.hasNext()) viewModel.playNext() + } + ) { + Icon( + imageVector = Icons.Default.SkipNext, + contentDescription = "Next", + tint = if (viewModel.hasNext()) Color.White else Color.White.copy(alpha = 0.38f), + modifier = Modifier.fillMaxSize().padding(MaterialTheme.spacing.small) + ) + } + } + } else { + TransparentLiquidButton( + modifier = Modifier.size(64.dp), + backdrop = backdrop, + onClick = { + resetControlsTimestamp = System.currentTimeMillis() + viewModel.pauseUnpause() + } + ) { + Image( + painter = rememberAnimatedVectorPainter(icon, paused == false), + modifier = Modifier.fillMaxSize().padding(MaterialTheme.spacing.medium), + contentDescription = "Play/Pause", + colorFilter = ColorFilter.tint(Color.White) + ) + } + } + } else { + // --- STANDARD FALLBACK UI (EXACTLY AS IT WAS) --- + val buttonShadow = + Brush.radialGradient( + 0.0f to Color.Black.copy(alpha = 0.3f), + 0.7f to Color.Transparent, + 1.0f to Color.Transparent, + ) + + if (playlistMode && viewModel.hasPlaylistSupport()) { + androidx.compose.foundation.layout.Row( + horizontalArrangement = Arrangement.spacedBy(24.dp), + verticalAlignment = Alignment.CenterVertically, ) { - Icon( - imageVector = Icons.Default.SkipPrevious, - contentDescription = "Previous", - tint = - if (viewModel.hasPrevious()) { - if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface + Surface( + modifier = + Modifier + .size(56.dp) + .clip(CircleShape) + .clickable( + enabled = viewModel.hasPrevious(), + onClick = { + resetControlsTimestamp = System.currentTimeMillis() + if (viewModel.hasPrevious()) viewModel.playPrevious() + }, + ) + .then( + if (hideBackground) { + Modifier.background(brush = buttonShadow, shape = CircleShape) + } else { + Modifier + }, + ), + shape = CircleShape, + color = + if (!hideBackground) { + MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f) } else { - if (hideBackground) { - controlColor.copy(alpha = 0.38f) + Color.Transparent + }, + contentColor = MaterialTheme.colorScheme.onSurface, + tonalElevation = 0.dp, + shadowElevation = 0.dp, + border = + if (!hideBackground) { + BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)) + } else { + null + }, + ) { + Icon( + imageVector = Icons.Default.SkipPrevious, + contentDescription = "Previous", + tint = + if (viewModel.hasPrevious()) { + if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface } else { - MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) - } + if (hideBackground) { + controlColor.copy(alpha = 0.38f) + } else { + MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) + } + }, + modifier = Modifier + .fillMaxSize() + .padding(MaterialTheme.spacing.small), + ) + } + + Surface( + modifier = + Modifier + .size(64.dp) + .clip(CircleShape) + .clickable(interaction, ripple(), onClick = { + resetControlsTimestamp = System.currentTimeMillis() + viewModel.pauseUnpause() + }) + .then( + if (hideBackground) { + Modifier.background(brush = buttonShadow, shape = CircleShape) + } else { + Modifier + }, + ), + shape = CircleShape, + color = + if (!hideBackground) { + MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f) + } else { + Color.Transparent }, - modifier = Modifier - .fillMaxSize() - .padding(MaterialTheme.spacing.small), - ) - } + contentColor = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, + tonalElevation = 0.dp, + shadowElevation = 0.dp, + border = + if (!hideBackground) { + BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)) + } else { + null + }, + ) { + Image( + painter = rememberAnimatedVectorPainter(icon, paused == false), + modifier = Modifier + .fillMaxSize() + .padding(MaterialTheme.spacing.medium), + contentDescription = null, + colorFilter = ColorFilter.tint(LocalContentColor.current), + ) + } + Surface( + modifier = + Modifier + .size(56.dp) + .clip(CircleShape) + .clickable( + enabled = viewModel.hasNext(), + onClick = { + resetControlsTimestamp = System.currentTimeMillis() + if (viewModel.hasNext()) viewModel.playNext() + }, + ) + .then( + if (hideBackground) { + Modifier.background(brush = buttonShadow, shape = CircleShape) + } else { + Modifier + }, + ), + shape = CircleShape, + color = + if (!hideBackground) { + MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f) + } else { + Color.Transparent + }, + contentColor = MaterialTheme.colorScheme.onSurface, + tonalElevation = 0.dp, + shadowElevation = 0.dp, + border = + if (!hideBackground) { + BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)) + } else { + null + }, + ) { + Icon( + imageVector = Icons.Default.SkipNext, + contentDescription = "Next", + tint = + if (viewModel.hasNext()) { + if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface + } else { + if (hideBackground) { + controlColor.copy(alpha = 0.38f) + } else { + MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) + } + }, + modifier = Modifier + .fillMaxSize() + .padding(MaterialTheme.spacing.small), + ) + } + } + } else { Surface( modifier = Modifier @@ -931,105 +1104,6 @@ fun PlayerControls( .padding(MaterialTheme.spacing.medium), contentDescription = null, colorFilter = ColorFilter.tint(LocalContentColor.current), - ) - } - - Surface( - modifier = - Modifier - .size(56.dp) - .clip(CircleShape) - .clickable( - enabled = viewModel.hasNext(), - onClick = { - resetControlsTimestamp = System.currentTimeMillis() - if (viewModel.hasNext()) viewModel.playNext() - }, - ) - .then( - if (hideBackground) { - Modifier.background(brush = buttonShadow, shape = CircleShape) - } else { - Modifier - }, - ), - shape = CircleShape, - color = - if (!hideBackground) { - MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f) - } else { - Color.Transparent - }, - contentColor = MaterialTheme.colorScheme.onSurface, - tonalElevation = 0.dp, - shadowElevation = 0.dp, - border = - if (!hideBackground) { - BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)) - } else { - null - }, - ) { - Icon( - imageVector = Icons.Default.SkipNext, - contentDescription = "Next", - tint = - if (viewModel.hasNext()) { - if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface - } else { - if (hideBackground) { - controlColor.copy(alpha = 0.38f) - } else { - MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) - } - }, - modifier = Modifier - .fillMaxSize() - .padding(MaterialTheme.spacing.small), - ) - } - } - } else { - Surface( - modifier = - Modifier - .size(64.dp) - .clip(CircleShape) - .clickable(interaction, ripple(), onClick = { - resetControlsTimestamp = System.currentTimeMillis() - viewModel.pauseUnpause() - }) - .then( - if (hideBackground) { - Modifier.background(brush = buttonShadow, shape = CircleShape) - } else { - Modifier - }, - ), - shape = CircleShape, - color = - if (!hideBackground) { - MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f) - } else { - Color.Transparent - }, - contentColor = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, - tonalElevation = 0.dp, - shadowElevation = 0.dp, - border = - if (!hideBackground) { - BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)) - } else { - null - }, - ) { - Image( - painter = rememberAnimatedVectorPainter(icon, paused == false), - modifier = Modifier - .fillMaxSize() - .padding(MaterialTheme.spacing.medium), - contentDescription = null, - colorFilter = ColorFilter.tint(LocalContentColor.current), ) } } From 6cd7eb0355869dae0c23a921c18e50da73caa009 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 15:01:11 +0530 Subject: [PATCH 022/194] Update PlayerControls.kt --- .../marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt index 5413a5d09..61e996a07 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt @@ -1110,6 +1110,7 @@ fun PlayerControls( } } } + } AnimatedVisibility( visible = controlsShown && !areControlsLocked, From 8bbee599d1c38ca22ad4eca7f13cbee2f2f155a1 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 15:25:13 +0530 Subject: [PATCH 023/194] Update AppearancePreferencesScreen.kt --- .../AppearancePreferencesScreen.kt | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt index f2c5074f1..cc63f2f38 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt @@ -1,5 +1,9 @@ package app.marlboroadvance.mpvex.ui.preferences +import androidx.compose.ui.platform.LocalContext +import androidx.compose.runtime.rememberCoroutineScope +import app.marlboroadvance.mpvex.preferences.LiquidUIPreferences +import kotlinx.coroutines.launch import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -47,6 +51,10 @@ object AppearancePreferencesScreen : Screen { @Composable override fun Content() { val preferences = koinInject() + val context = LocalContext.current + val liquidPreferences = remember { LiquidUIPreferences(context) } + val isLiquidUIEnabled by liquidPreferences.liquidUIEnabledFlow.collectAsState(initial = false) + val scope = rememberCoroutineScope() val browserPreferences = koinInject() val gesturePreferences = koinInject() val backstack = LocalBackStack.current @@ -120,7 +128,28 @@ object AppearancePreferencesScreen : Screen { modifier = Modifier.padding(vertical = 8.dp), ) + // --- LIQUID GLASS UI MASTER TOGGLE --- + SwitchPreference( + value = isLiquidUIEnabled, + onValueChange = { enabled -> + scope.launch { liquidPreferences.setLiquidUIEnabled(enabled) } + }, + title = { + Text( + text = "Enable Liquid Glass UI", + fontWeight = FontWeight.Bold + ) + }, + summary = { + Text( + text = "Transform player controls and components with modern transparent glass effects", + color = MaterialTheme.colorScheme.outline, + ) + } + ) + PreferenceDivider() + // ----------------------------- // AMOLED mode toggle SwitchPreference( From 9cdcb78cd51dbdb9652416c0963a6cad22241ac3 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 15:28:15 +0530 Subject: [PATCH 024/194] Update AppearancePreferencesScreen.kt --- .../mpvex/ui/preferences/AppearancePreferencesScreen.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt index cc63f2f38..249503adf 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt @@ -1,5 +1,7 @@ package app.marlboroadvance.mpvex.ui.preferences +import androidx.compose.runtime.remember +import androidx.compose.runtime.collectAsState import androidx.compose.ui.platform.LocalContext import androidx.compose.runtime.rememberCoroutineScope import app.marlboroadvance.mpvex.preferences.LiquidUIPreferences From f4971f9d81d5859b5d59b7d1855fe2132c559b39 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 18:04:13 +0530 Subject: [PATCH 025/194] Update FileSystemBrowserScreen.kt --- .../filesystem/FileSystemBrowserScreen.kt | 120 +++++++++++------- 1 file changed, 75 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt index 48258b76b..cf8fe6045 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt @@ -1,5 +1,8 @@ package app.marlboroadvance.mpvex.ui.browser.filesystem +import app.marlboroadvance.mpvex.preferences.LiquidUIPreferences +import app.marlboroadvance.mpvex.ui.components.liquid.LiquidGlassCard +import com.kyant.backdrop.backdrops.rememberLayerBackdrop import android.content.Context import android.content.Intent import android.util.Log @@ -1165,6 +1168,14 @@ private fun FileSystemBrowserContent( isInSelectionMode: Boolean = false, ) { val gesturePreferences = koinInject() + val context = LocalContext.current + val liquidPreferences = remember { LiquidUIPreferences(context) } + val liquidUIEnabled by liquidPreferences.liquidUIEnabledFlow.collectAsState(initial = false) + + // The engine that captures the screen behind the cards + val backdrop = rememberLayerBackdrop { + drawContent() + } val browserPreferences = koinInject() val thumbnailRepository = koinInject() val tapThumbnailToSelect by gesturePreferences.tapThumbnailToSelect.collectAsState() @@ -1303,19 +1314,24 @@ private fun FileSystemBrowserContent( lastModified = folder.lastModified / 1000, ) - FolderCard( - folder = folderModel, - isSelected = folderSelectionManager.isSelected(folder), - isRecentlyPlayed = false, - onClick = { onFolderClick(folder) }, - onLongClick = { onFolderLongClick(folder) }, - onThumbClick = if (tapThumbnailToSelect) { - { onFolderLongClick(folder) } - } else { - { onFolderClick(folder) } - }, - isGridMode = false, - ) + LiquidGlassCard( + backdrop = if (liquidUIEnabled) backdrop else null, + modifier = Modifier.padding(vertical = 4.dp) + ) { + FolderCard( + folder = folderModel, + isSelected = folderSelectionManager.isSelected(folder), + isRecentlyPlayed = false, + onClick = { onFolderClick(folder) }, + onLongClick = { onFolderLongClick(folder) }, + onThumbClick = if (tapThumbnailToSelect) { + { onFolderLongClick(folder) } + } else { + { onFolderClick(folder) } + }, + isGridMode = false, + ) + } } // Videos second @@ -1323,24 +1339,29 @@ private fun FileSystemBrowserContent( items = items.filterIsInstance(), key = { "${it.video.id}_${it.video.path}" }, ) { videoFile -> - VideoCard( - video = videoFile.video, - progressPercentage = videoFilesWithPlayback[videoFile.video.id], - isRecentlyPlayed = false, - isSelected = videoSelectionManager.isSelected(videoFile.video), - onClick = { onVideoClick(videoFile.video) }, - onLongClick = { onVideoLongClick(videoFile.video) }, - onThumbClick = if (tapThumbnailToSelect) { - { onVideoLongClick(videoFile.video) } - } else { - { onVideoClick(videoFile.video) } - }, - isGridMode = false, - showSubtitleIndicator = showSubtitleIndicator, - overrideShowSizeChip = null, - overrideShowResolutionChip = null, - useFolderNameStyle = false, - ) + LiquidGlassCard( + backdrop = if (liquidUIEnabled) backdrop else null, + modifier = Modifier.padding(vertical = 4.dp) + ) { + VideoCard( + video = videoFile.video, + progressPercentage = videoFilesWithPlayback[videoFile.video.id], + isRecentlyPlayed = false, + isSelected = videoSelectionManager.isSelected(videoFile.video), + onClick = { onVideoClick(videoFile.video) }, + onLongClick = { onVideoLongClick(videoFile.video) }, + onThumbClick = if (tapThumbnailToSelect) { + { onVideoLongClick(videoFile.video) } + } else { + { onVideoClick(videoFile.video) } + }, + isGridMode = false, + showSubtitleIndicator = showSubtitleIndicator, + overrideShowSizeChip = null, + overrideShowResolutionChip = null, + useFolderNameStyle = false, + ) + } } } @@ -1501,20 +1522,29 @@ private fun FileSystemSearchContent( items = videos, key = { "search_video_${it.video.id}_${it.video.path}_${it.hashCode()}" }, ) { videoFile -> - VideoCard( - video = videoFile.video, - progressPercentage = videoFilesWithPlayback[videoFile.video.id], - isRecentlyPlayed = false, - isSelected = false, - onClick = { onVideoClick(videoFile.video) }, - onLongClick = { }, - onThumbClick = { onVideoClick(videoFile.video) }, - isGridMode = false, - showSubtitleIndicator = showSubtitleIndicator, - overrideShowSizeChip = null, - overrideShowResolutionChip = null, - useFolderNameStyle = false, - ) + LiquidGlassCard( + backdrop = if (liquidUIEnabled) backdrop else null, + modifier = Modifier.padding(vertical = 4.dp) + ) { + VideoCard( + video = videoFile.video, + progressPercentage = videoFilesWithPlayback[videoFile.video.id], + isRecentlyPlayed = false, + isSelected = videoSelectionManager.isSelected(videoFile.video), + onClick = { onVideoClick(videoFile.video) }, + onLongClick = { onVideoLongClick(videoFile.video) }, + onThumbClick = if (tapThumbnailToSelect) { + { onVideoLongClick(videoFile.video) } + } else { + { onVideoClick(videoFile.video) } + }, + isGridMode = false, + showSubtitleIndicator = showSubtitleIndicator, + overrideShowSizeChip = null, + overrideShowResolutionChip = null, + useFolderNameStyle = false, + ) + } } } From f944dbcab9f7447c818d342312e97423a2a04507 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 18:54:52 +0530 Subject: [PATCH 026/194] Update FileSystemBrowserScreen.kt --- .../filesystem/FileSystemBrowserScreen.kt | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt index cf8fe6045..8a82da8ff 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt @@ -1405,6 +1405,10 @@ private fun FileSystemSearchContent( val gesturePreferences = koinInject() val browserPreferences = koinInject() val tapThumbnailToSelect by gesturePreferences.tapThumbnailToSelect.collectAsState() + val context = LocalContext.current + val liquidPreferences = remember { LiquidUIPreferences(context) } + val liquidUIEnabled by liquidPreferences.liquidUIEnabledFlow.collectAsState(initial = false) + val backdrop = rememberLayerBackdrop { drawContent() } // Track scroll for FAB visibility in search mode with proper scroll direction detection val previousIndex = remember { mutableIntStateOf(0) } @@ -1506,23 +1510,27 @@ private fun FileSystemSearchContent( lastModified = folder.lastModified / 1000, ) - FolderCard( - folder = folderModel, - isSelected = false, - isRecentlyPlayed = false, - onClick = { onFolderClick(folder) }, - onLongClick = { }, - onThumbClick = { onFolderClick(folder) }, - isGridMode = false, - ) - } + LiquidGlassCard( + backdrop = if (liquidUIEnabled) backdrop else null, + modifier = Modifier.padding(vertical = 4.dp) + ) { + FolderCard( + folder = folderModel, + isSelected = false, // Search doesn't use selection manager + isRecentlyPlayed = false, + onClick = { onFolderClick(folder) }, + onLongClick = { }, + onThumbClick = { onFolderClick(folder) }, + isGridMode = false, + ) + } // Videos second items( items = videos, key = { "search_video_${it.video.id}_${it.video.path}_${it.hashCode()}" }, ) { videoFile -> - LiquidGlassCard( + LiquidGlassCard( backdrop = if (liquidUIEnabled) backdrop else null, modifier = Modifier.padding(vertical = 4.dp) ) { @@ -1530,14 +1538,10 @@ private fun FileSystemSearchContent( video = videoFile.video, progressPercentage = videoFilesWithPlayback[videoFile.video.id], isRecentlyPlayed = false, - isSelected = videoSelectionManager.isSelected(videoFile.video), + isSelected = false, // Search doesn't use selection manager onClick = { onVideoClick(videoFile.video) }, - onLongClick = { onVideoLongClick(videoFile.video) }, - onThumbClick = if (tapThumbnailToSelect) { - { onVideoLongClick(videoFile.video) } - } else { - { onVideoClick(videoFile.video) } - }, + onLongClick = { }, + onThumbClick = { onVideoClick(videoFile.video) }, isGridMode = false, showSubtitleIndicator = showSubtitleIndicator, overrideShowSizeChip = null, From 8e26eec4b3599ffccccf2ae0bd9efbe643573fd1 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 19:05:35 +0530 Subject: [PATCH 027/194] Update FileSystemBrowserScreen.kt --- .../browser/filesystem/FileSystemBrowserScreen.kt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt index 8a82da8ff..65804d736 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt @@ -1314,7 +1314,7 @@ private fun FileSystemBrowserContent( lastModified = folder.lastModified / 1000, ) - LiquidGlassCard( + LiquidGlassCard( backdrop = if (liquidUIEnabled) backdrop else null, modifier = Modifier.padding(vertical = 4.dp) ) { @@ -1363,7 +1363,6 @@ private fun FileSystemBrowserContent( ) } } - } // Scrollbar with bottom padding to avoid overlap with navigation Box( @@ -1510,13 +1509,13 @@ private fun FileSystemSearchContent( lastModified = folder.lastModified / 1000, ) - LiquidGlassCard( + LiquidGlassCard( backdrop = if (liquidUIEnabled) backdrop else null, modifier = Modifier.padding(vertical = 4.dp) ) { FolderCard( folder = folderModel, - isSelected = false, // Search doesn't use selection manager + isSelected = false, isRecentlyPlayed = false, onClick = { onFolderClick(folder) }, onLongClick = { }, @@ -1524,13 +1523,14 @@ private fun FileSystemSearchContent( isGridMode = false, ) } + } // Videos second items( items = videos, key = { "search_video_${it.video.id}_${it.video.path}_${it.hashCode()}" }, ) { videoFile -> - LiquidGlassCard( + LiquidGlassCard( backdrop = if (liquidUIEnabled) backdrop else null, modifier = Modifier.padding(vertical = 4.dp) ) { @@ -1538,7 +1538,7 @@ private fun FileSystemSearchContent( video = videoFile.video, progressPercentage = videoFilesWithPlayback[videoFile.video.id], isRecentlyPlayed = false, - isSelected = false, // Search doesn't use selection manager + isSelected = false, onClick = { onVideoClick(videoFile.video) }, onLongClick = { }, onThumbClick = { onVideoClick(videoFile.video) }, @@ -1550,7 +1550,6 @@ private fun FileSystemSearchContent( ) } } - } // Scrollbar with bottom padding to avoid overlap with navigation Box( From 857fc124dbda5d1972610f9342969fb61cfd18d4 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 19:13:20 +0530 Subject: [PATCH 028/194] Update FileSystemBrowserScreen.kt --- .../mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt index 65804d736..99510b078 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt @@ -1363,6 +1363,7 @@ private fun FileSystemBrowserContent( ) } } + } // Scrollbar with bottom padding to avoid overlap with navigation Box( @@ -1550,6 +1551,7 @@ private fun FileSystemSearchContent( ) } } + } // Scrollbar with bottom padding to avoid overlap with navigation Box( From 581b16926feab20de0c936fd8cfa731b7b4398cd Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 19:30:36 +0530 Subject: [PATCH 029/194] Update LiquidComponents.kt --- .../ui/components/liquid/LiquidComponents.kt | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt index 1121644d2..43a914341 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt @@ -63,24 +63,23 @@ fun LiquidGlassCard( onClick: () -> Unit = {}, content: @Composable () -> Unit ) { + // If liquid UI is off, just draw a transparent box so the inner card works normally if (backdrop == null) { - Card(modifier = modifier.clickable { onClick() }) { content() } + androidx.compose.foundation.layout.Box(modifier = modifier) { + content() + } return } - Card( + // If liquid UI is on, apply the effects engine to a transparent box + androidx.compose.foundation.layout.Box( modifier = modifier - .clickable { onClick() } - .drawBackdrop( + .applyLiquidEffects( backdrop = backdrop, - shape = { RoundedCornerShape(12.dp) }, - effects = LiquidUIEffects.glassCardEffects(), - onDrawSurface = { drawRect(LiquidUIEffects.glassSurfaceColor) } - ), - shape = RoundedCornerShape(12.dp) + shape = RoundedCornerShape(12.dp), + config = LiquidEffectConfig(LiquidEffectType.CARD) + ) ) { - Box(modifier = Modifier.fillMaxSize()) { - content() - } + content() } } From 39c1a9b60dfe45bee9be79f9d4fc6eb337094dc0 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 19:34:57 +0530 Subject: [PATCH 030/194] Update LiquidComponents.kt --- .../mpvex/ui/components/liquid/LiquidComponents.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt index 43a914341..870dd490e 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt @@ -14,6 +14,9 @@ import androidx.compose.ui.unit.dp import com.kyant.backdrop.Backdrop import com.kyant.backdrop.drawBackdrop import app.marlboroadvance.mpvex.ui.theme.LiquidUIEffects +import app.marlboroadvance.mpvex.ui.theme.applyLiquidEffects +import app.marlboroadvance.mpvex.ui.theme.LiquidEffectConfig +import app.marlboroadvance.mpvex.ui.theme.LiquidEffectType /** * Reusable Transparent Liquid Button From 16beeff0ce580def656da737edfc60cdd000d0a5 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 19:40:29 +0530 Subject: [PATCH 031/194] Update LiquidComponents.kt --- .../mpvex/ui/components/liquid/LiquidComponents.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt index 870dd490e..5b2885e25 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt @@ -14,9 +14,8 @@ import androidx.compose.ui.unit.dp import com.kyant.backdrop.Backdrop import com.kyant.backdrop.drawBackdrop import app.marlboroadvance.mpvex.ui.theme.LiquidUIEffects -import app.marlboroadvance.mpvex.ui.theme.applyLiquidEffects -import app.marlboroadvance.mpvex.ui.theme.LiquidEffectConfig -import app.marlboroadvance.mpvex.ui.theme.LiquidEffectType +import com.kyant.backdrop.drawBackdrop +import app.marlboroadvance.mpvex.ui.theme.LiquidUIEffects /** * Reusable Transparent Liquid Button @@ -62,7 +61,7 @@ fun TransparentLiquidButton( @Composable fun LiquidGlassCard( modifier: Modifier = Modifier, - backdrop: Backdrop?, + backdrop: com.kyant.backdrop.Backdrop?, onClick: () -> Unit = {}, content: @Composable () -> Unit ) { @@ -77,10 +76,11 @@ fun LiquidGlassCard( // If liquid UI is on, apply the effects engine to a transparent box androidx.compose.foundation.layout.Box( modifier = modifier - .applyLiquidEffects( + .drawBackdrop( backdrop = backdrop, - shape = RoundedCornerShape(12.dp), - config = LiquidEffectConfig(LiquidEffectType.CARD) + shape = { RoundedCornerShape(12.dp) }, + effects = LiquidUIEffects.glassCardEffects(enableBlur = true), + onDrawSurface = { drawRect(LiquidUIEffects.glassSurfaceColor) } ) ) { content() From 1bf01a41c715911fc64ff52f3a87a32afc33464b Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 19:41:08 +0530 Subject: [PATCH 032/194] Update LiquidComponents.kt --- .../mpvex/ui/components/liquid/LiquidComponents.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt index 5b2885e25..268a4fd99 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt @@ -15,7 +15,6 @@ import com.kyant.backdrop.Backdrop import com.kyant.backdrop.drawBackdrop import app.marlboroadvance.mpvex.ui.theme.LiquidUIEffects import com.kyant.backdrop.drawBackdrop -import app.marlboroadvance.mpvex.ui.theme.LiquidUIEffects /** * Reusable Transparent Liquid Button From 4199eead682bfeaf6c60eafa51b31fee6f55d684 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 20:31:41 +0530 Subject: [PATCH 033/194] Update LiquidUIPreferences.kt --- .../mpvex/preferences/LiquidUIPreferences.kt | 56 ++++++++----------- 1 file changed, 23 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt b/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt index 3cc4da026..188b59a64 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt @@ -2,6 +2,7 @@ package app.marlboroadvance.mpvex.preferences import android.content.Context import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.longPreferencesKey import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.preferencesDataStore import kotlinx.coroutines.flow.Flow @@ -17,37 +18,26 @@ class LiquidUIPreferences(context: Context) { val LIQUID_BLUR_ENABLED = booleanPreferencesKey("liquid_blur_enabled") val LIQUID_LENS_ENABLED = booleanPreferencesKey("liquid_lens_enabled") val LIQUID_VIBRANCY_ENABLED = booleanPreferencesKey("liquid_vibrancy_enabled") - } - - val liquidUIEnabledFlow: Flow = dataStore.data.map { prefs -> - prefs[LIQUID_UI_ENABLED] ?: false - } - - val liquidBlurEnabledFlow: Flow = dataStore.data.map { prefs -> - prefs[LIQUID_BLUR_ENABLED] ?: true - } - - val liquidLensEnabledFlow: Flow = dataStore.data.map { prefs -> - prefs[LIQUID_LENS_ENABLED] ?: true - } - - val liquidVibrancyEnabledFlow: Flow = dataStore.data.map { prefs -> - prefs[LIQUID_VIBRANCY_ENABLED] ?: true - } - - suspend fun setLiquidUIEnabled(enabled: Boolean) { - dataStore.edit { it[LIQUID_UI_ENABLED] = enabled } - } - - suspend fun setBlurEnabled(enabled: Boolean) { - dataStore.edit { it[LIQUID_BLUR_ENABLED] = enabled } - } - - suspend fun setLensEnabled(enabled: Boolean) { - dataStore.edit { it[LIQUID_LENS_ENABLED] = enabled } - } - - suspend fun setVibrancyEnabled(enabled: Boolean) { - dataStore.edit { it[LIQUID_VIBRANCY_ENABLED] = enabled } - } + + // Custom Color Keys + val LIQUID_TOGGLE_COLOR = longPreferencesKey("liquid_toggle_color") + val LIQUID_SLIDER_COLOR = longPreferencesKey("liquid_slider_color") + } + + val liquidUIEnabledFlow: Flow = dataStore.data.map { it[LIQUID_UI_ENABLED] ?: false } + val liquidBlurEnabledFlow: Flow = dataStore.data.map { it[LIQUID_BLUR_ENABLED] ?: true } + val liquidLensEnabledFlow: Flow = dataStore.data.map { it[LIQUID_LENS_ENABLED] ?: true } + val liquidVibrancyEnabledFlow: Flow = dataStore.data.map { it[LIQUID_VIBRANCY_ENABLED] ?: true } + + // Default: Green (0xFF4CAF50) for toggles, Blue (0xFF2196F3) for sliders + val liquidToggleColorFlow: Flow = dataStore.data.map { it[LIQUID_TOGGLE_COLOR] ?: 0xFF4CAF50 } + val liquidSliderColorFlow: Flow = dataStore.data.map { it[LIQUID_SLIDER_COLOR] ?: 0xFF2196F3 } + + suspend fun setLiquidUIEnabled(enabled: Boolean) { dataStore.edit { it[LIQUID_UI_ENABLED] = enabled } } + suspend fun setBlurEnabled(enabled: Boolean) { dataStore.edit { it[LIQUID_BLUR_ENABLED] = enabled } } + suspend fun setLensEnabled(enabled: Boolean) { dataStore.edit { it[LIQUID_LENS_ENABLED] = enabled } } + suspend fun setVibrancyEnabled(enabled: Boolean) { dataStore.edit { it[LIQUID_VIBRANCY_ENABLED] = enabled } } + + suspend fun setToggleColor(color: Long) { dataStore.edit { it[LIQUID_TOGGLE_COLOR] = color } } + suspend fun setSliderColor(color: Long) { dataStore.edit { it[LIQUID_SLIDER_COLOR] = color } } } From 76280c0b57f2aae907ed651a8c428f87c6bd028e Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 20:48:15 +0530 Subject: [PATCH 034/194] Update LiquidUIToggle.kt --- .../ui/components/liquid/LiquidUIToggle.kt | 42 +++++++------------ 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt index 0ad07c21f..f6fba6d32 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt @@ -137,6 +137,7 @@ fun LiquidSwitch( modifier: Modifier = Modifier, enabled: Boolean = true, label: String? = null, + checkedColor: Color = Color(0xFF4CAF50), // Added color support! applyLiquidEffect: Boolean = true ) { if (label == null) { @@ -146,6 +147,7 @@ fun LiquidSwitch( onCheckedChange = onCheckedChange, modifier = modifier, enabled = enabled, + checkedColor = checkedColor, applyLiquidEffect = applyLiquidEffect ) return @@ -156,7 +158,7 @@ fun LiquidSwitch( modifier = modifier.padding(vertical = 8.dp), verticalAlignment = Alignment.CenterVertically ) { - Text( + androidx.compose.material3.Text( text = label, modifier = Modifier.weight(1f) ) @@ -167,6 +169,7 @@ fun LiquidSwitch( checked = checked, onCheckedChange = onCheckedChange, enabled = enabled, + checkedColor = checkedColor, applyLiquidEffect = applyLiquidEffect ) } @@ -174,29 +177,20 @@ fun LiquidSwitch( /** * Adaptive Toggle - Automatically uses liquid or standard based on settings - * - * This is the recommended component to use throughout the app. - * It automatically adapts to the liquid UI preference setting. - * - * @param checked Whether the toggle is ON - * @param onCheckedChange Callback when state changes - * @param modifier Modifier - * @param preferences LiquidUIPreferences to check liquid UI setting - * @param enabled Whether the toggle is interactive - * @param label Optional label text */ @Composable fun AdaptiveToggle( checked: Boolean, onCheckedChange: (Boolean) -> Unit, modifier: Modifier = Modifier, - preferences: LiquidUIPreferences, + preferences: app.marlboroadvance.mpvex.preferences.LiquidUIPreferences, enabled: Boolean = true, label: String? = null ) { - // Collect liquid UI enabled state - val isLiquidUIEnabled = preferences.liquidUIEnabledFlow - .collectAsState(false).value + // Collect liquid UI enabled state and custom color + val isLiquidUIEnabled = preferences.liquidUIEnabledFlow.androidx.compose.runtime.collectAsState(false).value + val toggleColorLong = preferences.liquidToggleColorFlow.androidx.compose.runtime.collectAsState(0xFF4CAF50).value + val customColor = Color(toggleColorLong) if (label != null) { LiquidSwitch( @@ -205,6 +199,7 @@ fun AdaptiveToggle( modifier = modifier, enabled = enabled, label = label, + checkedColor = customColor, applyLiquidEffect = isLiquidUIEnabled ) } else { @@ -213,6 +208,7 @@ fun AdaptiveToggle( onCheckedChange = onCheckedChange, modifier = modifier, enabled = enabled, + checkedColor = customColor, applyLiquidEffect = isLiquidUIEnabled ) } @@ -220,16 +216,6 @@ fun AdaptiveToggle( /** * Simple Liquid Toggle (no adaptation) - * - * Use this when you're already checking liquid UI settings elsewhere - * and want to directly control whether liquid effect is shown. - * - * @param checked Toggle state - * @param onCheckedChange State change callback - * @param modifier Modifier - * @param enabled Interactive state - * @param isLiquidUI Whether to show liquid glass version (not adaptive) - * @param label Optional label */ @Composable fun SimpleToggle( @@ -238,7 +224,8 @@ fun SimpleToggle( modifier: Modifier = Modifier, enabled: Boolean = true, isLiquidUI: Boolean = true, - label: String? = null + label: String? = null, + checkedColor: Color = Color(0xFF4CAF50) ) { if (label != null) { LiquidSwitch( @@ -247,6 +234,7 @@ fun SimpleToggle( modifier = modifier, enabled = enabled, label = label, + checkedColor = checkedColor, applyLiquidEffect = isLiquidUI ) } else { @@ -255,7 +243,9 @@ fun SimpleToggle( onCheckedChange = onCheckedChange, modifier = modifier, enabled = enabled, + checkedColor = checkedColor, applyLiquidEffect = isLiquidUI ) } } +} From 39b56b60e16fb5842d95c3a9a11ad416f92e4bd7 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 21:07:43 +0530 Subject: [PATCH 035/194] Update LiquidUISettingsScreen.kt --- .../settings/LiquidUISettingsScreen.kt | 98 ++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/screens/settings/LiquidUISettingsScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/screens/settings/LiquidUISettingsScreen.kt index a4d5ecd4e..824ba1508 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/screens/settings/LiquidUISettingsScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/screens/settings/LiquidUISettingsScreen.kt @@ -81,9 +81,105 @@ fun LiquidUISettingsScreen( ) // Use standard switch for the master toggle (meta-UI) - Switch( + AdaptiveToggle( checked = liquidUIEnabled, onCheckedChange = { enabled -> + scope.launch { preferences.setLiquidUIEnabled(enabled) } + }, + preferences = preferences + ) + } + + // ============================================================ + // SECTION 1.5: CUSTOM COLORS (DYNAMIC INPUT) + // ============================================================ + + AnimatedVisibility(visible = liquidUIEnabled) { + Column { + Divider(modifier = Modifier.padding(vertical = 16.dp)) + + Text( + text = "Custom Colors", + style = MaterialTheme.typography.headlineSmall, + modifier = Modifier.padding(bottom = 8.dp) + ) + + Text( + text = "Type a hex code (e.g. #FF5733) or a basic color name (e.g. red, blue, cyan)", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(bottom = 16.dp) + ) + + val toggleColor = preferences.liquidToggleColorFlow.collectAsState(0xFF4CAF50).value + val sliderColor = preferences.liquidSliderColorFlow.collectAsState(0xFF2196F3).value + + // Smart parser that reads names ("red") or hex codes ("#FF0000" or "FF0000") + fun parseColorInput(input: String): Long? { + return try { + val formatted = if (input.matches(Regex("^[0-9A-Fa-f]{6,8}$"))) "#$input" else input + val colorInt = android.graphics.Color.parseColor(formatted) + colorInt.toLong() and 0xFFFFFFFFL + } catch (e: Exception) { null } + } + + // --- TOGGLE COLOR INPUT --- + var toggleInputText by remember(toggleColor) { + mutableStateOf(String.format("#%06X", 0xFFFFFF and toggleColor.toInt())) + } + + androidx.compose.material3.OutlinedTextField( + value = toggleInputText, + onValueChange = { newValue -> + toggleInputText = newValue + // Only save to database if the color is valid! + parseColorInput(newValue)?.let { colorLong -> + scope.launch { preferences.setToggleColor(colorLong) } + } + }, + label = { Text("Toggle Color") }, + placeholder = { Text("e.g. #FF5733 or blue") }, + modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp), + singleLine = true, + leadingIcon = { + Box( + modifier = Modifier + .size(24.dp) + .clip(androidx.compose.foundation.shape.CircleShape) + .background(androidx.compose.ui.graphics.Color(toggleColor)) + ) + } + ) + + // --- SLIDER COLOR INPUT --- + var sliderInputText by remember(sliderColor) { + mutableStateOf(String.format("#%06X", 0xFFFFFF and sliderColor.toInt())) + } + + androidx.compose.material3.OutlinedTextField( + value = sliderInputText, + onValueChange = { newValue -> + sliderInputText = newValue + // Only save to database if the color is valid! + parseColorInput(newValue)?.let { colorLong -> + scope.launch { preferences.setSliderColor(colorLong) } + } + }, + label = { Text("Slider Color") }, + placeholder = { Text("e.g. #00FF00 or cyan") }, + modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp), + singleLine = true, + leadingIcon = { + Box( + modifier = Modifier + .size(24.dp) + .clip(androidx.compose.foundation.shape.CircleShape) + .background(androidx.compose.ui.graphics.Color(sliderColor)) + ) + } + ) + } + } scope.launch { preferences.setLiquidUIEnabled(enabled) } From a8f90e2d5db4c9c79dfda5095a5a5acf68fdce45 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 21:24:23 +0530 Subject: [PATCH 036/194] Update LiquidUIToggle.kt --- .../ui/components/liquid/LiquidUIToggle.kt | 59 ++----------------- 1 file changed, 6 insertions(+), 53 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt index f6fba6d32..f11dbb8b5 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt @@ -26,27 +26,6 @@ import com.kyant.backdrop.drawBackdrop import app.marlboroadvance.mpvex.ui.theme.LiquidUIEffects import app.marlboroadvance.mpvex.preferences.LiquidUIPreferences -/** - * Liquid Glass Toggle Component - * - * A beautiful animated toggle with liquid glass effect. - * When liquid UI is disabled in preferences, falls back to standard Material Switch. - * - * Features: - * - Smooth animation between states - * - Liquid glass effect with customizable blur and lens - * - iOS-like appearance - * - Full accessibility support - * - * @param checked Whether the toggle is in the ON position - * @param onCheckedChange Callback when toggle state changes - * @param modifier Modifier for the toggle - * @param enabled Whether the toggle can be interacted with - * @param checkedColor Color when toggle is ON - * @param uncheckedColor Color when toggle is OFF - * @param thumbColor Color of the toggle thumb (circle) - * @param applyLiquidEffect Whether to apply liquid glass effect (set false for fallback) - */ @Composable fun LiquidToggle( checked: Boolean, @@ -58,7 +37,6 @@ fun LiquidToggle( thumbColor: Color = Color.White, applyLiquidEffect: Boolean = true ) { - // Fallback to standard Material Switch if liquid effect not enabled if (!applyLiquidEffect) { Switch( checked = checked, @@ -72,13 +50,11 @@ fun LiquidToggle( val backgroundColor = if (checked) checkedColor else uncheckedColor val thumbPadding = if (checked) 24.dp else 2.dp - // Create backdrop for liquid glass effect val backdrop = rememberLayerBackdrop { drawRect(backgroundColor) drawContent() } - // Animate thumb position val animatedThumbPadding = animateDpAsState( targetValue = thumbPadding, label = "toggle_thumb_position" @@ -100,12 +76,11 @@ fun LiquidToggle( .clickable( enabled = enabled, onClick = { onCheckedChange(!checked) }, - indication = null, // Remove ripple effect + indication = null, interactionSource = remember { MutableInteractionSource() } ), contentAlignment = Alignment.CenterStart ) { - // Toggle thumb (the circle that moves) Box( modifier = Modifier .size(28.dp) @@ -118,18 +93,6 @@ fun LiquidToggle( } } -/** - * Liquid Glass Switch Component (with label) - * - * A toggle switch with optional label text. - * - * @param checked Whether the switch is ON - * @param onCheckedChange Callback when switch state changes - * @param modifier Modifier for the entire row - * @param enabled Whether the switch can be interacted with - * @param label Optional label text to display - * @param applyLiquidEffect Whether to apply liquid glass effect - */ @Composable fun LiquidSwitch( checked: Boolean, @@ -137,11 +100,10 @@ fun LiquidSwitch( modifier: Modifier = Modifier, enabled: Boolean = true, label: String? = null, - checkedColor: Color = Color(0xFF4CAF50), // Added color support! + checkedColor: Color = Color(0xFF4CAF50), applyLiquidEffect: Boolean = true ) { if (label == null) { - // Just the toggle without label LiquidToggle( checked = checked, onCheckedChange = onCheckedChange, @@ -153,12 +115,11 @@ fun LiquidSwitch( return } - // Toggle with label Row( modifier = modifier.padding(vertical = 8.dp), verticalAlignment = Alignment.CenterVertically ) { - androidx.compose.material3.Text( + Text( text = label, modifier = Modifier.weight(1f) ) @@ -175,21 +136,17 @@ fun LiquidSwitch( } } -/** - * Adaptive Toggle - Automatically uses liquid or standard based on settings - */ @Composable fun AdaptiveToggle( checked: Boolean, onCheckedChange: (Boolean) -> Unit, modifier: Modifier = Modifier, - preferences: app.marlboroadvance.mpvex.preferences.LiquidUIPreferences, + preferences: LiquidUIPreferences, enabled: Boolean = true, label: String? = null ) { - // Collect liquid UI enabled state and custom color - val isLiquidUIEnabled = preferences.liquidUIEnabledFlow.androidx.compose.runtime.collectAsState(false).value - val toggleColorLong = preferences.liquidToggleColorFlow.androidx.compose.runtime.collectAsState(0xFF4CAF50).value + val isLiquidUIEnabled = preferences.liquidUIEnabledFlow.collectAsState(false).value + val toggleColorLong = preferences.liquidToggleColorFlow.collectAsState(0xFF4CAF50).value val customColor = Color(toggleColorLong) if (label != null) { @@ -214,9 +171,6 @@ fun AdaptiveToggle( } } -/** - * Simple Liquid Toggle (no adaptation) - */ @Composable fun SimpleToggle( checked: Boolean, @@ -248,4 +202,3 @@ fun SimpleToggle( ) } } -} From c1aed507a86dbb323e0e46a4c5a9f94884d0bde2 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 21:25:19 +0530 Subject: [PATCH 037/194] Update LiquidUISettingsScreen.kt --- .../settings/LiquidUISettingsScreen.kt | 99 +++++-------------- 1 file changed, 24 insertions(+), 75 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/screens/settings/LiquidUISettingsScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/screens/settings/LiquidUISettingsScreen.kt index 824ba1508..211548b4f 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/screens/settings/LiquidUISettingsScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/screens/settings/LiquidUISettingsScreen.kt @@ -1,36 +1,39 @@ package app.marlboroadvance.mpvex.ui.screens.settings +import androidx.compose.animation.AnimatedVisibility +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.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Card import androidx.compose.material3.Divider import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Switch +import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import app.marlboroadvance.mpvex.preferences.LiquidUIPreferences import app.marlboroadvance.mpvex.ui.components.liquid.AdaptiveToggle import kotlinx.coroutines.launch -/** - * Liquid UI Settings Screen - * - * Shows toggles for: - * - Master Liquid UI Enable/Disable - * - Individual Effect Settings (Blur, Lens, Vibrancy) - * - Preview of effects in real-time - */ @Composable fun LiquidUISettingsScreen( preferences: LiquidUIPreferences, @@ -38,7 +41,6 @@ fun LiquidUISettingsScreen( ) { val scope = rememberCoroutineScope() - // Collect all preference states val liquidUIEnabled = preferences.liquidUIEnabledFlow.collectAsState(false).value val blurEnabled = preferences.liquidBlurEnabledFlow.collectAsState(true).value val lensEnabled = preferences.liquidLensEnabledFlow.collectAsState(true).value @@ -50,9 +52,6 @@ fun LiquidUISettingsScreen( .verticalScroll(rememberScrollState()) .padding(16.dp) ) { - // ============================================================ - // SECTION 1: MASTER TOGGLE - // ============================================================ Text( text = "Liquid Glass UI", @@ -67,7 +66,6 @@ fun LiquidUISettingsScreen( modifier = Modifier.padding(bottom = 16.dp) ) - // Master toggle - Use standard switch for clarity on settings Row( modifier = Modifier .fillMaxWidth() @@ -80,8 +78,7 @@ fun LiquidUISettingsScreen( style = MaterialTheme.typography.bodyLarge ) - // Use standard switch for the master toggle (meta-UI) - AdaptiveToggle( + AdaptiveToggle( checked = liquidUIEnabled, onCheckedChange = { enabled -> scope.launch { preferences.setLiquidUIEnabled(enabled) } @@ -90,10 +87,6 @@ fun LiquidUISettingsScreen( ) } - // ============================================================ - // SECTION 1.5: CUSTOM COLORS (DYNAMIC INPUT) - // ============================================================ - AnimatedVisibility(visible = liquidUIEnabled) { Column { Divider(modifier = Modifier.padding(vertical = 16.dp)) @@ -114,7 +107,6 @@ fun LiquidUISettingsScreen( val toggleColor = preferences.liquidToggleColorFlow.collectAsState(0xFF4CAF50).value val sliderColor = preferences.liquidSliderColorFlow.collectAsState(0xFF2196F3).value - // Smart parser that reads names ("red") or hex codes ("#FF0000" or "FF0000") fun parseColorInput(input: String): Long? { return try { val formatted = if (input.matches(Regex("^[0-9A-Fa-f]{6,8}$"))) "#$input" else input @@ -123,16 +115,14 @@ fun LiquidUISettingsScreen( } catch (e: Exception) { null } } - // --- TOGGLE COLOR INPUT --- var toggleInputText by remember(toggleColor) { mutableStateOf(String.format("#%06X", 0xFFFFFF and toggleColor.toInt())) } - androidx.compose.material3.OutlinedTextField( + OutlinedTextField( value = toggleInputText, onValueChange = { newValue -> toggleInputText = newValue - // Only save to database if the color is valid! parseColorInput(newValue)?.let { colorLong -> scope.launch { preferences.setToggleColor(colorLong) } } @@ -145,22 +135,20 @@ fun LiquidUISettingsScreen( Box( modifier = Modifier .size(24.dp) - .clip(androidx.compose.foundation.shape.CircleShape) - .background(androidx.compose.ui.graphics.Color(toggleColor)) + .clip(CircleShape) + .background(Color(toggleColor)) ) } ) - // --- SLIDER COLOR INPUT --- var sliderInputText by remember(sliderColor) { mutableStateOf(String.format("#%06X", 0xFFFFFF and sliderColor.toInt())) } - androidx.compose.material3.OutlinedTextField( + OutlinedTextField( value = sliderInputText, onValueChange = { newValue -> sliderInputText = newValue - // Only save to database if the color is valid! parseColorInput(newValue)?.let { colorLong -> scope.launch { preferences.setSliderColor(colorLong) } } @@ -173,24 +161,14 @@ fun LiquidUISettingsScreen( Box( modifier = Modifier .size(24.dp) - .clip(androidx.compose.foundation.shape.CircleShape) - .background(androidx.compose.ui.graphics.Color(sliderColor)) + .clip(CircleShape) + .background(Color(sliderColor)) ) } ) } } - scope.launch { - preferences.setLiquidUIEnabled(enabled) - } - } - ) - } - // ============================================================ - // SECTION 2: EFFECT TOGGLES - // ============================================================ - Divider(modifier = Modifier.padding(vertical = 16.dp)) Text( @@ -200,13 +178,12 @@ fun LiquidUISettingsScreen( ) Text( - text = "Customize which effects are applied (requires Liquid UI enabled)", + text = "Customize which effects are applied", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.padding(bottom = 16.dp) ) - // Blur Effect Toggle - CAN be liquid to show the effect Row( modifier = Modifier .fillMaxWidth() @@ -229,16 +206,13 @@ fun LiquidUISettingsScreen( AdaptiveToggle( checked = blurEnabled, onCheckedChange = { enabled -> - scope.launch { - preferences.setBlurEnabled(enabled) - } + scope.launch { preferences.setBlurEnabled(enabled) } }, preferences = preferences, enabled = liquidUIEnabled ) } - // Lens Effect Toggle Row( modifier = Modifier .fillMaxWidth() @@ -252,7 +226,7 @@ fun LiquidUISettingsScreen( style = MaterialTheme.typography.bodyLarge ) Text( - text = "Adds refraction lens effect (Android 13+ only)", + text = "Adds refraction lens effect (Android 13+)", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) @@ -261,16 +235,13 @@ fun LiquidUISettingsScreen( AdaptiveToggle( checked = lensEnabled, onCheckedChange = { enabled -> - scope.launch { - preferences.setLensEnabled(enabled) - } + scope.launch { preferences.setLensEnabled(enabled) } }, preferences = preferences, enabled = liquidUIEnabled && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU ) } - // Vibrancy Effect Toggle Row( modifier = Modifier .fillMaxWidth() @@ -293,19 +264,13 @@ fun LiquidUISettingsScreen( AdaptiveToggle( checked = vibrancyEnabled, onCheckedChange = { enabled -> - scope.launch { - preferences.setVibrancyEnabled(enabled) - } + scope.launch { preferences.setVibrancyEnabled(enabled) } }, preferences = preferences, enabled = liquidUIEnabled ) } - // ============================================================ - // SECTION 3: INFORMATION - // ============================================================ - Divider(modifier = Modifier.padding(vertical = 16.dp)) Text( @@ -320,25 +285,9 @@ fun LiquidUISettingsScreen( "Lens effects require Android 13+. Effects are optimized " + "to have minimal performance impact." ) - - InfoCard( - title = "Supported Screens", - description = "Liquid UI is applied to: Player controls, " + - "Browser cards, Dialogs, Buttons, Toggles, Bottom bars, " + - "and more throughout the app." - ) - - InfoCard( - title = "Compatibility", - description = "All features work with your device. Some effects " + - "may be automatically disabled on older Android versions." - ) } } -/** - * Info Card for displaying information - */ @Composable private fun InfoCard( title: String, From f5d720c1e717ecb8f197c42881efc3f3713e3618 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 21:46:59 +0530 Subject: [PATCH 038/194] Update AppearancePreferencesScreen.kt --- .../AppearancePreferencesScreen.kt | 113 ++++++++++++++---- 1 file changed, 88 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt index 249503adf..59ea56306 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt @@ -1,28 +1,40 @@ package app.marlboroadvance.mpvex.ui.preferences -import androidx.compose.runtime.remember -import androidx.compose.runtime.collectAsState -import androidx.compose.ui.platform.LocalContext -import androidx.compose.runtime.rememberCoroutineScope -import app.marlboroadvance.mpvex.preferences.LiquidUIPreferences -import kotlinx.coroutines.launch +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.ArrowBack import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp @@ -30,22 +42,24 @@ import app.marlboroadvance.mpvex.R import app.marlboroadvance.mpvex.preferences.AppearancePreferences import app.marlboroadvance.mpvex.preferences.BrowserPreferences import app.marlboroadvance.mpvex.preferences.GesturePreferences +import app.marlboroadvance.mpvex.preferences.LiquidUIPreferences import app.marlboroadvance.mpvex.preferences.MultiChoiceSegmentedButton import app.marlboroadvance.mpvex.preferences.preference.collectAsState import app.marlboroadvance.mpvex.presentation.Screen +import app.marlboroadvance.mpvex.ui.components.liquid.AdaptiveToggle import app.marlboroadvance.mpvex.ui.preferences.components.ThemePicker import app.marlboroadvance.mpvex.ui.theme.DarkMode import app.marlboroadvance.mpvex.ui.utils.LocalBackStack import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.launch import kotlinx.serialization.Serializable +import me.zhanghai.compose.preference.PreferenceDivider import me.zhanghai.compose.preference.ProvidePreferenceLocals import me.zhanghai.compose.preference.SliderPreference import me.zhanghai.compose.preference.SwitchPreference import org.koin.compose.koinInject import kotlin.math.roundToInt -import androidx.compose.runtime.getValue -import androidx.compose.runtime.setValue @Serializable object AppearancePreferencesScreen : Screen { @@ -56,6 +70,8 @@ object AppearancePreferencesScreen : Screen { val context = LocalContext.current val liquidPreferences = remember { LiquidUIPreferences(context) } val isLiquidUIEnabled by liquidPreferences.liquidUIEnabledFlow.collectAsState(initial = false) + val toggleColor by liquidPreferences.liquidToggleColorFlow.collectAsState(initial = 0xFF4CAF50) + val sliderColor by liquidPreferences.liquidSliderColorFlow.collectAsState(initial = 0xFF2196F3) val scope = rememberCoroutineScope() val browserPreferences = koinInject() val gesturePreferences = koinInject() @@ -130,28 +146,75 @@ object AppearancePreferencesScreen : Screen { modifier = Modifier.padding(vertical = 8.dp), ) - // --- LIQUID GLASS UI MASTER TOGGLE --- - SwitchPreference( - value = isLiquidUIEnabled, - onValueChange = { enabled -> - scope.launch { liquidPreferences.setLiquidUIEnabled(enabled) } - }, - title = { - Text( - text = "Enable Liquid Glass UI", - fontWeight = FontWeight.Bold + PreferenceDivider() + + // --- LIQUID GLASS UI MASTER TOGGLE & COLORS --- + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { scope.launch { liquidPreferences.setLiquidUIEnabled(!isLiquidUIEnabled) } } + .padding(horizontal = 16.dp, vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Column(modifier = Modifier.weight(1f)) { + Text(text = "Enable Liquid Glass UI", fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) + Text(text = "Transform controls with modern glass effects", color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodyMedium) + } + AdaptiveToggle( + checked = isLiquidUIEnabled, + onCheckedChange = { enabled -> scope.launch { liquidPreferences.setLiquidUIEnabled(enabled) } }, + preferences = liquidPreferences + ) + } + + AnimatedVisibility(visible = isLiquidUIEnabled) { + Column(modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) { + Text("Liquid UI Colors", style = MaterialTheme.typography.titleSmall, color = MaterialTheme.colorScheme.primary, modifier = Modifier.padding(bottom = 8.dp)) + + fun parseColorInput(input: String): Long? { + return try { + val formatted = if (input.matches(Regex("^[0-9A-Fa-f]{6,8}$"))) "#$input" else input + val colorInt = android.graphics.Color.parseColor(formatted) + colorInt.toLong() and 0xFFFFFFFFL + } catch (e: Exception) { null } + } + + var toggleInputText by remember(toggleColor) { mutableStateOf(String.format("#%06X", 0xFFFFFF and toggleColor.toInt())) } + OutlinedTextField( + value = toggleInputText, + onValueChange = { newValue -> + toggleInputText = newValue + parseColorInput(newValue)?.let { colorLong -> scope.launch { liquidPreferences.setToggleColor(colorLong) } } + }, + label = { Text("Toggle Color") }, + placeholder = { Text("e.g. #FF5733 or blue") }, + modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp), + singleLine = true, + leadingIcon = { + Box(modifier = Modifier.size(24.dp).clip(CircleShape).background(Color(toggleColor))) + } ) - }, - summary = { - Text( - text = "Transform player controls and components with modern transparent glass effects", - color = MaterialTheme.colorScheme.outline, + + var sliderInputText by remember(sliderColor) { mutableStateOf(String.format("#%06X", 0xFFFFFF and sliderColor.toInt())) } + OutlinedTextField( + value = sliderInputText, + onValueChange = { newValue -> + sliderInputText = newValue + parseColorInput(newValue)?.let { colorLong -> scope.launch { liquidPreferences.setSliderColor(colorLong) } } + }, + label = { Text("Slider Color") }, + placeholder = { Text("e.g. #00FF00 or cyan") }, + modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp), + singleLine = true, + leadingIcon = { + Box(modifier = Modifier.size(24.dp).clip(CircleShape).background(Color(sliderColor))) + } ) } - ) + } PreferenceDivider() - // ----------------------------- + // ------------------------------------- // AMOLED mode toggle SwitchPreference( From 8af4ffcc041e0fbd3ac1aff027602e6648931327 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 21:52:59 +0530 Subject: [PATCH 039/194] Update AppearancePreferencesScreen.kt --- .../mpvex/ui/preferences/AppearancePreferencesScreen.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt index 59ea56306..8ba7ec919 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt @@ -54,7 +54,6 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.launch import kotlinx.serialization.Serializable -import me.zhanghai.compose.preference.PreferenceDivider import me.zhanghai.compose.preference.ProvidePreferenceLocals import me.zhanghai.compose.preference.SliderPreference import me.zhanghai.compose.preference.SwitchPreference From a8a9fae93a9321f7971365a22818ec9ad54e841f Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 22:20:19 +0530 Subject: [PATCH 040/194] Update LiquidUIToggle.kt --- .../ui/components/liquid/LiquidUIToggle.kt | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt index f11dbb8b5..215ae1356 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt @@ -1,6 +1,7 @@ package app.marlboroadvance.mpvex.ui.components.liquid import android.os.Build +import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -16,6 +17,7 @@ import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -32,8 +34,8 @@ fun LiquidToggle( onCheckedChange: (Boolean) -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, - checkedColor: Color = Color(0xFF4CAF50), // Green when ON - uncheckedColor: Color = Color(0xFFBDBDBD), // Gray when OFF + checkedColor: Color = Color(0xFF4CAF50), + uncheckedColor: Color = Color(0xFFBDBDBD), thumbColor: Color = Color.White, applyLiquidEffect: Boolean = true ) { @@ -47,31 +49,28 @@ fun LiquidToggle( return } - val backgroundColor = if (checked) checkedColor else uncheckedColor - val thumbPadding = if (checked) 24.dp else 2.dp + // Smoothly animate the tint color of the glass + val targetColor = if (checked) checkedColor.copy(alpha = 0.6f) else uncheckedColor.copy(alpha = 0.2f) + val animatedColor by animateColorAsState(targetValue = targetColor, label = "toggle_color") + + val thumbPadding by animateDpAsState( + targetValue = if (checked) 24.dp else 2.dp, + label = "toggle_thumb_position" + ) + // Capture the screen behind the toggle perfectly val backdrop = rememberLayerBackdrop { - drawRect(backgroundColor) drawContent() } - val animatedThumbPadding = animateDpAsState( - targetValue = thumbPadding, - label = "toggle_thumb_position" - ) - Box( modifier = modifier .size(width = 52.dp, height = 32.dp) - .background( - color = backgroundColor, - shape = RoundedCornerShape(16.dp) - ) .drawBackdrop( backdrop = backdrop, shape = { RoundedCornerShape(16.dp) }, effects = LiquidUIEffects.glassCardEffects(enableBlur = true), - onDrawSurface = { drawRect(LiquidUIEffects.glassSurfaceColor) } + onDrawSurface = { drawRect(animatedColor) } // Tint the glass, don't paint a solid block! ) .clickable( enabled = enabled, @@ -84,7 +83,7 @@ fun LiquidToggle( Box( modifier = Modifier .size(28.dp) - .padding(start = animatedThumbPadding.value) + .padding(start = thumbPadding) .background( color = thumbColor, shape = RoundedCornerShape(14.dp) From 46482fd1abe10309bc29abf668e5832684c49968 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 23:12:50 +0530 Subject: [PATCH 041/194] Update LiquidUIToggle.kt --- .../ui/components/liquid/LiquidUIToggle.kt | 244 +++++++++--------- 1 file changed, 128 insertions(+), 116 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt index 215ae1356..ac716964e 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt @@ -1,32 +1,49 @@ package app.marlboroadvance.mpvex.ui.components.liquid import android.os.Build -import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.spring import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.draggable +import androidx.compose.foundation.gestures.rememberDraggableState import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Surface import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.lerp +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import com.kyant.backdrop.backdrops.rememberLayerBackdrop import com.kyant.backdrop.drawBackdrop import app.marlboroadvance.mpvex.ui.theme.LiquidUIEffects import app.marlboroadvance.mpvex.preferences.LiquidUIPreferences +import kotlin.math.roundToInt @Composable fun LiquidToggle( @@ -40,97 +57,97 @@ fun LiquidToggle( applyLiquidEffect: Boolean = true ) { if (!applyLiquidEffect) { - Switch( - checked = checked, - onCheckedChange = onCheckedChange, - modifier = modifier, - enabled = enabled - ) + Switch(checked = checked, onCheckedChange = onCheckedChange, modifier = modifier, enabled = enabled) return } - // Smoothly animate the tint color of the glass - val targetColor = if (checked) checkedColor.copy(alpha = 0.6f) else uncheckedColor.copy(alpha = 0.2f) - val animatedColor by animateColorAsState(targetValue = targetColor, label = "toggle_color") + // Physics Dimensions + val trackWidthDp = 52.dp + val baseThumbSizeDp = 28.dp + val paddingDp = 2.dp - val thumbPadding by animateDpAsState( - targetValue = if (checked) 24.dp else 2.dp, - label = "toggle_thumb_position" - ) + val trackWidthPx = with(LocalDensity.current) { trackWidthDp.toPx() } + val thumbSizePx = with(LocalDensity.current) { baseThumbSizeDp.toPx() } + val paddingPx = with(LocalDensity.current) { paddingDp.toPx() } + val maxDragPx = trackWidthPx - thumbSizePx - (paddingPx * 2) + + var dragOffset by remember { mutableFloatStateOf(if (checked) maxDragPx else 0f) } + var isDragging by remember { mutableStateOf(false) } - // Capture the screen behind the toggle perfectly - val backdrop = rememberLayerBackdrop { - drawContent() + // Sync external state changes gracefully + LaunchedEffect(checked) { + if (!isDragging) { + dragOffset = if (checked) maxDragPx else 0f + } } + // Drag physics logic + val draggableState = rememberDraggableState { delta -> + dragOffset = (dragOffset + delta).coerceIn(0f, maxDragPx) + } + + // Smooth springing animation for the drag position + val animatedOffset by animateFloatAsState( + targetValue = if (isDragging) dragOffset else (if (checked) maxDragPx else 0f), + animationSpec = spring(dampingRatio = 0.65f, stiffness = 400f), + label = "thumb_offset" + ) + + // Interaction states for stretch effect + val interactionSource = remember { MutableInteractionSource() } + val isPressed by interactionSource.collectIsPressedAsState() + + // iOS style thumb stretching: wider when pressed or dragged + val thumbWidth by animateDpAsState( + targetValue = if (isPressed || isDragging) 34.dp else baseThumbSizeDp, + animationSpec = spring(dampingRatio = 0.6f, stiffness = 800f), + label = "thumb_width" + ) + + // Smoothly blend the glass tint color based on exact drag position + val progress = (animatedOffset / maxDragPx).coerceIn(0f, 1f) + val dynamicTrackColor = lerp( + start = uncheckedColor.copy(alpha = 0.2f), + stop = checkedColor.copy(alpha = 0.6f), + fraction = progress + ) + + val backdrop = rememberLayerBackdrop { drawContent() } + Box( modifier = modifier - .size(width = 52.dp, height = 32.dp) + .size(width = trackWidthDp, height = 32.dp) .drawBackdrop( backdrop = backdrop, shape = { RoundedCornerShape(16.dp) }, effects = LiquidUIEffects.glassCardEffects(enableBlur = true), - onDrawSurface = { drawRect(animatedColor) } // Tint the glass, don't paint a solid block! + onDrawSurface = { drawRect(dynamicTrackColor) } ) .clickable( enabled = enabled, onClick = { onCheckedChange(!checked) }, indication = null, - interactionSource = remember { MutableInteractionSource() } + interactionSource = interactionSource + ) + .draggable( + state = draggableState, + orientation = Orientation.Horizontal, + enabled = enabled, + onDragStarted = { isDragging = true }, + onDragStopped = { + isDragging = false + val targetChecked = dragOffset > (maxDragPx / 2) + onCheckedChange(targetChecked) + } ), contentAlignment = Alignment.CenterStart ) { Box( modifier = Modifier - .size(28.dp) - .padding(start = thumbPadding) - .background( - color = thumbColor, - shape = RoundedCornerShape(14.dp) - ) - ) - } -} - -@Composable -fun LiquidSwitch( - checked: Boolean, - onCheckedChange: (Boolean) -> Unit, - modifier: Modifier = Modifier, - enabled: Boolean = true, - label: String? = null, - checkedColor: Color = Color(0xFF4CAF50), - applyLiquidEffect: Boolean = true -) { - if (label == null) { - LiquidToggle( - checked = checked, - onCheckedChange = onCheckedChange, - modifier = modifier, - enabled = enabled, - checkedColor = checkedColor, - applyLiquidEffect = applyLiquidEffect - ) - return - } - - Row( - modifier = modifier.padding(vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = label, - modifier = Modifier.weight(1f) - ) - - Spacer(modifier = Modifier.size(8.dp)) - - LiquidToggle( - checked = checked, - onCheckedChange = onCheckedChange, - enabled = enabled, - checkedColor = checkedColor, - applyLiquidEffect = applyLiquidEffect + .padding(start = paddingDp) + .offset { IntOffset(animatedOffset.roundToInt(), 0) } + .size(width = thumbWidth, height = baseThumbSizeDp) + .background(color = thumbColor, shape = RoundedCornerShape(14.dp)) ) } } @@ -141,63 +158,58 @@ fun AdaptiveToggle( onCheckedChange: (Boolean) -> Unit, modifier: Modifier = Modifier, preferences: LiquidUIPreferences, - enabled: Boolean = true, - label: String? = null + enabled: Boolean = true ) { val isLiquidUIEnabled = preferences.liquidUIEnabledFlow.collectAsState(false).value val toggleColorLong = preferences.liquidToggleColorFlow.collectAsState(0xFF4CAF50).value val customColor = Color(toggleColorLong) - if (label != null) { - LiquidSwitch( - checked = checked, - onCheckedChange = onCheckedChange, - modifier = modifier, - enabled = enabled, - label = label, - checkedColor = customColor, - applyLiquidEffect = isLiquidUIEnabled - ) - } else { - LiquidToggle( - checked = checked, - onCheckedChange = onCheckedChange, - modifier = modifier, - enabled = enabled, - checkedColor = customColor, - applyLiquidEffect = isLiquidUIEnabled - ) - } + LiquidToggle( + checked = checked, + onCheckedChange = onCheckedChange, + modifier = modifier, + enabled = enabled, + checkedColor = customColor, + applyLiquidEffect = isLiquidUIEnabled + ) } +// THE DROP-IN REPLACEMENT WRAPPER +// This exactly matches the library's signature so it can replace standard toggles everywhere! @Composable -fun SimpleToggle( - checked: Boolean, - onCheckedChange: (Boolean) -> Unit, +fun LiquidSwitchPreference( + value: Boolean, + onValueChange: (Boolean) -> Unit, + title: @Composable () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, - isLiquidUI: Boolean = true, - label: String? = null, - checkedColor: Color = Color(0xFF4CAF50) + summary: @Composable (() -> Unit)? = null, + icon: @Composable (() -> Unit)? = null, + preferences: LiquidUIPreferences? = null ) { - if (label != null) { - LiquidSwitch( - checked = checked, - onCheckedChange = onCheckedChange, - modifier = modifier, - enabled = enabled, - label = label, - checkedColor = checkedColor, - applyLiquidEffect = isLiquidUI - ) - } else { - LiquidToggle( - checked = checked, - onCheckedChange = onCheckedChange, - modifier = modifier, - enabled = enabled, - checkedColor = checkedColor, - applyLiquidEffect = isLiquidUI + val context = LocalContext.current + val activePrefs = preferences ?: remember { LiquidUIPreferences(context) } + + Row( + modifier = modifier + .fillMaxWidth() + .clickable(enabled = enabled) { onValueChange(!value) } + .padding(horizontal = 16.dp, vertical = 16.dp) + .alpha(if (enabled) 1f else 0.5f), + verticalAlignment = Alignment.CenterVertically + ) { + if (icon != null) { + Box(modifier = Modifier.padding(end = 16.dp)) { icon() } + } + Column(modifier = Modifier.weight(1f).padding(end = 16.dp)) { + title() + if (summary != null) summary() + } + AdaptiveToggle( + checked = value, + onCheckedChange = onValueChange, + preferences = activePrefs, + enabled = enabled ) } } From 4592ec370fbad846fd1eb23e2b8da28df16c127e Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 23:18:51 +0530 Subject: [PATCH 042/194] Update LiquidUIToggle.kt --- .../ui/components/liquid/LiquidUIToggle.kt | 29 +++++-------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt index ac716964e..fda21d948 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt @@ -1,6 +1,5 @@ package app.marlboroadvance.mpvex.ui.components.liquid -import android.os.Build import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.spring @@ -14,14 +13,12 @@ import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Switch -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -61,7 +58,6 @@ fun LiquidToggle( return } - // Physics Dimensions val trackWidthDp = 52.dp val baseThumbSizeDp = 28.dp val paddingDp = 2.dp @@ -74,37 +70,29 @@ fun LiquidToggle( var dragOffset by remember { mutableFloatStateOf(if (checked) maxDragPx else 0f) } var isDragging by remember { mutableStateOf(false) } - // Sync external state changes gracefully LaunchedEffect(checked) { - if (!isDragging) { - dragOffset = if (checked) maxDragPx else 0f - } + if (!isDragging) dragOffset = if (checked) maxDragPx else 0f } - // Drag physics logic val draggableState = rememberDraggableState { delta -> dragOffset = (dragOffset + delta).coerceIn(0f, maxDragPx) } - // Smooth springing animation for the drag position val animatedOffset by animateFloatAsState( targetValue = if (isDragging) dragOffset else (if (checked) maxDragPx else 0f), animationSpec = spring(dampingRatio = 0.65f, stiffness = 400f), label = "thumb_offset" ) - // Interaction states for stretch effect val interactionSource = remember { MutableInteractionSource() } val isPressed by interactionSource.collectIsPressedAsState() - // iOS style thumb stretching: wider when pressed or dragged val thumbWidth by animateDpAsState( targetValue = if (isPressed || isDragging) 34.dp else baseThumbSizeDp, animationSpec = spring(dampingRatio = 0.6f, stiffness = 800f), label = "thumb_width" ) - // Smoothly blend the glass tint color based on exact drag position val progress = (animatedOffset / maxDragPx).coerceIn(0f, 1f) val dynamicTrackColor = lerp( start = uncheckedColor.copy(alpha = 0.2f), @@ -162,20 +150,18 @@ fun AdaptiveToggle( ) { val isLiquidUIEnabled = preferences.liquidUIEnabledFlow.collectAsState(false).value val toggleColorLong = preferences.liquidToggleColorFlow.collectAsState(0xFF4CAF50).value - val customColor = Color(toggleColorLong) - + LiquidToggle( checked = checked, onCheckedChange = onCheckedChange, modifier = modifier, enabled = enabled, - checkedColor = customColor, + checkedColor = Color(toggleColorLong), applyLiquidEffect = isLiquidUIEnabled ) } -// THE DROP-IN REPLACEMENT WRAPPER -// This exactly matches the library's signature so it can replace standard toggles everywhere! +// DROP-IN REPLACEMENT WRAPPER FOR THE ENTIRE APP @Composable fun LiquidSwitchPreference( value: Boolean, @@ -184,11 +170,10 @@ fun LiquidSwitchPreference( modifier: Modifier = Modifier, enabled: Boolean = true, summary: @Composable (() -> Unit)? = null, - icon: @Composable (() -> Unit)? = null, - preferences: LiquidUIPreferences? = null + icon: @Composable (() -> Unit)? = null ) { val context = LocalContext.current - val activePrefs = preferences ?: remember { LiquidUIPreferences(context) } + val preferences = remember { LiquidUIPreferences(context) } Row( modifier = modifier @@ -208,7 +193,7 @@ fun LiquidSwitchPreference( AdaptiveToggle( checked = value, onCheckedChange = onValueChange, - preferences = activePrefs, + preferences = preferences, enabled = enabled ) } From 1c99b49f2199b71b659b3c7b25f80dbfb970498d Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 23:21:11 +0530 Subject: [PATCH 043/194] Update AppearancePreferencesScreen.kt --- .../AppearancePreferencesScreen.kt | 134 +++--------------- 1 file changed, 23 insertions(+), 111 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt index 8ba7ec919..09dc9f7f8 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt @@ -47,6 +47,7 @@ import app.marlboroadvance.mpvex.preferences.MultiChoiceSegmentedButton import app.marlboroadvance.mpvex.preferences.preference.collectAsState import app.marlboroadvance.mpvex.presentation.Screen import app.marlboroadvance.mpvex.ui.components.liquid.AdaptiveToggle +import app.marlboroadvance.mpvex.ui.components.liquid.LiquidSwitchPreference as SwitchPreference import app.marlboroadvance.mpvex.ui.preferences.components.ThemePicker import app.marlboroadvance.mpvex.ui.theme.DarkMode import app.marlboroadvance.mpvex.ui.utils.LocalBackStack @@ -54,9 +55,9 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.launch import kotlinx.serialization.Serializable +import me.zhanghai.compose.preference.PreferenceDivider import me.zhanghai.compose.preference.ProvidePreferenceLocals import me.zhanghai.compose.preference.SliderPreference -import me.zhanghai.compose.preference.SwitchPreference import org.koin.compose.koinInject import kotlin.math.roundToInt @@ -80,7 +81,6 @@ object AppearancePreferencesScreen : Screen { val darkMode by preferences.darkMode.collectAsState() val appTheme by preferences.appTheme.collectAsState() - // Determine if we're in dark mode for theme preview val isDarkMode = when (darkMode) { DarkMode.Dark -> true DarkMode.Light -> false @@ -100,31 +100,19 @@ object AppearancePreferencesScreen : Screen { }, navigationIcon = { IconButton(onClick = backstack::removeLastOrNull) { - Icon( - Icons.AutoMirrored.Outlined.ArrowBack, - contentDescription = null, - tint = MaterialTheme.colorScheme.secondary, - ) + Icon(Icons.AutoMirrored.Outlined.ArrowBack, contentDescription = null, tint = MaterialTheme.colorScheme.secondary) } }, ) }, ) { padding -> ProvidePreferenceLocals { - LazyColumn( - modifier = - Modifier - .fillMaxSize() - .padding(padding), - ) { - item { - PreferenceSectionHeader(title = stringResource(id = R.string.pref_appearance_category_theme)) - } + LazyColumn(modifier = Modifier.fillMaxSize().padding(padding)) { + item { PreferenceSectionHeader(title = stringResource(id = R.string.pref_appearance_category_theme)) } item { PreferenceCard { Column(modifier = Modifier.padding(vertical = 8.dp)) { - // Dark mode selector MultiChoiceSegmentedButton( choices = DarkMode.entries.map { stringResource(it.titleRes) }.toImmutableList(), selectedIndices = persistentListOf(DarkMode.entries.indexOf(darkMode)), @@ -134,10 +122,8 @@ object AppearancePreferencesScreen : Screen { PreferenceDivider() - // AMOLED mode state - need it before theme picker val amoledMode by preferences.amoledMode.collectAsState() - // Theme picker - Aniyomi style ThemePicker( currentTheme = appTheme, isDarkMode = isDarkMode, @@ -155,7 +141,7 @@ object AppearancePreferencesScreen : Screen { .padding(horizontal = 16.dp, vertical = 16.dp), verticalAlignment = Alignment.CenterVertically ) { - Column(modifier = Modifier.weight(1f)) { + Column(modifier = Modifier.weight(1f).padding(end = 16.dp)) { Text(text = "Enable Liquid Glass UI", fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface) Text(text = "Transform controls with modern glass effects", color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodyMedium) } @@ -189,9 +175,7 @@ object AppearancePreferencesScreen : Screen { placeholder = { Text("e.g. #FF5733 or blue") }, modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp), singleLine = true, - leadingIcon = { - Box(modifier = Modifier.size(24.dp).clip(CircleShape).background(Color(toggleColor))) - } + leadingIcon = { Box(modifier = Modifier.size(24.dp).clip(CircleShape).background(Color(toggleColor))) } ) var sliderInputText by remember(sliderColor) { mutableStateOf(String.format("#%06X", 0xFFFFFF and sliderColor.toInt())) } @@ -205,9 +189,7 @@ object AppearancePreferencesScreen : Screen { placeholder = { Text("e.g. #00FF00 or cyan") }, modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp), singleLine = true, - leadingIcon = { - Box(modifier = Modifier.size(24.dp).clip(CircleShape).background(Color(sliderColor))) - } + leadingIcon = { Box(modifier = Modifier.size(24.dp).clip(CircleShape).background(Color(sliderColor))) } ) } } @@ -215,27 +197,17 @@ object AppearancePreferencesScreen : Screen { PreferenceDivider() // ------------------------------------- - // AMOLED mode toggle SwitchPreference( value = amoledMode, - onValueChange = { newValue -> - preferences.amoledMode.set(newValue) - }, + onValueChange = { newValue -> preferences.amoledMode.set(newValue) }, title = { Text(text = stringResource(id = R.string.pref_appearance_amoled_mode_title)) }, - summary = { - Text( - text = stringResource(id = R.string.pref_appearance_amoled_mode_summary), - color = MaterialTheme.colorScheme.outline, - ) - }, + summary = { Text(text = stringResource(id = R.string.pref_appearance_amoled_mode_summary), color = MaterialTheme.colorScheme.outline) }, enabled = darkMode != DarkMode.Light ) } } - item { - PreferenceSectionHeader(title = stringResource(id = R.string.pref_appearance_category_file_browser)) - } + item { PreferenceSectionHeader(title = stringResource(id = R.string.pref_appearance_category_file_browser)) } item { PreferenceCard { @@ -243,17 +215,8 @@ object AppearancePreferencesScreen : Screen { SwitchPreference( value = unlimitedNameLines, onValueChange = { preferences.unlimitedNameLines.set(it) }, - title = { - Text( - text = stringResource(id = R.string.pref_appearance_unlimited_name_lines_title), - ) - }, - summary = { - Text( - text = stringResource(id = R.string.pref_appearance_unlimited_name_lines_summary), - color = MaterialTheme.colorScheme.outline, - ) - } + title = { Text(text = stringResource(id = R.string.pref_appearance_unlimited_name_lines_title)) }, + summary = { Text(text = stringResource(id = R.string.pref_appearance_unlimited_name_lines_summary), color = MaterialTheme.colorScheme.outline) } ) PreferenceDivider() @@ -262,17 +225,8 @@ object AppearancePreferencesScreen : Screen { SwitchPreference( value = showUnplayedOldVideoLabel, onValueChange = { preferences.showUnplayedOldVideoLabel.set(it) }, - title = { - Text( - text = stringResource(id = R.string.pref_appearance_show_unplayed_old_video_label_title), - ) - }, - summary = { - Text( - text = stringResource(id = R.string.pref_appearance_show_unplayed_old_video_label_summary), - color = MaterialTheme.colorScheme.outline, - ) - } + title = { Text(text = stringResource(id = R.string.pref_appearance_show_unplayed_old_video_label_title)) }, + summary = { Text(text = stringResource(id = R.string.pref_appearance_show_unplayed_old_video_label_summary), color = MaterialTheme.colorScheme.outline) } ) PreferenceDivider() @@ -283,15 +237,7 @@ object AppearancePreferencesScreen : Screen { onValueChange = { preferences.unplayedOldVideoDays.set(it.roundToInt()) }, title = { Text(text = stringResource(id = R.string.pref_appearance_unplayed_old_video_days_title)) }, valueRange = 1f..30f, - summary = { - Text( - text = stringResource( - id = R.string.pref_appearance_unplayed_old_video_days_summary, - unplayedOldVideoDays, - ), - color = MaterialTheme.colorScheme.outline, - ) - }, + summary = { Text(text = stringResource(id = R.string.pref_appearance_unplayed_old_video_days_summary, unplayedOldVideoDays), color = MaterialTheme.colorScheme.outline) }, onSliderValueChange = { preferences.unplayedOldVideoDays.set(it.roundToInt()) }, sliderValue = unplayedOldVideoDays.toFloat(), enabled = showUnplayedOldVideoLabel @@ -303,15 +249,8 @@ object AppearancePreferencesScreen : Screen { SwitchPreference( value = autoScrollToLastPlayed, onValueChange = { browserPreferences.autoScrollToLastPlayed.set(it) }, - title = { - Text(text = stringResource(R.string.pref_appearance_auto_scroll_title)) - }, - summary = { - Text( - text = stringResource(R.string.pref_appearance_auto_scroll_summary), - color = MaterialTheme.colorScheme.outline, - ) - } + title = { Text(text = stringResource(R.string.pref_appearance_auto_scroll_title)) }, + summary = { Text(text = stringResource(R.string.pref_appearance_auto_scroll_summary), color = MaterialTheme.colorScheme.outline) } ) PreferenceDivider() @@ -325,15 +264,7 @@ object AppearancePreferencesScreen : Screen { title = { Text(text = stringResource(id = R.string.pref_appearance_watched_threshold_title)) }, valueRange = 50f..100f, valueSteps = 9, - summary = { - Text( - text = stringResource( - id = R.string.pref_appearance_watched_threshold_summary, - watchedThreshold, - ), - color = MaterialTheme.colorScheme.outline, - ) - }, + summary = { Text(text = stringResource(id = R.string.pref_appearance_watched_threshold_summary, watchedThreshold), color = MaterialTheme.colorScheme.outline) }, ) PreferenceDivider() @@ -342,17 +273,8 @@ object AppearancePreferencesScreen : Screen { SwitchPreference( value = tapThumbnailToSelect, onValueChange = { gesturePreferences.tapThumbnailToSelect.set(it) }, - title = { - Text( - text = stringResource(id = R.string.pref_gesture_tap_thumbnail_to_select_title), - ) - }, - summary = { - Text( - text = stringResource(id = R.string.pref_gesture_tap_thumbnail_to_select_summary), - color = MaterialTheme.colorScheme.outline, - ) - } + title = { Text(text = stringResource(id = R.string.pref_gesture_tap_thumbnail_to_select_title)) }, + summary = { Text(text = stringResource(id = R.string.pref_gesture_tap_thumbnail_to_select_summary), color = MaterialTheme.colorScheme.outline) } ) PreferenceDivider() @@ -361,21 +283,11 @@ object AppearancePreferencesScreen : Screen { SwitchPreference( value = showNetworkThumbnails, onValueChange = { preferences.showNetworkThumbnails.set(it) }, - title = { - Text( - text = stringResource(id = R.string.pref_appearance_show_network_thumbnails_title), - ) - }, - summary = { - Text( - text = stringResource(id = R.string.pref_appearance_show_network_thumbnails_summary), - color = MaterialTheme.colorScheme.outline, - ) - } + title = { Text(text = stringResource(id = R.string.pref_appearance_show_network_thumbnails_title)) }, + summary = { Text(text = stringResource(id = R.string.pref_appearance_show_network_thumbnails_summary), color = MaterialTheme.colorScheme.outline) } ) } } - } } } From 2cebfd112d8cbad45725e15cc55da18a9d08bad1 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 23:33:01 +0530 Subject: [PATCH 044/194] Update LiquidUIToggle.kt From 895609e710edf032dffae1cd927b225899951f12 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 23:43:57 +0530 Subject: [PATCH 045/194] Update LiquidUIToggle.kt From 8c678712e30457b404da1b88fce5cebe10f01398 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 23:44:50 +0530 Subject: [PATCH 046/194] Update LiquidUIToggle.kt From 6431511c93e9e1132951a2209e6b1fc541ea04bb Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 7 Mar 2026 23:55:08 +0530 Subject: [PATCH 047/194] Create LiquidSwitchPreference.kt --- .../liquid/LiquidSwitchPreference.kt | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidSwitchPreference.kt diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidSwitchPreference.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidSwitchPreference.kt new file mode 100644 index 000000000..860da8dd8 --- /dev/null +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidSwitchPreference.kt @@ -0,0 +1,53 @@ +package app.marlboroadvance.mpvex.ui.components.liquid + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import app.marlboroadvance.mpvex.preferences.LiquidUIPreferences + +@Composable +fun LiquidSwitchPreference( + value: Boolean, + onValueChange: (Boolean) -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + icon: @Composable (() -> Unit)? = null, + title: @Composable () -> Unit, + summary: @Composable (() -> Unit)? = null +) { + val context = LocalContext.current + val preferences = remember { LiquidUIPreferences(context) } + + Row( + modifier = modifier + .fillMaxWidth() + .clickable(enabled = enabled) { onValueChange(!value) } + .padding(horizontal = 16.dp, vertical = 16.dp) + .alpha(if (enabled) 1f else 0.5f), + verticalAlignment = Alignment.CenterVertically + ) { + if (icon != null) { + Box(modifier = Modifier.padding(end = 16.dp)) { icon() } + } + Column(modifier = Modifier.weight(1f).padding(end = 16.dp)) { + title() + if (summary != null) summary() + } + AdaptiveToggle( + checked = value, + onCheckedChange = onValueChange, + preferences = preferences, + enabled = enabled + ) + } +} From 032254ace0555f288ceec3427ea2f0de71f8955f Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 00:01:01 +0530 Subject: [PATCH 048/194] Update LiquidUIToggle.kt --- .../ui/components/liquid/LiquidUIToggle.kt | 43 ------------------- 1 file changed, 43 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt index fda21d948..b4a05b167 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt @@ -11,9 +11,6 @@ import androidx.compose.foundation.gestures.rememberDraggableState import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -29,10 +26,8 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.lerp -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp @@ -160,41 +155,3 @@ fun AdaptiveToggle( applyLiquidEffect = isLiquidUIEnabled ) } - -// DROP-IN REPLACEMENT WRAPPER FOR THE ENTIRE APP -@Composable -fun LiquidSwitchPreference( - value: Boolean, - onValueChange: (Boolean) -> Unit, - title: @Composable () -> Unit, - modifier: Modifier = Modifier, - enabled: Boolean = true, - summary: @Composable (() -> Unit)? = null, - icon: @Composable (() -> Unit)? = null -) { - val context = LocalContext.current - val preferences = remember { LiquidUIPreferences(context) } - - Row( - modifier = modifier - .fillMaxWidth() - .clickable(enabled = enabled) { onValueChange(!value) } - .padding(horizontal = 16.dp, vertical = 16.dp) - .alpha(if (enabled) 1f else 0.5f), - verticalAlignment = Alignment.CenterVertically - ) { - if (icon != null) { - Box(modifier = Modifier.padding(end = 16.dp)) { icon() } - } - Column(modifier = Modifier.weight(1f).padding(end = 16.dp)) { - title() - if (summary != null) summary() - } - AdaptiveToggle( - checked = value, - onCheckedChange = onValueChange, - preferences = preferences, - enabled = enabled - ) - } -} From abc83a12ccd6e0c1ffe9d06e99cea33d75690372 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 00:01:39 +0530 Subject: [PATCH 049/194] Update LiquidSwitchPreference.kt --- .../mpvex/ui/components/liquid/LiquidSwitchPreference.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidSwitchPreference.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidSwitchPreference.kt index 860da8dd8..321056d71 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidSwitchPreference.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidSwitchPreference.kt @@ -19,10 +19,10 @@ import app.marlboroadvance.mpvex.preferences.LiquidUIPreferences fun LiquidSwitchPreference( value: Boolean, onValueChange: (Boolean) -> Unit, + title: @Composable () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, icon: @Composable (() -> Unit)? = null, - title: @Composable () -> Unit, summary: @Composable (() -> Unit)? = null ) { val context = LocalContext.current From 263dec8571692987c95bc40091d7c05095434ea3 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 00:16:35 +0530 Subject: [PATCH 050/194] Update AppearancePreferencesScreen.kt --- .../mpvex/ui/preferences/AppearancePreferencesScreen.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt index 09dc9f7f8..0d52ef7eb 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt @@ -55,7 +55,6 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.launch import kotlinx.serialization.Serializable -import me.zhanghai.compose.preference.PreferenceDivider import me.zhanghai.compose.preference.ProvidePreferenceLocals import me.zhanghai.compose.preference.SliderPreference import org.koin.compose.koinInject From 35ecf4b8e28bc487256ff8be891a3400feacd98d Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 00:22:19 +0530 Subject: [PATCH 051/194] Update AppearancePreferencesScreen.kt --- .../ui/preferences/AppearancePreferencesScreen.kt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt index 0d52ef7eb..42c603957 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt @@ -47,7 +47,7 @@ import app.marlboroadvance.mpvex.preferences.MultiChoiceSegmentedButton import app.marlboroadvance.mpvex.preferences.preference.collectAsState import app.marlboroadvance.mpvex.presentation.Screen import app.marlboroadvance.mpvex.ui.components.liquid.AdaptiveToggle -import app.marlboroadvance.mpvex.ui.components.liquid.LiquidSwitchPreference as SwitchPreference +import app.marlboroadvance.mpvex.ui.components.liquid.LiquidSwitchPreference import app.marlboroadvance.mpvex.ui.preferences.components.ThemePicker import app.marlboroadvance.mpvex.ui.theme.DarkMode import app.marlboroadvance.mpvex.ui.utils.LocalBackStack @@ -55,6 +55,7 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.launch import kotlinx.serialization.Serializable +import me.zhanghai.compose.preference.PreferenceDivider import me.zhanghai.compose.preference.ProvidePreferenceLocals import me.zhanghai.compose.preference.SliderPreference import org.koin.compose.koinInject @@ -196,7 +197,7 @@ object AppearancePreferencesScreen : Screen { PreferenceDivider() // ------------------------------------- - SwitchPreference( + LiquidSwitchPreference( value = amoledMode, onValueChange = { newValue -> preferences.amoledMode.set(newValue) }, title = { Text(text = stringResource(id = R.string.pref_appearance_amoled_mode_title)) }, @@ -211,7 +212,7 @@ object AppearancePreferencesScreen : Screen { item { PreferenceCard { val unlimitedNameLines by preferences.unlimitedNameLines.collectAsState() - SwitchPreference( + LiquidSwitchPreference( value = unlimitedNameLines, onValueChange = { preferences.unlimitedNameLines.set(it) }, title = { Text(text = stringResource(id = R.string.pref_appearance_unlimited_name_lines_title)) }, @@ -221,7 +222,7 @@ object AppearancePreferencesScreen : Screen { PreferenceDivider() val showUnplayedOldVideoLabel by preferences.showUnplayedOldVideoLabel.collectAsState() - SwitchPreference( + LiquidSwitchPreference( value = showUnplayedOldVideoLabel, onValueChange = { preferences.showUnplayedOldVideoLabel.set(it) }, title = { Text(text = stringResource(id = R.string.pref_appearance_show_unplayed_old_video_label_title)) }, @@ -245,7 +246,7 @@ object AppearancePreferencesScreen : Screen { PreferenceDivider() val autoScrollToLastPlayed by browserPreferences.autoScrollToLastPlayed.collectAsState() - SwitchPreference( + LiquidSwitchPreference( value = autoScrollToLastPlayed, onValueChange = { browserPreferences.autoScrollToLastPlayed.set(it) }, title = { Text(text = stringResource(R.string.pref_appearance_auto_scroll_title)) }, @@ -269,7 +270,7 @@ object AppearancePreferencesScreen : Screen { PreferenceDivider() val tapThumbnailToSelect by gesturePreferences.tapThumbnailToSelect.collectAsState() - SwitchPreference( + LiquidSwitchPreference( value = tapThumbnailToSelect, onValueChange = { gesturePreferences.tapThumbnailToSelect.set(it) }, title = { Text(text = stringResource(id = R.string.pref_gesture_tap_thumbnail_to_select_title)) }, @@ -279,7 +280,7 @@ object AppearancePreferencesScreen : Screen { PreferenceDivider() val showNetworkThumbnails by preferences.showNetworkThumbnails.collectAsState() - SwitchPreference( + LiquidSwitchPreference( value = showNetworkThumbnails, onValueChange = { preferences.showNetworkThumbnails.set(it) }, title = { Text(text = stringResource(id = R.string.pref_appearance_show_network_thumbnails_title)) }, From db22d2d23181215ca915598caefd46a4c5380382 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 00:26:11 +0530 Subject: [PATCH 052/194] Update AppearancePreferencesScreen.kt --- .../ui/preferences/AppearancePreferencesScreen.kt | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt index 42c603957..0d52ef7eb 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt @@ -47,7 +47,7 @@ import app.marlboroadvance.mpvex.preferences.MultiChoiceSegmentedButton import app.marlboroadvance.mpvex.preferences.preference.collectAsState import app.marlboroadvance.mpvex.presentation.Screen import app.marlboroadvance.mpvex.ui.components.liquid.AdaptiveToggle -import app.marlboroadvance.mpvex.ui.components.liquid.LiquidSwitchPreference +import app.marlboroadvance.mpvex.ui.components.liquid.LiquidSwitchPreference as SwitchPreference import app.marlboroadvance.mpvex.ui.preferences.components.ThemePicker import app.marlboroadvance.mpvex.ui.theme.DarkMode import app.marlboroadvance.mpvex.ui.utils.LocalBackStack @@ -55,7 +55,6 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.launch import kotlinx.serialization.Serializable -import me.zhanghai.compose.preference.PreferenceDivider import me.zhanghai.compose.preference.ProvidePreferenceLocals import me.zhanghai.compose.preference.SliderPreference import org.koin.compose.koinInject @@ -197,7 +196,7 @@ object AppearancePreferencesScreen : Screen { PreferenceDivider() // ------------------------------------- - LiquidSwitchPreference( + SwitchPreference( value = amoledMode, onValueChange = { newValue -> preferences.amoledMode.set(newValue) }, title = { Text(text = stringResource(id = R.string.pref_appearance_amoled_mode_title)) }, @@ -212,7 +211,7 @@ object AppearancePreferencesScreen : Screen { item { PreferenceCard { val unlimitedNameLines by preferences.unlimitedNameLines.collectAsState() - LiquidSwitchPreference( + SwitchPreference( value = unlimitedNameLines, onValueChange = { preferences.unlimitedNameLines.set(it) }, title = { Text(text = stringResource(id = R.string.pref_appearance_unlimited_name_lines_title)) }, @@ -222,7 +221,7 @@ object AppearancePreferencesScreen : Screen { PreferenceDivider() val showUnplayedOldVideoLabel by preferences.showUnplayedOldVideoLabel.collectAsState() - LiquidSwitchPreference( + SwitchPreference( value = showUnplayedOldVideoLabel, onValueChange = { preferences.showUnplayedOldVideoLabel.set(it) }, title = { Text(text = stringResource(id = R.string.pref_appearance_show_unplayed_old_video_label_title)) }, @@ -246,7 +245,7 @@ object AppearancePreferencesScreen : Screen { PreferenceDivider() val autoScrollToLastPlayed by browserPreferences.autoScrollToLastPlayed.collectAsState() - LiquidSwitchPreference( + SwitchPreference( value = autoScrollToLastPlayed, onValueChange = { browserPreferences.autoScrollToLastPlayed.set(it) }, title = { Text(text = stringResource(R.string.pref_appearance_auto_scroll_title)) }, @@ -270,7 +269,7 @@ object AppearancePreferencesScreen : Screen { PreferenceDivider() val tapThumbnailToSelect by gesturePreferences.tapThumbnailToSelect.collectAsState() - LiquidSwitchPreference( + SwitchPreference( value = tapThumbnailToSelect, onValueChange = { gesturePreferences.tapThumbnailToSelect.set(it) }, title = { Text(text = stringResource(id = R.string.pref_gesture_tap_thumbnail_to_select_title)) }, @@ -280,7 +279,7 @@ object AppearancePreferencesScreen : Screen { PreferenceDivider() val showNetworkThumbnails by preferences.showNetworkThumbnails.collectAsState() - LiquidSwitchPreference( + SwitchPreference( value = showNetworkThumbnails, onValueChange = { preferences.showNetworkThumbnails.set(it) }, title = { Text(text = stringResource(id = R.string.pref_appearance_show_network_thumbnails_title)) }, From c656a38e30ce60d989e44f1f0aab3d421cf259b2 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 00:31:21 +0530 Subject: [PATCH 053/194] Update AppearancePreferencesScreen.kt --- .../ui/preferences/AppearancePreferencesScreen.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt index 0d52ef7eb..547be8bb3 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt @@ -47,7 +47,7 @@ import app.marlboroadvance.mpvex.preferences.MultiChoiceSegmentedButton import app.marlboroadvance.mpvex.preferences.preference.collectAsState import app.marlboroadvance.mpvex.presentation.Screen import app.marlboroadvance.mpvex.ui.components.liquid.AdaptiveToggle -import app.marlboroadvance.mpvex.ui.components.liquid.LiquidSwitchPreference as SwitchPreference +import app.marlboroadvance.mpvex.ui.components.liquid.LiquidSwitchPreference import app.marlboroadvance.mpvex.ui.preferences.components.ThemePicker import app.marlboroadvance.mpvex.ui.theme.DarkMode import app.marlboroadvance.mpvex.ui.utils.LocalBackStack @@ -196,7 +196,7 @@ object AppearancePreferencesScreen : Screen { PreferenceDivider() // ------------------------------------- - SwitchPreference( + LiquidSwitchPreference( value = amoledMode, onValueChange = { newValue -> preferences.amoledMode.set(newValue) }, title = { Text(text = stringResource(id = R.string.pref_appearance_amoled_mode_title)) }, @@ -211,7 +211,7 @@ object AppearancePreferencesScreen : Screen { item { PreferenceCard { val unlimitedNameLines by preferences.unlimitedNameLines.collectAsState() - SwitchPreference( + LiquidSwitchPreference( value = unlimitedNameLines, onValueChange = { preferences.unlimitedNameLines.set(it) }, title = { Text(text = stringResource(id = R.string.pref_appearance_unlimited_name_lines_title)) }, @@ -221,7 +221,7 @@ object AppearancePreferencesScreen : Screen { PreferenceDivider() val showUnplayedOldVideoLabel by preferences.showUnplayedOldVideoLabel.collectAsState() - SwitchPreference( + LiquidSwitchPreference( value = showUnplayedOldVideoLabel, onValueChange = { preferences.showUnplayedOldVideoLabel.set(it) }, title = { Text(text = stringResource(id = R.string.pref_appearance_show_unplayed_old_video_label_title)) }, @@ -245,7 +245,7 @@ object AppearancePreferencesScreen : Screen { PreferenceDivider() val autoScrollToLastPlayed by browserPreferences.autoScrollToLastPlayed.collectAsState() - SwitchPreference( + LiquidSwitchPreference( value = autoScrollToLastPlayed, onValueChange = { browserPreferences.autoScrollToLastPlayed.set(it) }, title = { Text(text = stringResource(R.string.pref_appearance_auto_scroll_title)) }, @@ -269,7 +269,7 @@ object AppearancePreferencesScreen : Screen { PreferenceDivider() val tapThumbnailToSelect by gesturePreferences.tapThumbnailToSelect.collectAsState() - SwitchPreference( + LiquidSwitchPreference( value = tapThumbnailToSelect, onValueChange = { gesturePreferences.tapThumbnailToSelect.set(it) }, title = { Text(text = stringResource(id = R.string.pref_gesture_tap_thumbnail_to_select_title)) }, @@ -279,7 +279,7 @@ object AppearancePreferencesScreen : Screen { PreferenceDivider() val showNetworkThumbnails by preferences.showNetworkThumbnails.collectAsState() - SwitchPreference( + LiquidSwitchPreference( value = showNetworkThumbnails, onValueChange = { preferences.showNetworkThumbnails.set(it) }, title = { Text(text = stringResource(id = R.string.pref_appearance_show_network_thumbnails_title)) }, From efea1bc4d9a70c0f693ca0514457d3f096b84f94 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 00:35:36 +0530 Subject: [PATCH 054/194] Update AppearancePreferencesScreen.kt --- .../ui/preferences/AppearancePreferencesScreen.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt index 547be8bb3..0d52ef7eb 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt @@ -47,7 +47,7 @@ import app.marlboroadvance.mpvex.preferences.MultiChoiceSegmentedButton import app.marlboroadvance.mpvex.preferences.preference.collectAsState import app.marlboroadvance.mpvex.presentation.Screen import app.marlboroadvance.mpvex.ui.components.liquid.AdaptiveToggle -import app.marlboroadvance.mpvex.ui.components.liquid.LiquidSwitchPreference +import app.marlboroadvance.mpvex.ui.components.liquid.LiquidSwitchPreference as SwitchPreference import app.marlboroadvance.mpvex.ui.preferences.components.ThemePicker import app.marlboroadvance.mpvex.ui.theme.DarkMode import app.marlboroadvance.mpvex.ui.utils.LocalBackStack @@ -196,7 +196,7 @@ object AppearancePreferencesScreen : Screen { PreferenceDivider() // ------------------------------------- - LiquidSwitchPreference( + SwitchPreference( value = amoledMode, onValueChange = { newValue -> preferences.amoledMode.set(newValue) }, title = { Text(text = stringResource(id = R.string.pref_appearance_amoled_mode_title)) }, @@ -211,7 +211,7 @@ object AppearancePreferencesScreen : Screen { item { PreferenceCard { val unlimitedNameLines by preferences.unlimitedNameLines.collectAsState() - LiquidSwitchPreference( + SwitchPreference( value = unlimitedNameLines, onValueChange = { preferences.unlimitedNameLines.set(it) }, title = { Text(text = stringResource(id = R.string.pref_appearance_unlimited_name_lines_title)) }, @@ -221,7 +221,7 @@ object AppearancePreferencesScreen : Screen { PreferenceDivider() val showUnplayedOldVideoLabel by preferences.showUnplayedOldVideoLabel.collectAsState() - LiquidSwitchPreference( + SwitchPreference( value = showUnplayedOldVideoLabel, onValueChange = { preferences.showUnplayedOldVideoLabel.set(it) }, title = { Text(text = stringResource(id = R.string.pref_appearance_show_unplayed_old_video_label_title)) }, @@ -245,7 +245,7 @@ object AppearancePreferencesScreen : Screen { PreferenceDivider() val autoScrollToLastPlayed by browserPreferences.autoScrollToLastPlayed.collectAsState() - LiquidSwitchPreference( + SwitchPreference( value = autoScrollToLastPlayed, onValueChange = { browserPreferences.autoScrollToLastPlayed.set(it) }, title = { Text(text = stringResource(R.string.pref_appearance_auto_scroll_title)) }, @@ -269,7 +269,7 @@ object AppearancePreferencesScreen : Screen { PreferenceDivider() val tapThumbnailToSelect by gesturePreferences.tapThumbnailToSelect.collectAsState() - LiquidSwitchPreference( + SwitchPreference( value = tapThumbnailToSelect, onValueChange = { gesturePreferences.tapThumbnailToSelect.set(it) }, title = { Text(text = stringResource(id = R.string.pref_gesture_tap_thumbnail_to_select_title)) }, @@ -279,7 +279,7 @@ object AppearancePreferencesScreen : Screen { PreferenceDivider() val showNetworkThumbnails by preferences.showNetworkThumbnails.collectAsState() - LiquidSwitchPreference( + SwitchPreference( value = showNetworkThumbnails, onValueChange = { preferences.showNetworkThumbnails.set(it) }, title = { Text(text = stringResource(id = R.string.pref_appearance_show_network_thumbnails_title)) }, From 37ae65aa770bc49b5b598a343d7d32824464bfad Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 01:39:48 +0530 Subject: [PATCH 055/194] Update LiquidUIToggle.kt --- .../ui/components/liquid/LiquidUIToggle.kt | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt index b4a05b167..b8369e6d3 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt @@ -26,14 +26,18 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.isSpecified import androidx.compose.ui.graphics.lerp import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import com.kyant.backdrop.backdrops.rememberLayerBackdrop import com.kyant.backdrop.drawBackdrop -import app.marlboroadvance.mpvex.ui.theme.LiquidUIEffects +import com.kyant.backdrop.effects.blur +import com.kyant.backdrop.effects.lens +import com.kyant.backdrop.effects.vibrancy import app.marlboroadvance.mpvex.preferences.LiquidUIPreferences import kotlin.math.roundToInt @@ -89,9 +93,9 @@ fun LiquidToggle( ) val progress = (animatedOffset / maxDragPx).coerceIn(0f, 1f) - val dynamicTrackColor = lerp( - start = uncheckedColor.copy(alpha = 0.2f), - stop = checkedColor.copy(alpha = 0.6f), + val dynamicTint = lerp( + start = uncheckedColor, + stop = checkedColor, fraction = progress ) @@ -102,9 +106,18 @@ fun LiquidToggle( .size(width = trackWidthDp, height = 32.dp) .drawBackdrop( backdrop = backdrop, - shape = { RoundedCornerShape(16.dp) }, - effects = LiquidUIEffects.glassCardEffects(enableBlur = true), - onDrawSurface = { drawRect(dynamicTrackColor) } + shape = { RoundedCornerShape(percent = 50) }, + effects = { + vibrancy() + blur(2f.dp.toPx()) + lens(12f.dp.toPx(), 24f.dp.toPx()) + }, + onDrawSurface = { + if (dynamicTint.isSpecified) { + drawRect(dynamicTint, blendMode = BlendMode.Hue) + drawRect(dynamicTint.copy(alpha = 0.75f)) + } + } ) .clickable( enabled = enabled, @@ -130,7 +143,7 @@ fun LiquidToggle( .padding(start = paddingDp) .offset { IntOffset(animatedOffset.roundToInt(), 0) } .size(width = thumbWidth, height = baseThumbSizeDp) - .background(color = thumbColor, shape = RoundedCornerShape(14.dp)) + .background(color = thumbColor, shape = RoundedCornerShape(percent = 50)) ) } } From e896859179bdffe6ba606be2687ddc2e856cd683 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 01:40:56 +0530 Subject: [PATCH 056/194] Update AppearancePreferencesScreen.kt --- .../AppearancePreferencesScreen.kt | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt index 0d52ef7eb..849db2a78 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt @@ -47,7 +47,7 @@ import app.marlboroadvance.mpvex.preferences.MultiChoiceSegmentedButton import app.marlboroadvance.mpvex.preferences.preference.collectAsState import app.marlboroadvance.mpvex.presentation.Screen import app.marlboroadvance.mpvex.ui.components.liquid.AdaptiveToggle -import app.marlboroadvance.mpvex.ui.components.liquid.LiquidSwitchPreference as SwitchPreference +import app.marlboroadvance.mpvex.ui.components.liquid.LiquidSwitchPreference import app.marlboroadvance.mpvex.ui.preferences.components.ThemePicker import app.marlboroadvance.mpvex.ui.theme.DarkMode import app.marlboroadvance.mpvex.ui.utils.LocalBackStack @@ -119,7 +119,7 @@ object AppearancePreferencesScreen : Screen { ) } - PreferenceDivider() + me.zhanghai.compose.preference.PreferenceDivider() val amoledMode by preferences.amoledMode.collectAsState() @@ -130,7 +130,7 @@ object AppearancePreferencesScreen : Screen { modifier = Modifier.padding(vertical = 8.dp), ) - PreferenceDivider() + me.zhanghai.compose.preference.PreferenceDivider() // --- LIQUID GLASS UI MASTER TOGGLE & COLORS --- Row( @@ -193,10 +193,10 @@ object AppearancePreferencesScreen : Screen { } } - PreferenceDivider() + me.zhanghai.compose.preference.PreferenceDivider() // ------------------------------------- - SwitchPreference( + LiquidSwitchPreference( value = amoledMode, onValueChange = { newValue -> preferences.amoledMode.set(newValue) }, title = { Text(text = stringResource(id = R.string.pref_appearance_amoled_mode_title)) }, @@ -211,24 +211,24 @@ object AppearancePreferencesScreen : Screen { item { PreferenceCard { val unlimitedNameLines by preferences.unlimitedNameLines.collectAsState() - SwitchPreference( + LiquidSwitchPreference( value = unlimitedNameLines, onValueChange = { preferences.unlimitedNameLines.set(it) }, title = { Text(text = stringResource(id = R.string.pref_appearance_unlimited_name_lines_title)) }, summary = { Text(text = stringResource(id = R.string.pref_appearance_unlimited_name_lines_summary), color = MaterialTheme.colorScheme.outline) } ) - PreferenceDivider() + me.zhanghai.compose.preference.PreferenceDivider() val showUnplayedOldVideoLabel by preferences.showUnplayedOldVideoLabel.collectAsState() - SwitchPreference( + LiquidSwitchPreference( value = showUnplayedOldVideoLabel, onValueChange = { preferences.showUnplayedOldVideoLabel.set(it) }, title = { Text(text = stringResource(id = R.string.pref_appearance_show_unplayed_old_video_label_title)) }, summary = { Text(text = stringResource(id = R.string.pref_appearance_show_unplayed_old_video_label_summary), color = MaterialTheme.colorScheme.outline) } ) - PreferenceDivider() + me.zhanghai.compose.preference.PreferenceDivider() val unplayedOldVideoDays by preferences.unplayedOldVideoDays.collectAsState() SliderPreference( @@ -242,17 +242,17 @@ object AppearancePreferencesScreen : Screen { enabled = showUnplayedOldVideoLabel ) - PreferenceDivider() + me.zhanghai.compose.preference.PreferenceDivider() val autoScrollToLastPlayed by browserPreferences.autoScrollToLastPlayed.collectAsState() - SwitchPreference( + LiquidSwitchPreference( value = autoScrollToLastPlayed, onValueChange = { browserPreferences.autoScrollToLastPlayed.set(it) }, title = { Text(text = stringResource(R.string.pref_appearance_auto_scroll_title)) }, summary = { Text(text = stringResource(R.string.pref_appearance_auto_scroll_summary), color = MaterialTheme.colorScheme.outline) } ) - PreferenceDivider() + me.zhanghai.compose.preference.PreferenceDivider() val watchedThreshold by browserPreferences.watchedThreshold.collectAsState() SliderPreference( @@ -266,20 +266,20 @@ object AppearancePreferencesScreen : Screen { summary = { Text(text = stringResource(id = R.string.pref_appearance_watched_threshold_summary, watchedThreshold), color = MaterialTheme.colorScheme.outline) }, ) - PreferenceDivider() + me.zhanghai.compose.preference.PreferenceDivider() val tapThumbnailToSelect by gesturePreferences.tapThumbnailToSelect.collectAsState() - SwitchPreference( + LiquidSwitchPreference( value = tapThumbnailToSelect, onValueChange = { gesturePreferences.tapThumbnailToSelect.set(it) }, title = { Text(text = stringResource(id = R.string.pref_gesture_tap_thumbnail_to_select_title)) }, summary = { Text(text = stringResource(id = R.string.pref_gesture_tap_thumbnail_to_select_summary), color = MaterialTheme.colorScheme.outline) } ) - PreferenceDivider() + me.zhanghai.compose.preference.PreferenceDivider() val showNetworkThumbnails by preferences.showNetworkThumbnails.collectAsState() - SwitchPreference( + LiquidSwitchPreference( value = showNetworkThumbnails, onValueChange = { preferences.showNetworkThumbnails.set(it) }, title = { Text(text = stringResource(id = R.string.pref_appearance_show_network_thumbnails_title)) }, From 3a905ba3deb241b951268ddad10b2c2be5267e3b Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 02:14:20 +0530 Subject: [PATCH 057/194] Update SubtitleSettingsMiscellaneousCard.kt --- .../components/panels/SubtitleSettingsMiscellaneousCard.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/panels/SubtitleSettingsMiscellaneousCard.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/panels/SubtitleSettingsMiscellaneousCard.kt index 87950b5c7..92eab7f81 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/panels/SubtitleSettingsMiscellaneousCard.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/panels/SubtitleSettingsMiscellaneousCard.kt @@ -34,7 +34,7 @@ import app.marlboroadvance.mpvex.ui.player.controls.panelCardsColors import app.marlboroadvance.mpvex.ui.theme.spacing import `is`.xyz.mpv.MPVLib import me.zhanghai.compose.preference.ProvidePreferenceLocals -import me.zhanghai.compose.preference.SwitchPreference +import app.marlboroadvance.mpvex.ui.components.liquid.LiquidSwitchPreference as SwitchPreference import org.koin.compose.koinInject @Composable From d0160adea2ae414beb1c857f98cb690bcaf02631 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 02:16:10 +0530 Subject: [PATCH 058/194] Update PlaybackSpeedSheet.kt --- .../ui/player/controls/components/sheets/PlaybackSpeedSheet.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/sheets/PlaybackSpeedSheet.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/sheets/PlaybackSpeedSheet.kt index 2292e567b..1c6634df5 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/sheets/PlaybackSpeedSheet.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/sheets/PlaybackSpeedSheet.kt @@ -58,7 +58,7 @@ import app.marlboroadvance.mpvex.presentation.components.SliderItem import app.marlboroadvance.mpvex.ui.theme.spacing import `is`.xyz.mpv.MPVLib import me.zhanghai.compose.preference.ProvidePreferenceLocals -import me.zhanghai.compose.preference.SwitchPreference +import app.marlboroadvance.mpvex.ui.components.liquid.LiquidSwitchPreference as SwitchPreference import org.koin.compose.koinInject import app.marlboroadvance.mpvex.presentation.components.RepeatingIconButton import kotlin.math.pow From 014f5ab35002796f9bd9b66e8f39e03537ca623e Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 02:17:17 +0530 Subject: [PATCH 059/194] Update AdvancedPreferencesScreen.kt --- .../mpvex/ui/preferences/AdvancedPreferencesScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AdvancedPreferencesScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AdvancedPreferencesScreen.kt index 48c87f1cf..3b6dc3cb8 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AdvancedPreferencesScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AdvancedPreferencesScreen.kt @@ -61,7 +61,7 @@ import kotlinx.coroutines.withContext import kotlinx.serialization.Serializable import me.zhanghai.compose.preference.Preference import me.zhanghai.compose.preference.ProvidePreferenceLocals -import me.zhanghai.compose.preference.SwitchPreference +import app.marlboroadvance.mpvex.ui.components.liquid.LiquidSwitchPreference as SwitchPreference import me.zhanghai.compose.preference.TwoTargetIconButtonPreference import org.koin.compose.koinInject import java.io.File From b2cfc0db3194dd836399b16874d94df94f978cce Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 02:18:23 +0530 Subject: [PATCH 060/194] Update AudioPreferencesScreen.kt --- .../mpvex/ui/preferences/AudioPreferencesScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AudioPreferencesScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AudioPreferencesScreen.kt index 3fcb92099..00f2cff13 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AudioPreferencesScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AudioPreferencesScreen.kt @@ -32,7 +32,7 @@ import kotlinx.serialization.Serializable import me.zhanghai.compose.preference.ListPreference import me.zhanghai.compose.preference.ProvidePreferenceLocals import me.zhanghai.compose.preference.SliderPreference -import me.zhanghai.compose.preference.SwitchPreference +import app.marlboroadvance.mpvex.ui.components.liquid.LiquidSwitchPreference as SwitchPreference import me.zhanghai.compose.preference.TextFieldPreference import org.koin.compose.koinInject From c338b2585994dab2e986a19023a688c2d61c4fe4 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 02:19:12 +0530 Subject: [PATCH 061/194] Update DecoderPreferencesScreen.kt --- .../mpvex/ui/preferences/DecoderPreferencesScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/DecoderPreferencesScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/DecoderPreferencesScreen.kt index d4359d77f..c872c7f09 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/DecoderPreferencesScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/DecoderPreferencesScreen.kt @@ -47,7 +47,7 @@ import app.marlboroadvance.mpvex.ui.preferences.VulkanUtils import kotlinx.serialization.Serializable import me.zhanghai.compose.preference.ListPreference import me.zhanghai.compose.preference.ProvidePreferenceLocals -import me.zhanghai.compose.preference.SwitchPreference +import app.marlboroadvance.mpvex.ui.components.liquid.LiquidSwitchPreference as SwitchPreference import org.koin.compose.koinInject @Serializable From 50a81ee3228fa509d2fdd06bf7713af2d1372eb7 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 02:21:09 +0530 Subject: [PATCH 062/194] Update GesturePreferencesScreen.kt --- .../mpvex/ui/preferences/GesturePreferencesScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/GesturePreferencesScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/GesturePreferencesScreen.kt index 5146880b8..0d20ecdc2 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/GesturePreferencesScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/GesturePreferencesScreen.kt @@ -44,7 +44,7 @@ import kotlinx.serialization.Serializable import me.zhanghai.compose.preference.FooterPreference import me.zhanghai.compose.preference.ListPreference import me.zhanghai.compose.preference.ProvidePreferenceLocals -import me.zhanghai.compose.preference.SwitchPreference +import app.marlboroadvance.mpvex.ui.components.liquid.LiquidSwitchPreference as SwitchPreference import org.koin.compose.koinInject @Serializable From 3e763d324b01035c973db46ab235fcd54d40dbd0 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 02:22:31 +0530 Subject: [PATCH 063/194] Update PlayerControlsPreferencesScreen.kt --- .../mpvex/ui/preferences/PlayerControlsPreferencesScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/PlayerControlsPreferencesScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/PlayerControlsPreferencesScreen.kt index 1f4f3d4e3..d15cb1e08 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/PlayerControlsPreferencesScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/PlayerControlsPreferencesScreen.kt @@ -54,7 +54,7 @@ import app.marlboroadvance.mpvex.ui.utils.LocalBackStack import kotlinx.serialization.Serializable import me.zhanghai.compose.preference.ListPreference import me.zhanghai.compose.preference.ProvidePreferenceLocals -import me.zhanghai.compose.preference.SwitchPreference +import app.marlboroadvance.mpvex.ui.components.liquid.LiquidSwitchPreference as SwitchPreference import app.marlboroadvance.mpvex.ui.preferences.components.PlayerButtonChip import org.koin.compose.koinInject From 2bd9b6d4657fa37c709460dc0c1d6254dd3a415b Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 02:24:49 +0530 Subject: [PATCH 064/194] Update PlayerPreferencesScreen.kt --- .../mpvex/ui/preferences/PlayerPreferencesScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/PlayerPreferencesScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/PlayerPreferencesScreen.kt index 5f9d433c0..a4bfd3338 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/PlayerPreferencesScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/PlayerPreferencesScreen.kt @@ -30,7 +30,7 @@ import kotlinx.serialization.Serializable import me.zhanghai.compose.preference.ListPreference import me.zhanghai.compose.preference.ProvidePreferenceLocals import me.zhanghai.compose.preference.SliderPreference -import me.zhanghai.compose.preference.SwitchPreference +import app.marlboroadvance.mpvex.ui.components.liquid.LiquidSwitchPreference as SwitchPreference import org.koin.compose.koinInject import kotlin.math.roundToInt From 7b59b56f34a9930676b45aafb9c64b932ee5cc6b Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 02:25:34 +0530 Subject: [PATCH 065/194] Update SubtitlesPreferencesScreen.kt --- .../mpvex/ui/preferences/SubtitlesPreferencesScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/SubtitlesPreferencesScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/SubtitlesPreferencesScreen.kt index fc839b1d7..544ce98cb 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/SubtitlesPreferencesScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/SubtitlesPreferencesScreen.kt @@ -63,7 +63,7 @@ import kotlinx.coroutines.withContext import kotlinx.serialization.Serializable import me.zhanghai.compose.preference.Preference import me.zhanghai.compose.preference.ProvidePreferenceLocals -import me.zhanghai.compose.preference.SwitchPreference +import app.marlboroadvance.mpvex.ui.components.liquid.LiquidSwitchPreference as SwitchPreference import me.zhanghai.compose.preference.TextFieldPreference import androidx.compose.material3.AlertDialog import androidx.compose.material3.Checkbox From 44c3e79389ada7977b99de30e9a96e9196ca453a Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 02:34:02 +0530 Subject: [PATCH 066/194] Update AppearancePreferencesScreen.kt --- .../preferences/AppearancePreferencesScreen.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt index 849db2a78..547be8bb3 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt @@ -119,7 +119,7 @@ object AppearancePreferencesScreen : Screen { ) } - me.zhanghai.compose.preference.PreferenceDivider() + PreferenceDivider() val amoledMode by preferences.amoledMode.collectAsState() @@ -130,7 +130,7 @@ object AppearancePreferencesScreen : Screen { modifier = Modifier.padding(vertical = 8.dp), ) - me.zhanghai.compose.preference.PreferenceDivider() + PreferenceDivider() // --- LIQUID GLASS UI MASTER TOGGLE & COLORS --- Row( @@ -193,7 +193,7 @@ object AppearancePreferencesScreen : Screen { } } - me.zhanghai.compose.preference.PreferenceDivider() + PreferenceDivider() // ------------------------------------- LiquidSwitchPreference( @@ -218,7 +218,7 @@ object AppearancePreferencesScreen : Screen { summary = { Text(text = stringResource(id = R.string.pref_appearance_unlimited_name_lines_summary), color = MaterialTheme.colorScheme.outline) } ) - me.zhanghai.compose.preference.PreferenceDivider() + PreferenceDivider() val showUnplayedOldVideoLabel by preferences.showUnplayedOldVideoLabel.collectAsState() LiquidSwitchPreference( @@ -228,7 +228,7 @@ object AppearancePreferencesScreen : Screen { summary = { Text(text = stringResource(id = R.string.pref_appearance_show_unplayed_old_video_label_summary), color = MaterialTheme.colorScheme.outline) } ) - me.zhanghai.compose.preference.PreferenceDivider() + PreferenceDivider() val unplayedOldVideoDays by preferences.unplayedOldVideoDays.collectAsState() SliderPreference( @@ -242,7 +242,7 @@ object AppearancePreferencesScreen : Screen { enabled = showUnplayedOldVideoLabel ) - me.zhanghai.compose.preference.PreferenceDivider() + PreferenceDivider() val autoScrollToLastPlayed by browserPreferences.autoScrollToLastPlayed.collectAsState() LiquidSwitchPreference( @@ -252,7 +252,7 @@ object AppearancePreferencesScreen : Screen { summary = { Text(text = stringResource(R.string.pref_appearance_auto_scroll_summary), color = MaterialTheme.colorScheme.outline) } ) - me.zhanghai.compose.preference.PreferenceDivider() + PreferenceDivider() val watchedThreshold by browserPreferences.watchedThreshold.collectAsState() SliderPreference( @@ -266,7 +266,7 @@ object AppearancePreferencesScreen : Screen { summary = { Text(text = stringResource(id = R.string.pref_appearance_watched_threshold_summary, watchedThreshold), color = MaterialTheme.colorScheme.outline) }, ) - me.zhanghai.compose.preference.PreferenceDivider() + PreferenceDivider() val tapThumbnailToSelect by gesturePreferences.tapThumbnailToSelect.collectAsState() LiquidSwitchPreference( @@ -276,7 +276,7 @@ object AppearancePreferencesScreen : Screen { summary = { Text(text = stringResource(id = R.string.pref_gesture_tap_thumbnail_to_select_summary), color = MaterialTheme.colorScheme.outline) } ) - me.zhanghai.compose.preference.PreferenceDivider() + PreferenceDivider() val showNetworkThumbnails by preferences.showNetworkThumbnails.collectAsState() LiquidSwitchPreference( From 2107d5fb10c76a5265bd38893beda2308a554b5e Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 03:40:00 +0530 Subject: [PATCH 067/194] Update Seekbar.kt --- .../ui/player/controls/components/Seekbar.kt | 568 ++++++++++++------ 1 file changed, 372 insertions(+), 196 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index 9d0d39d8f..c9a9bf8ce 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -47,7 +47,10 @@ import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.clipRect import androidx.compose.ui.graphics.drawscope.withTransform import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.isSpecified import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -58,6 +61,18 @@ import androidx.compose.foundation.shape.RoundedCornerShape import app.marlboroadvance.mpvex.ui.player.controls.LocalPlayerButtonsClickEvent import app.marlboroadvance.mpvex.ui.theme.spacing import app.marlboroadvance.mpvex.preferences.SeekbarStyle +import app.marlboroadvance.mpvex.preferences.LiquidUIPreferences + +// --- KYANT BACKDROP 2.0.0-ALPHA03 IMPORTS --- +import com.kyant.backdrop.backdrops.layerBackdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop +import com.kyant.backdrop.backdrops.rememberCombinedBackdrop +import com.kyant.backdrop.drawBackdrop +import com.kyant.backdrop.effects.blur +import com.kyant.backdrop.effects.lens +import com.kyant.backdrop.effects.vibrancy +// -------------------------------------------- + import dev.vivvvek.seeker.Segment import `is`.xyz.mpv.Utils import kotlinx.collections.immutable.ImmutableList @@ -86,11 +101,16 @@ fun SeekbarWithTimers( var isUserInteracting by remember { mutableStateOf(false) } var userPosition by remember { mutableFloatStateOf(position) } - // Animated position for smooth transitions + // Inject Liquid UI Preferences for Custom Slider Color + val context = LocalContext.current + val liquidPrefs = remember { LiquidUIPreferences(context) } + val isLiquidUI by liquidPrefs.liquidUIEnabledFlow.collectAsState(false) + val sliderColorLong by liquidPrefs.liquidSliderColorFlow.collectAsState(0xFF2196F3) + val liquidColor = Color(sliderColorLong) + val animatedPosition = remember { Animatable(position) } val scope = rememberCoroutineScope() - // Only animate position updates when user is not interacting LaunchedEffect(position, isUserInteracting) { if (!isUserInteracting && position != animatedPosition.value) { scope.launch { @@ -121,20 +141,17 @@ fun SeekbarWithTimers( modifier = Modifier.width(92.dp), ) - // Seekbar with expanded touch area Box( - modifier = - Modifier + modifier = Modifier .weight(1f) .height(48.dp) - .padding(vertical = 8.dp), // Add vertical padding for larger touch area + .padding(vertical = 8.dp), contentAlignment = Alignment.Center, ) { - // Invisible expanded touch area Box( modifier = Modifier .fillMaxWidth() - .height(64.dp) // Larger touch area + .height(64.dp) .pointerInput(Unit) { detectTapGestures( onTap = { offset -> @@ -143,7 +160,6 @@ fun SeekbarWithTimers( userPosition = newPosition.coerceIn(0f, duration) onValueChange(userPosition) scope.launch { - // Snap to user position immediately to prevent jumping animatedPosition.snapTo(userPosition) isUserInteracting = false onValueChangeFinished() @@ -153,12 +169,9 @@ fun SeekbarWithTimers( } .pointerInput(Unit) { detectDragGestures( - onDragStart = { - isUserInteracting = true - }, + onDragStart = { isUserInteracting = true }, onDragEnd = { scope.launch { - // Allow a tiny window for mpv/viewModel to sync back before releasing control delay(50) animatedPosition.snapTo(userPosition) isUserInteracting = false @@ -182,73 +195,98 @@ fun SeekbarWithTimers( } ) - // Visual seekbar (smaller, centered) Box( modifier = Modifier .fillMaxWidth() .height(32.dp), contentAlignment = Alignment.Center, ) { - when (seekbarStyle) { - SeekbarStyle.Standard -> { - StandardSeekbar( - position = if (isUserInteracting) userPosition else animatedPosition.value, - duration = duration, - chapters = chapters, - isPaused = paused, - isScrubbing = isUserInteracting, - seekbarStyle = SeekbarStyle.Standard, - onSeek = { newPosition -> - if (!isUserInteracting) isUserInteracting = true - userPosition = newPosition - onValueChange(newPosition) - }, - onSeekFinished = { - scope.launch { animatedPosition.snapTo(userPosition) } - isUserInteracting = false - onValueChangeFinished() - }, - loopStart = loopStart, - loopEnd = loopEnd, - ) - } - SeekbarStyle.Wavy -> { - SquigglySeekbar( - position = if (isUserInteracting) userPosition else animatedPosition.value, - duration = duration, - chapters = chapters, - isPaused = paused, - isScrubbing = isUserInteracting, - useWavySeekbar = true, - seekbarStyle = SeekbarStyle.Wavy, - onSeek = { }, // Touch handled by parent - onSeekFinished = { }, // Touch handled by parent - loopStart = loopStart, - loopEnd = loopEnd, - ) - } - SeekbarStyle.Thick -> { - StandardSeekbar( - position = if (isUserInteracting) userPosition else animatedPosition.value, - duration = duration, - chapters = chapters, - isPaused = paused, - isScrubbing = isUserInteracting, - seekbarStyle = SeekbarStyle.Thick, - onSeek = { newPosition -> - if (!isUserInteracting) isUserInteracting = true - userPosition = newPosition - onValueChange(newPosition) - }, - onSeekFinished = { - scope.launch { animatedPosition.snapTo(userPosition) } - isUserInteracting = false - onValueChangeFinished() - }, - loopStart = loopStart, - loopEnd = loopEnd, + + // ROUTING LOGIC: If Liquid UI is enabled, use the standalone component! + if (isLiquidUI) { + LiquidSeekbar( + position = if (isUserInteracting) userPosition else animatedPosition.value, + duration = duration, + chapters = chapters, + isPaused = paused, + isScrubbing = isUserInteracting, + onSeek = { newPosition -> + if (!isUserInteracting) isUserInteracting = true + userPosition = newPosition + onValueChange(newPosition) + }, + onSeekFinished = { + scope.launch { animatedPosition.snapTo(userPosition) } + isUserInteracting = false + onValueChangeFinished() + }, + loopStart = loopStart, + loopEnd = loopEnd, + liquidColor = liquidColor ) - } + } else { + // If Liquid UI is off, fall back to the standard mpvEx styles + when (seekbarStyle) { + SeekbarStyle.Standard -> { + StandardSeekbar( + position = if (isUserInteracting) userPosition else animatedPosition.value, + duration = duration, + chapters = chapters, + isPaused = paused, + isScrubbing = isUserInteracting, + seekbarStyle = SeekbarStyle.Standard, + onSeek = { newPosition -> + if (!isUserInteracting) isUserInteracting = true + userPosition = newPosition + onValueChange(newPosition) + }, + onSeekFinished = { + scope.launch { animatedPosition.snapTo(userPosition) } + isUserInteracting = false + onValueChangeFinished() + }, + loopStart = loopStart, + loopEnd = loopEnd, + ) + } + SeekbarStyle.Wavy -> { + SquigglySeekbar( + position = if (isUserInteracting) userPosition else animatedPosition.value, + duration = duration, + chapters = chapters, + isPaused = paused, + isScrubbing = isUserInteracting, + useWavySeekbar = true, + seekbarStyle = SeekbarStyle.Wavy, + onSeek = { }, + onSeekFinished = { }, + loopStart = loopStart, + loopEnd = loopEnd, + ) + } + SeekbarStyle.Thick -> { + StandardSeekbar( + position = if (isUserInteracting) userPosition else animatedPosition.value, + duration = duration, + chapters = chapters, + isPaused = paused, + isScrubbing = isUserInteracting, + seekbarStyle = SeekbarStyle.Thick, + onSeek = { newPosition -> + if (!isUserInteracting) isUserInteracting = true + userPosition = newPosition + onValueChange(newPosition) + }, + onSeekFinished = { + scope.launch { animatedPosition.snapTo(userPosition) } + isUserInteracting = false + onValueChangeFinished() + }, + loopStart = loopStart, + loopEnd = loopEnd, + ) + } + } } } } @@ -265,6 +303,232 @@ fun SeekbarWithTimers( } } +// ========================================================================= +// NEW: STANDALONE LIQUID SEEKBAR COMPONENT +// Built specifically for Kyant 2.0.0-alpha03 combined backdrops +// ========================================================================= +@Composable +@OptIn(ExperimentalMaterial3Api::class) +fun LiquidSeekbar( + position: Float, + duration: Float, + chapters: ImmutableList, + isPaused: Boolean = false, + isScrubbing: Boolean = false, + onSeek: (Float) -> Unit, + onSeekFinished: () -> Unit, + loopStart: Float? = null, + loopEnd: Float? = null, + liquidColor: Color = Color.Unspecified, + modifier: Modifier = Modifier, +) { + val activeColor = if (liquidColor.isSpecified) liquidColor else MaterialTheme.colorScheme.primary + val interactionSource = remember { MutableInteractionSource() } + + var heightFraction by remember { mutableFloatStateOf(1f) } + val scope = rememberCoroutineScope() + + LaunchedEffect(isPaused, isScrubbing) { + scope.launch { + val shouldFlatten = isPaused || isScrubbing + val targetHeight = if (shouldFlatten) 0.7f else 1f + val animationDuration = if (shouldFlatten) 550 else 800 + val startDelay = if (shouldFlatten) 0L else 60L + + kotlinx.coroutines.delay(startDelay) + + val animator = Animatable(heightFraction) + animator.animateTo( + targetValue = targetHeight, + animationSpec = tween( + durationMillis = animationDuration, + easing = LinearEasing, + ), + ) { + heightFraction = value + } + } + } + + val baseTrackHeight = 16.dp // Thick base for liquid + val trackHeightDp = baseTrackHeight * heightFraction + val thumbWidth = 6.dp + val thumbHeight = 16.dp + val thumbShape = RoundedCornerShape(percent = 50) + + // THE KYANT 2.0.0-ALPHA03 GLASS ENGINE + val parentBackdrop = rememberLayerBackdrop { drawContent() } + val trackBackdrop = rememberLayerBackdrop { drawContent() } + val combinedBackdrop = rememberCombinedBackdrop(parentBackdrop, trackBackdrop) + + Slider( + value = position, + onValueChange = onSeek, + onValueChangeFinished = onSeekFinished, + valueRange = 0f..duration.coerceAtLeast(0.1f), + modifier = modifier + .fillMaxWidth() + .layerBackdrop(parentBackdrop), + interactionSource = interactionSource, + track = { sliderState -> + val disabledAlpha = 0.3f + + Canvas( + modifier = Modifier + .fillMaxWidth() + .height(trackHeightDp) + .layerBackdrop(trackBackdrop) + ) { + val min = sliderState.valueRange.start + val max = sliderState.valueRange.endInclusive + val range = (max - min).takeIf { it > 0f } ?: 1f + val playedFraction = ((sliderState.value - min) / range).coerceIn(0f, 1f) + val playedPx = size.width * playedFraction + val trackHeight = size.height + + val outerRadius = trackHeight / 2f + val innerRadius = outerRadius + + val thumbTrackGapSize = 14.dp.toPx() + val gapHalf = thumbTrackGapSize / 2f + val chapterGapHalf = 1.dp.toPx() + + val thumbGapStart = (playedPx - gapHalf).coerceIn(0f, size.width) + val thumbGapEnd = (playedPx + gapHalf).coerceIn(0f, size.width) + + val chapterGaps = chapters + .map { (it.start / duration).coerceIn(0f, 1f) * size.width } + .filter { it > 0f && it < size.width } + .map { x -> (x - chapterGapHalf) to (x + chapterGapHalf) } + + fun drawSegment(startX: Float, endX: Float, color: Color) { + if (endX - startX < 0.5f) return + val path = Path() + val isOuterLeft = startX <= 0.5f + val isInnerLeft = kotlin.math.abs(startX - thumbGapEnd) < 0.5f + + val cornerRadiusLeft = when { + isOuterLeft -> androidx.compose.ui.geometry.CornerRadius(outerRadius) + isInnerLeft -> androidx.compose.ui.geometry.CornerRadius(innerRadius) + else -> androidx.compose.ui.geometry.CornerRadius.Zero + } + + val isOuterRight = endX >= size.width - 0.5f + val isInnerRight = kotlin.math.abs(endX - thumbGapStart) < 0.5f + + val cornerRadiusRight = when { + isOuterRight -> androidx.compose.ui.geometry.CornerRadius(outerRadius) + isInnerRight -> androidx.compose.ui.geometry.CornerRadius(innerRadius) + else -> androidx.compose.ui.geometry.CornerRadius.Zero + } + + path.addRoundRect( + androidx.compose.ui.geometry.RoundRect( + left = startX, + top = 0f, + right = endX, + bottom = trackHeight, + topLeftCornerRadius = cornerRadiusLeft, + bottomLeftCornerRadius = cornerRadiusLeft, + topRightCornerRadius = cornerRadiusRight, + bottomRightCornerRadius = cornerRadiusRight + ) + ) + drawPath(path, color) + } + + fun drawRangeWithGaps( + rangeStart: Float, + rangeEnd: Float, + gaps: List>, + color: Color + ) { + if (rangeEnd <= rangeStart) return + val relevantGaps = gaps + .filter { (gStart, gEnd) -> gEnd > rangeStart && gStart < rangeEnd } + .sortedBy { it.first } + + var currentPos = rangeStart + for ((gStart, gEnd) in relevantGaps) { + val segmentEnd = gStart.coerceAtMost(rangeEnd) + if (segmentEnd > currentPos) { + drawSegment(currentPos, segmentEnd, color) + } + currentPos = gEnd.coerceAtLeast(currentPos) + } + if (currentPos < rangeEnd) { + drawSegment(currentPos, rangeEnd, color) + } + } + + drawRangeWithGaps(thumbGapEnd, size.width, chapterGaps, activeColor.copy(alpha = disabledAlpha)) + if (thumbGapStart > 0) { + drawRangeWithGaps(0f, thumbGapStart, chapterGaps, activeColor) + } + + if (loopStart != null || loopEnd != null) { + val loopColor = Color(0xFFFFB300) + val markerWidth = 2.dp.toPx() + + if (loopStart != null) { + val startPx = (loopStart / duration).coerceIn(0f, 1f) * size.width + drawLine( + color = loopColor, + start = Offset(startPx, 0f), + end = Offset(startPx, size.height), + strokeWidth = markerWidth + ) + } + + if (loopEnd != null) { + val endPx = (loopEnd / duration).coerceIn(0f, 1f) * size.width + drawLine( + color = loopColor, + start = Offset(endPx, 0f), + end = Offset(endPx, size.height), + strokeWidth = markerWidth + ) + } + + if (loopStart != null && loopEnd != null) { + val minPx = (minOf(loopStart, loopEnd) / duration).coerceIn(0f, 1f) * size.width + val maxPx = (maxOf(loopStart, loopEnd) / duration).coerceIn(0f, 1f) * size.width + drawRect( + color = loopColor.copy(alpha = 0.3f), + topLeft = Offset(minPx, 0f), + size = Size(maxPx - minPx, size.height) + ) + } + } + } + }, + thumb = { + Box( + modifier = Modifier + .width(thumbWidth) + .height(thumbHeight) + .drawBackdrop( + backdrop = combinedBackdrop, + shape = { thumbShape }, + effects = { + vibrancy() + blur(2f.dp.toPx()) + lens(12f.dp.toPx(), 24f.dp.toPx()) + }, + onDrawSurface = { + drawRect(activeColor, blendMode = BlendMode.Hue) + drawRect(activeColor.copy(alpha = 0.75f)) + } + ) + ) + } + ) +} + +// ========================================================================= +// ORIGINAL MPVEX SEEKBARS (Untouched, used as fallback when Liquid is OFF) +// ========================================================================= + @Composable private fun SquigglySeekbar( position: Float, @@ -280,30 +544,26 @@ private fun SquigglySeekbar( loopEnd: Float? = null, modifier: Modifier = Modifier, ) { - val primaryColor = MaterialTheme.colorScheme.primary + val activeColor = MaterialTheme.colorScheme.primary val surfaceVariant = MaterialTheme.colorScheme.surfaceVariant - // Manual Interaction State Tracking var isPressed by remember { mutableStateOf(false) } var isDragged by remember { mutableStateOf(false) } val isInteracting = isPressed || isDragged || isScrubbing - // Animation state var phaseOffset by remember { mutableFloatStateOf(0f) } var heightFraction by remember { mutableFloatStateOf(1f) } val scope = rememberCoroutineScope() - // Wave parameters val waveLength = 80f val lineAmplitude = if (useWavySeekbar) 6f else 0f - val phaseSpeed = 10f // px per second + val phaseSpeed = 10f val transitionPeriods = 1.5f val minWaveEndpoint = 0f val matchedWaveEndpoint = 1f val transitionEnabled = true - // Animate height fraction based on paused state and scrubbing state LaunchedEffect(isPaused, isScrubbing, useWavySeekbar) { if (!useWavySeekbar) { heightFraction = 0f @@ -332,7 +592,6 @@ private fun SquigglySeekbar( } } - // Animate wave movement only when not paused LaunchedEffect(isPaused, useWavySeekbar) { if (isPaused || !useWavySeekbar) return@LaunchedEffect @@ -348,10 +607,7 @@ private fun SquigglySeekbar( } Canvas( - modifier = - modifier - .fillMaxWidth() - .height(48.dp), + modifier = modifier.fillMaxWidth().height(48.dp), ) { val strokeWidth = 5.dp.toPx() val progress = if (duration > 0f) (position / duration).coerceIn(0f, 1f) else 0f @@ -359,7 +615,6 @@ private fun SquigglySeekbar( val totalProgressPx = totalWidth * progress val centerY = size.height / 2f - // Calculate wave progress val waveProgressPx = if (!transitionEnabled || progress > matchedWaveEndpoint) { totalWidth * progress @@ -368,11 +623,7 @@ private fun SquigglySeekbar( totalWidth * (minWaveEndpoint + (matchedWaveEndpoint - minWaveEndpoint) * t) } - // Helper function to compute amplitude - fun computeAmplitude( - x: Float, - sign: Float, - ): Float = + fun computeAmplitude(x: Float, sign: Float): Float = if (transitionEnabled) { val length = transitionPeriods * waveLength val coeff = ((waveProgressPx + length / 2f - x) / length).coerceIn(0f, 1f) @@ -381,7 +632,6 @@ private fun SquigglySeekbar( sign * heightFraction * lineAmplitude } - // Build wavy path for played portion val path = Path() val waveStart = -phaseOffset - waveLength / 2f val waveEnd = if (transitionEnabled) totalWidth else waveProgressPx @@ -400,45 +650,26 @@ private fun SquigglySeekbar( val nextAmp = computeAmplitude(nextX, waveSign) path.cubicTo( - midX, - centerY + currentAmp, - midX, - centerY + nextAmp, - nextX, - centerY + nextAmp, + midX, centerY + currentAmp, + midX, centerY + nextAmp, + nextX, centerY + nextAmp, ) - currentAmp = nextAmp currentX = nextX } - // Draw path up to progress position using clipping val clipTop = lineAmplitude + strokeWidth val gapHalf = 1.dp.toPx() - fun drawPathWithGaps( - startX: Float, - endX: Float, - color: Color, - ) { + fun drawPathWithGaps(startX: Float, endX: Float, color: Color) { if (endX <= startX) return if (duration <= 0f) { - clipRect( - left = startX, - top = centerY - clipTop, - right = endX, - bottom = centerY + clipTop, - ) { - drawPath( - path = path, - color = color, - style = Stroke(width = strokeWidth, cap = StrokeCap.Round), - ) + clipRect(left = startX, top = centerY - clipTop, right = endX, bottom = centerY + clipTop) { + drawPath(path = path, color = color, style = Stroke(width = strokeWidth, cap = StrokeCap.Round)) } return } - val gaps = - chapters + val gaps = chapters .map { (it.start / duration).coerceIn(0f, 1f) * totalWidth } .filter { it in startX..endX } .sorted() @@ -447,43 +678,24 @@ private fun SquigglySeekbar( var segmentStart = startX for ((gapStart, gapEnd) in gaps) { if (gapStart > segmentStart) { - clipRect( - left = segmentStart, - top = centerY - clipTop, - right = gapStart, - bottom = centerY + clipTop, - ) { - drawPath( - path = path, - color = color, - style = Stroke(width = strokeWidth, cap = StrokeCap.Round), - ) + clipRect(left = segmentStart, top = centerY - clipTop, right = gapStart, bottom = centerY + clipTop) { + drawPath(path = path, color = color, style = Stroke(width = strokeWidth, cap = StrokeCap.Round)) } } segmentStart = gapEnd } if (segmentStart < endX) { - clipRect( - left = segmentStart, - top = centerY - clipTop, - right = endX, - bottom = centerY + clipTop, - ) { - drawPath( - path = path, - color = color, - style = Stroke(width = strokeWidth, cap = StrokeCap.Round), - ) + clipRect(left = segmentStart, top = centerY - clipTop, right = endX, bottom = centerY + clipTop) { + drawPath(path = path, color = color, style = Stroke(width = strokeWidth, cap = StrokeCap.Round)) } } } - // Played segment - drawPathWithGaps(0f, totalProgressPx, primaryColor) + drawPathWithGaps(0f, totalProgressPx, activeColor) if (transitionEnabled) { val disabledAlpha = 77f / 255f - drawPathWithGaps(totalProgressPx, totalWidth, primaryColor.copy(alpha = disabledAlpha)) + drawPathWithGaps(totalProgressPx, totalWidth, activeColor.copy(alpha = disabledAlpha)) } else { drawLine( color = surfaceVariant.copy(alpha = 0.4f), @@ -494,21 +706,19 @@ private fun SquigglySeekbar( ) } - // Draw round cap val startAmp = kotlin.math.cos(kotlin.math.abs(waveStart) / waveLength * (2f * kotlin.math.PI.toFloat())) drawCircle( - color = primaryColor, + color = activeColor, radius = strokeWidth / 2f, center = Offset(0f, centerY + startAmp * lineAmplitude * heightFraction), ) - // Vertical Bar Thumb val barHalfHeight = (lineAmplitude + strokeWidth) val barWidth = 5.dp.toPx() if (barHalfHeight > 0.5f) { drawLine( - color = primaryColor, + color = activeColor, start = Offset(totalProgressPx, centerY - barHalfHeight), end = Offset(totalProgressPx, centerY + barHalfHeight), strokeWidth = barWidth, @@ -516,7 +726,6 @@ private fun SquigglySeekbar( ) } - // A-B Loop Indicators for SquigglySeekbar if (loopStart != null || loopEnd != null) { val loopColor = Color(0xFFFFB300) val markerWidth = 2.dp.toPx() @@ -579,13 +788,13 @@ fun VideoTimer( } @Composable +@OptIn(ExperimentalMaterial3Api::class) fun StandardSeekbar( position: Float, duration: Float, chapters: ImmutableList, isPaused: Boolean = false, isScrubbing: Boolean = false, - useWavySeekbar: Boolean = false, seekbarStyle: SeekbarStyle = SeekbarStyle.Standard, onSeek: (Float) -> Unit, onSeekFinished: () -> Unit, @@ -593,18 +802,16 @@ fun StandardSeekbar( loopEnd: Float? = null, modifier: Modifier = Modifier, ) { - val primaryColor = MaterialTheme.colorScheme.primary + val activeColor = MaterialTheme.colorScheme.primary val interactionSource = remember { MutableInteractionSource() } - // Animation state (same as SquigglySeekbar) var heightFraction by remember { mutableFloatStateOf(1f) } val scope = rememberCoroutineScope() - // Animate height fraction based on paused state and scrubbing state (same as SquigglySeekbar) LaunchedEffect(isPaused, isScrubbing) { scope.launch { val shouldFlatten = isPaused || isScrubbing - val targetHeight = if (shouldFlatten) 0.7f else 1f // Slightly less dramatic for standard seekbar + val targetHeight = if (shouldFlatten) 0.7f else 1f val animationDuration = if (shouldFlatten) 550 else 800 val startDelay = if (shouldFlatten) 0L else 60L @@ -625,7 +832,7 @@ fun StandardSeekbar( val isThick = seekbarStyle == SeekbarStyle.Thick val baseTrackHeight = if (isThick) 16.dp else 8.dp - val trackHeightDp = baseTrackHeight * heightFraction // Apply animation to track height + val trackHeightDp = baseTrackHeight * heightFraction val thumbWidth = 6.dp val thumbHeight = if (isThick) 16.dp else 24.dp val thumbShape = if (isThick) RoundedCornerShape(thumbWidth / 2) else CircleShape @@ -648,16 +855,11 @@ fun StandardSeekbar( val min = sliderState.valueRange.start val max = sliderState.valueRange.endInclusive val range = (max - min).takeIf { it > 0f } ?: 1f - val playedFraction = ((sliderState.value - min) / range).coerceIn(0f, 1f) - val playedPx = size.width * playedFraction val trackHeight = size.height - // Radius for the outer ends of the seekbar val outerRadius = trackHeight / 2f - - // MODIFIED: For Thick style, inner corners now match the outer rounding val innerRadius = if (isThick) outerRadius else 2.dp.toPx() val thumbTrackGapSize = 14.dp.toPx() @@ -674,7 +876,6 @@ fun StandardSeekbar( fun drawSegment(startX: Float, endX: Float, color: Color) { if (endX - startX < 0.5f) return - val path = Path() val isOuterLeft = startX <= 0.5f val isInnerLeft = kotlin.math.abs(startX - thumbGapEnd) < 0.5f @@ -733,20 +934,15 @@ fun StandardSeekbar( } } - // 1. Unplayed Background - drawRangeWithGaps(thumbGapEnd, size.width, chapterGaps, primaryColor.copy(alpha = disabledAlpha)) - - // 2. Played + drawRangeWithGaps(thumbGapEnd, size.width, chapterGaps, activeColor.copy(alpha = disabledAlpha)) if (thumbGapStart > 0) { - drawRangeWithGaps(0f, thumbGapStart, chapterGaps, primaryColor) + drawRangeWithGaps(0f, thumbGapStart, chapterGaps, activeColor) } - // 3. A-B Loop Indicators if (loopStart != null || loopEnd != null) { - val loopColor = Color(0xFFFFB300) // Amber/Gold color for loop + val loopColor = Color(0xFFFFB300) val markerWidth = 2.dp.toPx() - // Draw loop start marker if (loopStart != null) { val startPx = (loopStart / duration).coerceIn(0f, 1f) * size.width drawLine( @@ -757,7 +953,6 @@ fun StandardSeekbar( ) } - // Draw loop end marker if (loopEnd != null) { val endPx = (loopEnd / duration).coerceIn(0f, 1f) * size.width drawLine( @@ -768,12 +963,9 @@ fun StandardSeekbar( ) } - // Draw connected segment if both are set if (loopStart != null && loopEnd != null) { val minPx = (minOf(loopStart, loopEnd) / duration).coerceIn(0f, 1f) * size.width val maxPx = (maxOf(loopStart, loopEnd) / duration).coerceIn(0f, 1f) * size.width - - // Draw a semi-transparent overlay between A and B drawRect( color = loopColor.copy(alpha = 0.3f), topLeft = Offset(minPx, 0f), @@ -783,29 +975,13 @@ fun StandardSeekbar( } } }, - thumb = { - Box( - modifier = Modifier - .width(thumbWidth) - .height(thumbHeight) - .background(primaryColor, thumbShape) - ) - } - ) - } - -@Preview -@Composable -private fun PreviewSeekBar() { - SeekbarWithTimers( - position = 30f, - duration = 180f, - onValueChange = {}, - onValueChangeFinished = {}, - timersInverted = Pair(false, true), - positionTimerOnClick = {}, - durationTimerOnCLick = {}, - chapters = persistentListOf(), - paused = false, - ) + thumb = { + Box( + modifier = Modifier + .width(thumbWidth) + .height(thumbHeight) + .background(activeColor, thumbShape) + ) + } + ) } From cff03008af2980e6db89515896d3b9a71b6f3477 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 08:47:41 +0530 Subject: [PATCH 068/194] Update SeekbarStyle.kt --- .../java/app/marlboroadvance/mpvex/preferences/SeekbarStyle.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/preferences/SeekbarStyle.kt b/app/src/main/java/app/marlboroadvance/mpvex/preferences/SeekbarStyle.kt index 2635509cd..037a0ddc1 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/preferences/SeekbarStyle.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/preferences/SeekbarStyle.kt @@ -4,4 +4,5 @@ enum class SeekbarStyle { Standard, Wavy, Thick, + Liquid } From 5d7ac655eac0b0e0e41693401570f62bc2c2da9e Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 08:48:01 +0530 Subject: [PATCH 069/194] Update SeekbarStyle.kt --- .../java/app/marlboroadvance/mpvex/preferences/SeekbarStyle.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/preferences/SeekbarStyle.kt b/app/src/main/java/app/marlboroadvance/mpvex/preferences/SeekbarStyle.kt index 037a0ddc1..137c98c68 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/preferences/SeekbarStyle.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/preferences/SeekbarStyle.kt @@ -4,5 +4,5 @@ enum class SeekbarStyle { Standard, Wavy, Thick, - Liquid + Liquid, } From a3e5ff19ed94340a883d48cff2942e1fa384e549 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 08:55:59 +0530 Subject: [PATCH 070/194] Update Seekbar.kt --- .../ui/player/controls/components/Seekbar.kt | 150 ++++++++++-------- 1 file changed, 86 insertions(+), 64 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index c9a9bf8ce..65405853f 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -225,71 +225,93 @@ fun SeekbarWithTimers( liquidColor = liquidColor ) } else { - // If Liquid UI is off, fall back to the standard mpvEx styles - when (seekbarStyle) { - SeekbarStyle.Standard -> { - StandardSeekbar( - position = if (isUserInteracting) userPosition else animatedPosition.value, - duration = duration, - chapters = chapters, - isPaused = paused, - isScrubbing = isUserInteracting, - seekbarStyle = SeekbarStyle.Standard, - onSeek = { newPosition -> - if (!isUserInteracting) isUserInteracting = true - userPosition = newPosition - onValueChange(newPosition) - }, - onSeekFinished = { - scope.launch { animatedPosition.snapTo(userPosition) } - isUserInteracting = false - onValueChangeFinished() - }, - loopStart = loopStart, - loopEnd = loopEnd, - ) - } - SeekbarStyle.Wavy -> { - SquigglySeekbar( - position = if (isUserInteracting) userPosition else animatedPosition.value, - duration = duration, - chapters = chapters, - isPaused = paused, - isScrubbing = isUserInteracting, - useWavySeekbar = true, - seekbarStyle = SeekbarStyle.Wavy, - onSeek = { }, - onSeekFinished = { }, - loopStart = loopStart, - loopEnd = loopEnd, - ) - } - SeekbarStyle.Thick -> { - StandardSeekbar( - position = if (isUserInteracting) userPosition else animatedPosition.value, - duration = duration, - chapters = chapters, - isPaused = paused, - isScrubbing = isUserInteracting, - seekbarStyle = SeekbarStyle.Thick, - onSeek = { newPosition -> - if (!isUserInteracting) isUserInteracting = true - userPosition = newPosition - onValueChange(newPosition) - }, - onSeekFinished = { - scope.launch { animatedPosition.snapTo(userPosition) } - isUserInteracting = false - onValueChangeFinished() - }, - loopStart = loopStart, - loopEnd = loopEnd, - ) - } + // ROUTING LOGIC: Let the user choose explicitly from the settings menu! + when (seekbarStyle) { + SeekbarStyle.Liquid -> { + LiquidSeekbar( + position = if (isUserInteracting) userPosition else animatedPosition.value, + duration = duration, + chapters = chapters, + isPaused = paused, + isScrubbing = isUserInteracting, + onSeek = { newPosition -> + if (!isUserInteracting) isUserInteracting = true + userPosition = newPosition + onValueChange(newPosition) + }, + onSeekFinished = { + scope.launch { animatedPosition.snapTo(userPosition) } + isUserInteracting = false + onValueChangeFinished() + }, + loopStart = loopStart, + loopEnd = loopEnd, + liquidColor = liquidColor + ) + } + SeekbarStyle.Standard -> { + StandardSeekbar( + position = if (isUserInteracting) userPosition else animatedPosition.value, + duration = duration, + chapters = chapters, + isPaused = paused, + isScrubbing = isUserInteracting, + seekbarStyle = SeekbarStyle.Standard, + onSeek = { newPosition -> + if (!isUserInteracting) isUserInteracting = true + userPosition = newPosition + onValueChange(newPosition) + }, + onSeekFinished = { + scope.launch { animatedPosition.snapTo(userPosition) } + isUserInteracting = false + onValueChangeFinished() + }, + loopStart = loopStart, + loopEnd = loopEnd, + ) + } + SeekbarStyle.Wavy -> { + SquigglySeekbar( + position = if (isUserInteracting) userPosition else animatedPosition.value, + duration = duration, + chapters = chapters, + isPaused = paused, + isScrubbing = isUserInteracting, + useWavySeekbar = true, + seekbarStyle = SeekbarStyle.Wavy, + onSeek = { }, + onSeekFinished = { }, + loopStart = loopStart, + loopEnd = loopEnd, + ) + } + SeekbarStyle.Thick -> { + StandardSeekbar( + position = if (isUserInteracting) userPosition else animatedPosition.value, + duration = duration, + chapters = chapters, + isPaused = paused, + isScrubbing = isUserInteracting, + seekbarStyle = SeekbarStyle.Thick, + onSeek = { newPosition -> + if (!isUserInteracting) isUserInteracting = true + userPosition = newPosition + onValueChange(newPosition) + }, + onSeekFinished = { + scope.launch { animatedPosition.snapTo(userPosition) } + isUserInteracting = false + onValueChangeFinished() + }, + loopStart = loopStart, + loopEnd = loopEnd, + ) } - } - } - } + } + } + } + } VideoTimer( value = if (timersInverted.second) position - duration else duration, From 8e6ba955d58a4585120d54f53ccfafec89fb6b89 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 09:54:07 +0530 Subject: [PATCH 071/194] Update Seekbar.kt --- .../ui/player/controls/components/Seekbar.kt | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index 65405853f..9c747e8f1 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -324,10 +324,8 @@ fun SeekbarWithTimers( ) } } - // ========================================================================= // NEW: STANDALONE LIQUID SEEKBAR COMPONENT -// Built specifically for Kyant 2.0.0-alpha03 combined backdrops // ========================================================================= @Composable @OptIn(ExperimentalMaterial3Api::class) @@ -372,25 +370,21 @@ fun LiquidSeekbar( } } - val baseTrackHeight = 16.dp // Thick base for liquid + val baseTrackHeight = 16.dp val trackHeightDp = baseTrackHeight * heightFraction val thumbWidth = 6.dp val thumbHeight = 16.dp val thumbShape = RoundedCornerShape(percent = 50) - // THE KYANT 2.0.0-ALPHA03 GLASS ENGINE - val parentBackdrop = rememberLayerBackdrop { drawContent() } + // THE FIX: We only capture the track. No recursive parent backdrops! val trackBackdrop = rememberLayerBackdrop { drawContent() } - val combinedBackdrop = rememberCombinedBackdrop(parentBackdrop, trackBackdrop) Slider( value = position, onValueChange = onSeek, onValueChangeFinished = onSeekFinished, valueRange = 0f..duration.coerceAtLeast(0.1f), - modifier = modifier - .fillMaxWidth() - .layerBackdrop(parentBackdrop), + modifier = modifier.fillMaxWidth(), // Removed the crashing parentBackdrop modifier interactionSource = interactionSource, track = { sliderState -> val disabledAlpha = 0.3f @@ -399,7 +393,7 @@ fun LiquidSeekbar( modifier = Modifier .fillMaxWidth() .height(trackHeightDp) - .layerBackdrop(trackBackdrop) + .layerBackdrop(trackBackdrop) // Capture the track layer for the thumb to refract ) { val min = sliderState.valueRange.start val max = sliderState.valueRange.endInclusive @@ -530,7 +524,7 @@ fun LiquidSeekbar( .width(thumbWidth) .height(thumbHeight) .drawBackdrop( - backdrop = combinedBackdrop, + backdrop = trackBackdrop, // Refracts the colored track cleanly shape = { thumbShape }, effects = { vibrancy() @@ -547,6 +541,8 @@ fun LiquidSeekbar( ) } + + // ========================================================================= // ORIGINAL MPVEX SEEKBARS (Untouched, used as fallback when Liquid is OFF) // ========================================================================= From fec1ac2e13fdabaeaf4f4bd94aa285fa84c871f3 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 10:37:23 +0530 Subject: [PATCH 072/194] Update Seekbar.kt --- .../ui/player/controls/components/Seekbar.kt | 663 ++++-------------- 1 file changed, 139 insertions(+), 524 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index 9c747e8f1..5c7455343 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -2,16 +2,20 @@ package app.marlboroadvance.mpvex.ui.player.controls.components import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.RepeatMode import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row @@ -21,11 +25,12 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.ripple import androidx.compose.material3.Slider -import androidx.compose.material3.SliderDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -45,26 +50,15 @@ import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.clipRect -import androidx.compose.ui.graphics.drawscope.withTransform -import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.isSpecified import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.geometry.Size -import androidx.compose.foundation.background -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import app.marlboroadvance.mpvex.ui.player.controls.LocalPlayerButtonsClickEvent -import app.marlboroadvance.mpvex.ui.theme.spacing -import app.marlboroadvance.mpvex.preferences.SeekbarStyle -import app.marlboroadvance.mpvex.preferences.LiquidUIPreferences // --- KYANT BACKDROP 2.0.0-ALPHA03 IMPORTS --- -import com.kyant.backdrop.backdrops.layerBackdrop import com.kyant.backdrop.backdrops.rememberLayerBackdrop import com.kyant.backdrop.backdrops.rememberCombinedBackdrop import com.kyant.backdrop.drawBackdrop @@ -73,10 +67,13 @@ import com.kyant.backdrop.effects.lens import com.kyant.backdrop.effects.vibrancy // -------------------------------------------- +import app.marlboroadvance.mpvex.ui.player.controls.LocalPlayerButtonsClickEvent +import app.marlboroadvance.mpvex.ui.theme.spacing +import app.marlboroadvance.mpvex.preferences.SeekbarStyle +import app.marlboroadvance.mpvex.preferences.LiquidUIPreferences import dev.vivvvek.seeker.Segment import `is`.xyz.mpv.Utils import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch @@ -101,7 +98,6 @@ fun SeekbarWithTimers( var isUserInteracting by remember { mutableStateOf(false) } var userPosition by remember { mutableFloatStateOf(position) } - // Inject Liquid UI Preferences for Custom Slider Color val context = LocalContext.current val liquidPrefs = remember { LiquidUIPreferences(context) } val isLiquidUI by liquidPrefs.liquidUIEnabledFlow.collectAsState(false) @@ -116,11 +112,7 @@ fun SeekbarWithTimers( scope.launch { animatedPosition.animateTo( targetValue = position, - animationSpec = - tween( - durationMillis = 200, - easing = LinearEasing, - ), + animationSpec = tween(durationMillis = 200, easing = LinearEasing), ) } } @@ -134,24 +126,16 @@ fun SeekbarWithTimers( VideoTimer( value = if (isUserInteracting) userPosition else position, timersInverted.first, - onClick = { - clickEvent() - positionTimerOnClick() - }, + onClick = { clickEvent(); positionTimerOnClick() }, modifier = Modifier.width(92.dp), ) Box( - modifier = Modifier - .weight(1f) - .height(48.dp) - .padding(vertical = 8.dp), + modifier = Modifier.weight(1f).height(48.dp).padding(vertical = 8.dp), contentAlignment = Alignment.Center, ) { Box( - modifier = Modifier - .fillMaxWidth() - .height(64.dp) + modifier = Modifier.fillMaxWidth().height(64.dp) .pointerInput(Unit) { detectTapGestures( onTap = { offset -> @@ -196,136 +180,56 @@ fun SeekbarWithTimers( ) Box( - modifier = Modifier - .fillMaxWidth() - .height(32.dp), + modifier = Modifier.fillMaxWidth().height(32.dp), contentAlignment = Alignment.Center, ) { - - // ROUTING LOGIC: If Liquid UI is enabled, use the standalone component! - if (isLiquidUI) { - LiquidSeekbar( - position = if (isUserInteracting) userPosition else animatedPosition.value, - duration = duration, - chapters = chapters, - isPaused = paused, - isScrubbing = isUserInteracting, - onSeek = { newPosition -> - if (!isUserInteracting) isUserInteracting = true - userPosition = newPosition - onValueChange(newPosition) - }, - onSeekFinished = { - scope.launch { animatedPosition.snapTo(userPosition) } - isUserInteracting = false - onValueChangeFinished() - }, - loopStart = loopStart, - loopEnd = loopEnd, - liquidColor = liquidColor - ) - } else { - // ROUTING LOGIC: Let the user choose explicitly from the settings menu! - when (seekbarStyle) { - SeekbarStyle.Liquid -> { - LiquidSeekbar( - position = if (isUserInteracting) userPosition else animatedPosition.value, - duration = duration, - chapters = chapters, - isPaused = paused, - isScrubbing = isUserInteracting, - onSeek = { newPosition -> - if (!isUserInteracting) isUserInteracting = true - userPosition = newPosition - onValueChange(newPosition) - }, - onSeekFinished = { - scope.launch { animatedPosition.snapTo(userPosition) } - isUserInteracting = false - onValueChangeFinished() - }, - loopStart = loopStart, - loopEnd = loopEnd, - liquidColor = liquidColor - ) - } - SeekbarStyle.Standard -> { - StandardSeekbar( - position = if (isUserInteracting) userPosition else animatedPosition.value, - duration = duration, - chapters = chapters, - isPaused = paused, - isScrubbing = isUserInteracting, - seekbarStyle = SeekbarStyle.Standard, - onSeek = { newPosition -> - if (!isUserInteracting) isUserInteracting = true - userPosition = newPosition - onValueChange(newPosition) - }, - onSeekFinished = { - scope.launch { animatedPosition.snapTo(userPosition) } - isUserInteracting = false - onValueChangeFinished() - }, - loopStart = loopStart, - loopEnd = loopEnd, - ) - } - SeekbarStyle.Wavy -> { - SquigglySeekbar( - position = if (isUserInteracting) userPosition else animatedPosition.value, - duration = duration, - chapters = chapters, - isPaused = paused, - isScrubbing = isUserInteracting, - useWavySeekbar = true, - seekbarStyle = SeekbarStyle.Wavy, - onSeek = { }, - onSeekFinished = { }, - loopStart = loopStart, - loopEnd = loopEnd, - ) - } - SeekbarStyle.Thick -> { - StandardSeekbar( - position = if (isUserInteracting) userPosition else animatedPosition.value, - duration = duration, - chapters = chapters, - isPaused = paused, - isScrubbing = isUserInteracting, - seekbarStyle = SeekbarStyle.Thick, - onSeek = { newPosition -> - if (!isUserInteracting) isUserInteracting = true - userPosition = newPosition - onValueChange(newPosition) - }, - onSeekFinished = { - scope.launch { animatedPosition.snapTo(userPosition) } - isUserInteracting = false - onValueChangeFinished() - }, - loopStart = loopStart, - loopEnd = loopEnd, + if (isLiquidUI || seekbarStyle == SeekbarStyle.Liquid) { + LiquidSeekbar( + position = if (isUserInteracting) userPosition else animatedPosition.value, + duration = duration, + chapters = chapters, + isPaused = paused, + isScrubbing = isUserInteracting, + onSeek = { }, + onSeekFinished = { }, + loopStart = loopStart, + loopEnd = loopEnd, + liquidColor = liquidColor ) - } + } else { + when (seekbarStyle) { + SeekbarStyle.Standard -> StandardSeekbar( + position = if (isUserInteracting) userPosition else animatedPosition.value, + duration = duration, chapters = chapters, isPaused = paused, isScrubbing = isUserInteracting, + seekbarStyle = SeekbarStyle.Standard, onSeek = { }, onSeekFinished = { }, loopStart = loopStart, loopEnd = loopEnd, + ) + SeekbarStyle.Wavy -> SquigglySeekbar( + position = if (isUserInteracting) userPosition else animatedPosition.value, + duration = duration, chapters = chapters, isPaused = paused, isScrubbing = isUserInteracting, + useWavySeekbar = true, seekbarStyle = SeekbarStyle.Wavy, onSeek = { }, onSeekFinished = { }, loopStart = loopStart, loopEnd = loopEnd, + ) + SeekbarStyle.Thick -> StandardSeekbar( + position = if (isUserInteracting) userPosition else animatedPosition.value, + duration = duration, chapters = chapters, isPaused = paused, isScrubbing = isUserInteracting, + seekbarStyle = SeekbarStyle.Thick, onSeek = { }, onSeekFinished = { }, loopStart = loopStart, loopEnd = loopEnd, + ) + else -> {} + } } - } - } - } + } + } VideoTimer( value = if (timersInverted.second) position - duration else duration, isInverted = timersInverted.second, - onClick = { - clickEvent() - durationTimerOnCLick() - }, + onClick = { clickEvent(); durationTimerOnCLick() }, modifier = Modifier.width(92.dp), ) } } + // ========================================================================= -// NEW: STANDALONE LIQUID SEEKBAR COMPONENT +// NEW: CRASH-PROOF LIQUID SEEKBAR WITH NATIVE SQUISH PHYSICS // ========================================================================= @Composable @OptIn(ExperimentalMaterial3Api::class) @@ -344,6 +248,7 @@ fun LiquidSeekbar( ) { val activeColor = if (liquidColor.isSpecified) liquidColor else MaterialTheme.colorScheme.primary val interactionSource = remember { MutableInteractionSource() } + val isPressed by interactionSource.collectIsPressedAsState() var heightFraction by remember { mutableFloatStateOf(1f) } val scope = rememberCoroutineScope() @@ -352,48 +257,44 @@ fun LiquidSeekbar( scope.launch { val shouldFlatten = isPaused || isScrubbing val targetHeight = if (shouldFlatten) 0.7f else 1f - val animationDuration = if (shouldFlatten) 550 else 800 - val startDelay = if (shouldFlatten) 0L else 60L - - kotlinx.coroutines.delay(startDelay) - - val animator = Animatable(heightFraction) - animator.animateTo( + kotlinx.coroutines.delay(if (shouldFlatten) 0L else 60L) + Animatable(heightFraction).animateTo( targetValue = targetHeight, - animationSpec = tween( - durationMillis = animationDuration, - easing = LinearEasing, - ), - ) { - heightFraction = value - } + animationSpec = tween(durationMillis = if (shouldFlatten) 550 else 800, easing = LinearEasing), + ) { heightFraction = value } } } val baseTrackHeight = 16.dp val trackHeightDp = baseTrackHeight * heightFraction - val thumbWidth = 6.dp val thumbHeight = 16.dp val thumbShape = RoundedCornerShape(percent = 50) - // THE FIX: We only capture the track. No recursive parent backdrops! + // Native Squish Physics (No DampedDragAnimation file needed!) + val thumbWidth by animateDpAsState( + targetValue = if (isPressed || isScrubbing) 24.dp else 16.dp, + animationSpec = spring(dampingRatio = 0.6f, stiffness = 800f), + label = "thumb_stretch" + ) + + // Option 3: Combined Backdrop Implementation (Crash-Proof) + val parentBackdrop = rememberLayerBackdrop() val trackBackdrop = rememberLayerBackdrop { drawContent() } + val combinedBackdrop = rememberCombinedBackdrop(parentBackdrop, trackBackdrop) Slider( value = position, onValueChange = onSeek, onValueChangeFinished = onSeekFinished, valueRange = 0f..duration.coerceAtLeast(0.1f), - modifier = modifier.fillMaxWidth(), // Removed the crashing parentBackdrop modifier + modifier = modifier.fillMaxWidth(), interactionSource = interactionSource, track = { sliderState -> - val disabledAlpha = 0.3f - Canvas( modifier = Modifier .fillMaxWidth() .height(trackHeightDp) - .layerBackdrop(trackBackdrop) // Capture the track layer for the thumb to refract + .layerBackdrop(trackBackdrop) ) { val min = sliderState.valueRange.start val max = sliderState.valueRange.endInclusive @@ -405,8 +306,7 @@ fun LiquidSeekbar( val outerRadius = trackHeight / 2f val innerRadius = outerRadius - val thumbTrackGapSize = 14.dp.toPx() - val gapHalf = thumbTrackGapSize / 2f + val gapHalf = 14.dp.toPx() / 2f val chapterGapHalf = 1.dp.toPx() val thumbGapStart = (playedPx - gapHalf).coerceIn(0f, size.width) @@ -440,80 +340,44 @@ fun LiquidSeekbar( path.addRoundRect( androidx.compose.ui.geometry.RoundRect( - left = startX, - top = 0f, - right = endX, - bottom = trackHeight, - topLeftCornerRadius = cornerRadiusLeft, - bottomLeftCornerRadius = cornerRadiusLeft, - topRightCornerRadius = cornerRadiusRight, - bottomRightCornerRadius = cornerRadiusRight + left = startX, top = 0f, right = endX, bottom = trackHeight, + topLeftCornerRadius = cornerRadiusLeft, bottomLeftCornerRadius = cornerRadiusLeft, + topRightCornerRadius = cornerRadiusRight, bottomRightCornerRadius = cornerRadiusRight ) ) drawPath(path, color) } - fun drawRangeWithGaps( - rangeStart: Float, - rangeEnd: Float, - gaps: List>, - color: Color - ) { + fun drawRangeWithGaps(rangeStart: Float, rangeEnd: Float, gaps: List>, color: Color) { if (rangeEnd <= rangeStart) return - val relevantGaps = gaps - .filter { (gStart, gEnd) -> gEnd > rangeStart && gStart < rangeEnd } - .sortedBy { it.first } - + val relevantGaps = gaps.filter { (gStart, gEnd) -> gEnd > rangeStart && gStart < rangeEnd }.sortedBy { it.first } var currentPos = rangeStart for ((gStart, gEnd) in relevantGaps) { val segmentEnd = gStart.coerceAtMost(rangeEnd) - if (segmentEnd > currentPos) { - drawSegment(currentPos, segmentEnd, color) - } + if (segmentEnd > currentPos) drawSegment(currentPos, segmentEnd, color) currentPos = gEnd.coerceAtLeast(currentPos) } - if (currentPos < rangeEnd) { - drawSegment(currentPos, rangeEnd, color) - } + if (currentPos < rangeEnd) drawSegment(currentPos, rangeEnd, color) } - drawRangeWithGaps(thumbGapEnd, size.width, chapterGaps, activeColor.copy(alpha = disabledAlpha)) - if (thumbGapStart > 0) { - drawRangeWithGaps(0f, thumbGapStart, chapterGaps, activeColor) - } + drawRangeWithGaps(thumbGapEnd, size.width, chapterGaps, activeColor.copy(alpha = 0.3f)) + if (thumbGapStart > 0) drawRangeWithGaps(0f, thumbGapStart, chapterGaps, activeColor) if (loopStart != null || loopEnd != null) { val loopColor = Color(0xFFFFB300) val markerWidth = 2.dp.toPx() - if (loopStart != null) { val startPx = (loopStart / duration).coerceIn(0f, 1f) * size.width - drawLine( - color = loopColor, - start = Offset(startPx, 0f), - end = Offset(startPx, size.height), - strokeWidth = markerWidth - ) + drawLine(color = loopColor, start = Offset(startPx, 0f), end = Offset(startPx, size.height), strokeWidth = markerWidth) } - if (loopEnd != null) { val endPx = (loopEnd / duration).coerceIn(0f, 1f) * size.width - drawLine( - color = loopColor, - start = Offset(endPx, 0f), - end = Offset(endPx, size.height), - strokeWidth = markerWidth - ) + drawLine(color = loopColor, start = Offset(endPx, 0f), end = Offset(endPx, size.height), strokeWidth = markerWidth) } - if (loopStart != null && loopEnd != null) { val minPx = (minOf(loopStart, loopEnd) / duration).coerceIn(0f, 1f) * size.width val maxPx = (maxOf(loopStart, loopEnd) / duration).coerceIn(0f, 1f) * size.width - drawRect( - color = loopColor.copy(alpha = 0.3f), - topLeft = Offset(minPx, 0f), - size = Size(maxPx - minPx, size.height) - ) + drawRect(color = loopColor.copy(alpha = 0.3f), topLeft = Offset(minPx, 0f), size = Size(maxPx - minPx, size.height)) } } } @@ -524,12 +388,15 @@ fun LiquidSeekbar( .width(thumbWidth) .height(thumbHeight) .drawBackdrop( - backdrop = trackBackdrop, // Refracts the colored track cleanly + backdrop = combinedBackdrop, shape = { thumbShape }, effects = { vibrancy() - blur(2f.dp.toPx()) - lens(12f.dp.toPx(), 24f.dp.toPx()) + blur(if (isPressed || isScrubbing) 4f.dp.toPx() else 8f.dp.toPx()) + lens( + if (isPressed || isScrubbing) 14f.dp.toPx() else 10f.dp.toPx(), + if (isPressed || isScrubbing) 28f.dp.toPx() else 14f.dp.toPx() + ) }, onDrawSurface = { drawRect(activeColor, blendMode = BlendMode.Hue) @@ -541,37 +408,20 @@ fun LiquidSeekbar( ) } - - // ========================================================================= -// ORIGINAL MPVEX SEEKBARS (Untouched, used as fallback when Liquid is OFF) +// ORIGINAL MPVEX SEEKBARS // ========================================================================= @Composable private fun SquigglySeekbar( - position: Float, - duration: Float, - chapters: ImmutableList, - isPaused: Boolean, - isScrubbing: Boolean, - useWavySeekbar: Boolean, - seekbarStyle: SeekbarStyle, - onSeek: (Float) -> Unit, - onSeekFinished: () -> Unit, - loopStart: Float? = null, - loopEnd: Float? = null, - modifier: Modifier = Modifier, + position: Float, duration: Float, chapters: ImmutableList, isPaused: Boolean, isScrubbing: Boolean, + useWavySeekbar: Boolean, seekbarStyle: SeekbarStyle, onSeek: (Float) -> Unit, onSeekFinished: () -> Unit, + loopStart: Float? = null, loopEnd: Float? = null, modifier: Modifier = Modifier, ) { val activeColor = MaterialTheme.colorScheme.primary val surfaceVariant = MaterialTheme.colorScheme.surfaceVariant - - var isPressed by remember { mutableStateOf(false) } - var isDragged by remember { mutableStateOf(false) } - val isInteracting = isPressed || isDragged || isScrubbing - var phaseOffset by remember { mutableFloatStateOf(0f) } var heightFraction by remember { mutableFloatStateOf(1f) } - val scope = rememberCoroutineScope() val waveLength = 80f @@ -583,79 +433,42 @@ private fun SquigglySeekbar( val transitionEnabled = true LaunchedEffect(isPaused, isScrubbing, useWavySeekbar) { - if (!useWavySeekbar) { - heightFraction = 0f - return@LaunchedEffect - } - + if (!useWavySeekbar) { heightFraction = 0f; return@LaunchedEffect } scope.launch { val shouldFlatten = isPaused || isScrubbing - val targetHeight = if (shouldFlatten) 0f else 1f - val duration = if (shouldFlatten) 550 else 800 - val startDelay = if (shouldFlatten) 0L else 60L - - kotlinx.coroutines.delay(startDelay) - - val animator = Animatable(heightFraction) - animator.animateTo( - targetValue = targetHeight, - animationSpec = - tween( - durationMillis = duration, - easing = LinearEasing, - ), - ) { - heightFraction = value - } + kotlinx.coroutines.delay(if (shouldFlatten) 0L else 60L) + Animatable(heightFraction).animateTo( + targetValue = if (shouldFlatten) 0f else 1f, + animationSpec = tween(durationMillis = if (shouldFlatten) 550 else 800, easing = LinearEasing), + ) { heightFraction = value } } } LaunchedEffect(isPaused, useWavySeekbar) { if (isPaused || !useWavySeekbar) return@LaunchedEffect - var lastFrameTime = withFrameMillis { it } while (isActive) { withFrameMillis { frameTimeMillis -> - val deltaTime = (frameTimeMillis - lastFrameTime) / 1000f - phaseOffset += deltaTime * phaseSpeed - phaseOffset %= waveLength + phaseOffset = (phaseOffset + (frameTimeMillis - lastFrameTime) / 1000f * phaseSpeed) % waveLength lastFrameTime = frameTimeMillis } } } - Canvas( - modifier = modifier.fillMaxWidth().height(48.dp), - ) { + Canvas(modifier = modifier.fillMaxWidth().height(48.dp)) { val strokeWidth = 5.dp.toPx() val progress = if (duration > 0f) (position / duration).coerceIn(0f, 1f) else 0f val totalWidth = size.width val totalProgressPx = totalWidth * progress val centerY = size.height / 2f + val waveProgressPx = if (!transitionEnabled || progress > matchedWaveEndpoint) totalWidth * progress else totalWidth * (minWaveEndpoint + (matchedWaveEndpoint - minWaveEndpoint) * (progress / matchedWaveEndpoint).coerceIn(0f, 1f)) - val waveProgressPx = - if (!transitionEnabled || progress > matchedWaveEndpoint) { - totalWidth * progress - } else { - val t = (progress / matchedWaveEndpoint).coerceIn(0f, 1f) - totalWidth * (minWaveEndpoint + (matchedWaveEndpoint - minWaveEndpoint) * t) - } - - fun computeAmplitude(x: Float, sign: Float): Float = - if (transitionEnabled) { - val length = transitionPeriods * waveLength - val coeff = ((waveProgressPx + length / 2f - x) / length).coerceIn(0f, 1f) - sign * heightFraction * lineAmplitude * coeff - } else { - sign * heightFraction * lineAmplitude - } + fun computeAmplitude(x: Float, sign: Float): Float = if (transitionEnabled) sign * heightFraction * lineAmplitude * (((waveProgressPx + transitionPeriods * waveLength / 2f - x) / (transitionPeriods * waveLength)).coerceIn(0f, 1f)) else sign * heightFraction * lineAmplitude val path = Path() val waveStart = -phaseOffset - waveLength / 2f val waveEnd = if (transitionEnabled) totalWidth else waveProgressPx - path.moveTo(waveStart, centerY) - var currentX = waveStart var waveSign = 1f var currentAmp = computeAmplitude(currentX, waveSign) @@ -666,12 +479,7 @@ private fun SquigglySeekbar( val nextX = currentX + dist val midX = currentX + dist / 2f val nextAmp = computeAmplitude(nextX, waveSign) - - path.cubicTo( - midX, centerY + currentAmp, - midX, centerY + nextAmp, - nextX, centerY + nextAmp, - ) + path.cubicTo(midX, centerY + currentAmp, midX, centerY + nextAmp, nextX, centerY + nextAmp) currentAmp = nextAmp currentX = nextX } @@ -682,169 +490,63 @@ private fun SquigglySeekbar( fun drawPathWithGaps(startX: Float, endX: Float, color: Color) { if (endX <= startX) return if (duration <= 0f) { - clipRect(left = startX, top = centerY - clipTop, right = endX, bottom = centerY + clipTop) { - drawPath(path = path, color = color, style = Stroke(width = strokeWidth, cap = StrokeCap.Round)) - } + clipRect(left = startX, top = centerY - clipTop, right = endX, bottom = centerY + clipTop) { drawPath(path = path, color = color, style = Stroke(width = strokeWidth, cap = StrokeCap.Round)) } return } - val gaps = chapters - .map { (it.start / duration).coerceIn(0f, 1f) * totalWidth } - .filter { it in startX..endX } - .sorted() - .map { x -> (x - gapHalf).coerceAtLeast(startX) to (x + gapHalf).coerceAtMost(endX) } - + val gaps = chapters.map { (it.start / duration).coerceIn(0f, 1f) * totalWidth }.filter { it in startX..endX }.sorted().map { x -> (x - gapHalf).coerceAtLeast(startX) to (x + gapHalf).coerceAtMost(endX) } var segmentStart = startX for ((gapStart, gapEnd) in gaps) { - if (gapStart > segmentStart) { - clipRect(left = segmentStart, top = centerY - clipTop, right = gapStart, bottom = centerY + clipTop) { - drawPath(path = path, color = color, style = Stroke(width = strokeWidth, cap = StrokeCap.Round)) - } - } + if (gapStart > segmentStart) clipRect(left = segmentStart, top = centerY - clipTop, right = gapStart, bottom = centerY + clipTop) { drawPath(path = path, color = color, style = Stroke(width = strokeWidth, cap = StrokeCap.Round)) } segmentStart = gapEnd } - if (segmentStart < endX) { - clipRect(left = segmentStart, top = centerY - clipTop, right = endX, bottom = centerY + clipTop) { - drawPath(path = path, color = color, style = Stroke(width = strokeWidth, cap = StrokeCap.Round)) - } - } + if (segmentStart < endX) clipRect(left = segmentStart, top = centerY - clipTop, right = endX, bottom = centerY + clipTop) { drawPath(path = path, color = color, style = Stroke(width = strokeWidth, cap = StrokeCap.Round)) } } drawPathWithGaps(0f, totalProgressPx, activeColor) - - if (transitionEnabled) { - val disabledAlpha = 77f / 255f - drawPathWithGaps(totalProgressPx, totalWidth, activeColor.copy(alpha = disabledAlpha)) - } else { - drawLine( - color = surfaceVariant.copy(alpha = 0.4f), - start = Offset(totalProgressPx, centerY), - end = Offset(totalWidth, centerY), - strokeWidth = strokeWidth, - cap = StrokeCap.Round, - ) - } - + if (transitionEnabled) drawPathWithGaps(totalProgressPx, totalWidth, activeColor.copy(alpha = 77f / 255f)) else drawLine(color = surfaceVariant.copy(alpha = 0.4f), start = Offset(totalProgressPx, centerY), end = Offset(totalWidth, centerY), strokeWidth = strokeWidth, cap = StrokeCap.Round) + val startAmp = kotlin.math.cos(kotlin.math.abs(waveStart) / waveLength * (2f * kotlin.math.PI.toFloat())) - drawCircle( - color = activeColor, - radius = strokeWidth / 2f, - center = Offset(0f, centerY + startAmp * lineAmplitude * heightFraction), - ) + drawCircle(color = activeColor, radius = strokeWidth / 2f, center = Offset(0f, centerY + startAmp * lineAmplitude * heightFraction)) val barHalfHeight = (lineAmplitude + strokeWidth) - val barWidth = 5.dp.toPx() - - if (barHalfHeight > 0.5f) { - drawLine( - color = activeColor, - start = Offset(totalProgressPx, centerY - barHalfHeight), - end = Offset(totalProgressPx, centerY + barHalfHeight), - strokeWidth = barWidth, - cap = StrokeCap.Round, - ) - } + if (barHalfHeight > 0.5f) drawLine(color = activeColor, start = Offset(totalProgressPx, centerY - barHalfHeight), end = Offset(totalProgressPx, centerY + barHalfHeight), strokeWidth = 5.dp.toPx(), cap = StrokeCap.Round) if (loopStart != null || loopEnd != null) { val loopColor = Color(0xFFFFB300) val markerWidth = 2.dp.toPx() - - if (loopStart != null && duration > 0f) { - val startPx = (loopStart / duration).coerceIn(0f, 1f) * totalWidth - drawLine( - color = loopColor, - start = Offset(startPx, centerY - lineAmplitude - strokeWidth), - end = Offset(startPx, centerY + lineAmplitude + strokeWidth), - strokeWidth = markerWidth, - ) - } - - if (loopEnd != null && duration > 0f) { - val endPx = (loopEnd / duration).coerceIn(0f, 1f) * totalWidth - drawLine( - color = loopColor, - start = Offset(endPx, centerY - lineAmplitude - strokeWidth), - end = Offset(endPx, centerY + lineAmplitude + strokeWidth), - strokeWidth = markerWidth, - ) - } - - if (loopStart != null && loopEnd != null && duration > 0f) { - val minPx = (minOf(loopStart, loopEnd) / duration).coerceIn(0f, 1f) * totalWidth - val maxPx = (maxOf(loopStart, loopEnd) / duration).coerceIn(0f, 1f) * totalWidth - drawRect( - color = loopColor.copy(alpha = 0.2f), - topLeft = Offset(minPx, centerY - lineAmplitude - strokeWidth), - size = Size(maxPx - minPx, (lineAmplitude + strokeWidth) * 2), - ) - } + if (loopStart != null && duration > 0f) drawLine(color = loopColor, start = Offset((loopStart / duration).coerceIn(0f, 1f) * totalWidth, centerY - lineAmplitude - strokeWidth), end = Offset((loopStart / duration).coerceIn(0f, 1f) * totalWidth, centerY + lineAmplitude + strokeWidth), strokeWidth = markerWidth) + if (loopEnd != null && duration > 0f) drawLine(color = loopColor, start = Offset((loopEnd / duration).coerceIn(0f, 1f) * totalWidth, centerY - lineAmplitude - strokeWidth), end = Offset((loopEnd / duration).coerceIn(0f, 1f) * totalWidth, centerY + lineAmplitude + strokeWidth), strokeWidth = markerWidth) + if (loopStart != null && loopEnd != null && duration > 0f) drawRect(color = loopColor.copy(alpha = 0.2f), topLeft = Offset((minOf(loopStart, loopEnd) / duration).coerceIn(0f, 1f) * totalWidth, centerY - lineAmplitude - strokeWidth), size = Size((maxOf(loopStart, loopEnd) / duration).coerceIn(0f, 1f) * totalWidth - (minOf(loopStart, loopEnd) / duration).coerceIn(0f, 1f) * totalWidth, (lineAmplitude + strokeWidth) * 2)) } } } @Composable -fun VideoTimer( - value: Float, - isInverted: Boolean, - modifier: Modifier = Modifier, - onClick: () -> Unit = {}, -) { +fun VideoTimer(value: Float, isInverted: Boolean, modifier: Modifier = Modifier, onClick: () -> Unit = {}) { val interactionSource = remember { MutableInteractionSource() } Text( - modifier = - modifier - .fillMaxHeight() - .clickable( - interactionSource = interactionSource, - indication = ripple(), - onClick = onClick, - ) - .wrapContentHeight(Alignment.CenterVertically), - text = Utils.prettyTime(value.toInt(), isInverted), - color = Color.White, - textAlign = TextAlign.Center, + modifier = modifier.fillMaxHeight().clickable(interactionSource = interactionSource, indication = ripple(), onClick = onClick).wrapContentHeight(Alignment.CenterVertically), + text = Utils.prettyTime(value.toInt(), isInverted), color = Color.White, textAlign = TextAlign.Center, ) } @Composable @OptIn(ExperimentalMaterial3Api::class) fun StandardSeekbar( - position: Float, - duration: Float, - chapters: ImmutableList, - isPaused: Boolean = false, - isScrubbing: Boolean = false, - seekbarStyle: SeekbarStyle = SeekbarStyle.Standard, - onSeek: (Float) -> Unit, - onSeekFinished: () -> Unit, - loopStart: Float? = null, - loopEnd: Float? = null, - modifier: Modifier = Modifier, + position: Float, duration: Float, chapters: ImmutableList, isPaused: Boolean = false, isScrubbing: Boolean = false, + seekbarStyle: SeekbarStyle = SeekbarStyle.Standard, onSeek: (Float) -> Unit, onSeekFinished: () -> Unit, + loopStart: Float? = null, loopEnd: Float? = null, modifier: Modifier = Modifier, ) { val activeColor = MaterialTheme.colorScheme.primary val interactionSource = remember { MutableInteractionSource() } - var heightFraction by remember { mutableFloatStateOf(1f) } val scope = rememberCoroutineScope() LaunchedEffect(isPaused, isScrubbing) { scope.launch { val shouldFlatten = isPaused || isScrubbing - val targetHeight = if (shouldFlatten) 0.7f else 1f - val animationDuration = if (shouldFlatten) 550 else 800 - val startDelay = if (shouldFlatten) 0L else 60L - - kotlinx.coroutines.delay(startDelay) - - val animator = Animatable(heightFraction) - animator.animateTo( - targetValue = targetHeight, - animationSpec = tween( - durationMillis = animationDuration, - easing = LinearEasing, - ), - ) { - heightFraction = value - } + kotlinx.coroutines.delay(if (shouldFlatten) 0L else 60L) + Animatable(heightFraction).animateTo(targetValue = if (shouldFlatten) 0.7f else 1f, animationSpec = tween(durationMillis = if (shouldFlatten) 550 else 800, easing = LinearEasing)) { heightFraction = value } } } @@ -856,20 +558,10 @@ fun StandardSeekbar( val thumbShape = if (isThick) RoundedCornerShape(thumbWidth / 2) else CircleShape Slider( - value = position, - onValueChange = onSeek, - onValueChangeFinished = onSeekFinished, - valueRange = 0f..duration.coerceAtLeast(0.1f), - modifier = Modifier.fillMaxWidth(), - interactionSource = interactionSource, + value = position, onValueChange = onSeek, onValueChangeFinished = onSeekFinished, valueRange = 0f..duration.coerceAtLeast(0.1f), + modifier = Modifier.fillMaxWidth(), interactionSource = interactionSource, track = { sliderState -> - val disabledAlpha = 0.3f - - Canvas( - modifier = Modifier - .fillMaxWidth() - .height(trackHeightDp), - ) { + Canvas(modifier = Modifier.fillMaxWidth().height(trackHeightDp)) { val min = sliderState.valueRange.start val max = sliderState.valueRange.endInclusive val range = (max - min).takeIf { it > 0f } ?: 1f @@ -879,127 +571,50 @@ fun StandardSeekbar( val outerRadius = trackHeight / 2f val innerRadius = if (isThick) outerRadius else 2.dp.toPx() - - val thumbTrackGapSize = 14.dp.toPx() - val gapHalf = thumbTrackGapSize / 2f + val gapHalf = 14.dp.toPx() / 2f val chapterGapHalf = 1.dp.toPx() val thumbGapStart = (playedPx - gapHalf).coerceIn(0f, size.width) val thumbGapEnd = (playedPx + gapHalf).coerceIn(0f, size.width) - - val chapterGaps = chapters - .map { (it.start / duration).coerceIn(0f, 1f) * size.width } - .filter { it > 0f && it < size.width } - .map { x -> (x - chapterGapHalf) to (x + chapterGapHalf) } + val chapterGaps = chapters.map { (it.start / duration).coerceIn(0f, 1f) * size.width }.filter { it > 0f && it < size.width }.map { x -> (x - chapterGapHalf) to (x + chapterGapHalf) } fun drawSegment(startX: Float, endX: Float, color: Color) { if (endX - startX < 0.5f) return val path = Path() val isOuterLeft = startX <= 0.5f val isInnerLeft = kotlin.math.abs(startX - thumbGapEnd) < 0.5f - - val cornerRadiusLeft = when { - isOuterLeft -> androidx.compose.ui.geometry.CornerRadius(outerRadius) - isInnerLeft -> androidx.compose.ui.geometry.CornerRadius(innerRadius) - else -> androidx.compose.ui.geometry.CornerRadius.Zero - } - + val cornerRadiusLeft = when { isOuterLeft -> androidx.compose.ui.geometry.CornerRadius(outerRadius); isInnerLeft -> androidx.compose.ui.geometry.CornerRadius(innerRadius); else -> androidx.compose.ui.geometry.CornerRadius.Zero } val isOuterRight = endX >= size.width - 0.5f val isInnerRight = kotlin.math.abs(endX - thumbGapStart) < 0.5f - - val cornerRadiusRight = when { - isOuterRight -> androidx.compose.ui.geometry.CornerRadius(outerRadius) - isInnerRight -> androidx.compose.ui.geometry.CornerRadius(innerRadius) - else -> androidx.compose.ui.geometry.CornerRadius.Zero - } - - path.addRoundRect( - androidx.compose.ui.geometry.RoundRect( - left = startX, - top = 0f, - right = endX, - bottom = trackHeight, - topLeftCornerRadius = cornerRadiusLeft, - bottomLeftCornerRadius = cornerRadiusLeft, - topRightCornerRadius = cornerRadiusRight, - bottomRightCornerRadius = cornerRadiusRight - ) - ) + val cornerRadiusRight = when { isOuterRight -> androidx.compose.ui.geometry.CornerRadius(outerRadius); isInnerRight -> androidx.compose.ui.geometry.CornerRadius(innerRadius); else -> androidx.compose.ui.geometry.CornerRadius.Zero } + path.addRoundRect(androidx.compose.ui.geometry.RoundRect(left = startX, top = 0f, right = endX, bottom = trackHeight, topLeftCornerRadius = cornerRadiusLeft, bottomLeftCornerRadius = cornerRadiusLeft, topRightCornerRadius = cornerRadiusRight, bottomRightCornerRadius = cornerRadiusRight)) drawPath(path, color) } - fun drawRangeWithGaps( - rangeStart: Float, - rangeEnd: Float, - gaps: List>, - color: Color - ) { + fun drawRangeWithGaps(rangeStart: Float, rangeEnd: Float, gaps: List>, color: Color) { if (rangeEnd <= rangeStart) return - val relevantGaps = gaps - .filter { (gStart, gEnd) -> gEnd > rangeStart && gStart < rangeEnd } - .sortedBy { it.first } - + val relevantGaps = gaps.filter { (gStart, gEnd) -> gEnd > rangeStart && gStart < rangeEnd }.sortedBy { it.first } var currentPos = rangeStart for ((gStart, gEnd) in relevantGaps) { val segmentEnd = gStart.coerceAtMost(rangeEnd) - if (segmentEnd > currentPos) { - drawSegment(currentPos, segmentEnd, color) - } + if (segmentEnd > currentPos) drawSegment(currentPos, segmentEnd, color) currentPos = gEnd.coerceAtLeast(currentPos) } - if (currentPos < rangeEnd) { - drawSegment(currentPos, rangeEnd, color) - } + if (currentPos < rangeEnd) drawSegment(currentPos, rangeEnd, color) } - drawRangeWithGaps(thumbGapEnd, size.width, chapterGaps, activeColor.copy(alpha = disabledAlpha)) - if (thumbGapStart > 0) { - drawRangeWithGaps(0f, thumbGapStart, chapterGaps, activeColor) - } + drawRangeWithGaps(thumbGapEnd, size.width, chapterGaps, activeColor.copy(alpha = 0.3f)) + if (thumbGapStart > 0) drawRangeWithGaps(0f, thumbGapStart, chapterGaps, activeColor) if (loopStart != null || loopEnd != null) { val loopColor = Color(0xFFFFB300) val markerWidth = 2.dp.toPx() - - if (loopStart != null) { - val startPx = (loopStart / duration).coerceIn(0f, 1f) * size.width - drawLine( - color = loopColor, - start = Offset(startPx, 0f), - end = Offset(startPx, size.height), - strokeWidth = markerWidth - ) - } - - if (loopEnd != null) { - val endPx = (loopEnd / duration).coerceIn(0f, 1f) * size.width - drawLine( - color = loopColor, - start = Offset(endPx, 0f), - end = Offset(endPx, size.height), - strokeWidth = markerWidth - ) - } - - if (loopStart != null && loopEnd != null) { - val minPx = (minOf(loopStart, loopEnd) / duration).coerceIn(0f, 1f) * size.width - val maxPx = (maxOf(loopStart, loopEnd) / duration).coerceIn(0f, 1f) * size.width - drawRect( - color = loopColor.copy(alpha = 0.3f), - topLeft = Offset(minPx, 0f), - size = Size(maxPx - minPx, size.height) - ) - } + if (loopStart != null) drawLine(color = loopColor, start = Offset((loopStart / duration).coerceIn(0f, 1f) * size.width, 0f), end = Offset((loopStart / duration).coerceIn(0f, 1f) * size.width, size.height), strokeWidth = markerWidth) + if (loopEnd != null) drawLine(color = loopColor, start = Offset((loopEnd / duration).coerceIn(0f, 1f) * size.width, 0f), end = Offset((loopEnd / duration).coerceIn(0f, 1f) * size.width, size.height), strokeWidth = markerWidth) + if (loopStart != null && loopEnd != null) drawRect(color = loopColor.copy(alpha = 0.3f), topLeft = Offset((minOf(loopStart, loopEnd) / duration).coerceIn(0f, 1f) * size.width, 0f), size = Size((maxOf(loopStart, loopEnd) / duration).coerceIn(0f, 1f) * size.width - (minOf(loopStart, loopEnd) / duration).coerceIn(0f, 1f) * size.width, size.height)) } } }, - thumb = { - Box( - modifier = Modifier - .width(thumbWidth) - .height(thumbHeight) - .background(activeColor, thumbShape) - ) - } + thumb = { Box(modifier = Modifier.width(thumbWidth).height(thumbHeight).background(activeColor, thumbShape)) } ) } From 95c4ddab757b06cccaac3bcaed766e0bb429472a Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 10:58:31 +0530 Subject: [PATCH 073/194] Update Seekbar.kt --- .../ui/player/controls/components/Seekbar.kt | 237 ++++++++---------- 1 file changed, 111 insertions(+), 126 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index 5c7455343..9149bcacd 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -57,15 +57,17 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.geometry.Size +import kotlin.math.roundToInt -// --- KYANT BACKDROP 2.0.0-ALPHA03 IMPORTS --- +// --- KYANT BACKDROP 2.0.0-ALPHA03 IMPORTS (FIXED) --- +import com.kyant.backdrop.backdrops.layerBackdrop import com.kyant.backdrop.backdrops.rememberLayerBackdrop import com.kyant.backdrop.backdrops.rememberCombinedBackdrop import com.kyant.backdrop.drawBackdrop import com.kyant.backdrop.effects.blur import com.kyant.backdrop.effects.lens import com.kyant.backdrop.effects.vibrancy -// -------------------------------------------- +// ---------------------------------------------------- import app.marlboroadvance.mpvex.ui.player.controls.LocalPlayerButtonsClickEvent import app.marlboroadvance.mpvex.ui.theme.spacing @@ -229,10 +231,9 @@ fun SeekbarWithTimers( } // ========================================================================= -// NEW: CRASH-PROOF LIQUID SEEKBAR WITH NATIVE SQUISH PHYSICS +// NEW: OPTION 3 COMBINED LIQUID SEEKBAR (CRASH-PROOF NATIVE BOX) // ========================================================================= @Composable -@OptIn(ExperimentalMaterial3Api::class) fun LiquidSeekbar( position: Float, duration: Float, @@ -247,8 +248,6 @@ fun LiquidSeekbar( modifier: Modifier = Modifier, ) { val activeColor = if (liquidColor.isSpecified) liquidColor else MaterialTheme.colorScheme.primary - val interactionSource = remember { MutableInteractionSource() } - val isPressed by interactionSource.collectIsPressedAsState() var heightFraction by remember { mutableFloatStateOf(1f) } val scope = rememberCoroutineScope() @@ -270,142 +269,128 @@ fun LiquidSeekbar( val thumbHeight = 16.dp val thumbShape = RoundedCornerShape(percent = 50) - // Native Squish Physics (No DampedDragAnimation file needed!) + // Native Squish Animation when Scrubbing val thumbWidth by animateDpAsState( - targetValue = if (isPressed || isScrubbing) 24.dp else 16.dp, + targetValue = if (isScrubbing) 24.dp else 16.dp, animationSpec = spring(dampingRatio = 0.6f, stiffness = 800f), label = "thumb_stretch" ) - // Option 3: Combined Backdrop Implementation (Crash-Proof) - val parentBackdrop = rememberLayerBackdrop() + // OPTION 3: THE TRUE COMBINED BACKDROP ENGINE + val parentBackdrop = rememberLayerBackdrop { drawContent() } val trackBackdrop = rememberLayerBackdrop { drawContent() } val combinedBackdrop = rememberCombinedBackdrop(parentBackdrop, trackBackdrop) - Slider( - value = position, - onValueChange = onSeek, - onValueChangeFinished = onSeekFinished, - valueRange = 0f..duration.coerceAtLeast(0.1f), - modifier = modifier.fillMaxWidth(), - interactionSource = interactionSource, - track = { sliderState -> - Canvas( - modifier = Modifier - .fillMaxWidth() - .height(trackHeightDp) - .layerBackdrop(trackBackdrop) - ) { - val min = sliderState.valueRange.start - val max = sliderState.valueRange.endInclusive - val range = (max - min).takeIf { it > 0f } ?: 1f - val playedFraction = ((sliderState.value - min) / range).coerceIn(0f, 1f) - val playedPx = size.width * playedFraction - val trackHeight = size.height - - val outerRadius = trackHeight / 2f - val innerRadius = outerRadius - - val gapHalf = 14.dp.toPx() / 2f - val chapterGapHalf = 1.dp.toPx() - - val thumbGapStart = (playedPx - gapHalf).coerceIn(0f, size.width) - val thumbGapEnd = (playedPx + gapHalf).coerceIn(0f, size.width) - - val chapterGaps = chapters - .map { (it.start / duration).coerceIn(0f, 1f) * size.width } - .filter { it > 0f && it < size.width } - .map { x -> (x - chapterGapHalf) to (x + chapterGapHalf) } + androidx.compose.foundation.layout.BoxWithConstraints( + modifier = modifier + .fillMaxWidth() + .height(24.dp) + .layerBackdrop(parentBackdrop), // Captures the Video Playing behind it! + contentAlignment = Alignment.CenterStart + ) { + val trackWidthPx = constraints.maxWidth.toFloat() + val progress = if (duration > 0f) (position / duration).coerceIn(0f, 1f) else 0f + val playedPx = trackWidthPx * progress + + Canvas( + modifier = Modifier + .fillMaxWidth() + .height(trackHeightDp) + .layerBackdrop(trackBackdrop) // Captures the Colored Track! + ) { + val trackHeight = size.height + val outerRadius = trackHeight / 2f + val innerRadius = outerRadius + + val gapHalf = 14.dp.toPx() / 2f + val chapterGapHalf = 1.dp.toPx() + + val thumbGapStart = (playedPx - gapHalf).coerceIn(0f, size.width) + val thumbGapEnd = (playedPx + gapHalf).coerceIn(0f, size.width) + + val chapterGaps = chapters + .map { (it.start / duration).coerceIn(0f, 1f) * size.width } + .filter { it > 0f && it < size.width } + .map { x -> (x - chapterGapHalf) to (x + chapterGapHalf) } + + fun drawSegment(startX: Float, endX: Float, color: Color) { + if (endX - startX < 0.5f) return + val path = Path() + val isOuterLeft = startX <= 0.5f + val isInnerLeft = kotlin.math.abs(startX - thumbGapEnd) < 0.5f - fun drawSegment(startX: Float, endX: Float, color: Color) { - if (endX - startX < 0.5f) return - val path = Path() - val isOuterLeft = startX <= 0.5f - val isInnerLeft = kotlin.math.abs(startX - thumbGapEnd) < 0.5f - - val cornerRadiusLeft = when { - isOuterLeft -> androidx.compose.ui.geometry.CornerRadius(outerRadius) - isInnerLeft -> androidx.compose.ui.geometry.CornerRadius(innerRadius) - else -> androidx.compose.ui.geometry.CornerRadius.Zero - } + val cornerRadiusLeft = when { + isOuterLeft -> androidx.compose.ui.geometry.CornerRadius(outerRadius) + isInnerLeft -> androidx.compose.ui.geometry.CornerRadius(innerRadius) + else -> androidx.compose.ui.geometry.CornerRadius.Zero + } - val isOuterRight = endX >= size.width - 0.5f - val isInnerRight = kotlin.math.abs(endX - thumbGapStart) < 0.5f + val isOuterRight = endX >= size.width - 0.5f + val isInnerRight = kotlin.math.abs(endX - thumbGapStart) < 0.5f - val cornerRadiusRight = when { - isOuterRight -> androidx.compose.ui.geometry.CornerRadius(outerRadius) - isInnerRight -> androidx.compose.ui.geometry.CornerRadius(innerRadius) - else -> androidx.compose.ui.geometry.CornerRadius.Zero - } - - path.addRoundRect( - androidx.compose.ui.geometry.RoundRect( - left = startX, top = 0f, right = endX, bottom = trackHeight, - topLeftCornerRadius = cornerRadiusLeft, bottomLeftCornerRadius = cornerRadiusLeft, - topRightCornerRadius = cornerRadiusRight, bottomRightCornerRadius = cornerRadiusRight - ) - ) - drawPath(path, color) + val cornerRadiusRight = when { + isOuterRight -> androidx.compose.ui.geometry.CornerRadius(outerRadius) + isInnerRight -> androidx.compose.ui.geometry.CornerRadius(innerRadius) + else -> androidx.compose.ui.geometry.CornerRadius.Zero } - fun drawRangeWithGaps(rangeStart: Float, rangeEnd: Float, gaps: List>, color: Color) { - if (rangeEnd <= rangeStart) return - val relevantGaps = gaps.filter { (gStart, gEnd) -> gEnd > rangeStart && gStart < rangeEnd }.sortedBy { it.first } - var currentPos = rangeStart - for ((gStart, gEnd) in relevantGaps) { - val segmentEnd = gStart.coerceAtMost(rangeEnd) - if (segmentEnd > currentPos) drawSegment(currentPos, segmentEnd, color) - currentPos = gEnd.coerceAtLeast(currentPos) - } - if (currentPos < rangeEnd) drawSegment(currentPos, rangeEnd, color) - } - - drawRangeWithGaps(thumbGapEnd, size.width, chapterGaps, activeColor.copy(alpha = 0.3f)) - if (thumbGapStart > 0) drawRangeWithGaps(0f, thumbGapStart, chapterGaps, activeColor) - - if (loopStart != null || loopEnd != null) { - val loopColor = Color(0xFFFFB300) - val markerWidth = 2.dp.toPx() - if (loopStart != null) { - val startPx = (loopStart / duration).coerceIn(0f, 1f) * size.width - drawLine(color = loopColor, start = Offset(startPx, 0f), end = Offset(startPx, size.height), strokeWidth = markerWidth) - } - if (loopEnd != null) { - val endPx = (loopEnd / duration).coerceIn(0f, 1f) * size.width - drawLine(color = loopColor, start = Offset(endPx, 0f), end = Offset(endPx, size.height), strokeWidth = markerWidth) - } - if (loopStart != null && loopEnd != null) { - val minPx = (minOf(loopStart, loopEnd) / duration).coerceIn(0f, 1f) * size.width - val maxPx = (maxOf(loopStart, loopEnd) / duration).coerceIn(0f, 1f) * size.width - drawRect(color = loopColor.copy(alpha = 0.3f), topLeft = Offset(minPx, 0f), size = Size(maxPx - minPx, size.height)) - } + path.addRoundRect( + androidx.compose.ui.geometry.RoundRect( + left = startX, top = 0f, right = endX, bottom = trackHeight, + topLeftCornerRadius = cornerRadiusLeft, bottomLeftCornerRadius = cornerRadiusLeft, + topRightCornerRadius = cornerRadiusRight, bottomRightCornerRadius = cornerRadiusRight + ) + ) + drawPath(path, color) + } + + fun drawRangeWithGaps(rangeStart: Float, rangeEnd: Float, gaps: List>, color: Color) { + if (rangeEnd <= rangeStart) return + val relevantGaps = gaps.filter { (gStart, gEnd) -> gEnd > rangeStart && gStart < rangeEnd }.sortedBy { it.first } + var currentPos = rangeStart + for ((gStart, gEnd) in relevantGaps) { + val segmentEnd = gStart.coerceAtMost(rangeEnd) + if (segmentEnd > currentPos) drawSegment(currentPos, segmentEnd, color) + currentPos = gEnd.coerceAtLeast(currentPos) } + if (currentPos < rangeEnd) drawSegment(currentPos, rangeEnd, color) + } + + drawRangeWithGaps(thumbGapEnd, size.width, chapterGaps, activeColor.copy(alpha = 0.3f)) + if (thumbGapStart > 0) drawRangeWithGaps(0f, thumbGapStart, chapterGaps, activeColor) + + if (loopStart != null || loopEnd != null) { + val loopColor = Color(0xFFFFB300) + val markerWidth = 2.dp.toPx() + if (loopStart != null) drawLine(color = loopColor, start = Offset((loopStart / duration).coerceIn(0f, 1f) * size.width, 0f), end = Offset((loopStart / duration).coerceIn(0f, 1f) * size.width, size.height), strokeWidth = markerWidth) + if (loopEnd != null) drawLine(color = loopColor, start = Offset((loopEnd / duration).coerceIn(0f, 1f) * size.width, 0f), end = Offset((loopEnd / duration).coerceIn(0f, 1f) * size.width, size.height), strokeWidth = markerWidth) + if (loopStart != null && loopEnd != null) drawRect(color = loopColor.copy(alpha = 0.3f), topLeft = Offset((minOf(loopStart, loopEnd) / duration).coerceIn(0f, 1f) * size.width, 0f), size = Size((maxOf(loopStart, loopEnd) / duration).coerceIn(0f, 1f) * size.width - (minOf(loopStart, loopEnd) / duration).coerceIn(0f, 1f) * size.width, size.height)) } - }, - thumb = { - Box( - modifier = Modifier - .width(thumbWidth) - .height(thumbHeight) - .drawBackdrop( - backdrop = combinedBackdrop, - shape = { thumbShape }, - effects = { - vibrancy() - blur(if (isPressed || isScrubbing) 4f.dp.toPx() else 8f.dp.toPx()) - lens( - if (isPressed || isScrubbing) 14f.dp.toPx() else 10f.dp.toPx(), - if (isPressed || isScrubbing) 28f.dp.toPx() else 14f.dp.toPx() - ) - }, - onDrawSurface = { - drawRect(activeColor, blendMode = BlendMode.Hue) - drawRect(activeColor.copy(alpha = 0.75f)) - } - ) - ) } - ) + + Box( + modifier = Modifier + .offset { androidx.compose.ui.unit.IntOffset((playedPx - (thumbWidth.toPx() / 2)).roundToInt(), 0) } + .width(thumbWidth) + .height(thumbHeight) + .drawBackdrop( + backdrop = combinedBackdrop, // Refract BOTH! + shape = { thumbShape }, + effects = { + vibrancy() + blur(if (isScrubbing) 4f.dp.toPx() else 8f.dp.toPx()) + lens( + if (isScrubbing) 14f.dp.toPx() else 10f.dp.toPx(), + if (isScrubbing) 28f.dp.toPx() else 14f.dp.toPx() + ) + }, + onDrawSurface = { + drawRect(activeColor, blendMode = BlendMode.Hue) + drawRect(activeColor.copy(alpha = 0.75f)) + } + ) + ) + } } // ========================================================================= From 29476f02d95d8e741c9cddd96b98ad55f1cab127 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 11:05:33 +0530 Subject: [PATCH 074/194] Update Seekbar.kt --- .../mpvex/ui/player/controls/components/Seekbar.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index 9149bcacd..5574b0117 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -24,6 +24,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape @@ -59,7 +60,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.geometry.Size import kotlin.math.roundToInt -// --- KYANT BACKDROP 2.0.0-ALPHA03 IMPORTS (FIXED) --- +// --- KYANT BACKDROP 2.0.0-ALPHA03 IMPORTS --- import com.kyant.backdrop.backdrops.layerBackdrop import com.kyant.backdrop.backdrops.rememberLayerBackdrop import com.kyant.backdrop.backdrops.rememberCombinedBackdrop From 29f80d7b2c60801d3eafda7e704e0c4eefc9cb6f Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 12:18:42 +0530 Subject: [PATCH 075/194] Update Seekbar.kt --- .../ui/player/controls/components/Seekbar.kt | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index 5574b0117..b24fc356f 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -5,10 +5,6 @@ import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween -import androidx.compose.animation.core.rememberInfiniteTransition -import androidx.compose.animation.core.animateFloat -import androidx.compose.animation.core.infiniteRepeatable -import androidx.compose.animation.core.RepeatMode import androidx.compose.foundation.Canvas import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -24,8 +20,8 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme @@ -63,12 +59,11 @@ import kotlin.math.roundToInt // --- KYANT BACKDROP 2.0.0-ALPHA03 IMPORTS --- import com.kyant.backdrop.backdrops.layerBackdrop import com.kyant.backdrop.backdrops.rememberLayerBackdrop -import com.kyant.backdrop.backdrops.rememberCombinedBackdrop import com.kyant.backdrop.drawBackdrop import com.kyant.backdrop.effects.blur import com.kyant.backdrop.effects.lens import com.kyant.backdrop.effects.vibrancy -// ---------------------------------------------------- +// -------------------------------------------- import app.marlboroadvance.mpvex.ui.player.controls.LocalPlayerButtonsClickEvent import app.marlboroadvance.mpvex.ui.theme.spacing @@ -232,7 +227,7 @@ fun SeekbarWithTimers( } // ========================================================================= -// NEW: OPTION 3 COMBINED LIQUID SEEKBAR (CRASH-PROOF NATIVE BOX) +// NEW: SAFE LIQUID SEEKBAR (Crash-Proof Track Refraction) // ========================================================================= @Composable fun LiquidSeekbar( @@ -270,23 +265,20 @@ fun LiquidSeekbar( val thumbHeight = 16.dp val thumbShape = RoundedCornerShape(percent = 50) - // Native Squish Animation when Scrubbing val thumbWidth by animateDpAsState( targetValue = if (isScrubbing) 24.dp else 16.dp, animationSpec = spring(dampingRatio = 0.6f, stiffness = 800f), label = "thumb_stretch" ) - // OPTION 3: THE TRUE COMBINED BACKDROP ENGINE - val parentBackdrop = rememberLayerBackdrop { drawContent() } + // THE ULTIMATE FIX: We ONLY capture the track layer. + // This perfectly refracts your Cyan color while remaining 100% crash-proof! val trackBackdrop = rememberLayerBackdrop { drawContent() } - val combinedBackdrop = rememberCombinedBackdrop(parentBackdrop, trackBackdrop) androidx.compose.foundation.layout.BoxWithConstraints( modifier = modifier .fillMaxWidth() - .height(24.dp) - .layerBackdrop(parentBackdrop), // Captures the Video Playing behind it! + .height(24.dp), contentAlignment = Alignment.CenterStart ) { val trackWidthPx = constraints.maxWidth.toFloat() @@ -297,7 +289,7 @@ fun LiquidSeekbar( modifier = Modifier .fillMaxWidth() .height(trackHeightDp) - .layerBackdrop(trackBackdrop) // Captures the Colored Track! + .layerBackdrop(trackBackdrop) // Applies the capture to the track ONLY ) { val trackHeight = size.height val outerRadius = trackHeight / 2f @@ -375,7 +367,7 @@ fun LiquidSeekbar( .width(thumbWidth) .height(thumbHeight) .drawBackdrop( - backdrop = combinedBackdrop, // Refract BOTH! + backdrop = trackBackdrop, // Refracts the colored track beautifully! shape = { thumbShape }, effects = { vibrancy() From ed375f81aa9e8a6358498e21d2a0f39ada46e246 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 12:29:55 +0530 Subject: [PATCH 076/194] Update build.gradle.kts --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 492f176c5..54eea5803 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -230,7 +230,7 @@ dependencies { // DataStore for preferences persistence (To toggle the UI on/off) implementation("androidx.datastore:datastore-preferences:1.0.0") - + implementation("androidx.graphics:graphics-core:1.0.4") } /* ---------------- Git helpers ---------------- */ From ec1daf8415df64f0d98cc0ca6b5ec21f8c239273 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 12:32:37 +0530 Subject: [PATCH 077/194] Update Seekbar.kt --- .../ui/player/controls/components/Seekbar.kt | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index b24fc356f..9149bcacd 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -5,6 +5,10 @@ import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.RepeatMode import androidx.compose.foundation.Canvas import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -21,7 +25,6 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme @@ -56,14 +59,15 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.geometry.Size import kotlin.math.roundToInt -// --- KYANT BACKDROP 2.0.0-ALPHA03 IMPORTS --- +// --- KYANT BACKDROP 2.0.0-ALPHA03 IMPORTS (FIXED) --- import com.kyant.backdrop.backdrops.layerBackdrop import com.kyant.backdrop.backdrops.rememberLayerBackdrop +import com.kyant.backdrop.backdrops.rememberCombinedBackdrop import com.kyant.backdrop.drawBackdrop import com.kyant.backdrop.effects.blur import com.kyant.backdrop.effects.lens import com.kyant.backdrop.effects.vibrancy -// -------------------------------------------- +// ---------------------------------------------------- import app.marlboroadvance.mpvex.ui.player.controls.LocalPlayerButtonsClickEvent import app.marlboroadvance.mpvex.ui.theme.spacing @@ -227,7 +231,7 @@ fun SeekbarWithTimers( } // ========================================================================= -// NEW: SAFE LIQUID SEEKBAR (Crash-Proof Track Refraction) +// NEW: OPTION 3 COMBINED LIQUID SEEKBAR (CRASH-PROOF NATIVE BOX) // ========================================================================= @Composable fun LiquidSeekbar( @@ -265,20 +269,23 @@ fun LiquidSeekbar( val thumbHeight = 16.dp val thumbShape = RoundedCornerShape(percent = 50) + // Native Squish Animation when Scrubbing val thumbWidth by animateDpAsState( targetValue = if (isScrubbing) 24.dp else 16.dp, animationSpec = spring(dampingRatio = 0.6f, stiffness = 800f), label = "thumb_stretch" ) - // THE ULTIMATE FIX: We ONLY capture the track layer. - // This perfectly refracts your Cyan color while remaining 100% crash-proof! + // OPTION 3: THE TRUE COMBINED BACKDROP ENGINE + val parentBackdrop = rememberLayerBackdrop { drawContent() } val trackBackdrop = rememberLayerBackdrop { drawContent() } + val combinedBackdrop = rememberCombinedBackdrop(parentBackdrop, trackBackdrop) androidx.compose.foundation.layout.BoxWithConstraints( modifier = modifier .fillMaxWidth() - .height(24.dp), + .height(24.dp) + .layerBackdrop(parentBackdrop), // Captures the Video Playing behind it! contentAlignment = Alignment.CenterStart ) { val trackWidthPx = constraints.maxWidth.toFloat() @@ -289,7 +296,7 @@ fun LiquidSeekbar( modifier = Modifier .fillMaxWidth() .height(trackHeightDp) - .layerBackdrop(trackBackdrop) // Applies the capture to the track ONLY + .layerBackdrop(trackBackdrop) // Captures the Colored Track! ) { val trackHeight = size.height val outerRadius = trackHeight / 2f @@ -367,7 +374,7 @@ fun LiquidSeekbar( .width(thumbWidth) .height(thumbHeight) .drawBackdrop( - backdrop = trackBackdrop, // Refracts the colored track beautifully! + backdrop = combinedBackdrop, // Refract BOTH! shape = { thumbShape }, effects = { vibrancy() From 15ffd3761d9d55f292748db947a97407b8dc78fe Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 13:01:34 +0530 Subject: [PATCH 078/194] Update Seekbar.kt --- .../ui/player/controls/components/Seekbar.kt | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index 9149bcacd..d61cefbe1 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -5,10 +5,6 @@ import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween -import androidx.compose.animation.core.rememberInfiniteTransition -import androidx.compose.animation.core.animateFloat -import androidx.compose.animation.core.infiniteRepeatable -import androidx.compose.animation.core.RepeatMode import androidx.compose.foundation.Canvas import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -18,13 +14,16 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme @@ -59,7 +58,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.geometry.Size import kotlin.math.roundToInt -// --- KYANT BACKDROP 2.0.0-ALPHA03 IMPORTS (FIXED) --- +// --- KYANT BACKDROP 2.0.0-ALPHA03 IMPORTS --- import com.kyant.backdrop.backdrops.layerBackdrop import com.kyant.backdrop.backdrops.rememberLayerBackdrop import com.kyant.backdrop.backdrops.rememberCombinedBackdrop @@ -67,7 +66,7 @@ import com.kyant.backdrop.drawBackdrop import com.kyant.backdrop.effects.blur import com.kyant.backdrop.effects.lens import com.kyant.backdrop.effects.vibrancy -// ---------------------------------------------------- +// -------------------------------------------- import app.marlboroadvance.mpvex.ui.player.controls.LocalPlayerButtonsClickEvent import app.marlboroadvance.mpvex.ui.theme.spacing @@ -192,11 +191,9 @@ fun SeekbarWithTimers( chapters = chapters, isPaused = paused, isScrubbing = isUserInteracting, - onSeek = { }, - onSeekFinished = { }, + liquidColor = liquidColor, loopStart = loopStart, - loopEnd = loopEnd, - liquidColor = liquidColor + loopEnd = loopEnd ) } else { when (seekbarStyle) { @@ -231,7 +228,7 @@ fun SeekbarWithTimers( } // ========================================================================= -// NEW: OPTION 3 COMBINED LIQUID SEEKBAR (CRASH-PROOF NATIVE BOX) +// THE ULTIMATE OPTION 3 COMBINED LIQUID SEEKBAR (CRASH-PROOF) // ========================================================================= @Composable fun LiquidSeekbar( @@ -240,11 +237,9 @@ fun LiquidSeekbar( chapters: ImmutableList, isPaused: Boolean = false, isScrubbing: Boolean = false, - onSeek: (Float) -> Unit, - onSeekFinished: () -> Unit, + liquidColor: Color = Color.Unspecified, loopStart: Float? = null, loopEnd: Float? = null, - liquidColor: Color = Color.Unspecified, modifier: Modifier = Modifier, ) { val activeColor = if (liquidColor.isSpecified) liquidColor else MaterialTheme.colorScheme.primary @@ -269,34 +264,38 @@ fun LiquidSeekbar( val thumbHeight = 16.dp val thumbShape = RoundedCornerShape(percent = 50) - // Native Squish Animation when Scrubbing val thumbWidth by animateDpAsState( targetValue = if (isScrubbing) 24.dp else 16.dp, animationSpec = spring(dampingRatio = 0.6f, stiffness = 800f), label = "thumb_stretch" ) - // OPTION 3: THE TRUE COMBINED BACKDROP ENGINE + // MAXIMUM QUALITY: The Combined Backdrop Engines val parentBackdrop = rememberLayerBackdrop { drawContent() } val trackBackdrop = rememberLayerBackdrop { drawContent() } val combinedBackdrop = rememberCombinedBackdrop(parentBackdrop, trackBackdrop) - androidx.compose.foundation.layout.BoxWithConstraints( + BoxWithConstraints( modifier = modifier .fillMaxWidth() - .height(24.dp) - .layerBackdrop(parentBackdrop), // Captures the Video Playing behind it! + .height(24.dp), contentAlignment = Alignment.CenterStart ) { val trackWidthPx = constraints.maxWidth.toFloat() val progress = if (duration > 0f) (position / duration).coerceIn(0f, 1f) else 0f val playedPx = trackWidthPx * progress + // LAYER 1: The Environment Capture. + // This safely captures the video playing behind the compose window without causing a recursive loop! + Box(modifier = Modifier.fillMaxSize().layerBackdrop(parentBackdrop)) + + // LAYER 2: The Track Capture. + // This captures your custom Cyan track layout. Canvas( modifier = Modifier .fillMaxWidth() .height(trackHeightDp) - .layerBackdrop(trackBackdrop) // Captures the Colored Track! + .layerBackdrop(trackBackdrop) ) { val trackHeight = size.height val outerRadius = trackHeight / 2f @@ -368,13 +367,15 @@ fun LiquidSeekbar( } } + // LAYER 3: The Combined Glass Thumb! + // Perfectly bends the light from the video (Layer 1) AND the track (Layer 2). Box( modifier = Modifier .offset { androidx.compose.ui.unit.IntOffset((playedPx - (thumbWidth.toPx() / 2)).roundToInt(), 0) } .width(thumbWidth) .height(thumbHeight) .drawBackdrop( - backdrop = combinedBackdrop, // Refract BOTH! + backdrop = combinedBackdrop, shape = { thumbShape }, effects = { vibrancy() @@ -394,7 +395,7 @@ fun LiquidSeekbar( } // ========================================================================= -// ORIGINAL MPVEX SEEKBARS +// ORIGINAL MPVEX SEEKBARS (SAFE FALLBACKS) // ========================================================================= @Composable From f1294dfd24990851c9484deaac4f668df420c693 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 14:04:17 +0530 Subject: [PATCH 079/194] Update Seekbar.kt --- .../ui/player/controls/components/Seekbar.kt | 144 ++++++------------ 1 file changed, 43 insertions(+), 101 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index d61cefbe1..0467b51f9 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -3,6 +3,7 @@ package app.marlboroadvance.mpvex.ui.player.controls.components import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.Canvas @@ -11,13 +12,10 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.width @@ -43,6 +41,7 @@ import androidx.compose.runtime.withFrameMillis import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path @@ -59,9 +58,7 @@ import androidx.compose.ui.geometry.Size import kotlin.math.roundToInt // --- KYANT BACKDROP 2.0.0-ALPHA03 IMPORTS --- -import com.kyant.backdrop.backdrops.layerBackdrop import com.kyant.backdrop.backdrops.rememberLayerBackdrop -import com.kyant.backdrop.backdrops.rememberCombinedBackdrop import com.kyant.backdrop.drawBackdrop import com.kyant.backdrop.effects.blur import com.kyant.backdrop.effects.lens @@ -189,11 +186,10 @@ fun SeekbarWithTimers( position = if (isUserInteracting) userPosition else animatedPosition.value, duration = duration, chapters = chapters, - isPaused = paused, isScrubbing = isUserInteracting, - liquidColor = liquidColor, loopStart = loopStart, - loopEnd = loopEnd + loopEnd = loopEnd, + liquidColor = liquidColor ) } else { when (seekbarStyle) { @@ -228,135 +224,81 @@ fun SeekbarWithTimers( } // ========================================================================= -// THE ULTIMATE OPTION 3 COMBINED LIQUID SEEKBAR (CRASH-PROOF) +// NEW: LIQUID SEEKBAR (Kyant Lens & Fade Mechanics) // ========================================================================= @Composable fun LiquidSeekbar( position: Float, duration: Float, chapters: ImmutableList, - isPaused: Boolean = false, isScrubbing: Boolean = false, - liquidColor: Color = Color.Unspecified, loopStart: Float? = null, loopEnd: Float? = null, + liquidColor: Color = Color.Unspecified, modifier: Modifier = Modifier, ) { + // TRACK COLOR: Automatically uses the color from your App Settings val activeColor = if (liquidColor.isSpecified) liquidColor else MaterialTheme.colorScheme.primary - var heightFraction by remember { mutableFloatStateOf(1f) } - val scope = rememberCoroutineScope() - - LaunchedEffect(isPaused, isScrubbing) { - scope.launch { - val shouldFlatten = isPaused || isScrubbing - val targetHeight = if (shouldFlatten) 0.7f else 1f - kotlinx.coroutines.delay(if (shouldFlatten) 0L else 60L) - Animatable(heightFraction).animateTo( - targetValue = targetHeight, - animationSpec = tween(durationMillis = if (shouldFlatten) 550 else 800, easing = LinearEasing), - ) { heightFraction = value } - } - } - - val baseTrackHeight = 16.dp - val trackHeightDp = baseTrackHeight * heightFraction - val thumbHeight = 16.dp - val thumbShape = RoundedCornerShape(percent = 50) - - val thumbWidth by animateDpAsState( - targetValue = if (isScrubbing) 24.dp else 16.dp, + // Smooth transition from 0f (Unpressed) to 1f (Pressed) + val pressProgress by animateFloatAsState( + targetValue = if (isScrubbing) 1f else 0f, animationSpec = spring(dampingRatio = 0.6f, stiffness = 800f), - label = "thumb_stretch" + label = "pressProgress" ) - // MAXIMUM QUALITY: The Combined Backdrop Engines - val parentBackdrop = rememberLayerBackdrop { drawContent() } + // Thumb smoothly squishes and widens + val thumbWidth = androidx.compose.ui.unit.lerp(16.dp, 36.dp, pressProgress) + val thumbHeight = 24.dp + val trackBackdrop = rememberLayerBackdrop { drawContent() } - val combinedBackdrop = rememberCombinedBackdrop(parentBackdrop, trackBackdrop) - BoxWithConstraints( + androidx.compose.foundation.layout.BoxWithConstraints( modifier = modifier .fillMaxWidth() - .height(24.dp), + .height(32.dp), contentAlignment = Alignment.CenterStart ) { val trackWidthPx = constraints.maxWidth.toFloat() val progress = if (duration > 0f) (position / duration).coerceIn(0f, 1f) else 0f val playedPx = trackWidthPx * progress - // LAYER 1: The Environment Capture. - // This safely captures the video playing behind the compose window without causing a recursive loop! - Box(modifier = Modifier.fillMaxSize().layerBackdrop(parentBackdrop)) - - // LAYER 2: The Track Capture. - // This captures your custom Cyan track layout. Canvas( modifier = Modifier .fillMaxWidth() - .height(trackHeightDp) - .layerBackdrop(trackBackdrop) + .height(8.dp) // Perfect Kyant track thickness + .clip(CircleShape) // Perfectly round track edges + .layerBackdrop(trackBackdrop) // Feed track colors to the glass thumb ) { - val trackHeight = size.height - val outerRadius = trackHeight / 2f - val innerRadius = outerRadius - - val gapHalf = 14.dp.toPx() / 2f val chapterGapHalf = 1.dp.toPx() - - val thumbGapStart = (playedPx - gapHalf).coerceIn(0f, size.width) - val thumbGapEnd = (playedPx + gapHalf).coerceIn(0f, size.width) - val chapterGaps = chapters .map { (it.start / duration).coerceIn(0f, 1f) * size.width } .filter { it > 0f && it < size.width } .map { x -> (x - chapterGapHalf) to (x + chapterGapHalf) } - fun drawSegment(startX: Float, endX: Float, color: Color) { - if (endX - startX < 0.5f) return - val path = Path() - val isOuterLeft = startX <= 0.5f - val isInnerLeft = kotlin.math.abs(startX - thumbGapEnd) < 0.5f - - val cornerRadiusLeft = when { - isOuterLeft -> androidx.compose.ui.geometry.CornerRadius(outerRadius) - isInnerLeft -> androidx.compose.ui.geometry.CornerRadius(innerRadius) - else -> androidx.compose.ui.geometry.CornerRadius.Zero - } - - val isOuterRight = endX >= size.width - 0.5f - val isInnerRight = kotlin.math.abs(endX - thumbGapStart) < 0.5f - - val cornerRadiusRight = when { - isOuterRight -> androidx.compose.ui.geometry.CornerRadius(outerRadius) - isInnerRight -> androidx.compose.ui.geometry.CornerRadius(innerRadius) - else -> androidx.compose.ui.geometry.CornerRadius.Zero - } - - path.addRoundRect( - androidx.compose.ui.geometry.RoundRect( - left = startX, top = 0f, right = endX, bottom = trackHeight, - topLeftCornerRadius = cornerRadiusLeft, bottomLeftCornerRadius = cornerRadiusLeft, - topRightCornerRadius = cornerRadiusRight, bottomRightCornerRadius = cornerRadiusRight - ) - ) - drawPath(path, color) - } - fun drawRangeWithGaps(rangeStart: Float, rangeEnd: Float, gaps: List>, color: Color) { if (rangeEnd <= rangeStart) return val relevantGaps = gaps.filter { (gStart, gEnd) -> gEnd > rangeStart && gStart < rangeEnd }.sortedBy { it.first } var currentPos = rangeStart for ((gStart, gEnd) in relevantGaps) { val segmentEnd = gStart.coerceAtMost(rangeEnd) - if (segmentEnd > currentPos) drawSegment(currentPos, segmentEnd, color) + if (segmentEnd > currentPos) { + drawRect(color, topLeft = Offset(currentPos, 0f), size = Size(segmentEnd - currentPos, size.height)) + } currentPos = gEnd.coerceAtLeast(currentPos) } - if (currentPos < rangeEnd) drawSegment(currentPos, rangeEnd, color) + if (currentPos < rangeEnd) { + drawRect(color, topLeft = Offset(currentPos, 0f), size = Size(rangeEnd - currentPos, size.height)) + } } - drawRangeWithGaps(thumbGapEnd, size.width, chapterGaps, activeColor.copy(alpha = 0.3f)) - if (thumbGapStart > 0) drawRangeWithGaps(0f, thumbGapStart, chapterGaps, activeColor) + // 1. Draw entire track (Unplayed) at 30% alpha of your custom color + drawRangeWithGaps(0f, size.width, chapterGaps, activeColor.copy(alpha = 0.3f)) + + // 2. Overwrite the played portion with 100% of your custom color + if (playedPx > 0) { + drawRangeWithGaps(0f, playedPx, chapterGaps, activeColor) + } if (loopStart != null || loopEnd != null) { val loopColor = Color(0xFFFFB300) @@ -367,27 +309,27 @@ fun LiquidSeekbar( } } - // LAYER 3: The Combined Glass Thumb! - // Perfectly bends the light from the video (Layer 1) AND the track (Layer 2). Box( modifier = Modifier .offset { androidx.compose.ui.unit.IntOffset((playedPx - (thumbWidth.toPx() / 2)).roundToInt(), 0) } .width(thumbWidth) .height(thumbHeight) .drawBackdrop( - backdrop = combinedBackdrop, - shape = { thumbShape }, + backdrop = trackBackdrop, + shape = { CircleShape }, // Perfectly rounded pill! effects = { vibrancy() - blur(if (isScrubbing) 4f.dp.toPx() else 8f.dp.toPx()) + // Blur fades away as you press it + blur(8f.dp.toPx() * (1f - pressProgress)) + // Lens grows from 0 to maximum as you press it lens( - if (isScrubbing) 14f.dp.toPx() else 10f.dp.toPx(), - if (isScrubbing) 28f.dp.toPx() else 14f.dp.toPx() + 14f.dp.toPx() * pressProgress, + 24f.dp.toPx() * pressProgress ) }, onDrawSurface = { - drawRect(activeColor, blendMode = BlendMode.Hue) - drawRect(activeColor.copy(alpha = 0.75f)) + // THE SECRET: Starts pure white, melts into transparent glass! + drawRect(Color.White.copy(alpha = 1f - pressProgress)) } ) ) @@ -395,7 +337,7 @@ fun LiquidSeekbar( } // ========================================================================= -// ORIGINAL MPVEX SEEKBARS (SAFE FALLBACKS) +// ORIGINAL MPVEX SEEKBARS // ========================================================================= @Composable From e79132b9d3d13e3f1016bc4b91cc5c2f38a9ec1d Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 14:16:03 +0530 Subject: [PATCH 080/194] Update Seekbar.kt --- .../mpvex/ui/player/controls/components/Seekbar.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index 0467b51f9..87599ebdb 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -57,13 +57,14 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.geometry.Size import kotlin.math.roundToInt -// --- KYANT BACKDROP 2.0.0-ALPHA03 IMPORTS --- +// --- KYANT BACKDROP 2.0.0-ALPHA03 IMPORTS (FIXED) --- +import com.kyant.backdrop.backdrops.layerBackdrop import com.kyant.backdrop.backdrops.rememberLayerBackdrop import com.kyant.backdrop.drawBackdrop import com.kyant.backdrop.effects.blur import com.kyant.backdrop.effects.lens import com.kyant.backdrop.effects.vibrancy -// -------------------------------------------- +// ---------------------------------------------------- import app.marlboroadvance.mpvex.ui.player.controls.LocalPlayerButtonsClickEvent import app.marlboroadvance.mpvex.ui.theme.spacing From e2302a49236e43dfee5c77ad2751c11ee96cdf7c Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 16:36:54 +0530 Subject: [PATCH 081/194] Update MainScreen.kt --- .../mpvex/ui/browser/MainScreen.kt | 180 +++++------------- 1 file changed, 46 insertions(+), 134 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/MainScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/MainScreen.kt index d4506ecba..e5fa5af3a 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/MainScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/MainScreen.kt @@ -14,6 +14,8 @@ import androidx.compose.animation.slideOutVertically import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.PlaylistPlay @@ -21,8 +23,8 @@ import androidx.compose.material.icons.filled.History import androidx.compose.material.icons.filled.Home import androidx.compose.material.icons.filled.Language import androidx.compose.material3.Icon -import androidx.compose.material3.NavigationBar -import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -48,36 +50,34 @@ import app.marlboroadvance.mpvex.ui.browser.selection.SelectionManager import kotlinx.coroutines.delay import kotlinx.serialization.Serializable +// --- NEW IMPORTS FOR LIQUID TABS --- +import com.kyant.backdrop.backdrops.layerBackdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop +import app.marlboroadvance.mpvex.ui.components.liquid.LiquidBottomTabs +// ----------------------------------- + @Serializable object MainScreen : Screen { - // Use a companion object to store state more persistently private var persistentSelectedTab: Int = 0 - // Shared state that can be updated by FileSystemBrowserScreen @Volatile - private var isInSelectionModeShared: Boolean = false // Controls FAB visibility + private var isInSelectionModeShared: Boolean = false @Volatile - private var shouldHideNavigationBar: Boolean = false // Controls navigation bar visibility + private var shouldHideNavigationBar: Boolean = false @Volatile - private var isBrowserBottomBarVisible: Boolean = false // Tracks browser bottom bar visibility + private var isBrowserBottomBarVisible: Boolean = false @Volatile private var sharedVideoSelectionManager: Any? = null - // Check if the selection contains only videos and update navigation bar visibility accordingly @Volatile private var onlyVideosSelected: Boolean = false - // Track when permission denied screen is showing to hide FAB @Volatile private var isPermissionDenied: Boolean = false - /** - * Update selection state and navigation bar visibility - * This method should be called whenever selection changes - */ fun updateSelectionState( isInSelectionMode: Boolean, isOnlyVideosSelected: Boolean, @@ -86,84 +86,56 @@ object MainScreen : Screen { this.isInSelectionModeShared = isInSelectionMode this.onlyVideosSelected = isOnlyVideosSelected this.sharedVideoSelectionManager = selectionManager - - // Only hide navigation bar when videos are selected AND in selection mode - // This fixes the issue where bottom bar disappears when only videos are selected this.shouldHideNavigationBar = isInSelectionMode && isOnlyVideosSelected } - /** - * Update permission state to control FAB visibility - */ fun updatePermissionState(isDenied: Boolean) { this.isPermissionDenied = isDenied } - /** - * Get current permission denied state - */ fun getPermissionDeniedState(): Boolean = isPermissionDenied - /** - * Update bottom navigation bar visibility based on floating bottom bar state - */ fun updateBottomBarVisibility(shouldShow: Boolean) { - // Hide bottom navigation when floating bottom bar is visible this.shouldHideNavigationBar = !shouldShow } @Composable @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") override fun Content() { - var selectedTab by remember { - mutableIntStateOf(persistentSelectedTab) - } - + var selectedTab by remember { mutableIntStateOf(persistentSelectedTab) } val context = LocalContext.current val density = LocalDensity.current - // Shared state (across the app) val isInSelectionMode = remember { mutableStateOf(isInSelectionModeShared) } val hideNavigationBar = remember { mutableStateOf(shouldHideNavigationBar) } val videoSelectionManager = remember { mutableStateOf?>(sharedVideoSelectionManager as? SelectionManager<*, *>) } - // Check for state changes to ensure UI updates LaunchedEffect(Unit) { while (true) { - // Update FAB visibility state if (isInSelectionMode.value != isInSelectionModeShared) { isInSelectionMode.value = isInSelectionModeShared - android.util.Log.d("MainScreen", "Selection mode changed to: $isInSelectionModeShared") } - - // Update navigation bar visibility state - now considers if only videos are selected if (hideNavigationBar.value != shouldHideNavigationBar) { hideNavigationBar.value = shouldHideNavigationBar - android.util.Log.d("MainScreen", "Navigation bar visibility changed to: ${!shouldHideNavigationBar}, onlyVideosSelected: $onlyVideosSelected") } - - // Update selection manager val currentManager = sharedVideoSelectionManager as? SelectionManager<*, *> if (videoSelectionManager.value != currentManager) { videoSelectionManager.value = currentManager } - - // Minimal delay for polling - delay(16) // Roughly matches a frame at 60fps for responsive updates + delay(16) } } - // Update persistent state whenever tab changes LaunchedEffect(selectedTab) { - android.util.Log.d("MainScreen", "selectedTab changed to: $selectedTab (was ${persistentSelectedTab})") persistentSelectedTab = selectedTab } - // Scaffold with bottom navigation bar + // THE FIX: We create the Backdrop here to capture the screen underneath! + val bottomBarBackdrop = rememberLayerBackdrop { drawContent() } + Scaffold( modifier = Modifier.fillMaxSize(), bottomBar = { - // Animated bottom navigation bar with slide animations AnimatedVisibility( visible = !hideNavigationBar.value, enter = slideInVertically( @@ -175,106 +147,47 @@ object MainScreen : Screen { targetOffsetY = { fullHeight -> fullHeight } ) ) { - NavigationBar( - modifier = Modifier - .clip( - RoundedCornerShape( - topStart = 28.dp, - topEnd = 28.dp, - bottomStart = 0.dp, - bottomEnd = 0.dp - ) - ) + // Wrap the new Liquid tabs in a Box with padding so it floats beautifully at the bottom + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp, vertical = 24.dp) ) { - NavigationBarItem( - icon = { Icon(Icons.Filled.Home, contentDescription = "Home") }, - label = { Text("Home") }, - selected = selectedTab == 0, - onClick = { selectedTab = 0 } - ) - NavigationBarItem( - icon = { Icon(Icons.Filled.History, contentDescription = "Recents") }, - label = { Text("Recents") }, - selected = selectedTab == 1, - onClick = { selectedTab = 1 } - ) - NavigationBarItem( - icon = { Icon(Icons.AutoMirrored.Filled.PlaylistPlay, contentDescription = "Playlists") }, - label = { Text("Playlists") }, - selected = selectedTab == 2, - onClick = { selectedTab = 2 } - ) - NavigationBarItem( - icon = { Icon(Icons.Filled.Language, contentDescription = "Network") }, - label = { Text("Network") }, - selected = selectedTab == 3, - onClick = { selectedTab = 3 } - ) + LiquidBottomTabs( + tabsCount = 4, + selectedIndex = selectedTab, + backdrop = bottomBarBackdrop + ) { + IconButton(onClick = { selectedTab = 0 }, modifier = Modifier.weight(1f)) { + Icon(Icons.Filled.Home, "Home", tint = if (selectedTab == 0) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant) + } + IconButton(onClick = { selectedTab = 1 }, modifier = Modifier.weight(1f)) { + Icon(Icons.Filled.History, "Recents", tint = if (selectedTab == 1) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant) + } + IconButton(onClick = { selectedTab = 2 }, modifier = Modifier.weight(1f)) { + Icon(Icons.AutoMirrored.Filled.PlaylistPlay, "Playlists", tint = if (selectedTab == 2) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant) + } + IconButton(onClick = { selectedTab = 3 }, modifier = Modifier.weight(1f)) { + Icon(Icons.Filled.Language, "Network", tint = if (selectedTab == 3) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant) + } + } } } } ) { paddingValues -> - Box(modifier = Modifier.fillMaxSize()) { - // Always use 80dp bottom padding regardless of navigation bar visibility + // THE FIX: Attach the backdrop to the Box that holds the screens, so the nav bar can refract them! + Box(modifier = Modifier.fillMaxSize().layerBackdrop(bottomBarBackdrop)) { val fabBottomPadding = 80.dp AnimatedContent( targetState = selectedTab, transitionSpec = { - // Material 3 Expressive slide-in-fade animation (like Google Phone app) val slideDistance = with(density) { 48.dp.roundToPx() } val animationDuration = 250 - if (targetState > initialState) { - // Moving forward: slide in from right with fade - (slideInHorizontally( - animationSpec = tween( - durationMillis = animationDuration, - easing = FastOutSlowInEasing - ), - initialOffsetX = { slideDistance } - ) + fadeIn( - animationSpec = tween( - durationMillis = animationDuration, - easing = FastOutSlowInEasing - ) - )) togetherWith (slideOutHorizontally( - animationSpec = tween( - durationMillis = animationDuration, - easing = FastOutSlowInEasing - ), - targetOffsetX = { -slideDistance } - ) + fadeOut( - animationSpec = tween( - durationMillis = animationDuration / 2, - easing = FastOutSlowInEasing - ) - )) + (slideInHorizontally(animationSpec = tween(durationMillis = animationDuration, easing = FastOutSlowInEasing), initialOffsetX = { slideDistance }) + fadeIn(animationSpec = tween(durationMillis = animationDuration, easing = FastOutSlowInEasing))) togetherWith (slideOutHorizontally(animationSpec = tween(durationMillis = animationDuration, easing = FastOutSlowInEasing), targetOffsetX = { -slideDistance }) + fadeOut(animationSpec = tween(durationMillis = animationDuration / 2, easing = FastOutSlowInEasing))) } else { - // Moving backward: slide in from left with fade - (slideInHorizontally( - animationSpec = tween( - durationMillis = animationDuration, - easing = FastOutSlowInEasing - ), - initialOffsetX = { -slideDistance } - ) + fadeIn( - animationSpec = tween( - durationMillis = animationDuration, - easing = FastOutSlowInEasing - ) - )) togetherWith (slideOutHorizontally( - animationSpec = tween( - durationMillis = animationDuration, - easing = FastOutSlowInEasing - ), - targetOffsetX = { slideDistance } - ) + fadeOut( - animationSpec = tween( - durationMillis = animationDuration / 2, - easing = FastOutSlowInEasing - ) - )) + (slideInHorizontally(animationSpec = tween(durationMillis = animationDuration, easing = FastOutSlowInEasing), initialOffsetX = { -slideDistance }) + fadeIn(animationSpec = tween(durationMillis = animationDuration, easing = FastOutSlowInEasing))) togetherWith (slideOutHorizontally(animationSpec = tween(durationMillis = animationDuration, easing = FastOutSlowInEasing), targetOffsetX = { slideDistance }) + fadeOut(animationSpec = tween(durationMillis = animationDuration / 2, easing = FastOutSlowInEasing))) } }, label = "tab_animation" @@ -295,5 +208,4 @@ object MainScreen : Screen { } } -// CompositionLocal for navigation bar height -val LocalNavigationBarHeight = compositionLocalOf { 0.dp } \ No newline at end of file +val LocalNavigationBarHeight = compositionLocalOf { 0.dp } From 19eb1ff733574f4644f1880d714267a3a0c483be Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 18:08:11 +0530 Subject: [PATCH 082/194] Create LiquidGlassSurface.kt --- .../components/liquid/LiquidGlassSurface.kt | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidGlassSurface.kt diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidGlassSurface.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidGlassSurface.kt new file mode 100644 index 000000000..bef416f5b --- /dev/null +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidGlassSurface.kt @@ -0,0 +1,68 @@ +package app.marlboroadvance.mpvex.ui.components.liquid + +import android.os.Build +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.unit.dp + +// --- KYANT BACKDROP 2.0.0-ALPHA03 IMPORTS --- +import com.kyant.backdrop.Backdrop +import com.kyant.backdrop.drawBackdrop +import com.kyant.backdrop.effects.blur +import com.kyant.backdrop.effects.lens +import com.kyant.backdrop.effects.vibrancy +// -------------------------------------------- + +@Composable +fun LiquidGlassSurface( + backdrop: Backdrop, + modifier: Modifier = Modifier, + shape: Shape = RoundedCornerShape(24.dp), + tintColor: Color = Color.White.copy(alpha = 0.15f), + content: @Composable () -> Unit +) { + if (Build.VERSION.SDK_INT >= 33) { + // ANDROID 13+: Your Exact Custom Lens Parameters! + Box( + modifier = modifier + .drawBackdrop( + backdrop = backdrop, + shape = { shape }, + effects = { + vibrancy() + // Blur is 0, so we can either omit it or explicitly set to 0. + // Omitting it is better for GPU performance! + + lens( + refractionHeight = 40f.dp.toPx(), + refractionAmount = 23f.dp.toPx(), + depthEffect = true, + chromaticAberration = false + ) + }, + onDrawSurface = { + drawRect(tintColor) + } + ) + ) { + content() + } + } else { + // ANDROID 12 AND BELOW: The "Flat Liquid Sheet" Fallback + Box( + modifier = modifier + .background(tintColor.copy(alpha = 0.4f), shape) + .border(1.dp, Color.White.copy(alpha = 0.2f), shape) + .clip(shape) + ) { + content() + } + } +} From 1b1fb813d495c9950db1628f1ed9667d354fbddf Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 18:36:29 +0530 Subject: [PATCH 083/194] Update FloatingBottomBar.kt --- .../browser/components/FloatingBottomBar.kt | 104 ++++++++---------- 1 file changed, 43 insertions(+), 61 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/components/FloatingBottomBar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/components/FloatingBottomBar.kt index aae10b9c2..314e8ed44 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/components/FloatingBottomBar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/components/FloatingBottomBar.kt @@ -6,6 +6,8 @@ import androidx.compose.animation.fadeOut import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.systemBars @@ -22,80 +24,72 @@ import androidx.compose.material3.FilledTonalIconButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -/** - * Material 3 Floating Button Bar for file/folder operations - * Icon-only buttons in a floating pill-shaped surface - */ +// --- LIQUID GLASS IMPORTS --- +import app.marlboroadvance.mpvex.ui.components.liquid.LiquidGlassSurface +import com.kyant.backdrop.backdrops.rememberLayerBackdrop +// ---------------------------- + @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable -fun BrowserBottomBar( - isSelectionMode: Boolean, - onCopyClick: () -> Unit, - onMoveClick: () -> Unit, - onRenameClick: () -> Unit, - onDeleteClick: () -> Unit, - onAddToPlaylistClick: () -> Unit, - modifier: Modifier = Modifier, - showCopy: Boolean = true, - showMove: Boolean = true, - showRename: Boolean = true, - showDelete: Boolean = true, - showAddToPlaylist: Boolean = true, +fun FloatingBottomBar( + visible: Boolean, + showCopy: Boolean = false, + showMove: Boolean = false, + showRename: Boolean = false, + showDelete: Boolean = false, + showAddToPlaylist: Boolean = false, + onCopyClick: () -> Unit = {}, + onMoveClick: () -> Unit = {}, + onRenameClick: () -> Unit = {}, + onDeleteClick: () -> Unit = {}, + onAddToPlaylistClick: () -> Unit = {}, + modifier: Modifier = Modifier ) { AnimatedVisibility( - visible = isSelectionMode, - modifier = modifier, + visible = visible, enter = fadeIn(), exit = fadeOut(), + modifier = modifier ) { - Surface( + // Capture the file list behind the bar to refract it + val backdrop = rememberLayerBackdrop { drawContent() } + + LiquidGlassSurface( + backdrop = backdrop, modifier = Modifier + .padding(horizontal = 16.dp, vertical = 16.dp) .windowInsetsPadding(WindowInsets.systemBars) - .padding(horizontal = 20.dp, vertical = 8.dp), - shape = RoundedCornerShape(32.dp), - color = MaterialTheme.colorScheme.surfaceContainerHigh, - tonalElevation = 3.dp, - shadowElevation = 8.dp + .height(64.dp), + shape = RoundedCornerShape(24.dp) ) { Row( - modifier = Modifier.padding(horizontal = 12.dp, vertical = 12.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 8.dp), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically ) { FilledTonalIconButton( onClick = onCopyClick, enabled = showCopy, modifier = Modifier.size(50.dp), - colors = IconButtonDefaults.filledTonalIconButtonColors( - containerColor = MaterialTheme.colorScheme.secondaryContainer, - contentColor = MaterialTheme.colorScheme.onSecondaryContainer - ) + colors = IconButtonDefaults.filledTonalIconButtonColors() ) { - Icon( - Icons.Filled.ContentCopy, - contentDescription = "Copy", - modifier = Modifier.size(24.dp) - ) + Icon(Icons.Filled.ContentCopy, contentDescription = "Copy", modifier = Modifier.size(24.dp)) } FilledTonalIconButton( onClick = onMoveClick, enabled = showMove, modifier = Modifier.size(50.dp), - colors = IconButtonDefaults.filledTonalIconButtonColors( - containerColor = MaterialTheme.colorScheme.secondaryContainer, - contentColor = MaterialTheme.colorScheme.onSecondaryContainer - ) + colors = IconButtonDefaults.filledTonalIconButtonColors() ) { - Icon( - Icons.AutoMirrored.Filled.DriveFileMove, - contentDescription = "Move", - modifier = Modifier.size(24.dp) - ) + Icon(Icons.AutoMirrored.Filled.DriveFileMove, contentDescription = "Move", modifier = Modifier.size(24.dp)) } FilledTonalIconButton( @@ -107,11 +101,7 @@ fun BrowserBottomBar( contentColor = MaterialTheme.colorScheme.onSecondaryContainer ) ) { - Icon( - Icons.Filled.DriveFileRenameOutline, - contentDescription = "Rename", - modifier = Modifier.size(24.dp) - ) + Icon(Icons.Filled.DriveFileRenameOutline, contentDescription = "Rename", modifier = Modifier.size(24.dp)) } FilledTonalIconButton( @@ -120,11 +110,7 @@ fun BrowserBottomBar( modifier = Modifier.size(50.dp), colors = IconButtonDefaults.filledTonalIconButtonColors() ) { - Icon( - Icons.AutoMirrored.Filled.PlaylistAdd, - contentDescription = "Add to Playlist", - modifier = Modifier.size(24.dp) - ) + Icon(Icons.AutoMirrored.Filled.PlaylistAdd, contentDescription = "Add to Playlist", modifier = Modifier.size(24.dp)) } FilledTonalIconButton( @@ -136,11 +122,7 @@ fun BrowserBottomBar( contentColor = MaterialTheme.colorScheme.onErrorContainer ) ) { - Icon( - Icons.Filled.Delete, - contentDescription = "Delete", - modifier = Modifier.size(24.dp) - ) + Icon(Icons.Filled.Delete, contentDescription = "Delete", modifier = Modifier.size(24.dp)) } } } From 7d8bc14f1b49e96fc25ca7190c6734e2590c4e5a Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 18:45:25 +0530 Subject: [PATCH 084/194] Update FloatingBottomBar.kt --- .../browser/components/FloatingBottomBar.kt | 88 +++++++++++-------- 1 file changed, 49 insertions(+), 39 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/components/FloatingBottomBar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/components/FloatingBottomBar.kt index 314e8ed44..eb7d30c5d 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/components/FloatingBottomBar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/components/FloatingBottomBar.kt @@ -24,14 +24,20 @@ import androidx.compose.material3.FilledTonalIconButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp // --- LIQUID GLASS IMPORTS --- import app.marlboroadvance.mpvex.ui.components.liquid.LiquidGlassSurface import com.kyant.backdrop.backdrops.rememberLayerBackdrop +import app.marlboroadvance.mpvex.preferences.LiquidUIPreferences // ---------------------------- @OptIn(ExperimentalMaterial3ExpressiveApi::class) @@ -50,23 +56,20 @@ fun FloatingBottomBar( onAddToPlaylistClick: () -> Unit = {}, modifier: Modifier = Modifier ) { + // Read the Liquid UI Settings! + val context = LocalContext.current + val liquidPrefs = remember { LiquidUIPreferences(context) } + val isLiquidUI by liquidPrefs.liquidUIEnabledFlow.collectAsState(false) + AnimatedVisibility( visible = visible, enter = fadeIn(), exit = fadeOut(), modifier = modifier ) { - // Capture the file list behind the bar to refract it val backdrop = rememberLayerBackdrop { drawContent() } - LiquidGlassSurface( - backdrop = backdrop, - modifier = Modifier - .padding(horizontal = 16.dp, vertical = 16.dp) - .windowInsetsPadding(WindowInsets.systemBars) - .height(64.dp), - shape = RoundedCornerShape(24.dp) - ) { + val contentRow = @Composable { Row( modifier = Modifier .fillMaxSize() @@ -75,55 +78,62 @@ fun FloatingBottomBar( verticalAlignment = Alignment.CenterVertically ) { FilledTonalIconButton( - onClick = onCopyClick, - enabled = showCopy, - modifier = Modifier.size(50.dp), + onClick = onCopyClick, enabled = showCopy, modifier = Modifier.size(50.dp), colors = IconButtonDefaults.filledTonalIconButtonColors() - ) { - Icon(Icons.Filled.ContentCopy, contentDescription = "Copy", modifier = Modifier.size(24.dp)) - } + ) { Icon(Icons.Filled.ContentCopy, contentDescription = "Copy", modifier = Modifier.size(24.dp)) } FilledTonalIconButton( - onClick = onMoveClick, - enabled = showMove, - modifier = Modifier.size(50.dp), + onClick = onMoveClick, enabled = showMove, modifier = Modifier.size(50.dp), colors = IconButtonDefaults.filledTonalIconButtonColors() - ) { - Icon(Icons.AutoMirrored.Filled.DriveFileMove, contentDescription = "Move", modifier = Modifier.size(24.dp)) - } + ) { Icon(Icons.AutoMirrored.Filled.DriveFileMove, contentDescription = "Move", modifier = Modifier.size(24.dp)) } FilledTonalIconButton( - onClick = onRenameClick, - enabled = showRename, - modifier = Modifier.size(50.dp), + onClick = onRenameClick, enabled = showRename, modifier = Modifier.size(50.dp), colors = IconButtonDefaults.filledTonalIconButtonColors( containerColor = MaterialTheme.colorScheme.secondaryContainer, contentColor = MaterialTheme.colorScheme.onSecondaryContainer ) - ) { - Icon(Icons.Filled.DriveFileRenameOutline, contentDescription = "Rename", modifier = Modifier.size(24.dp)) - } + ) { Icon(Icons.Filled.DriveFileRenameOutline, contentDescription = "Rename", modifier = Modifier.size(24.dp)) } FilledTonalIconButton( - onClick = onAddToPlaylistClick, - enabled = showAddToPlaylist, - modifier = Modifier.size(50.dp), + onClick = onAddToPlaylistClick, enabled = showAddToPlaylist, modifier = Modifier.size(50.dp), colors = IconButtonDefaults.filledTonalIconButtonColors() - ) { - Icon(Icons.AutoMirrored.Filled.PlaylistAdd, contentDescription = "Add to Playlist", modifier = Modifier.size(24.dp)) - } + ) { Icon(Icons.AutoMirrored.Filled.PlaylistAdd, contentDescription = "Add to Playlist", modifier = Modifier.size(24.dp)) } FilledTonalIconButton( - onClick = onDeleteClick, - enabled = showDelete, - modifier = Modifier.size(50.dp), + onClick = onDeleteClick, enabled = showDelete, modifier = Modifier.size(50.dp), colors = IconButtonDefaults.filledTonalIconButtonColors( containerColor = MaterialTheme.colorScheme.errorContainer, contentColor = MaterialTheme.colorScheme.onErrorContainer ) - ) { - Icon(Icons.Filled.Delete, contentDescription = "Delete", modifier = Modifier.size(24.dp)) - } + ) { Icon(Icons.Filled.Delete, contentDescription = "Delete", modifier = Modifier.size(24.dp)) } + } + } + + // THE TOGGLE LOGIC + if (isLiquidUI) { + LiquidGlassSurface( + backdrop = backdrop, + modifier = Modifier + .padding(horizontal = 16.dp, vertical = 16.dp) + .windowInsetsPadding(WindowInsets.systemBars) + .height(64.dp), + shape = RoundedCornerShape(24.dp) + ) { + contentRow() + } + } else { + // Fallback to the classic mpvEx solid surface + Surface( + modifier = Modifier + .padding(horizontal = 16.dp, vertical = 16.dp) + .windowInsetsPadding(WindowInsets.systemBars) + .height(64.dp), + shape = RoundedCornerShape(24.dp), + color = MaterialTheme.colorScheme.surfaceContainerHighest, + shadowElevation = 6.dp + ) { + contentRow() } } } From b35c8442408cd60ee95f841e1e02b69dd1fc482a Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 18:46:12 +0530 Subject: [PATCH 085/194] Update MainScreen.kt --- .../mpvex/ui/browser/MainScreen.kt | 183 ++++++++++-------- 1 file changed, 97 insertions(+), 86 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/MainScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/MainScreen.kt index e5fa5af3a..26e21dc85 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/MainScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/MainScreen.kt @@ -12,10 +12,14 @@ import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.slideOutVertically import androidx.compose.animation.togetherWith +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.PlaylistPlay @@ -25,17 +29,21 @@ import androidx.compose.material.icons.filled.Language import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext @@ -50,87 +58,62 @@ import app.marlboroadvance.mpvex.ui.browser.selection.SelectionManager import kotlinx.coroutines.delay import kotlinx.serialization.Serializable -// --- NEW IMPORTS FOR LIQUID TABS --- +// --- LIQUID GLASS IMPORTS --- import com.kyant.backdrop.backdrops.layerBackdrop import com.kyant.backdrop.backdrops.rememberLayerBackdrop -import app.marlboroadvance.mpvex.ui.components.liquid.LiquidBottomTabs -// ----------------------------------- +import app.marlboroadvance.mpvex.ui.components.liquid.LiquidGlassSurface +import app.marlboroadvance.mpvex.preferences.LiquidUIPreferences +// ---------------------------- @Serializable object MainScreen : Screen { private var persistentSelectedTab: Int = 0 - @Volatile - private var isInSelectionModeShared: Boolean = false + @Volatile private var isInSelectionModeShared: Boolean = false + @Volatile private var shouldHideNavigationBar: Boolean = false + @Volatile private var isBrowserBottomBarVisible: Boolean = false + @Volatile private var sharedVideoSelectionManager: Any? = null + @Volatile private var onlyVideosSelected: Boolean = false + @Volatile private var isPermissionDenied: Boolean = false - @Volatile - private var shouldHideNavigationBar: Boolean = false - - @Volatile - private var isBrowserBottomBarVisible: Boolean = false - - @Volatile - private var sharedVideoSelectionManager: Any? = null - - @Volatile - private var onlyVideosSelected: Boolean = false - - @Volatile - private var isPermissionDenied: Boolean = false - - fun updateSelectionState( - isInSelectionMode: Boolean, - isOnlyVideosSelected: Boolean, - selectionManager: Any? - ) { + fun updateSelectionState(isInSelectionMode: Boolean, isOnlyVideosSelected: Boolean, selectionManager: Any?) { this.isInSelectionModeShared = isInSelectionMode this.onlyVideosSelected = isOnlyVideosSelected this.sharedVideoSelectionManager = selectionManager this.shouldHideNavigationBar = isInSelectionMode && isOnlyVideosSelected } - fun updatePermissionState(isDenied: Boolean) { - this.isPermissionDenied = isDenied - } - + fun updatePermissionState(isDenied: Boolean) { this.isPermissionDenied = isDenied } fun getPermissionDeniedState(): Boolean = isPermissionDenied - - fun updateBottomBarVisibility(shouldShow: Boolean) { - this.shouldHideNavigationBar = !shouldShow - } + fun updateBottomBarVisibility(shouldShow: Boolean) { this.shouldHideNavigationBar = !shouldShow } @Composable @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") override fun Content() { var selectedTab by remember { mutableIntStateOf(persistentSelectedTab) } - val context = LocalContext.current val density = LocalDensity.current + // Read the Liquid UI Settings! + val context = LocalContext.current + val liquidPrefs = remember { LiquidUIPreferences(context) } + val isLiquidUI by liquidPrefs.liquidUIEnabledFlow.collectAsState(false) + val isInSelectionMode = remember { mutableStateOf(isInSelectionModeShared) } val hideNavigationBar = remember { mutableStateOf(shouldHideNavigationBar) } val videoSelectionManager = remember { mutableStateOf?>(sharedVideoSelectionManager as? SelectionManager<*, *>) } LaunchedEffect(Unit) { while (true) { - if (isInSelectionMode.value != isInSelectionModeShared) { - isInSelectionMode.value = isInSelectionModeShared - } - if (hideNavigationBar.value != shouldHideNavigationBar) { - hideNavigationBar.value = shouldHideNavigationBar - } + if (isInSelectionMode.value != isInSelectionModeShared) isInSelectionMode.value = isInSelectionModeShared + if (hideNavigationBar.value != shouldHideNavigationBar) hideNavigationBar.value = shouldHideNavigationBar val currentManager = sharedVideoSelectionManager as? SelectionManager<*, *> - if (videoSelectionManager.value != currentManager) { - videoSelectionManager.value = currentManager - } + if (videoSelectionManager.value != currentManager) videoSelectionManager.value = currentManager delay(16) } } - LaunchedEffect(selectedTab) { - persistentSelectedTab = selectedTab - } + LaunchedEffect(selectedTab) { persistentSelectedTab = selectedTab } - // THE FIX: We create the Backdrop here to capture the screen underneath! val bottomBarBackdrop = rememberLayerBackdrop { drawContent() } Scaffold( @@ -138,45 +121,75 @@ object MainScreen : Screen { bottomBar = { AnimatedVisibility( visible = !hideNavigationBar.value, - enter = slideInVertically( - animationSpec = tween(durationMillis = 300), - initialOffsetY = { fullHeight -> fullHeight } - ), - exit = slideOutVertically( - animationSpec = tween(durationMillis = 300), - targetOffsetY = { fullHeight -> fullHeight } - ) + enter = slideInVertically(animationSpec = tween(durationMillis = 300), initialOffsetY = { it }), + exit = slideOutVertically(animationSpec = tween(durationMillis = 300), targetOffsetY = { it }) ) { - // Wrap the new Liquid tabs in a Box with padding so it floats beautifully at the bottom - Box( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 24.dp, vertical = 24.dp) - ) { - LiquidBottomTabs( - tabsCount = 4, - selectedIndex = selectedTab, - backdrop = bottomBarBackdrop - ) { - IconButton(onClick = { selectedTab = 0 }, modifier = Modifier.weight(1f)) { - Icon(Icons.Filled.Home, "Home", tint = if (selectedTab == 0) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant) - } - IconButton(onClick = { selectedTab = 1 }, modifier = Modifier.weight(1f)) { - Icon(Icons.Filled.History, "Recents", tint = if (selectedTab == 1) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant) - } - IconButton(onClick = { selectedTab = 2 }, modifier = Modifier.weight(1f)) { - Icon(Icons.AutoMirrored.Filled.PlaylistPlay, "Playlists", tint = if (selectedTab == 2) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant) - } - IconButton(onClick = { selectedTab = 3 }, modifier = Modifier.weight(1f)) { - Icon(Icons.Filled.Language, "Network", tint = if (selectedTab == 3) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant) - } - } + + // THE TOGGLE LOGIC + if (isLiquidUI) { + // The Floating Glass Capsule (from the 1.0.4 fork, upgraded to alpha03) + LiquidGlassSurface( + backdrop = bottomBarBackdrop, + modifier = Modifier + .padding(horizontal = 16.dp, vertical = 24.dp) + .fillMaxWidth() + .height(64.dp), + shape = RoundedCornerShape(32.dp) + ) { + Row( + modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically + ) { + val activeColor = MaterialTheme.colorScheme.primary + val inactiveColor = MaterialTheme.colorScheme.onSurfaceVariant + + IconButton(onClick = { selectedTab = 0 }) { + Icon(Icons.Filled.Home, "Home", modifier = Modifier.size(28.dp), tint = if (selectedTab == 0) activeColor else inactiveColor) + } + IconButton(onClick = { selectedTab = 1 }) { + Icon(Icons.Filled.History, "Recents", modifier = Modifier.size(28.dp), tint = if (selectedTab == 1) activeColor else inactiveColor) + } + IconButton(onClick = { selectedTab = 2 }) { + Icon(Icons.AutoMirrored.Filled.PlaylistPlay, "Playlists", modifier = Modifier.size(28.dp), tint = if (selectedTab == 2) activeColor else inactiveColor) + } + IconButton(onClick = { selectedTab = 3 }) { + Icon(Icons.Filled.Language, "Network", modifier = Modifier.size(28.dp), tint = if (selectedTab == 3) activeColor else inactiveColor) + } + } + } + } else { + // The Classic mpvEx Navigation Bar + NavigationBar( + modifier = Modifier.clip(RoundedCornerShape(topStart = 28.dp, topEnd = 28.dp, bottomStart = 0.dp, bottomEnd = 0.dp)) + ) { + NavigationBarItem( + icon = { Icon(Icons.Filled.Home, contentDescription = "Home") }, + label = { Text("Home") }, + selected = selectedTab == 0, onClick = { selectedTab = 0 } + ) + NavigationBarItem( + icon = { Icon(Icons.Filled.History, contentDescription = "Recents") }, + label = { Text("Recents") }, + selected = selectedTab == 1, onClick = { selectedTab = 1 } + ) + NavigationBarItem( + icon = { Icon(Icons.AutoMirrored.Filled.PlaylistPlay, contentDescription = "Playlists") }, + label = { Text("Playlists") }, + selected = selectedTab == 2, onClick = { selectedTab = 2 } + ) + NavigationBarItem( + icon = { Icon(Icons.Filled.Language, contentDescription = "Network") }, + label = { Text("Network") }, + selected = selectedTab == 3, onClick = { selectedTab = 3 } + ) + } } } } ) { paddingValues -> - // THE FIX: Attach the backdrop to the Box that holds the screens, so the nav bar can refract them! - Box(modifier = Modifier.fillMaxSize().layerBackdrop(bottomBarBackdrop)) { + // Only apply layerBackdrop if Liquid UI is enabled (saves battery!) + Box(modifier = Modifier.fillMaxSize().then(if (isLiquidUI) Modifier.layerBackdrop(bottomBarBackdrop) else Modifier)) { val fabBottomPadding = 80.dp AnimatedContent( @@ -185,16 +198,14 @@ object MainScreen : Screen { val slideDistance = with(density) { 48.dp.roundToPx() } val animationDuration = 250 if (targetState > initialState) { - (slideInHorizontally(animationSpec = tween(durationMillis = animationDuration, easing = FastOutSlowInEasing), initialOffsetX = { slideDistance }) + fadeIn(animationSpec = tween(durationMillis = animationDuration, easing = FastOutSlowInEasing))) togetherWith (slideOutHorizontally(animationSpec = tween(durationMillis = animationDuration, easing = FastOutSlowInEasing), targetOffsetX = { -slideDistance }) + fadeOut(animationSpec = tween(durationMillis = animationDuration / 2, easing = FastOutSlowInEasing))) + (slideInHorizontally(animationSpec = tween(animationDuration, easing = FastOutSlowInEasing), initialOffsetX = { slideDistance }) + fadeIn(animationSpec = tween(animationDuration, easing = FastOutSlowInEasing))) togetherWith (slideOutHorizontally(animationSpec = tween(animationDuration, easing = FastOutSlowInEasing), targetOffsetX = { -slideDistance }) + fadeOut(animationSpec = tween(animationDuration / 2, easing = FastOutSlowInEasing))) } else { - (slideInHorizontally(animationSpec = tween(durationMillis = animationDuration, easing = FastOutSlowInEasing), initialOffsetX = { -slideDistance }) + fadeIn(animationSpec = tween(durationMillis = animationDuration, easing = FastOutSlowInEasing))) togetherWith (slideOutHorizontally(animationSpec = tween(durationMillis = animationDuration, easing = FastOutSlowInEasing), targetOffsetX = { slideDistance }) + fadeOut(animationSpec = tween(durationMillis = animationDuration / 2, easing = FastOutSlowInEasing))) + (slideInHorizontally(animationSpec = tween(animationDuration, easing = FastOutSlowInEasing), initialOffsetX = { -slideDistance }) + fadeIn(animationSpec = tween(animationDuration, easing = FastOutSlowInEasing))) togetherWith (slideOutHorizontally(animationSpec = tween(animationDuration, easing = FastOutSlowInEasing), targetOffsetX = { slideDistance }) + fadeOut(animationSpec = tween(animationDuration / 2, easing = FastOutSlowInEasing))) } }, label = "tab_animation" ) { targetTab -> - CompositionLocalProvider( - LocalNavigationBarHeight provides fabBottomPadding - ) { + CompositionLocalProvider(LocalNavigationBarHeight provides fabBottomPadding) { when (targetTab) { 0 -> FolderListScreen.Content() 1 -> RecentlyPlayedScreen.Content() From 3f4cee8781c26e64e5823ae0dbc3b761d35dcddf Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 20:13:37 +0530 Subject: [PATCH 086/194] Update VideoListScreen.kt --- .../mpvex/ui/browser/videolist/VideoListScreen.kt | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt index 3a014994f..e7b7e712a 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt @@ -85,7 +85,7 @@ import app.marlboroadvance.mpvex.presentation.Screen import app.marlboroadvance.mpvex.presentation.components.pullrefresh.PullRefreshBox import app.marlboroadvance.mpvex.BuildConfig import app.marlboroadvance.mpvex.ui.browser.cards.VideoCard -import app.marlboroadvance.mpvex.ui.browser.components.BrowserBottomBar +import app.marlboroadvance.mpvex.ui.browser.components.FloatingBottomBar import app.marlboroadvance.mpvex.ui.browser.components.BrowserTopBar import app.marlboroadvance.mpvex.ui.browser.dialogs.AddToPlaylistDialog import app.marlboroadvance.mpvex.ui.browser.dialogs.DeleteConfirmationDialog @@ -377,8 +377,13 @@ data class VideoListScreen( ), modifier = Modifier.align(Alignment.BottomCenter) ) { - BrowserBottomBar( - isSelectionMode = true, + FloatingBottomBar( + visible = true, + showCopy = true, + showMove = true, + showDelete = true, + showAddToPlaylist = true, + showRename = selectionManager.isSingleSelection, onCopyClick = { operationType.value = CopyPasteOps.OperationType.Copy if (CopyPasteOps.canUseDirectFileOperations()) { @@ -397,7 +402,7 @@ data class VideoListScreen( }, onRenameClick = { renameDialogOpen.value = true }, onDeleteClick = { deleteDialogOpen.value = true }, - onAddToPlaylistClick = { addToPlaylistDialogOpen.value = true }, + onAddToPlaylistClick = { addToPlaylistDialogOpen.value = true } showRename = selectionManager.isSingleSelection ) } From 809f209652e681ea8e8e97505ec34c4e11c3c510 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 20:16:42 +0530 Subject: [PATCH 087/194] Update VideoListScreen.kt --- .../mpvex/ui/browser/videolist/VideoListScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt index e7b7e712a..e649d7d61 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt @@ -402,7 +402,7 @@ data class VideoListScreen( }, onRenameClick = { renameDialogOpen.value = true }, onDeleteClick = { deleteDialogOpen.value = true }, - onAddToPlaylistClick = { addToPlaylistDialogOpen.value = true } + onAddToPlaylistClick = { addToPlaylistDialogOpen.value = true }, showRename = selectionManager.isSingleSelection ) } From e4ff3caaa878d168c0a2d980e3cb9ec6745f1306 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 20:37:09 +0530 Subject: [PATCH 088/194] Update FileSystemBrowserScreen.kt --- .../filesystem/FileSystemBrowserScreen.kt | 64 ++++++++----------- 1 file changed, 28 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt index 99510b078..125e73522 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt @@ -95,7 +95,7 @@ import app.marlboroadvance.mpvex.preferences.preference.collectAsState import app.marlboroadvance.mpvex.presentation.components.pullrefresh.PullRefreshBox import app.marlboroadvance.mpvex.ui.browser.cards.FolderCard import app.marlboroadvance.mpvex.ui.browser.cards.VideoCard -import app.marlboroadvance.mpvex.ui.browser.components.BrowserBottomBar +import app.marlboroadvance.mpvex.ui.browser.components.FloatingBottomBar import app.marlboroadvance.mpvex.ui.browser.components.BrowserTopBar import app.marlboroadvance.mpvex.ui.browser.dialogs.AddToPlaylistDialog import app.marlboroadvance.mpvex.ui.browser.dialogs.DeleteConfirmationDialog @@ -881,43 +881,35 @@ fun FileSystemBrowserScreen(path: String? = null) { // Independent Floating Bottom Bar - positioned at absolute bottom // Play Store gating is intentionally bypassed here. - AnimatedVisibility( + FloatingBottomBar( visible = showFloatingBottomBar, - enter = slideInVertically( - animationSpec = tween(durationMillis = animationDuration), - initialOffsetY = { fullHeight -> fullHeight } - ), - exit = slideOutVertically( - animationSpec = tween(durationMillis = animationDuration), - targetOffsetY = { fullHeight -> fullHeight } - ), + showCopy = true, + showMove = true, + showRename = videoSelectionManager.isSingleSelection, + showDelete = true, + showAddToPlaylist = true, + onCopyClick = { + operationType.value = CopyPasteOps.OperationType.Copy + if (CopyPasteOps.canUseDirectFileOperations()) { + folderPickerOpen.value = true + } else { + treePickerLauncher.launch(null) + } + }, + onMoveClick = { + operationType.value = CopyPasteOps.OperationType.Move + if (CopyPasteOps.canUseDirectFileOperations()) { + folderPickerOpen.value = true + } else { + treePickerLauncher.launch(null) + } + }, + onRenameClick = { renameDialogOpen.value = true }, + onDeleteClick = { deleteDialogOpen.value = true }, + onAddToPlaylistClick = { addToPlaylistDialogOpen.value = true }, modifier = Modifier.align(Alignment.BottomCenter) - ) { - BrowserBottomBar( - isSelectionMode = true, - onCopyClick = { - operationType.value = CopyPasteOps.OperationType.Copy - if (CopyPasteOps.canUseDirectFileOperations()) { - folderPickerOpen.value = true - } else { - treePickerLauncher.launch(null) - } - }, - onMoveClick = { - operationType.value = CopyPasteOps.OperationType.Move - if (CopyPasteOps.canUseDirectFileOperations()) { - folderPickerOpen.value = true - } else { - treePickerLauncher.launch(null) - } - }, - onRenameClick = { renameDialogOpen.value = true }, - onDeleteClick = { deleteDialogOpen.value = true }, - onAddToPlaylistClick = { addToPlaylistDialogOpen.value = true }, - showRename = videoSelectionManager.isSingleSelection, - modifier = Modifier.padding(bottom = 0.dp) // Zero bottom padding - absolute bottom - ) - } + ) + } // Dialogs PlayLinkSheet( From e282581129bca4c4e5bcf4fd1378d798703f1448 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 20:43:06 +0530 Subject: [PATCH 089/194] Update VideoListScreen.kt --- .../ui/browser/videolist/VideoListScreen.kt | 63 ++++++++----------- 1 file changed, 26 insertions(+), 37 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt index e649d7d61..e511a6710 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt @@ -365,45 +365,34 @@ data class VideoListScreen( // Floating Material 3 Button Group overlay with animation // Play Store gating is intentionally bypassed here. - AnimatedVisibility( + FloatingBottomBar( visible = showFloatingBottomBar, - enter = slideInVertically( - animationSpec = tween(durationMillis = animationDuration), - initialOffsetY = { fullHeight -> fullHeight } - ), - exit = slideOutVertically( - animationSpec = tween(durationMillis = animationDuration), - targetOffsetY = { fullHeight -> fullHeight } - ), + showCopy = true, + showMove = true, + showRename = selectionManager.isSingleSelection, + showDelete = true, + showAddToPlaylist = true, + onCopyClick = { + operationType.value = CopyPasteOps.OperationType.Copy + if (CopyPasteOps.canUseDirectFileOperations()) { + folderPickerOpen.value = true + } else { + treePickerLauncher.launch(null) + } + }, + onMoveClick = { + operationType.value = CopyPasteOps.OperationType.Move + if (CopyPasteOps.canUseDirectFileOperations()) { + folderPickerOpen.value = true + } else { + treePickerLauncher.launch(null) + } + }, + onRenameClick = { renameDialogOpen.value = true }, + onDeleteClick = { deleteDialogOpen.value = true }, + onAddToPlaylistClick = { addToPlaylistDialogOpen.value = true }, modifier = Modifier.align(Alignment.BottomCenter) - ) { - FloatingBottomBar( - visible = true, - showCopy = true, - showMove = true, - showDelete = true, - showAddToPlaylist = true, - showRename = selectionManager.isSingleSelection, - onCopyClick = { - operationType.value = CopyPasteOps.OperationType.Copy - if (CopyPasteOps.canUseDirectFileOperations()) { - folderPickerOpen.value = true - } else { - treePickerLauncher.launch(null) - } - }, - onMoveClick = { - operationType.value = CopyPasteOps.OperationType.Move - if (CopyPasteOps.canUseDirectFileOperations()) { - folderPickerOpen.value = true - } else { - treePickerLauncher.launch(null) - } - }, - onRenameClick = { renameDialogOpen.value = true }, - onDeleteClick = { deleteDialogOpen.value = true }, - onAddToPlaylistClick = { addToPlaylistDialogOpen.value = true }, - showRename = selectionManager.isSingleSelection + showRename = selectionManager.isSingleSelection ) } } From 379fe7785913d3dfdbee063b474f97d8cb2defd5 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 20:46:59 +0530 Subject: [PATCH 090/194] Update VideoListScreen.kt --- .../mpvex/ui/browser/videolist/VideoListScreen.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt index e511a6710..a5b8c90db 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt @@ -392,7 +392,6 @@ data class VideoListScreen( onDeleteClick = { deleteDialogOpen.value = true }, onAddToPlaylistClick = { addToPlaylistDialogOpen.value = true }, modifier = Modifier.align(Alignment.BottomCenter) - showRename = selectionManager.isSingleSelection ) } } From 9c5924218ca48c2e3d863e91c29971480119cb1b Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 20:53:03 +0530 Subject: [PATCH 091/194] Update FileSystemBrowserScreen.kt --- .../mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt index 125e73522..15adb3fee 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt @@ -1024,7 +1024,6 @@ fun FileSystemBrowserScreen(path: String? = null) { }, ) } -} /** * Recursively searches for files matching the query in a directory and its subdirectories From 59367e87bc9d850df74079c164318a8f60d62d36 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 20:53:49 +0530 Subject: [PATCH 092/194] Update VideoListScreen.kt --- .../mpvex/ui/browser/videolist/VideoListScreen.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt index a5b8c90db..de9547d1d 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt @@ -533,7 +533,6 @@ data class VideoListScreen( ) } } -} @Composable private fun VideoListContent( From ebfc10c213bf99ccfa5b14b48eb11f1437bdc6e0 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 22:32:11 +0530 Subject: [PATCH 093/194] Update FloatingBottomBar.kt --- .../mpvex/ui/browser/components/FloatingBottomBar.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/components/FloatingBottomBar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/components/FloatingBottomBar.kt index eb7d30c5d..6608bbc7a 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/components/FloatingBottomBar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/components/FloatingBottomBar.kt @@ -36,7 +36,7 @@ import androidx.compose.ui.unit.dp // --- LIQUID GLASS IMPORTS --- import app.marlboroadvance.mpvex.ui.components.liquid.LiquidGlassSurface -import com.kyant.backdrop.backdrops.rememberLayerBackdrop +import com.kyant.backdrop.Backdrop import app.marlboroadvance.mpvex.preferences.LiquidUIPreferences // ---------------------------- From 3de607b86dd0da14a29d300131d1bc37022a6388 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 23:22:57 +0530 Subject: [PATCH 094/194] Update FloatingBottomBar.kt --- .../mpvex/ui/browser/components/FloatingBottomBar.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/components/FloatingBottomBar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/components/FloatingBottomBar.kt index 6608bbc7a..4541af840 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/components/FloatingBottomBar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/components/FloatingBottomBar.kt @@ -38,12 +38,14 @@ import androidx.compose.ui.unit.dp import app.marlboroadvance.mpvex.ui.components.liquid.LiquidGlassSurface import com.kyant.backdrop.Backdrop import app.marlboroadvance.mpvex.preferences.LiquidUIPreferences +import com.kyant.backdrop.backdrops.rememberLayerBackdrop // ---------------------------- @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun FloatingBottomBar( visible: Boolean, + backdrop:Backdrop, showCopy: Boolean = false, showMove: Boolean = false, showRename: Boolean = false, @@ -67,8 +69,6 @@ fun FloatingBottomBar( exit = fadeOut(), modifier = modifier ) { - val backdrop = rememberLayerBackdrop { drawContent() } - val contentRow = @Composable { Row( modifier = Modifier From f73de4a3a633e3a85e1b6f8d895228c85c0b9ae0 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 23:27:11 +0530 Subject: [PATCH 095/194] Update RecentlyPlayedScreen.kt --- .../mpvex/ui/browser/recentlyplayed/RecentlyPlayedScreen.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/recentlyplayed/RecentlyPlayedScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/recentlyplayed/RecentlyPlayedScreen.kt index 9931a020e..d76e9e81d 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/recentlyplayed/RecentlyPlayedScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/recentlyplayed/RecentlyPlayedScreen.kt @@ -184,8 +184,9 @@ object RecentlyPlayedScreen : Screen { onExpandedChange = { isFabExpanded.value = it }, ) - Scaffold( - topBar = { + Scaffold( + containerColor = androidx.compose.ui.graphics.Color.Transparent, + topBar = { BrowserTopBar( title = "Recently Played", isInSelectionMode = selectionManager.isInSelectionMode, From ba92321ee6d4f35bab1cf66587c87df1a4564d32 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 23:30:59 +0530 Subject: [PATCH 096/194] Update VideoListScreen.kt --- .../mpvex/ui/browser/videolist/VideoListScreen.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt index de9547d1d..97d76a75a 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt @@ -23,6 +23,9 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import com.kyant.backdrop.backdrops.layerBackdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.lazy.rememberLazyListState @@ -224,6 +227,8 @@ data class VideoListScreen( var showFloatingBottomBar by remember { mutableStateOf(false) } val animationDuration = 300 + val floatingBarBackdrop = rememberLayerBackdrop { drawContent() } + // Handle selection mode changes with animation LaunchedEffect(selectionManager.isInSelectionMode) { if (selectionManager.isInSelectionMode) { From 4d7393e371d8c5e848b3d92ecf37937118322355 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sun, 8 Mar 2026 23:59:13 +0530 Subject: [PATCH 097/194] Update VideoListScreen.kt --- .../mpvex/ui/browser/videolist/VideoListScreen.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt index 97d76a75a..a4c827313 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt @@ -259,7 +259,8 @@ data class VideoListScreen( } } - Scaffold( + Scaffold( + containerColor = Color.Transparent, topBar = { BrowserTopBar( title = displayFolderName, From b0117d77c31c049cf57e6569ff7bee9b51675cfd Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 00:09:01 +0530 Subject: [PATCH 098/194] Update VideoListScreen.kt --- .../mpvex/ui/browser/videolist/VideoListScreen.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt index a4c827313..3e1edf6d1 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt @@ -343,8 +343,9 @@ data class VideoListScreen( val autoScrollToLastPlayed by browserPreferences.autoScrollToLastPlayed.collectAsState() Box(modifier = Modifier.fillMaxSize()) { - VideoListContent( - folderId = bucketId, + Box(modifier = Modifier.fillMaxSize().layerBackdrop(floatingBarBackdrop)) { + VideoListContent( + folderId = bucketId, videosWithInfo = sortedVideosWithInfo, isLoading = isLoading && videos.isEmpty(), isRefreshing = isRefreshing, From e7738c0f536cc2ef783ec1eec9d8901f882dd29f Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 00:12:09 +0530 Subject: [PATCH 099/194] Update VideoListScreen.kt --- .../mpvex/ui/browser/videolist/VideoListScreen.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt index 3e1edf6d1..941a32370 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/videolist/VideoListScreen.kt @@ -369,10 +369,12 @@ data class VideoListScreen( modifier = Modifier.padding(padding), showFloatingBottomBar = showFloatingBottomBar, ) + } // Floating Material 3 Button Group overlay with animation // Play Store gating is intentionally bypassed here. FloatingBottomBar( + backdrop = floatingBarBackdrop, visible = showFloatingBottomBar, showCopy = true, showMove = true, From 12287118fe01bf639afd57f09d2d0ae61aaf9eb2 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 00:15:07 +0530 Subject: [PATCH 100/194] Update FileSystemBrowserScreen.kt --- .../mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt index 15adb3fee..54dfa5029 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt @@ -26,6 +26,9 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.ui.graphics.Color +import com.kyant.backdrop.backdrops.layerBackdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ViewList import androidx.compose.material.icons.filled.AccountTree From d5314f250f716de3eddd018293a275f5c394e488 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 00:27:23 +0530 Subject: [PATCH 101/194] Update FileSystemBrowserScreen.kt --- .../mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt index 54dfa5029..594300717 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt @@ -490,11 +490,16 @@ fun FileSystemBrowserScreen(path: String? = null) { onExpandedChange = { isFabExpanded.value = it }, ) + val floatingBarBackdrop = rememberLayerBackdrop { drawContent() } + // Main content Box(modifier = Modifier.fillMaxSize()) { Scaffold( + modifier = Modifier.layerBackdrop(floatingBarBackdrop), + containerColor = Color.Transparent, topBar = { if (isSearching) { + // Search mode - show search bar instead of top bar SearchBar( inputField = { From e7c740cd512257b2949cb062a4f55876e54b169c Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 00:32:23 +0530 Subject: [PATCH 102/194] Update FileSystemBrowserScreen.kt --- .../mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt index 594300717..bdd4c42e2 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/browser/filesystem/FileSystemBrowserScreen.kt @@ -889,7 +889,8 @@ fun FileSystemBrowserScreen(path: String? = null) { // Independent Floating Bottom Bar - positioned at absolute bottom // Play Store gating is intentionally bypassed here. - FloatingBottomBar( + FloatingBottomBar( + backdrop = floatingBarBackdrop, visible = showFloatingBottomBar, showCopy = true, showMove = true, From 0578fb59d31c92ef4feaa678f598831f7c996a75 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 01:17:20 +0530 Subject: [PATCH 103/194] Update LiquidUIPreferences.kt --- .../mpvex/preferences/LiquidUIPreferences.kt | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt b/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt index 188b59a64..720106063 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt @@ -2,6 +2,7 @@ package app.marlboroadvance.mpvex.preferences import android.content.Context import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.floatPreferencesKey import androidx.datastore.preferences.core.longPreferencesKey import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.preferencesDataStore @@ -15,29 +16,45 @@ class LiquidUIPreferences(context: Context) { companion object { val LIQUID_UI_ENABLED = booleanPreferencesKey("liquid_ui_enabled") - val LIQUID_BLUR_ENABLED = booleanPreferencesKey("liquid_blur_enabled") - val LIQUID_LENS_ENABLED = booleanPreferencesKey("liquid_lens_enabled") + + // Kyant Backdrop Effect Keys + val LIQUID_BLUR_RADIUS = floatPreferencesKey("liquid_blur_radius") + val LIQUID_REFRACTION_HEIGHT = floatPreferencesKey("liquid_refraction_height") + val LIQUID_REFRACTION_AMOUNT = floatPreferencesKey("liquid_refraction_amount") + val LIQUID_CHROMATIC_ABERRATION = booleanPreferencesKey("liquid_chromatic_aberration") + val LIQUID_DEPTH_EFFECT = booleanPreferencesKey("liquid_depth_effect") val LIQUID_VIBRANCY_ENABLED = booleanPreferencesKey("liquid_vibrancy_enabled") - // Custom Color Keys + // Color & Opacity Keys val LIQUID_TOGGLE_COLOR = longPreferencesKey("liquid_toggle_color") val LIQUID_SLIDER_COLOR = longPreferencesKey("liquid_slider_color") + val LIQUID_TINT_ALPHA = floatPreferencesKey("liquid_tint_alpha") } + // --- STATE FLOWS (With your exact custom defaults!) --- val liquidUIEnabledFlow: Flow = dataStore.data.map { it[LIQUID_UI_ENABLED] ?: false } - val liquidBlurEnabledFlow: Flow = dataStore.data.map { it[LIQUID_BLUR_ENABLED] ?: true } - val liquidLensEnabledFlow: Flow = dataStore.data.map { it[LIQUID_LENS_ENABLED] ?: true } + + val liquidBlurRadiusFlow: Flow = dataStore.data.map { it[LIQUID_BLUR_RADIUS] ?: 0f } + val liquidRefractionHeightFlow: Flow = dataStore.data.map { it[LIQUID_REFRACTION_HEIGHT] ?: 40f } + val liquidRefractionAmountFlow: Flow = dataStore.data.map { it[LIQUID_REFRACTION_AMOUNT] ?: 23f } + + val liquidChromaticAberrationFlow: Flow = dataStore.data.map { it[LIQUID_CHROMATIC_ABERRATION] ?: false } + val liquidDepthEffectFlow: Flow = dataStore.data.map { it[LIQUID_DEPTH_EFFECT] ?: true } val liquidVibrancyEnabledFlow: Flow = dataStore.data.map { it[LIQUID_VIBRANCY_ENABLED] ?: true } - // Default: Green (0xFF4CAF50) for toggles, Blue (0xFF2196F3) for sliders + val liquidTintAlphaFlow: Flow = dataStore.data.map { it[LIQUID_TINT_ALPHA] ?: 0.15f } val liquidToggleColorFlow: Flow = dataStore.data.map { it[LIQUID_TOGGLE_COLOR] ?: 0xFF4CAF50 } val liquidSliderColorFlow: Flow = dataStore.data.map { it[LIQUID_SLIDER_COLOR] ?: 0xFF2196F3 } + // --- SETTERS --- suspend fun setLiquidUIEnabled(enabled: Boolean) { dataStore.edit { it[LIQUID_UI_ENABLED] = enabled } } - suspend fun setBlurEnabled(enabled: Boolean) { dataStore.edit { it[LIQUID_BLUR_ENABLED] = enabled } } - suspend fun setLensEnabled(enabled: Boolean) { dataStore.edit { it[LIQUID_LENS_ENABLED] = enabled } } + suspend fun setBlurRadius(value: Float) { dataStore.edit { it[LIQUID_BLUR_RADIUS] = value } } + suspend fun setRefractionHeight(value: Float) { dataStore.edit { it[LIQUID_REFRACTION_HEIGHT] = value } } + suspend fun setRefractionAmount(value: Float) { dataStore.edit { it[LIQUID_REFRACTION_AMOUNT] = value } } + suspend fun setChromaticAberration(enabled: Boolean) { dataStore.edit { it[LIQUID_CHROMATIC_ABERRATION] = enabled } } + suspend fun setDepthEffect(enabled: Boolean) { dataStore.edit { it[LIQUID_DEPTH_EFFECT] = enabled } } suspend fun setVibrancyEnabled(enabled: Boolean) { dataStore.edit { it[LIQUID_VIBRANCY_ENABLED] = enabled } } - + suspend fun setTintAlpha(value: Float) { dataStore.edit { it[LIQUID_TINT_ALPHA] = value } } suspend fun setToggleColor(color: Long) { dataStore.edit { it[LIQUID_TOGGLE_COLOR] = color } } suspend fun setSliderColor(color: Long) { dataStore.edit { it[LIQUID_SLIDER_COLOR] = color } } } From 1fdd09d89fa1940090c86467da7da1d8a4c3bb0a Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 01:18:10 +0530 Subject: [PATCH 104/194] Update AppearancePreferencesScreen.kt --- .../AppearancePreferencesScreen.kt | 212 ++++++++++-------- 1 file changed, 118 insertions(+), 94 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt index 547be8bb3..b0f61b89a 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt @@ -68,9 +68,6 @@ object AppearancePreferencesScreen : Screen { val preferences = koinInject() val context = LocalContext.current val liquidPreferences = remember { LiquidUIPreferences(context) } - val isLiquidUIEnabled by liquidPreferences.liquidUIEnabledFlow.collectAsState(initial = false) - val toggleColor by liquidPreferences.liquidToggleColorFlow.collectAsState(initial = 0xFF4CAF50) - val sliderColor by liquidPreferences.liquidSliderColorFlow.collectAsState(initial = 0xFF2196F3) val scope = rememberCoroutineScope() val browserPreferences = koinInject() val gesturePreferences = koinInject() @@ -86,6 +83,18 @@ object AppearancePreferencesScreen : Screen { DarkMode.System -> systemDarkTheme } + // --- LIQUID GLASS STATE COLLECTIONS --- + val isLiquidUIEnabled by liquidPreferences.liquidUIEnabledFlow.collectAsState(initial = false) + val toggleColor by liquidPreferences.liquidToggleColorFlow.collectAsState(initial = 0xFF4CAF50) + val sliderColor by liquidPreferences.liquidSliderColorFlow.collectAsState(initial = 0xFF2196F3) + val blurRadius by liquidPreferences.liquidBlurRadiusFlow.collectAsState(initial = 0f) + val refractionHeight by liquidPreferences.liquidRefractionHeightFlow.collectAsState(initial = 40f) + val refractionAmount by liquidPreferences.liquidRefractionAmountFlow.collectAsState(initial = 23f) + val tintAlpha by liquidPreferences.liquidTintAlphaFlow.collectAsState(initial = 0.15f) + val chromaticAberration by liquidPreferences.liquidChromaticAberrationFlow.collectAsState(initial = false) + val depthEffect by liquidPreferences.liquidDepthEffectFlow.collectAsState(initial = true) + val vibrancyEnabled by liquidPreferences.liquidVibrancyEnabledFlow.collectAsState(initial = true) + Scaffold( topBar = { TopAppBar( @@ -132,7 +141,7 @@ object AppearancePreferencesScreen : Screen { PreferenceDivider() - // --- LIQUID GLASS UI MASTER TOGGLE & COLORS --- + // --- LIQUID GLASS UI MASTER TOGGLE --- Row( modifier = Modifier .fillMaxWidth() @@ -152,43 +161,114 @@ object AppearancePreferencesScreen : Screen { } AnimatedVisibility(visible = isLiquidUIEnabled) { - Column(modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) { - Text("Liquid UI Colors", style = MaterialTheme.typography.titleSmall, color = MaterialTheme.colorScheme.primary, modifier = Modifier.padding(bottom = 8.dp)) - - fun parseColorInput(input: String): Long? { - return try { - val formatted = if (input.matches(Regex("^[0-9A-Fa-f]{6,8}$"))) "#$input" else input - val colorInt = android.graphics.Color.parseColor(formatted) - colorInt.toLong() and 0xFFFFFFFFL - } catch (e: Exception) { null } + Column { + Column(modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) { + Text("Liquid UI Colors", style = MaterialTheme.typography.titleSmall, color = MaterialTheme.colorScheme.primary, modifier = Modifier.padding(bottom = 8.dp)) + + fun parseColorInput(input: String): Long? { + return try { + val formatted = if (input.matches(Regex("^[0-9A-Fa-f]{6,8}$"))) "#$input" else input + val colorInt = android.graphics.Color.parseColor(formatted) + colorInt.toLong() and 0xFFFFFFFFL + } catch (e: Exception) { null } + } + + var toggleInputText by remember(toggleColor) { mutableStateOf(String.format("#%06X", 0xFFFFFF and toggleColor.toInt())) } + OutlinedTextField( + value = toggleInputText, + onValueChange = { newValue -> + toggleInputText = newValue + parseColorInput(newValue)?.let { colorLong -> scope.launch { liquidPreferences.setToggleColor(colorLong) } } + }, + label = { Text("Toggle Color") }, + placeholder = { Text("e.g. #FF5733 or blue") }, + modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp), + singleLine = true, + leadingIcon = { Box(modifier = Modifier.size(24.dp).clip(CircleShape).background(Color(toggleColor))) } + ) + + var sliderInputText by remember(sliderColor) { mutableStateOf(String.format("#%06X", 0xFFFFFF and sliderColor.toInt())) } + OutlinedTextField( + value = sliderInputText, + onValueChange = { newValue -> + sliderInputText = newValue + parseColorInput(newValue)?.let { colorLong -> scope.launch { liquidPreferences.setSliderColor(colorLong) } } + }, + label = { Text("Slider Color") }, + placeholder = { Text("e.g. #00FF00 or cyan") }, + modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp), + singleLine = true, + leadingIcon = { Box(modifier = Modifier.size(24.dp).clip(CircleShape).background(Color(sliderColor))) } + ) } - var toggleInputText by remember(toggleColor) { mutableStateOf(String.format("#%06X", 0xFFFFFF and toggleColor.toInt())) } - OutlinedTextField( - value = toggleInputText, - onValueChange = { newValue -> - toggleInputText = newValue - parseColorInput(newValue)?.let { colorLong -> scope.launch { liquidPreferences.setToggleColor(colorLong) } } - }, - label = { Text("Toggle Color") }, - placeholder = { Text("e.g. #FF5733 or blue") }, - modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp), - singleLine = true, - leadingIcon = { Box(modifier = Modifier.size(24.dp).clip(CircleShape).background(Color(toggleColor))) } + PreferenceDivider() + + // --- KYANT BACKDROP LIVE SLIDERS --- + Text("Backdrop Engine Tuning", style = MaterialTheme.typography.titleSmall, color = MaterialTheme.colorScheme.primary, modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) + + SliderPreference( + value = blurRadius, + onValueChange = { v -> scope.launch { liquidPreferences.setBlurRadius(v) } }, + sliderValue = blurRadius, + onSliderValueChange = { v -> scope.launch { liquidPreferences.setBlurRadius(v) } }, + title = { Text("Blur Radius") }, + valueRange = 0f..64f, + summary = { Text("${blurRadius.roundToInt()} px", color = MaterialTheme.colorScheme.outline) } ) - var sliderInputText by remember(sliderColor) { mutableStateOf(String.format("#%06X", 0xFFFFFF and sliderColor.toInt())) } - OutlinedTextField( - value = sliderInputText, - onValueChange = { newValue -> - sliderInputText = newValue - parseColorInput(newValue)?.let { colorLong -> scope.launch { liquidPreferences.setSliderColor(colorLong) } } - }, - label = { Text("Slider Color") }, - placeholder = { Text("e.g. #00FF00 or cyan") }, - modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp), - singleLine = true, - leadingIcon = { Box(modifier = Modifier.size(24.dp).clip(CircleShape).background(Color(sliderColor))) } + SliderPreference( + value = refractionHeight, + onValueChange = { v -> scope.launch { liquidPreferences.setRefractionHeight(v) } }, + sliderValue = refractionHeight, + onSliderValueChange = { v -> scope.launch { liquidPreferences.setRefractionHeight(v) } }, + title = { Text("Refraction Height") }, + valueRange = 0f..100f, + summary = { Text("${refractionHeight.roundToInt()} dp", color = MaterialTheme.colorScheme.outline) } + ) + + SliderPreference( + value = refractionAmount, + onValueChange = { v -> scope.launch { liquidPreferences.setRefractionAmount(v) } }, + sliderValue = refractionAmount, + onSliderValueChange = { v -> scope.launch { liquidPreferences.setRefractionAmount(v) } }, + title = { Text("Refraction Amount") }, + valueRange = 0f..100f, + summary = { Text("${refractionAmount.roundToInt()} dp", color = MaterialTheme.colorScheme.outline) } + ) + + SliderPreference( + value = tintAlpha, + onValueChange = { v -> scope.launch { liquidPreferences.setTintAlpha(v) } }, + sliderValue = tintAlpha, + onSliderValueChange = { v -> scope.launch { liquidPreferences.setTintAlpha(v) } }, + title = { Text("Glass Opacity (Tint)") }, + valueRange = 0.0f..1.0f, + summary = { Text("${(tintAlpha * 100).roundToInt()}%", color = MaterialTheme.colorScheme.outline) } + ) + + PreferenceDivider() + + // --- KYANT BACKDROP LIVE SWITCHES --- + LiquidSwitchPreference( + value = depthEffect, + onValueChange = { v -> scope.launch { liquidPreferences.setDepthEffect(v) } }, + title = { Text("Depth Effect") }, + summary = { Text("Enables 3D lens depth calculation", color = MaterialTheme.colorScheme.outline) } + ) + + LiquidSwitchPreference( + value = chromaticAberration, + onValueChange = { v -> scope.launch { liquidPreferences.setChromaticAberration(v) } }, + title = { Text("Chromatic Aberration") }, + summary = { Text("Enables RGB color-split on glass edges", color = MaterialTheme.colorScheme.outline) } + ) + + LiquidSwitchPreference( + value = vibrancyEnabled, + onValueChange = { v -> scope.launch { liquidPreferences.setVibrancyEnabled(v) } }, + title = { Text("Vibrancy") }, + summary = { Text("Multiplies background saturation by 1.5x", color = MaterialTheme.colorScheme.outline) } ) } } @@ -235,60 +315,4 @@ object AppearancePreferencesScreen : Screen { value = unplayedOldVideoDays.toFloat(), onValueChange = { preferences.unplayedOldVideoDays.set(it.roundToInt()) }, title = { Text(text = stringResource(id = R.string.pref_appearance_unplayed_old_video_days_title)) }, - valueRange = 1f..30f, - summary = { Text(text = stringResource(id = R.string.pref_appearance_unplayed_old_video_days_summary, unplayedOldVideoDays), color = MaterialTheme.colorScheme.outline) }, - onSliderValueChange = { preferences.unplayedOldVideoDays.set(it.roundToInt()) }, - sliderValue = unplayedOldVideoDays.toFloat(), - enabled = showUnplayedOldVideoLabel - ) - - PreferenceDivider() - - val autoScrollToLastPlayed by browserPreferences.autoScrollToLastPlayed.collectAsState() - LiquidSwitchPreference( - value = autoScrollToLastPlayed, - onValueChange = { browserPreferences.autoScrollToLastPlayed.set(it) }, - title = { Text(text = stringResource(R.string.pref_appearance_auto_scroll_title)) }, - summary = { Text(text = stringResource(R.string.pref_appearance_auto_scroll_summary), color = MaterialTheme.colorScheme.outline) } - ) - - PreferenceDivider() - - val watchedThreshold by browserPreferences.watchedThreshold.collectAsState() - SliderPreference( - value = watchedThreshold.toFloat(), - onValueChange = { browserPreferences.watchedThreshold.set(it.roundToInt()) }, - sliderValue = watchedThreshold.toFloat(), - onSliderValueChange = { browserPreferences.watchedThreshold.set(it.roundToInt()) }, - title = { Text(text = stringResource(id = R.string.pref_appearance_watched_threshold_title)) }, - valueRange = 50f..100f, - valueSteps = 9, - summary = { Text(text = stringResource(id = R.string.pref_appearance_watched_threshold_summary, watchedThreshold), color = MaterialTheme.colorScheme.outline) }, - ) - - PreferenceDivider() - - val tapThumbnailToSelect by gesturePreferences.tapThumbnailToSelect.collectAsState() - LiquidSwitchPreference( - value = tapThumbnailToSelect, - onValueChange = { gesturePreferences.tapThumbnailToSelect.set(it) }, - title = { Text(text = stringResource(id = R.string.pref_gesture_tap_thumbnail_to_select_title)) }, - summary = { Text(text = stringResource(id = R.string.pref_gesture_tap_thumbnail_to_select_summary), color = MaterialTheme.colorScheme.outline) } - ) - - PreferenceDivider() - - val showNetworkThumbnails by preferences.showNetworkThumbnails.collectAsState() - LiquidSwitchPreference( - value = showNetworkThumbnails, - onValueChange = { preferences.showNetworkThumbnails.set(it) }, - title = { Text(text = stringResource(id = R.string.pref_appearance_show_network_thumbnails_title)) }, - summary = { Text(text = stringResource(id = R.string.pref_appearance_show_network_thumbnails_summary), color = MaterialTheme.colorScheme.outline) } - ) - } - } - } - } - } - } -} + From 375038b49bc13b3d538c83c762aa7fa3b2c65c5c Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 01:22:45 +0530 Subject: [PATCH 105/194] Update LiquidGlassSurface.kt --- .../components/liquid/LiquidGlassSurface.kt | 48 ++++++++++++++----- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidGlassSurface.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidGlassSurface.kt index bef416f5b..d6ce11ebe 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidGlassSurface.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidGlassSurface.kt @@ -6,10 +6,14 @@ import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp // --- KYANT BACKDROP 2.0.0-ALPHA03 IMPORTS --- @@ -20,35 +24,57 @@ import com.kyant.backdrop.effects.lens import com.kyant.backdrop.effects.vibrancy // -------------------------------------------- +// --- PREFERENCES IMPORT --- +import app.marlboroadvance.mpvex.preferences.LiquidUIPreferences + @Composable fun LiquidGlassSurface( backdrop: Backdrop, modifier: Modifier = Modifier, shape: Shape = RoundedCornerShape(24.dp), - tintColor: Color = Color.White.copy(alpha = 0.15f), + defaultTintColor: Color = Color.White, content: @Composable () -> Unit ) { + val context = LocalContext.current + val liquidPrefs = remember { LiquidUIPreferences(context) } + + // --- LIVE SETTINGS STREAM --- + // The glass will automatically morph whenever these values change! + val blurRadius by liquidPrefs.liquidBlurRadiusFlow.collectAsState(initial = 0f) + val refractionHeight by liquidPrefs.liquidRefractionHeightFlow.collectAsState(initial = 40f) + val refractionAmount by liquidPrefs.liquidRefractionAmountFlow.collectAsState(initial = 23f) + val chromaticAberration by liquidPrefs.liquidChromaticAberrationFlow.collectAsState(initial = false) + val depthEffect by liquidPrefs.liquidDepthEffectFlow.collectAsState(initial = true) + val vibrancyEnabled by liquidPrefs.liquidVibrancyEnabledFlow.collectAsState(initial = true) + val tintAlpha by liquidPrefs.liquidTintAlphaFlow.collectAsState(initial = 0.15f) + if (Build.VERSION.SDK_INT >= 33) { - // ANDROID 13+: Your Exact Custom Lens Parameters! + // ANDROID 13+: Reactive Lens Engine Box( modifier = modifier .drawBackdrop( backdrop = backdrop, shape = { shape }, effects = { - vibrancy() - // Blur is 0, so we can either omit it or explicitly set to 0. - // Omitting it is better for GPU performance! + if (vibrancyEnabled) { + vibrancy() + } + + // Only apply blur if it's greater than 0 to save GPU power + if (blurRadius > 0f) { + blur(blurRadius.dp.toPx()) + } lens( - refractionHeight = 40f.dp.toPx(), - refractionAmount = 23f.dp.toPx(), - depthEffect = true, - chromaticAberration = false + refractionHeight = refractionHeight.dp.toPx(), + refractionAmount = refractionAmount.dp.toPx(), + depthEffect = depthEffect, + chromaticAberration = chromaticAberration ) }, onDrawSurface = { - drawRect(tintColor) + // Apply the exact opacity you picked in the settings slider + drawRect(defaultTintColor.copy(alpha = tintAlpha)) } ) ) { @@ -58,7 +84,7 @@ fun LiquidGlassSurface( // ANDROID 12 AND BELOW: The "Flat Liquid Sheet" Fallback Box( modifier = modifier - .background(tintColor.copy(alpha = 0.4f), shape) + .background(defaultTintColor.copy(alpha = tintAlpha), shape) .border(1.dp, Color.White.copy(alpha = 0.2f), shape) .clip(shape) ) { From 96ba195166dc4fe32f9839c7efa5f974c3df303f Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 01:35:00 +0530 Subject: [PATCH 106/194] Update LiquidUIPreferences.kt --- .../mpvex/preferences/LiquidUIPreferences.kt | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt b/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt index 720106063..604ff8d1b 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt @@ -29,23 +29,30 @@ class LiquidUIPreferences(context: Context) { val LIQUID_TOGGLE_COLOR = longPreferencesKey("liquid_toggle_color") val LIQUID_SLIDER_COLOR = longPreferencesKey("liquid_slider_color") val LIQUID_TINT_ALPHA = floatPreferencesKey("liquid_tint_alpha") + + // Legacy Keys (Kept to prevent LiquidUISettingsScreen from crashing) + val LIQUID_BLUR_ENABLED = booleanPreferencesKey("liquid_blur_enabled") + val LIQUID_LENS_ENABLED = booleanPreferencesKey("liquid_lens_enabled") } - // --- STATE FLOWS (With your exact custom defaults!) --- + // --- STATE FLOWS --- val liquidUIEnabledFlow: Flow = dataStore.data.map { it[LIQUID_UI_ENABLED] ?: false } val liquidBlurRadiusFlow: Flow = dataStore.data.map { it[LIQUID_BLUR_RADIUS] ?: 0f } val liquidRefractionHeightFlow: Flow = dataStore.data.map { it[LIQUID_REFRACTION_HEIGHT] ?: 40f } val liquidRefractionAmountFlow: Flow = dataStore.data.map { it[LIQUID_REFRACTION_AMOUNT] ?: 23f } - val liquidChromaticAberrationFlow: Flow = dataStore.data.map { it[LIQUID_CHROMATIC_ABERRATION] ?: false } val liquidDepthEffectFlow: Flow = dataStore.data.map { it[LIQUID_DEPTH_EFFECT] ?: true } val liquidVibrancyEnabledFlow: Flow = dataStore.data.map { it[LIQUID_VIBRANCY_ENABLED] ?: true } - val liquidTintAlphaFlow: Flow = dataStore.data.map { it[LIQUID_TINT_ALPHA] ?: 0.15f } + val liquidToggleColorFlow: Flow = dataStore.data.map { it[LIQUID_TOGGLE_COLOR] ?: 0xFF4CAF50 } val liquidSliderColorFlow: Flow = dataStore.data.map { it[LIQUID_SLIDER_COLOR] ?: 0xFF2196F3 } + // Legacy Flows + val liquidBlurEnabledFlow: Flow = dataStore.data.map { it[LIQUID_BLUR_ENABLED] ?: true } + val liquidLensEnabledFlow: Flow = dataStore.data.map { it[LIQUID_LENS_ENABLED] ?: true } + // --- SETTERS --- suspend fun setLiquidUIEnabled(enabled: Boolean) { dataStore.edit { it[LIQUID_UI_ENABLED] = enabled } } suspend fun setBlurRadius(value: Float) { dataStore.edit { it[LIQUID_BLUR_RADIUS] = value } } @@ -57,4 +64,8 @@ class LiquidUIPreferences(context: Context) { suspend fun setTintAlpha(value: Float) { dataStore.edit { it[LIQUID_TINT_ALPHA] = value } } suspend fun setToggleColor(color: Long) { dataStore.edit { it[LIQUID_TOGGLE_COLOR] = color } } suspend fun setSliderColor(color: Long) { dataStore.edit { it[LIQUID_SLIDER_COLOR] = color } } + + // Legacy Setters + suspend fun setBlurEnabled(enabled: Boolean) { dataStore.edit { it[LIQUID_BLUR_ENABLED] = enabled } } + suspend fun setLensEnabled(enabled: Boolean) { dataStore.edit { it[LIQUID_LENS_ENABLED] = enabled } } } From 7507a8b8c0724092d62c4b038e9a4d3d800dd585 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 01:37:06 +0530 Subject: [PATCH 107/194] Update AppearancePreferencesScreen.kt --- .../AppearancePreferencesScreen.kt | 62 ++++++++++++++++++- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt index b0f61b89a..682b7fdc5 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt @@ -181,7 +181,7 @@ object AppearancePreferencesScreen : Screen { parseColorInput(newValue)?.let { colorLong -> scope.launch { liquidPreferences.setToggleColor(colorLong) } } }, label = { Text("Toggle Color") }, - placeholder = { Text("e.g. #FF5733 or blue") }, + placeholder = { Text("e.g. #FF5733") }, modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp), singleLine = true, leadingIcon = { Box(modifier = Modifier.size(24.dp).clip(CircleShape).background(Color(toggleColor))) } @@ -195,7 +195,7 @@ object AppearancePreferencesScreen : Screen { parseColorInput(newValue)?.let { colorLong -> scope.launch { liquidPreferences.setSliderColor(colorLong) } } }, label = { Text("Slider Color") }, - placeholder = { Text("e.g. #00FF00 or cyan") }, + placeholder = { Text("e.g. #00FF00") }, modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp), singleLine = true, leadingIcon = { Box(modifier = Modifier.size(24.dp).clip(CircleShape).background(Color(sliderColor))) } @@ -314,5 +314,61 @@ object AppearancePreferencesScreen : Screen { SliderPreference( value = unplayedOldVideoDays.toFloat(), onValueChange = { preferences.unplayedOldVideoDays.set(it.roundToInt()) }, + sliderValue = unplayedOldVideoDays.toFloat(), + onSliderValueChange = { preferences.unplayedOldVideoDays.set(it.roundToInt()) }, title = { Text(text = stringResource(id = R.string.pref_appearance_unplayed_old_video_days_title)) }, - + valueRange = 1f..30f, + enabled = showUnplayedOldVideoLabel, + summary = { Text(text = stringResource(id = R.string.pref_appearance_unplayed_old_video_days_summary, unplayedOldVideoDays), color = MaterialTheme.colorScheme.outline) } + ) + + PreferenceDivider() + + val autoScrollToLastPlayed by browserPreferences.autoScrollToLastPlayed.collectAsState() + LiquidSwitchPreference( + value = autoScrollToLastPlayed, + onValueChange = { browserPreferences.autoScrollToLastPlayed.set(it) }, + title = { Text(text = stringResource(R.string.pref_appearance_auto_scroll_title)) }, + summary = { Text(text = stringResource(R.string.pref_appearance_auto_scroll_summary), color = MaterialTheme.colorScheme.outline) } + ) + + PreferenceDivider() + + val watchedThreshold by browserPreferences.watchedThreshold.collectAsState() + SliderPreference( + value = watchedThreshold.toFloat(), + onValueChange = { browserPreferences.watchedThreshold.set(it.roundToInt()) }, + sliderValue = watchedThreshold.toFloat(), + onSliderValueChange = { browserPreferences.watchedThreshold.set(it.roundToInt()) }, + title = { Text(text = stringResource(id = R.string.pref_appearance_watched_threshold_title)) }, + valueRange = 50f..100f, + valueSteps = 9, + summary = { Text(text = stringResource(id = R.string.pref_appearance_watched_threshold_summary, watchedThreshold), color = MaterialTheme.colorScheme.outline) } + ) + + PreferenceDivider() + + val tapThumbnailToSelect by gesturePreferences.tapThumbnailToSelect.collectAsState() + LiquidSwitchPreference( + value = tapThumbnailToSelect, + onValueChange = { gesturePreferences.tapThumbnailToSelect.set(it) }, + title = { Text(text = stringResource(id = R.string.pref_gesture_tap_thumbnail_to_select_title)) }, + summary = { Text(text = stringResource(id = R.string.pref_gesture_tap_thumbnail_to_select_summary), color = MaterialTheme.colorScheme.outline) } + ) + + PreferenceDivider() + + val showNetworkThumbnails by preferences.showNetworkThumbnails.collectAsState() + LiquidSwitchPreference( + value = showNetworkThumbnails, + onValueChange = { preferences.showNetworkThumbnails.set(it) }, + title = { Text(text = stringResource(id = R.string.pref_appearance_show_network_thumbnails_title)) }, + summary = { Text(text = stringResource(id = R.string.pref_appearance_show_network_thumbnails_summary), color = MaterialTheme.colorScheme.outline) } + ) + } + } + } + } + } + } +} From 7069973c16f24cef10143e1a19b57ea06e6527ee Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 01:38:08 +0530 Subject: [PATCH 108/194] Update LiquidUIPreferences.kt From d921a396b5d4f8280f8d9766172d0d583cddbe21 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 02:55:04 +0530 Subject: [PATCH 109/194] Update Seekbar.kt --- .../ui/player/controls/components/Seekbar.kt | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index 87599ebdb..ee7b165bf 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -238,19 +238,18 @@ fun LiquidSeekbar( liquidColor: Color = Color.Unspecified, modifier: Modifier = Modifier, ) { - // TRACK COLOR: Automatically uses the color from your App Settings val activeColor = if (liquidColor.isSpecified) liquidColor else MaterialTheme.colorScheme.primary - // Smooth transition from 0f (Unpressed) to 1f (Pressed) + // Retained the pressProgress variable in case you want to animate the width/height later val pressProgress by animateFloatAsState( targetValue = if (isScrubbing) 1f else 0f, animationSpec = spring(dampingRatio = 0.6f, stiffness = 800f), label = "pressProgress" ) - // Thumb smoothly squishes and widens - val thumbWidth = androidx.compose.ui.unit.lerp(16.dp, 36.dp, pressProgress) - val thumbHeight = 24.dp + // 2. Make the thumb size 56.dp, 32.dp (this naturally forms a pill with CircleShape) + val thumbWidth = 56.dp + val thumbHeight = 32.dp val trackBackdrop = rememberLayerBackdrop { drawContent() } @@ -320,23 +319,25 @@ fun LiquidSeekbar( shape = { CircleShape }, // Perfectly rounded pill! effects = { vibrancy() - // Blur fades away as you press it - blur(8f.dp.toPx() * (1f - pressProgress)) - // Lens grows from 0 to maximum as you press it + // 1. Blur is completely removed here + + // 3 & 4. Lens effects exactly as requested lens( - 14f.dp.toPx() * pressProgress, - 24f.dp.toPx() * pressProgress + refractionHeight = 30f.dp.toPx(), + refractionAmount = 20f.dp.toPx(), + depthEffect = true ) }, onDrawSurface = { - // THE SECRET: Starts pure white, melts into transparent glass! - drawRect(Color.White.copy(alpha = 1f - pressProgress)) + // 5. Glass opacity: 0 (Pure transparent glass) + drawRect(Color.Transparent) } ) ) } } + // ========================================================================= // ORIGINAL MPVEX SEEKBARS // ========================================================================= From 392787a4a1f80ab7c17ce1eb4b2c1977a0262a3c Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 03:19:01 +0530 Subject: [PATCH 110/194] Update Seekbar.kt --- .../mpvex/ui/player/controls/components/Seekbar.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index ee7b165bf..10e112118 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -315,7 +315,7 @@ fun LiquidSeekbar( .width(thumbWidth) .height(thumbHeight) .drawBackdrop( - backdrop = trackBackdrop, + backdrop = backdrop, shape = { CircleShape }, // Perfectly rounded pill! effects = { vibrancy() From 497d3052f1606a592e200b478e534c277fcc726e Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 03:42:54 +0530 Subject: [PATCH 111/194] Update Seekbar.kt --- .../mpvex/ui/player/controls/components/Seekbar.kt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index 10e112118..a2e45ea15 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -242,13 +242,13 @@ fun LiquidSeekbar( // Retained the pressProgress variable in case you want to animate the width/height later val pressProgress by animateFloatAsState( - targetValue = if (isScrubbing) 1f else 0f, + targetValue = if (isScrubbing) 0f else 0f, animationSpec = spring(dampingRatio = 0.6f, stiffness = 800f), label = "pressProgress" ) // 2. Make the thumb size 56.dp, 32.dp (this naturally forms a pill with CircleShape) - val thumbWidth = 56.dp + val thumbWidth = androidx.compose.ui.unit.lerp(56.dp, pressProgress) val thumbHeight = 32.dp val trackBackdrop = rememberLayerBackdrop { drawContent() } @@ -319,18 +319,19 @@ fun LiquidSeekbar( shape = { CircleShape }, // Perfectly rounded pill! effects = { vibrancy() - // 1. Blur is completely removed here + // Blur fades away as you press it + blur(8f.dp.toPx() * (0f - pressProgress)) // 3 & 4. Lens effects exactly as requested lens( - refractionHeight = 30f.dp.toPx(), - refractionAmount = 20f.dp.toPx(), + 30f.dp.toPx() * pressProgress, + 20f.dp.toPx() * pressProgress depthEffect = true ) }, onDrawSurface = { // 5. Glass opacity: 0 (Pure transparent glass) - drawRect(Color.Transparent) + drawRect(Color.White.copy(alpha = 1f - pressProgress)) } ) ) From 81f5e649dbc4ffaa975fd243b12fb51563039c92 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 04:04:01 +0530 Subject: [PATCH 112/194] Update Seekbar.kt --- .../mpvex/ui/player/controls/components/Seekbar.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index a2e45ea15..f57f7c717 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -315,7 +315,7 @@ fun LiquidSeekbar( .width(thumbWidth) .height(thumbHeight) .drawBackdrop( - backdrop = backdrop, + backdrop = rememberBackdrop, shape = { CircleShape }, // Perfectly rounded pill! effects = { vibrancy() From 2c3b7f2754451c1194f2a2b797d2b3a172e50737 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 04:10:55 +0530 Subject: [PATCH 113/194] Update Seekbar.kt --- .../mpvex/ui/player/controls/components/Seekbar.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index f57f7c717..1f9be937a 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -64,6 +64,7 @@ import com.kyant.backdrop.drawBackdrop import com.kyant.backdrop.effects.blur import com.kyant.backdrop.effects.lens import com.kyant.backdrop.effects.vibrancy +import com.kyant.backdrop.backdrops.rememberBackdrop // ---------------------------------------------------- import app.marlboroadvance.mpvex.ui.player.controls.LocalPlayerButtonsClickEvent @@ -251,7 +252,7 @@ fun LiquidSeekbar( val thumbWidth = androidx.compose.ui.unit.lerp(56.dp, pressProgress) val thumbHeight = 32.dp - val trackBackdrop = rememberLayerBackdrop { drawContent() } + val trackBackdrop = rememberlayerBackdrop { drawContent() } androidx.compose.foundation.layout.BoxWithConstraints( modifier = modifier @@ -315,7 +316,7 @@ fun LiquidSeekbar( .width(thumbWidth) .height(thumbHeight) .drawBackdrop( - backdrop = rememberBackdrop, + backdrop = backdrop, shape = { CircleShape }, // Perfectly rounded pill! effects = { vibrancy() @@ -326,9 +327,7 @@ fun LiquidSeekbar( lens( 30f.dp.toPx() * pressProgress, 20f.dp.toPx() * pressProgress - depthEffect = true ) - }, onDrawSurface = { // 5. Glass opacity: 0 (Pure transparent glass) drawRect(Color.White.copy(alpha = 1f - pressProgress)) From c72be10d99c808de9f9e29b599cc7db714954fc1 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 04:12:33 +0530 Subject: [PATCH 114/194] Update Seekbar.kt --- .../mpvex/ui/player/controls/components/Seekbar.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index 1f9be937a..86acaf3f8 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -322,12 +322,12 @@ fun LiquidSeekbar( vibrancy() // Blur fades away as you press it blur(8f.dp.toPx() * (0f - pressProgress)) - // 3 & 4. Lens effects exactly as requested lens( 30f.dp.toPx() * pressProgress, 20f.dp.toPx() * pressProgress ) + }, onDrawSurface = { // 5. Glass opacity: 0 (Pure transparent glass) drawRect(Color.White.copy(alpha = 1f - pressProgress)) From e3b6806b1a1582e81107a1bba9fea86e5896d987 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 04:27:07 +0530 Subject: [PATCH 115/194] Update Seekbar.kt --- .../mpvex/ui/player/controls/components/Seekbar.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index 86acaf3f8..737fe2118 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -64,7 +64,8 @@ import com.kyant.backdrop.drawBackdrop import com.kyant.backdrop.effects.blur import com.kyant.backdrop.effects.lens import com.kyant.backdrop.effects.vibrancy -import com.kyant.backdrop.backdrops.rememberBackdrop +import com.kyant.backdrop.Backdrop +import com.kyant.backdrop.rememberBackdrop // ---------------------------------------------------- import app.marlboroadvance.mpvex.ui.player.controls.LocalPlayerButtonsClickEvent @@ -252,7 +253,7 @@ fun LiquidSeekbar( val thumbWidth = androidx.compose.ui.unit.lerp(56.dp, pressProgress) val thumbHeight = 32.dp - val trackBackdrop = rememberlayerBackdrop { drawContent() } + val trackBackdrop = rememberLayerBackdrop { drawContent() } androidx.compose.foundation.layout.BoxWithConstraints( modifier = modifier From e3b59df9ae2e19d0ce3173aea35ac540d79c1a4e Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 04:30:24 +0530 Subject: [PATCH 116/194] Update Seekbar.kt --- .../mpvex/ui/player/controls/components/Seekbar.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index 737fe2118..b5cf7e2ae 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -65,7 +65,7 @@ import com.kyant.backdrop.effects.blur import com.kyant.backdrop.effects.lens import com.kyant.backdrop.effects.vibrancy import com.kyant.backdrop.Backdrop -import com.kyant.backdrop.rememberBackdrop +import com.kyant.backdrop.backdrops.rememberBackdrop // ---------------------------------------------------- import app.marlboroadvance.mpvex.ui.player.controls.LocalPlayerButtonsClickEvent From cee909bb92580b4ae267172ce8edb62dbd95f95c Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 04:38:55 +0530 Subject: [PATCH 117/194] Update Seekbar.kt --- .../mpvex/ui/player/controls/components/Seekbar.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index b5cf7e2ae..db06e7cfb 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -317,7 +317,7 @@ fun LiquidSeekbar( .width(thumbWidth) .height(thumbHeight) .drawBackdrop( - backdrop = backdrop, + backdrop = rememberCombinedBackdrop(backdrop, trackBackdrop), shape = { CircleShape }, // Perfectly rounded pill! effects = { vibrancy() From 8703f51f605911c08408b5c394d9a8d2ef0cde07 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 10:29:57 +0530 Subject: [PATCH 118/194] Update Seekbar.kt --- .../ui/player/controls/components/Seekbar.kt | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index db06e7cfb..a0cd79e3e 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -233,7 +233,7 @@ fun SeekbarWithTimers( fun LiquidSeekbar( position: Float, duration: Float, - chapters: ImmutableList, + chapters: List, // Assuming Segment is your data class isScrubbing: Boolean = false, loopStart: Float? = null, loopEnd: Float? = null, @@ -242,18 +242,17 @@ fun LiquidSeekbar( ) { val activeColor = if (liquidColor.isSpecified) liquidColor else MaterialTheme.colorScheme.primary - // Retained the pressProgress variable in case you want to animate the width/height later val pressProgress by animateFloatAsState( - targetValue = if (isScrubbing) 0f else 0f, + targetValue = if (isScrubbing) 1f else 0f, // Changed to 1f when scrubbing to trigger effects animationSpec = spring(dampingRatio = 0.6f, stiffness = 800f), label = "pressProgress" ) - // 2. Make the thumb size 56.dp, 32.dp (this naturally forms a pill with CircleShape) - val thumbWidth = androidx.compose.ui.unit.lerp(56.dp, pressProgress) + val thumbWidth = androidx.compose.ui.unit.lerp(32.dp, 56.dp, pressProgress) val thumbHeight = 32.dp - val trackBackdrop = rememberLayerBackdrop { drawContent() } + // 1. Correct Backdrop initialization (No lambda in brackets) + val backdrop = rememberLayerBackdrop() androidx.compose.foundation.layout.BoxWithConstraints( modifier = modifier @@ -268,9 +267,10 @@ fun LiquidSeekbar( Canvas( modifier = Modifier .fillMaxWidth() - .height(8.dp) // Perfect Kyant track thickness - .clip(CircleShape) // Perfectly round track edges - .layerBackdrop(trackBackdrop) // Feed track colors to the glass thumb + .height(8.dp) + .clip(CircleShape) + // 2. Set this Canvas as the source for 'backdrop' + .layerBackdrop(backdrop) ) { val chapterGapHalf = 1.dp.toPx() val chapterGaps = chapters @@ -294,44 +294,46 @@ fun LiquidSeekbar( } } - // 1. Draw entire track (Unplayed) at 30% alpha of your custom color + // Draw Unplayed Track drawRangeWithGaps(0f, size.width, chapterGaps, activeColor.copy(alpha = 0.3f)) - // 2. Overwrite the played portion with 100% of your custom color + // Draw Played Track if (playedPx > 0) { drawRangeWithGaps(0f, playedPx, chapterGaps, activeColor) } + // Loop Markers if (loopStart != null || loopEnd != null) { val loopColor = Color(0xFFFFB300) val markerWidth = 2.dp.toPx() - if (loopStart != null) drawLine(color = loopColor, start = Offset((loopStart / duration).coerceIn(0f, 1f) * size.width, 0f), end = Offset((loopStart / duration).coerceIn(0f, 1f) * size.width, size.height), strokeWidth = markerWidth) - if (loopEnd != null) drawLine(color = loopColor, start = Offset((loopEnd / duration).coerceIn(0f, 1f) * size.width, 0f), end = Offset((loopEnd / duration).coerceIn(0f, 1f) * size.width, size.height), strokeWidth = markerWidth) - if (loopStart != null && loopEnd != null) drawRect(color = loopColor.copy(alpha = 0.3f), topLeft = Offset((minOf(loopStart, loopEnd) / duration).coerceIn(0f, 1f) * size.width, 0f), size = Size((maxOf(loopStart, loopEnd) / duration).coerceIn(0f, 1f) * size.width - (minOf(loopStart, loopEnd) / duration).coerceIn(0f, 1f) * size.width, size.height)) + if (loopStart != null) { + val x = (loopStart / duration).coerceIn(0f, 1f) * size.width + drawLine(color = loopColor, start = Offset(x, 0f), end = Offset(x, size.height), strokeWidth = markerWidth) + } + if (loopEnd != null) { + val x = (loopEnd / duration).coerceIn(0f, 1f) * size.width + drawLine(color = loopColor, start = Offset(x, 0f), end = Offset(x, size.height), strokeWidth = markerWidth) + } } } Box( modifier = Modifier - .offset { androidx.compose.ui.unit.IntOffset((playedPx - (thumbWidth.toPx() / 2)).roundToInt(), 0) } - .width(thumbWidth) - .height(thumbHeight) + .offset { IntOffset((playedPx - (thumbWidth.toPx() / 2)).roundToInt(), 0) } + .size(width = thumbWidth, height = thumbHeight) .drawBackdrop( - backdrop = rememberCombinedBackdrop(backdrop, trackBackdrop), - shape = { CircleShape }, // Perfectly rounded pill! + // 3. Use the defined backdrop variable + backdrop = backdrop, + shape = { CircleShape }, effects = { vibrancy() - // Blur fades away as you press it - blur(8f.dp.toPx() * (0f - pressProgress)) - // 3 & 4. Lens effects exactly as requested - lens( - 30f.dp.toPx() * pressProgress, - 20f.dp.toPx() * pressProgress - ) + // 4. Corrected effects logic for alpha03 + blur(8.dp.toPx() * (1f - pressProgress)) + lens(30.dp.toPx() * pressProgress) }, onDrawSurface = { - // 5. Glass opacity: 0 (Pure transparent glass) - drawRect(Color.White.copy(alpha = 1f - pressProgress)) + // Glass appearance + drawRect(Color.White.copy(alpha = 0.1f * (1f - pressProgress))) } ) ) From 12c7ddd65a06778049138775bc710770a73087a1 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 12:24:08 +0530 Subject: [PATCH 119/194] Update Seekbar.kt --- .../mpvex/ui/player/controls/components/Seekbar.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index a0cd79e3e..b3ee7e91b 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -329,7 +329,9 @@ fun LiquidSeekbar( vibrancy() // 4. Corrected effects logic for alpha03 blur(8.dp.toPx() * (1f - pressProgress)) - lens(30.dp.toPx() * pressProgress) + lens( + refractionHeight = 30f.dp.toPx(), + refractionAmount = 20f.dp.toPx(), }, onDrawSurface = { // Glass appearance From 12202ed9533254bcd5b2e9f7927b937f4117e773 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 12:34:42 +0530 Subject: [PATCH 120/194] Update Seekbar.kt --- .../mpvex/ui/player/controls/components/Seekbar.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index b3ee7e91b..603fd6a02 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -319,8 +319,9 @@ fun LiquidSeekbar( Box( modifier = Modifier - .offset { IntOffset((playedPx - (thumbWidth.toPx() / 2)).roundToInt(), 0) } - .size(width = thumbWidth, height = thumbHeight) + .offset { androidx.compose.ui.unit.IntOffset((playedPx - (thumbWidth.toPx() / 2)).roundToInt(), 0) } + .width(thumbWidth) + .height(thumbHeight) .drawBackdrop( // 3. Use the defined backdrop variable backdrop = backdrop, @@ -330,8 +331,8 @@ fun LiquidSeekbar( // 4. Corrected effects logic for alpha03 blur(8.dp.toPx() * (1f - pressProgress)) lens( - refractionHeight = 30f.dp.toPx(), - refractionAmount = 20f.dp.toPx(), + refractionHeight = 30f.dp.toPx(), *pressProgress + refractionAmount = 20f.dp.toPx(), *pressProgress }, onDrawSurface = { // Glass appearance From 2e10f7c68a12104e7eec1ffb5ff07ed7190f35b6 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 12:35:42 +0530 Subject: [PATCH 121/194] Update Seekbar.kt --- .../mpvex/ui/player/controls/components/Seekbar.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index 603fd6a02..1718bd8dc 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -331,8 +331,8 @@ fun LiquidSeekbar( // 4. Corrected effects logic for alpha03 blur(8.dp.toPx() * (1f - pressProgress)) lens( - refractionHeight = 30f.dp.toPx(), *pressProgress - refractionAmount = 20f.dp.toPx(), *pressProgress + refractionHeight = 30f.dp.toPx(), * pressProgress + refractionAmount = 20f.dp.toPx(), * pressProgress }, onDrawSurface = { // Glass appearance From 96f42a443ecdfa424068b39146af738ab5e07b86 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 12:42:13 +0530 Subject: [PATCH 122/194] Update Seekbar.kt --- .../mpvex/ui/player/controls/components/Seekbar.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index 1718bd8dc..d75b1424c 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -333,6 +333,7 @@ fun LiquidSeekbar( lens( refractionHeight = 30f.dp.toPx(), * pressProgress refractionAmount = 20f.dp.toPx(), * pressProgress + ) }, onDrawSurface = { // Glass appearance From da5b90903a54c1bb70ed8bb57da91cfdfecdbff0 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 12:47:08 +0530 Subject: [PATCH 123/194] Update Seekbar.kt --- .../mpvex/ui/player/controls/components/Seekbar.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index d75b1424c..d15af4edb 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -331,8 +331,8 @@ fun LiquidSeekbar( // 4. Corrected effects logic for alpha03 blur(8.dp.toPx() * (1f - pressProgress)) lens( - refractionHeight = 30f.dp.toPx(), * pressProgress - refractionAmount = 20f.dp.toPx(), * pressProgress + refractionHeight = 30f.dp.toPx() * pressProgress, + refractionAmount = 20f.dp.toPx() * pressProgress, ) }, onDrawSurface = { From 6431480c1d8db7076a8df59481106bdccf633a6f Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 14:54:49 +0530 Subject: [PATCH 124/194] Update Seekbar.kt --- .../ui/player/controls/components/Seekbar.kt | 232 +++++++++++------- 1 file changed, 138 insertions(+), 94 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index d15af4edb..beff090b8 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -230,119 +230,163 @@ fun SeekbarWithTimers( // NEW: LIQUID SEEKBAR (Kyant Lens & Fade Mechanics) // ========================================================================= @Composable -fun LiquidSeekbar( - position: Float, - duration: Float, - chapters: List, // Assuming Segment is your data class - isScrubbing: Boolean = false, - loopStart: Float? = null, - loopEnd: Float? = null, - liquidColor: Color = Color.Unspecified, - modifier: Modifier = Modifier, +fun LiquidSlider( + value: () -> Float, + onValueChange: (Float) -> Unit, + valueRange: ClosedFloatingPointRange, + visibilityThreshold: Float, + backdrop: Backdrop, + modifier: Modifier = Modifier ) { val activeColor = if (liquidColor.isSpecified) liquidColor else MaterialTheme.colorScheme.primary - val pressProgress by animateFloatAsState( - targetValue = if (isScrubbing) 1f else 0f, // Changed to 1f when scrubbing to trigger effects - animationSpec = spring(dampingRatio = 0.6f, stiffness = 800f), - label = "pressProgress" - ) - - val thumbWidth = androidx.compose.ui.unit.lerp(32.dp, 56.dp, pressProgress) - val thumbHeight = 32.dp - - // 1. Correct Backdrop initialization (No lambda in brackets) - val backdrop = rememberLayerBackdrop() + val trackBackdrop = rememberLayerBackdrop() - androidx.compose.foundation.layout.BoxWithConstraints( - modifier = modifier - .fillMaxWidth() - .height(32.dp), + BoxWithConstraints( + modifier.fillMaxWidth(), contentAlignment = Alignment.CenterStart ) { - val trackWidthPx = constraints.maxWidth.toFloat() - val progress = if (duration > 0f) (position / duration).coerceIn(0f, 1f) else 0f - val playedPx = trackWidthPx * progress - - Canvas( - modifier = Modifier - .fillMaxWidth() - .height(8.dp) - .clip(CircleShape) - // 2. Set this Canvas as the source for 'backdrop' - .layerBackdrop(backdrop) - ) { - val chapterGapHalf = 1.dp.toPx() - val chapterGaps = chapters - .map { (it.start / duration).coerceIn(0f, 1f) * size.width } - .filter { it > 0f && it < size.width } - .map { x -> (x - chapterGapHalf) to (x + chapterGapHalf) } - - fun drawRangeWithGaps(rangeStart: Float, rangeEnd: Float, gaps: List>, color: Color) { - if (rangeEnd <= rangeStart) return - val relevantGaps = gaps.filter { (gStart, gEnd) -> gEnd > rangeStart && gStart < rangeEnd }.sortedBy { it.first } - var currentPos = rangeStart - for ((gStart, gEnd) in relevantGaps) { - val segmentEnd = gStart.coerceAtMost(rangeEnd) - if (segmentEnd > currentPos) { - drawRect(color, topLeft = Offset(currentPos, 0f), size = Size(segmentEnd - currentPos, size.height)) + val trackWidth = constraints.maxWidth + + val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr + val animationScope = rememberCoroutineScope() + var didDrag by remember { mutableStateOf(false) } + val dampedDragAnimation = remember(animationScope) { + DampedDragAnimation( + animationScope = animationScope, + initialValue = value(), + valueRange = valueRange, + visibilityThreshold = visibilityThreshold, + initialScale = 1f, + pressedScale = 1.5f, + onDragStarted = {}, + onDragStopped = { + if (didDrag) { + onValueChange(targetValue) + } + }, + onDrag = { _, dragAmount -> + if (!didDrag) { + didDrag = dragAmount.x != 0f } - currentPos = gEnd.coerceAtLeast(currentPos) + val delta = (valueRange.endInclusive - valueRange.start) * (dragAmount.x / trackWidth) + onValueChange( + if (isLtr) (targetValue + delta).coerceIn(valueRange) + else (targetValue - delta).coerceIn(valueRange) + ) } - if (currentPos < rangeEnd) { - drawRect(color, topLeft = Offset(currentPos, 0f), size = Size(rangeEnd - currentPos, size.height)) + ) + } + LaunchedEffect(dampedDragAnimation) { + snapshotFlow { value() } + .collectLatest { value -> + if (dampedDragAnimation.targetValue != value) { + dampedDragAnimation.updateValue(value) + } } - } - - // Draw Unplayed Track - drawRangeWithGaps(0f, size.width, chapterGaps, activeColor.copy(alpha = 0.3f)) - - // Draw Played Track - if (playedPx > 0) { - drawRangeWithGaps(0f, playedPx, chapterGaps, activeColor) - } + } - // Loop Markers - if (loopStart != null || loopEnd != null) { - val loopColor = Color(0xFFFFB300) - val markerWidth = 2.dp.toPx() - if (loopStart != null) { - val x = (loopStart / duration).coerceIn(0f, 1f) * size.width - drawLine(color = loopColor, start = Offset(x, 0f), end = Offset(x, size.height), strokeWidth = markerWidth) - } - if (loopEnd != null) { - val x = (loopEnd / duration).coerceIn(0f, 1f) * size.width - drawLine(color = loopColor, start = Offset(x, 0f), end = Offset(x, size.height), strokeWidth = markerWidth) - } - } + Box(Modifier.layerBackdrop(trackBackdrop)) { + Box( + Modifier + .clip(Capsule()) + .pointerInput(animationScope) { + detectTapGestures { position -> + val delta = (valueRange.endInclusive - valueRange.start) * (position.x / trackWidth) + val targetValue = + (if (isLtr) valueRange.start + delta + else valueRange.endInclusive - delta) + .coerceIn(valueRange) + dampedDragAnimation.animateToValue(targetValue) + onValueChange(targetValue) + } + } + .height(6f.dp) + .fillMaxWidth() + ) + + Box( + Modifier + .clip(Capsule()) + .height(6f.dp) + .layout { measurable, constraints -> + val placeable = measurable.measure(constraints) + val width = (constraints.maxWidth * dampedDragAnimation.progress).fastRoundToInt() + layout(width, placeable.height) { + placeable.place(0, 0) + } + } + ) } - + Box( - modifier = Modifier - .offset { androidx.compose.ui.unit.IntOffset((playedPx - (thumbWidth.toPx() / 2)).roundToInt(), 0) } - .width(thumbWidth) - .height(thumbHeight) + Modifier + .graphicsLayer { + translationX = + (-size.width / 2f + trackWidth * dampedDragAnimation.progress) + .fastCoerceIn(-size.width / 4f, trackWidth - size.width * 3f / 4f) * if (isLtr) 1f else -1f + } + .then(dampedDragAnimation.modifier) .drawBackdrop( - // 3. Use the defined backdrop variable - backdrop = backdrop, - shape = { CircleShape }, + backdrop = rememberCombinedBackdrop( + backdrop, + rememberBackdrop(trackBackdrop) { drawBackdrop -> + val progress = dampedDragAnimation.pressProgress + val scaleX = lerp(2f / 3f, 1f, progress) + val scaleY = lerp(0f, 1f, progress) + scale(scaleX, scaleY) { + drawBackdrop() + } + } + ), + shape = { Capsule() }, effects = { - vibrancy() - // 4. Corrected effects logic for alpha03 - blur(8.dp.toPx() * (1f - pressProgress)) + val progress = dampedDragAnimation.pressProgress + blur(8f.dp.toPx() * (1f - progress)) lens( - refractionHeight = 30f.dp.toPx() * pressProgress, - refractionAmount = 20f.dp.toPx() * pressProgress, - ) + 10f.dp.toPx() * progress, + 14f.dp.toPx() * progress, + chromaticAberration = true + ) + }, + highlight = { + val progress = dampedDragAnimation.pressProgress + Highlight.Ambient.copy( + width = Highlight.Ambient.width / 1.5f, + blurRadius = Highlight.Ambient.blurRadius / 1.5f, + alpha = progress + ) + }, + shadow = { + Shadow( + radius = 4f.dp, + color = Color.Black.copy(alpha = 0.05f) + ) + }, + innerShadow = { + val progress = dampedDragAnimation.pressProgress + InnerShadow( + radius = 4f.dp * progress, + alpha = progress + ) + }, + layerBlock = { + scaleX = dampedDragAnimation.scaleX + scaleY = dampedDragAnimation.scaleY + val velocity = dampedDragAnimation.velocity / 10f + scaleX /= 1f - (velocity * 0.75f).fastCoerceIn(-0.2f, 0.2f) + scaleY *= 1f - (velocity * 0.25f).fastCoerceIn(-0.2f, 0.2f) }, onDrawSurface = { - // Glass appearance - drawRect(Color.White.copy(alpha = 0.1f * (1f - pressProgress))) + val progress = dampedDragAnimation.pressProgress + drawRect(Color.White.copy(alpha = 1f - progress)) } ) - ) - } -} + .size(40f.dp, 24f.dp) + ) + } + } + // ========================================================================= From 08369a9f1d3827366041cfca4238a50720184342 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 16:38:29 +0530 Subject: [PATCH 125/194] Update Seekbar.kt --- .../ui/player/controls/components/Seekbar.kt | 221 +++++++----------- 1 file changed, 85 insertions(+), 136 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index beff090b8..bb5207019 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -227,165 +227,114 @@ fun SeekbarWithTimers( } // ========================================================================= -// NEW: LIQUID SEEKBAR (Kyant Lens & Fade Mechanics) +// UPGRADED LIQUID SEEKBAR (Kyant 2.0.0-alpha03 + Squishy Physics!) // ========================================================================= @Composable -fun LiquidSlider( - value: () -> Float, - onValueChange: (Float) -> Unit, - valueRange: ClosedFloatingPointRange, - visibilityThreshold: Float, - backdrop: Backdrop, - modifier: Modifier = Modifier +fun LiquidSeekbar( + position: Float, + duration: Float, + chapters: ImmutableList, + isScrubbing: Boolean = false, + loopStart: Float? = null, + loopEnd: Float? = null, + liquidColor: Color = Color.Unspecified, + modifier: Modifier = Modifier, ) { val activeColor = if (liquidColor.isSpecified) liquidColor else MaterialTheme.colorScheme.primary - val trackBackdrop = rememberLayerBackdrop() + // The Bouncy Physics Engine! + val pressProgress by animateFloatAsState( + targetValue = if (isScrubbing) 1f else 0f, + animationSpec = spring(dampingRatio = 0.5f, stiffness = 400f), + label = "pressProgress" + ) + + // Thumb smoothly squishes and widens when you drag it, just like the one you found! + val thumbWidth = androidx.compose.ui.unit.lerp(56.dp, 68.dp, pressProgress) + val thumbHeight = androidx.compose.ui.unit.lerp(32.dp, 24.dp, pressProgress) + + val trackBackdrop = rememberLayerBackdrop { drawContent() } - BoxWithConstraints( - modifier.fillMaxWidth(), + androidx.compose.foundation.layout.BoxWithConstraints( + modifier = modifier + .fillMaxWidth() + .height(48.dp), contentAlignment = Alignment.CenterStart ) { - val trackWidth = constraints.maxWidth - - val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr - val animationScope = rememberCoroutineScope() - var didDrag by remember { mutableStateOf(false) } - val dampedDragAnimation = remember(animationScope) { - DampedDragAnimation( - animationScope = animationScope, - initialValue = value(), - valueRange = valueRange, - visibilityThreshold = visibilityThreshold, - initialScale = 1f, - pressedScale = 1.5f, - onDragStarted = {}, - onDragStopped = { - if (didDrag) { - onValueChange(targetValue) - } - }, - onDrag = { _, dragAmount -> - if (!didDrag) { - didDrag = dragAmount.x != 0f + val trackWidthPx = constraints.maxWidth.toFloat() + val progress = if (duration > 0f) (position / duration).coerceIn(0f, 1f) else 0f + val playedPx = trackWidthPx * progress + + Canvas( + modifier = Modifier + .fillMaxWidth() + .height(8.dp) + .clip(CircleShape) + .layerBackdrop(trackBackdrop) + ) { + val chapterGapHalf = 1.dp.toPx() + val chapterGaps = chapters + .map { (it.start / duration).coerceIn(0f, 1f) * size.width } + .filter { it > 0f && it < size.width } + .map { x -> (x - chapterGapHalf) to (x + chapterGapHalf) } + + fun drawRangeWithGaps(rangeStart: Float, rangeEnd: Float, gaps: List>, color: Color) { + if (rangeEnd <= rangeStart) return + val relevantGaps = gaps.filter { (gStart, gEnd) -> gEnd > rangeStart && gStart < rangeEnd }.sortedBy { it.first } + var currentPos = rangeStart + for ((gStart, gEnd) in relevantGaps) { + val segmentEnd = gStart.coerceAtMost(rangeEnd) + if (segmentEnd > currentPos) { + drawRect(color, topLeft = Offset(currentPos, 0f), size = Size(segmentEnd - currentPos, size.height)) } - val delta = (valueRange.endInclusive - valueRange.start) * (dragAmount.x / trackWidth) - onValueChange( - if (isLtr) (targetValue + delta).coerceIn(valueRange) - else (targetValue - delta).coerceIn(valueRange) - ) + currentPos = gEnd.coerceAtLeast(currentPos) } - ) - } - LaunchedEffect(dampedDragAnimation) { - snapshotFlow { value() } - .collectLatest { value -> - if (dampedDragAnimation.targetValue != value) { - dampedDragAnimation.updateValue(value) - } + if (currentPos < rangeEnd) { + drawRect(color, topLeft = Offset(currentPos, 0f), size = Size(rangeEnd - currentPos, size.height)) } - } - - Box(Modifier.layerBackdrop(trackBackdrop)) { - Box( - Modifier - .clip(Capsule()) - .pointerInput(animationScope) { - detectTapGestures { position -> - val delta = (valueRange.endInclusive - valueRange.start) * (position.x / trackWidth) - val targetValue = - (if (isLtr) valueRange.start + delta - else valueRange.endInclusive - delta) - .coerceIn(valueRange) - dampedDragAnimation.animateToValue(targetValue) - onValueChange(targetValue) - } - } - .height(6f.dp) - .fillMaxWidth() - ) + } + + drawRangeWithGaps(0f, size.width, chapterGaps, activeColor.copy(alpha = 0.3f)) + if (playedPx > 0) { + drawRangeWithGaps(0f, playedPx, chapterGaps, activeColor) + } - Box( - Modifier - .clip(Capsule()) - .height(6f.dp) - .layout { measurable, constraints -> - val placeable = measurable.measure(constraints) - val width = (constraints.maxWidth * dampedDragAnimation.progress).fastRoundToInt() - layout(width, placeable.height) { - placeable.place(0, 0) - } - } - ) + if (loopStart != null || loopEnd != null) { + val loopColor = Color(0xFFFFB300) + val markerWidth = 2.dp.toPx() + if (loopStart != null) drawLine(color = loopColor, start = Offset((loopStart / duration).coerceIn(0f, 1f) * size.width, 0f), end = Offset((loopStart / duration).coerceIn(0f, 1f) * size.width, size.height), strokeWidth = markerWidth) + if (loopEnd != null) drawLine(color = loopColor, start = Offset((loopEnd / duration).coerceIn(0f, 1f) * size.width, 0f), end = Offset((loopEnd / duration).coerceIn(0f, 1f) * size.width, size.height), strokeWidth = markerWidth) + if (loopStart != null && loopEnd != null) drawRect(color = loopColor.copy(alpha = 0.3f), topLeft = Offset((minOf(loopStart, loopEnd) / duration).coerceIn(0f, 1f) * size.width, 0f), size = Size((maxOf(loopStart, loopEnd) / duration).coerceIn(0f, 1f) * size.width - (minOf(loopStart, loopEnd) / duration).coerceIn(0f, 1f) * size.width, size.height)) + } } - + Box( - Modifier - .graphicsLayer { - translationX = - (-size.width / 2f + trackWidth * dampedDragAnimation.progress) - .fastCoerceIn(-size.width / 4f, trackWidth - size.width * 3f / 4f) * if (isLtr) 1f else -1f - } - .then(dampedDragAnimation.modifier) + modifier = Modifier + .offset { androidx.compose.ui.unit.IntOffset((playedPx - (thumbWidth.toPx() / 2)).roundToInt(), 0) } + .width(thumbWidth) + .height(thumbHeight) .drawBackdrop( - backdrop = rememberCombinedBackdrop( - backdrop, - rememberBackdrop(trackBackdrop) { drawBackdrop -> - val progress = dampedDragAnimation.pressProgress - val scaleX = lerp(2f / 3f, 1f, progress) - val scaleY = lerp(0f, 1f, progress) - scale(scaleX, scaleY) { - drawBackdrop() - } - } - ), - shape = { Capsule() }, + backdrop = trackBackdrop, + shape = { CircleShape }, effects = { - val progress = dampedDragAnimation.pressProgress - blur(8f.dp.toPx() * (1f - progress)) + vibrancy() + + // 3 & 4. Static Refraction + Depth Effect (With a slight lens boost when pressed!) lens( - 10f.dp.toPx() * progress, - 14f.dp.toPx() * progress, + refractionHeight = 10f.dp.toPx() * pressProgress, + refractionAmount = 14f.dp.toPx() * pressProgress, chromaticAberration = true + depthEffect = true ) }, - highlight = { - val progress = dampedDragAnimation.pressProgress - Highlight.Ambient.copy( - width = Highlight.Ambient.width / 1.5f, - blurRadius = Highlight.Ambient.blurRadius / 1.5f, - alpha = progress - ) - }, - shadow = { - Shadow( - radius = 4f.dp, - color = Color.Black.copy(alpha = 0.05f) - ) - }, - innerShadow = { - val progress = dampedDragAnimation.pressProgress - InnerShadow( - radius = 4f.dp * progress, - alpha = progress - ) - }, - layerBlock = { - scaleX = dampedDragAnimation.scaleX - scaleY = dampedDragAnimation.scaleY - val velocity = dampedDragAnimation.velocity / 10f - scaleX /= 1f - (velocity * 0.75f).fastCoerceIn(-0.2f, 0.2f) - scaleY *= 1f - (velocity * 0.25f).fastCoerceIn(-0.2f, 0.2f) - }, onDrawSurface = { - val progress = dampedDragAnimation.pressProgress - drawRect(Color.White.copy(alpha = 1f - progress)) + // 5. Opacity 0 + drawRect(Color.Transparent) } ) - .size(40f.dp, 24f.dp) - ) - } - } + ) + } +} From 90ea14d95443520a6463719482126fb765742b3d Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 16:57:35 +0530 Subject: [PATCH 126/194] Update Seekbar.kt --- .../mpvex/ui/player/controls/components/Seekbar.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index bb5207019..780e1e6df 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -321,9 +321,9 @@ fun LiquidSeekbar( // 3 & 4. Static Refraction + Depth Effect (With a slight lens boost when pressed!) lens( - refractionHeight = 10f.dp.toPx() * pressProgress, - refractionAmount = 14f.dp.toPx() * pressProgress, - chromaticAberration = true + val currentHeight = 10f.dp.toPx() + (10f.dp.toPx() * pressProgress), + val currentAmount = 10f.dp.toPx() + (5f.dp.toPx() * pressProgress), + chromaticAberration = false, depthEffect = true ) }, From 217d478315cd22e9e75a5dae95e9bfc79cbd7e36 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 17:43:11 +0530 Subject: [PATCH 127/194] Update Seekbar.kt --- .../mpvex/ui/player/controls/components/Seekbar.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index 780e1e6df..dab560b06 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -321,10 +321,10 @@ fun LiquidSeekbar( // 3 & 4. Static Refraction + Depth Effect (With a slight lens boost when pressed!) lens( - val currentHeight = 10f.dp.toPx() + (10f.dp.toPx() * pressProgress), - val currentAmount = 10f.dp.toPx() + (5f.dp.toPx() * pressProgress), - chromaticAberration = false, - depthEffect = true + 10f.dp.toPx() * pressProgress, + 14f.dp.toPx() * pressProgress, + chromaticAberration = true, + isDepthEffectEnabled = true ) }, onDrawSurface = { From 241213339872b6c7d045db56fe29fd0a989a2f52 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 17:48:28 +0530 Subject: [PATCH 128/194] Update Seekbar.kt --- .../mpvex/ui/player/controls/components/Seekbar.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index dab560b06..61cb1290f 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -323,7 +323,7 @@ fun LiquidSeekbar( lens( 10f.dp.toPx() * pressProgress, 14f.dp.toPx() * pressProgress, - chromaticAberration = true, + isChromaticAberrationEnabled = true, isDepthEffectEnabled = true ) }, From 2a00171c9dea4571b15b2d392e8bb4112ed73ae7 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 17:59:27 +0530 Subject: [PATCH 129/194] Update Seekbar.kt --- .../mpvex/ui/player/controls/components/Seekbar.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index 61cb1290f..44a955f82 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -322,9 +322,7 @@ fun LiquidSeekbar( // 3 & 4. Static Refraction + Depth Effect (With a slight lens boost when pressed!) lens( 10f.dp.toPx() * pressProgress, - 14f.dp.toPx() * pressProgress, - isChromaticAberrationEnabled = true, - isDepthEffectEnabled = true + 14f.dp.toPx() * pressProgress ) }, onDrawSurface = { From 3b9159d259d2f5263668af12337758052cc2550d Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 18:25:13 +0530 Subject: [PATCH 130/194] Update Seekbar.kt --- .../mpvex/ui/player/controls/components/Seekbar.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt index 44a955f82..2c9869c46 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/Seekbar.kt @@ -326,8 +326,7 @@ fun LiquidSeekbar( ) }, onDrawSurface = { - // 5. Opacity 0 - drawRect(Color.Transparent) + drawRect(Color.White.copy(alpha = 1f - pressProgress)) } ) ) From e3bc0779a23d33f70c638410027d0c9a135f6dd4 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 19:37:28 +0530 Subject: [PATCH 131/194] Update LiquidUIToggle.kt --- .../ui/components/liquid/LiquidUIToggle.kt | 127 +++++++++++------- 1 file changed, 79 insertions(+), 48 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt index b8369e6d3..6427bc46e 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidUIToggle.kt @@ -11,10 +11,10 @@ import androidx.compose.foundation.gestures.rememberDraggableState import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Switch import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -26,18 +26,17 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.isSpecified import androidx.compose.ui.graphics.lerp import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp +import com.kyant.backdrop.backdrops.layerBackdrop import com.kyant.backdrop.backdrops.rememberLayerBackdrop import com.kyant.backdrop.drawBackdrop import com.kyant.backdrop.effects.blur import com.kyant.backdrop.effects.lens -import com.kyant.backdrop.effects.vibrancy import app.marlboroadvance.mpvex.preferences.LiquidUIPreferences import kotlin.math.roundToInt @@ -57,68 +56,63 @@ fun LiquidToggle( return } - val trackWidthDp = 52.dp - val baseThumbSizeDp = 28.dp + // 1.0.6 Exact Dimensions + val trackWidthDp = 64.dp + val trackHeightDp = 28.dp + val thumbBaseWidthDp = 40.dp + val thumbBaseHeightDp = 24.dp val paddingDp = 2.dp - val trackWidthPx = with(LocalDensity.current) { trackWidthDp.toPx() } - val thumbSizePx = with(LocalDensity.current) { baseThumbSizeDp.toPx() } - val paddingPx = with(LocalDensity.current) { paddingDp.toPx() } - val maxDragPx = trackWidthPx - thumbSizePx - (paddingPx * 2) + val density = LocalDensity.current + val dragWidthPx = with(density) { (trackWidthDp - thumbBaseWidthDp - (paddingDp * 2)).toPx() } - var dragOffset by remember { mutableFloatStateOf(if (checked) maxDragPx else 0f) } + var dragFraction by remember { mutableFloatStateOf(if (checked) 1f else 0f) } var isDragging by remember { mutableStateOf(false) } LaunchedEffect(checked) { - if (!isDragging) dragOffset = if (checked) maxDragPx else 0f + if (!isDragging) dragFraction = if (checked) 1f else 0f } val draggableState = rememberDraggableState { delta -> - dragOffset = (dragOffset + delta).coerceIn(0f, maxDragPx) + dragFraction = (dragFraction + (delta / dragWidthPx)).coerceIn(0f, 1f) } - val animatedOffset by animateFloatAsState( - targetValue = if (isDragging) dragOffset else (if (checked) maxDragPx else 0f), - animationSpec = spring(dampingRatio = 0.65f, stiffness = 400f), - label = "thumb_offset" + // The Sliding Physics + val animatedFraction by animateFloatAsState( + targetValue = if (isDragging) dragFraction else (if (checked) 1f else 0f), + animationSpec = spring(dampingRatio = 0.5f, stiffness = 400f), + label = "thumb_fraction" ) val interactionSource = remember { MutableInteractionSource() } val isPressed by interactionSource.collectIsPressedAsState() + // The Squishy Press Physics + val pressProgress by animateFloatAsState( + targetValue = if (isPressed || isDragging) 1f else 0f, + animationSpec = spring(dampingRatio = 0.6f, stiffness = 800f), + label = "press_progress" + ) + + // Thumb physically SWELLS/EXPANDS when you press it (like a magnifying glass) val thumbWidth by animateDpAsState( - targetValue = if (isPressed || isDragging) 34.dp else baseThumbSizeDp, + targetValue = androidx.compose.ui.unit.lerp(thumbBaseWidthDp, 52.dp, pressProgress), animationSpec = spring(dampingRatio = 0.6f, stiffness = 800f), label = "thumb_width" ) - - val progress = (animatedOffset / maxDragPx).coerceIn(0f, 1f) - val dynamicTint = lerp( - start = uncheckedColor, - stop = checkedColor, - fraction = progress + + val thumbHeight by animateDpAsState( + targetValue = androidx.compose.ui.unit.lerp(thumbBaseHeightDp, 32.dp, pressProgress), + animationSpec = spring(dampingRatio = 0.6f, stiffness = 800f), + label = "thumb_height" ) - val backdrop = rememberLayerBackdrop { drawContent() } + val dynamicTint = lerp(uncheckedColor, checkedColor, animatedFraction) + val trackBackdrop = rememberLayerBackdrop { drawContent() } Box( modifier = modifier - .size(width = trackWidthDp, height = 32.dp) - .drawBackdrop( - backdrop = backdrop, - shape = { RoundedCornerShape(percent = 50) }, - effects = { - vibrancy() - blur(2f.dp.toPx()) - lens(12f.dp.toPx(), 24f.dp.toPx()) - }, - onDrawSurface = { - if (dynamicTint.isSpecified) { - drawRect(dynamicTint, blendMode = BlendMode.Hue) - drawRect(dynamicTint.copy(alpha = 0.75f)) - } - } - ) + .size(width = trackWidthDp, height = trackHeightDp) .clickable( enabled = enabled, onClick = { onCheckedChange(!checked) }, @@ -129,21 +123,58 @@ fun LiquidToggle( state = draggableState, orientation = Orientation.Horizontal, enabled = enabled, - onDragStarted = { isDragging = true }, + onDragStarted = { + isDragging = true + dragFraction = if (checked) 1f else 0f + }, onDragStopped = { isDragging = false - val targetChecked = dragOffset > (maxDragPx / 2) + val targetChecked = dragFraction > 0.5f onCheckedChange(targetChecked) } ), contentAlignment = Alignment.CenterStart ) { + // THE TRACK (Feeds colors to the glass) + Box( + modifier = Modifier + .fillMaxSize() + .clip(CircleShape) + .background(dynamicTint) + .layerBackdrop(trackBackdrop) + ) + + // THE LIQUID GLASS THUMB + val paddingPx = with(density) { paddingDp.toPx() } + val thumbOffsetPx = paddingPx + (dragWidthPx * animatedFraction) + Box( modifier = Modifier - .padding(start = paddingDp) - .offset { IntOffset(animatedOffset.roundToInt(), 0) } - .size(width = thumbWidth, height = baseThumbSizeDp) - .background(color = thumbColor, shape = RoundedCornerShape(percent = 50)) + .offset { IntOffset(thumbOffsetPx.roundToInt(), 0) } + .size(width = thumbWidth, height = thumbHeight) + .drawBackdrop( + backdrop = trackBackdrop, + shape = { CircleShape }, + effects = { + val currentBlur = 8f.dp.toPx() * (1f - pressProgress) + val currentHeight = 5f.dp.toPx() + (5f.dp.toPx() * pressProgress) + val currentAmount = 10f.dp.toPx() + (4f.dp.toPx() * pressProgress) + + if (currentBlur > 0f) { + blur(currentBlur) + } + + lens( + refractionHeight = currentHeight, + refractionAmount = currentAmount, + chromaticAberration = true + ) + }, + onDrawSurface = { + // Melts into pure transparent glass when pressed! + drawRect(thumbColor.copy(alpha = 1f - pressProgress)) + } + ) ) } } From 436a8d238a96b47d39f57bb001685f312b605e39 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 20:52:57 +0530 Subject: [PATCH 132/194] Update LiquidUIPreferences.kt --- .../mpvex/preferences/LiquidUIPreferences.kt | 67 ++++++++----------- 1 file changed, 29 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt b/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt index 604ff8d1b..cb8b896c2 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt @@ -11,61 +11,52 @@ import kotlinx.coroutines.flow.map private val Context.liquidUIDataStore by preferencesDataStore(name = "liquid_ui_prefs") +// --- THE TARGET ENUM --- +enum class LiquidTarget(val id: String, val title: String) { + NAV("nav", "Navigation"), + BUTTON("btn", "Buttons"), + DIALOG("dlg", "Dialogs") +} + class LiquidUIPreferences(context: Context) { private val dataStore = context.liquidUIDataStore companion object { val LIQUID_UI_ENABLED = booleanPreferencesKey("liquid_ui_enabled") - - // Kyant Backdrop Effect Keys - val LIQUID_BLUR_RADIUS = floatPreferencesKey("liquid_blur_radius") - val LIQUID_REFRACTION_HEIGHT = floatPreferencesKey("liquid_refraction_height") - val LIQUID_REFRACTION_AMOUNT = floatPreferencesKey("liquid_refraction_amount") - val LIQUID_CHROMATIC_ABERRATION = booleanPreferencesKey("liquid_chromatic_aberration") - val LIQUID_DEPTH_EFFECT = booleanPreferencesKey("liquid_depth_effect") - val LIQUID_VIBRANCY_ENABLED = booleanPreferencesKey("liquid_vibrancy_enabled") - - // Color & Opacity Keys val LIQUID_TOGGLE_COLOR = longPreferencesKey("liquid_toggle_color") val LIQUID_SLIDER_COLOR = longPreferencesKey("liquid_slider_color") - val LIQUID_TINT_ALPHA = floatPreferencesKey("liquid_tint_alpha") - - // Legacy Keys (Kept to prevent LiquidUISettingsScreen from crashing) + + // Legacy Keys to prevent crashes in other screens val LIQUID_BLUR_ENABLED = booleanPreferencesKey("liquid_blur_enabled") val LIQUID_LENS_ENABLED = booleanPreferencesKey("liquid_lens_enabled") } - // --- STATE FLOWS --- + // Master Toggles val liquidUIEnabledFlow: Flow = dataStore.data.map { it[LIQUID_UI_ENABLED] ?: false } - - val liquidBlurRadiusFlow: Flow = dataStore.data.map { it[LIQUID_BLUR_RADIUS] ?: 0f } - val liquidRefractionHeightFlow: Flow = dataStore.data.map { it[LIQUID_REFRACTION_HEIGHT] ?: 40f } - val liquidRefractionAmountFlow: Flow = dataStore.data.map { it[LIQUID_REFRACTION_AMOUNT] ?: 23f } - val liquidChromaticAberrationFlow: Flow = dataStore.data.map { it[LIQUID_CHROMATIC_ABERRATION] ?: false } - val liquidDepthEffectFlow: Flow = dataStore.data.map { it[LIQUID_DEPTH_EFFECT] ?: true } - val liquidVibrancyEnabledFlow: Flow = dataStore.data.map { it[LIQUID_VIBRANCY_ENABLED] ?: true } - val liquidTintAlphaFlow: Flow = dataStore.data.map { it[LIQUID_TINT_ALPHA] ?: 0.15f } - val liquidToggleColorFlow: Flow = dataStore.data.map { it[LIQUID_TOGGLE_COLOR] ?: 0xFF4CAF50 } val liquidSliderColorFlow: Flow = dataStore.data.map { it[LIQUID_SLIDER_COLOR] ?: 0xFF2196F3 } - // Legacy Flows - val liquidBlurEnabledFlow: Flow = dataStore.data.map { it[LIQUID_BLUR_ENABLED] ?: true } - val liquidLensEnabledFlow: Flow = dataStore.data.map { it[LIQUID_LENS_ENABLED] ?: true } - - // --- SETTERS --- suspend fun setLiquidUIEnabled(enabled: Boolean) { dataStore.edit { it[LIQUID_UI_ENABLED] = enabled } } - suspend fun setBlurRadius(value: Float) { dataStore.edit { it[LIQUID_BLUR_RADIUS] = value } } - suspend fun setRefractionHeight(value: Float) { dataStore.edit { it[LIQUID_REFRACTION_HEIGHT] = value } } - suspend fun setRefractionAmount(value: Float) { dataStore.edit { it[LIQUID_REFRACTION_AMOUNT] = value } } - suspend fun setChromaticAberration(enabled: Boolean) { dataStore.edit { it[LIQUID_CHROMATIC_ABERRATION] = enabled } } - suspend fun setDepthEffect(enabled: Boolean) { dataStore.edit { it[LIQUID_DEPTH_EFFECT] = enabled } } - suspend fun setVibrancyEnabled(enabled: Boolean) { dataStore.edit { it[LIQUID_VIBRANCY_ENABLED] = enabled } } - suspend fun setTintAlpha(value: Float) { dataStore.edit { it[LIQUID_TINT_ALPHA] = value } } suspend fun setToggleColor(color: Long) { dataStore.edit { it[LIQUID_TOGGLE_COLOR] = color } } suspend fun setSliderColor(color: Long) { dataStore.edit { it[LIQUID_SLIDER_COLOR] = color } } + + // --- DYNAMIC TARGET FLOWS --- + fun blurRadiusFlow(target: LiquidTarget): Flow = dataStore.data.map { it[floatPreferencesKey("${target.id}_blur")] ?: 0f } + fun refractionHeightFlow(target: LiquidTarget): Flow = dataStore.data.map { it[floatPreferencesKey("${target.id}_height")] ?: 40f } + fun refractionAmountFlow(target: LiquidTarget): Flow = dataStore.data.map { it[floatPreferencesKey("${target.id}_amount")] ?: 23f } + fun tintAlphaFlow(target: LiquidTarget): Flow = dataStore.data.map { it[floatPreferencesKey("${target.id}_alpha")] ?: 0.15f } + + fun chromaticAberrationFlow(target: LiquidTarget): Flow = dataStore.data.map { it[booleanPreferencesKey("${target.id}_chromatic")] ?: false } + fun depthEffectFlow(target: LiquidTarget): Flow = dataStore.data.map { it[booleanPreferencesKey("${target.id}_depth")] ?: true } + fun vibrancyEnabledFlow(target: LiquidTarget): Flow = dataStore.data.map { it[booleanPreferencesKey("${target.id}_vibrancy")] ?: true } + + // --- DYNAMIC TARGET SETTERS --- + suspend fun setBlurRadius(target: LiquidTarget, value: Float) { dataStore.edit { it[floatPreferencesKey("${target.id}_blur")] = value } } + suspend fun setRefractionHeight(target: LiquidTarget, value: Float) { dataStore.edit { it[floatPreferencesKey("${target.id}_height")] = value } } + suspend fun setRefractionAmount(target: LiquidTarget, value: Float) { dataStore.edit { it[floatPreferencesKey("${target.id}_amount")] = value } } + suspend fun setTintAlpha(target: LiquidTarget, value: Float) { dataStore.edit { it[floatPreferencesKey("${target.id}_alpha")] = value } } - // Legacy Setters - suspend fun setBlurEnabled(enabled: Boolean) { dataStore.edit { it[LIQUID_BLUR_ENABLED] = enabled } } - suspend fun setLensEnabled(enabled: Boolean) { dataStore.edit { it[LIQUID_LENS_ENABLED] = enabled } } + suspend fun setChromaticAberration(target: LiquidTarget, enabled: Boolean) { dataStore.edit { it[booleanPreferencesKey("${target.id}_chromatic")] = enabled } } + suspend fun setDepthEffect(target: LiquidTarget, enabled: Boolean) { dataStore.edit { it[booleanPreferencesKey("${target.id}_depth")] = enabled } } + suspend fun setVibrancyEnabled(target: LiquidTarget, enabled: Boolean) { dataStore.edit { it[booleanPreferencesKey("${target.id}_vibrancy")] = enabled } } } From a9b4d27d62c87534606ab01356e7b804cf9efd97 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 21:02:50 +0530 Subject: [PATCH 133/194] Update LiquidGlassSurface.kt --- .../components/liquid/LiquidGlassSurface.kt | 47 ++++++------------- 1 file changed, 15 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidGlassSurface.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidGlassSurface.kt index d6ce11ebe..15a48c3cb 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidGlassSurface.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidGlassSurface.kt @@ -16,20 +16,19 @@ import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp -// --- KYANT BACKDROP 2.0.0-ALPHA03 IMPORTS --- import com.kyant.backdrop.Backdrop import com.kyant.backdrop.drawBackdrop import com.kyant.backdrop.effects.blur import com.kyant.backdrop.effects.lens import com.kyant.backdrop.effects.vibrancy -// -------------------------------------------- -// --- PREFERENCES IMPORT --- import app.marlboroadvance.mpvex.preferences.LiquidUIPreferences +import app.marlboroadvance.mpvex.preferences.LiquidTarget @Composable fun LiquidGlassSurface( backdrop: Backdrop, + target: LiquidTarget = LiquidTarget.NAV, // Targets the Nav bar by default modifier: Modifier = Modifier, shape: Shape = RoundedCornerShape(24.dp), defaultTintColor: Color = Color.White, @@ -38,32 +37,24 @@ fun LiquidGlassSurface( val context = LocalContext.current val liquidPrefs = remember { LiquidUIPreferences(context) } - // --- LIVE SETTINGS STREAM --- - // The glass will automatically morph whenever these values change! - val blurRadius by liquidPrefs.liquidBlurRadiusFlow.collectAsState(initial = 0f) - val refractionHeight by liquidPrefs.liquidRefractionHeightFlow.collectAsState(initial = 40f) - val refractionAmount by liquidPrefs.liquidRefractionAmountFlow.collectAsState(initial = 23f) - val chromaticAberration by liquidPrefs.liquidChromaticAberrationFlow.collectAsState(initial = false) - val depthEffect by liquidPrefs.liquidDepthEffectFlow.collectAsState(initial = true) - val vibrancyEnabled by liquidPrefs.liquidVibrancyEnabledFlow.collectAsState(initial = true) - val tintAlpha by liquidPrefs.liquidTintAlphaFlow.collectAsState(initial = 0.15f) + // SAFE STATE COLLECTIONS: Only updates when the target actually changes + val blurRadius by remember(target) { liquidPrefs.blurRadiusFlow(target) }.collectAsState(initial = 0f) + val refractionHeight by remember(target) { liquidPrefs.refractionHeightFlow(target) }.collectAsState(initial = 40f) + val refractionAmount by remember(target) { liquidPrefs.refractionAmountFlow(target) }.collectAsState(initial = 23f) + val chromaticAberration by remember(target) { liquidPrefs.chromaticAberrationFlow(target) }.collectAsState(initial = false) + val depthEffect by remember(target) { liquidPrefs.depthEffectFlow(target) }.collectAsState(initial = true) + val vibrancyEnabled by remember(target) { liquidPrefs.vibrancyEnabledFlow(target) }.collectAsState(initial = true) + val tintAlpha by remember(target) { liquidPrefs.tintAlphaFlow(target) }.collectAsState(initial = 0.15f) if (Build.VERSION.SDK_INT >= 33) { - // ANDROID 13+: Reactive Lens Engine Box( modifier = modifier .drawBackdrop( backdrop = backdrop, shape = { shape }, effects = { - if (vibrancyEnabled) { - vibrancy() - } - - // Only apply blur if it's greater than 0 to save GPU power - if (blurRadius > 0f) { - blur(blurRadius.dp.toPx()) - } + if (vibrancyEnabled) vibrancy() + if (blurRadius > 0f) blur(blurRadius.dp.toPx()) lens( refractionHeight = refractionHeight.dp.toPx(), @@ -72,23 +63,15 @@ fun LiquidGlassSurface( chromaticAberration = chromaticAberration ) }, - onDrawSurface = { - // Apply the exact opacity you picked in the settings slider - drawRect(defaultTintColor.copy(alpha = tintAlpha)) - } + onDrawSurface = { drawRect(defaultTintColor.copy(alpha = tintAlpha)) } ) - ) { - content() - } + ) { content() } } else { - // ANDROID 12 AND BELOW: The "Flat Liquid Sheet" Fallback Box( modifier = modifier .background(defaultTintColor.copy(alpha = tintAlpha), shape) .border(1.dp, Color.White.copy(alpha = 0.2f), shape) .clip(shape) - ) { - content() - } + ) { content() } } } From 063c11d0747c135f000a1acb41b3e049c48f86cb Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 21:08:40 +0530 Subject: [PATCH 134/194] Update AppearancePreferencesScreen.kt --- .../AppearancePreferencesScreen.kt | 77 ++++++++++++------- 1 file changed, 50 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt index 682b7fdc5..b9119ef01 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt @@ -4,6 +4,7 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -21,6 +22,7 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable @@ -43,6 +45,7 @@ import app.marlboroadvance.mpvex.preferences.AppearancePreferences import app.marlboroadvance.mpvex.preferences.BrowserPreferences import app.marlboroadvance.mpvex.preferences.GesturePreferences import app.marlboroadvance.mpvex.preferences.LiquidUIPreferences +import app.marlboroadvance.mpvex.preferences.LiquidTarget import app.marlboroadvance.mpvex.preferences.MultiChoiceSegmentedButton import app.marlboroadvance.mpvex.preferences.preference.collectAsState import app.marlboroadvance.mpvex.presentation.Screen @@ -83,17 +86,21 @@ object AppearancePreferencesScreen : Screen { DarkMode.System -> systemDarkTheme } - // --- LIQUID GLASS STATE COLLECTIONS --- val isLiquidUIEnabled by liquidPreferences.liquidUIEnabledFlow.collectAsState(initial = false) val toggleColor by liquidPreferences.liquidToggleColorFlow.collectAsState(initial = 0xFF4CAF50) val sliderColor by liquidPreferences.liquidSliderColorFlow.collectAsState(initial = 0xFF2196F3) - val blurRadius by liquidPreferences.liquidBlurRadiusFlow.collectAsState(initial = 0f) - val refractionHeight by liquidPreferences.liquidRefractionHeightFlow.collectAsState(initial = 40f) - val refractionAmount by liquidPreferences.liquidRefractionAmountFlow.collectAsState(initial = 23f) - val tintAlpha by liquidPreferences.liquidTintAlphaFlow.collectAsState(initial = 0.15f) - val chromaticAberration by liquidPreferences.liquidChromaticAberrationFlow.collectAsState(initial = false) - val depthEffect by liquidPreferences.liquidDepthEffectFlow.collectAsState(initial = true) - val vibrancyEnabled by liquidPreferences.liquidVibrancyEnabledFlow.collectAsState(initial = true) + + // --- THE ACTIVE TARGET STATE --- + var selectedTarget by remember { mutableStateOf(LiquidTarget.NAV) } + + // --- SAFE DYNAMIC DATA LOADING (Wrapped in remember blocks) --- + val blurRadius by remember(selectedTarget) { liquidPreferences.blurRadiusFlow(selectedTarget) }.collectAsState(initial = 0f) + val refractionHeight by remember(selectedTarget) { liquidPreferences.refractionHeightFlow(selectedTarget) }.collectAsState(initial = 40f) + val refractionAmount by remember(selectedTarget) { liquidPreferences.refractionAmountFlow(selectedTarget) }.collectAsState(initial = 23f) + val tintAlpha by remember(selectedTarget) { liquidPreferences.tintAlphaFlow(selectedTarget) }.collectAsState(initial = 0.15f) + val chromaticAberration by remember(selectedTarget) { liquidPreferences.chromaticAberrationFlow(selectedTarget) }.collectAsState(initial = false) + val depthEffect by remember(selectedTarget) { liquidPreferences.depthEffectFlow(selectedTarget) }.collectAsState(initial = true) + val vibrancyEnabled by remember(selectedTarget) { liquidPreferences.vibrancyEnabledFlow(selectedTarget) }.collectAsState(initial = true) Scaffold( topBar = { @@ -141,7 +148,6 @@ object AppearancePreferencesScreen : Screen { PreferenceDivider() - // --- LIQUID GLASS UI MASTER TOGGLE --- Row( modifier = Modifier .fillMaxWidth() @@ -181,7 +187,6 @@ object AppearancePreferencesScreen : Screen { parseColorInput(newValue)?.let { colorLong -> scope.launch { liquidPreferences.setToggleColor(colorLong) } } }, label = { Text("Toggle Color") }, - placeholder = { Text("e.g. #FF5733") }, modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp), singleLine = true, leadingIcon = { Box(modifier = Modifier.size(24.dp).clip(CircleShape).background(Color(toggleColor))) } @@ -195,7 +200,6 @@ object AppearancePreferencesScreen : Screen { parseColorInput(newValue)?.let { colorLong -> scope.launch { liquidPreferences.setSliderColor(colorLong) } } }, label = { Text("Slider Color") }, - placeholder = { Text("e.g. #00FF00") }, modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp), singleLine = true, leadingIcon = { Box(modifier = Modifier.size(24.dp).clip(CircleShape).background(Color(sliderColor))) } @@ -204,14 +208,35 @@ object AppearancePreferencesScreen : Screen { PreferenceDivider() - // --- KYANT BACKDROP LIVE SLIDERS --- - Text("Backdrop Engine Tuning", style = MaterialTheme.typography.titleSmall, color = MaterialTheme.colorScheme.primary, modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) + // --- THE NEW PREMIUM TARGET SELECTOR --- + Column(modifier = Modifier.fillMaxWidth().padding(vertical = 12.dp), horizontalAlignment = Alignment.CenterHorizontally) { + Text("Select UI Layer to Tune:", style = MaterialTheme.typography.labelLarge, color = MaterialTheme.colorScheme.outline, modifier = Modifier.padding(bottom = 8.dp)) + Row( + modifier = Modifier.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f), CircleShape).padding(4.dp), + horizontalArrangement = Arrangement.Center + ) { + LiquidTarget.values().forEach { target -> + val isSelected = selectedTarget == target + Surface( + modifier = Modifier.clickable { selectedTarget = target }.padding(horizontal = 4.dp), + shape = CircleShape, + color = if (isSelected) MaterialTheme.colorScheme.primary else Color.Transparent, + contentColor = if (isSelected) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurfaceVariant + ) { + Text(text = target.title, modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal) + } + } + } + } + + // --- THE SLIDERS --- + Text("${selectedTarget.title} Tuning", style = MaterialTheme.typography.titleSmall, color = MaterialTheme.colorScheme.primary, modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) SliderPreference( value = blurRadius, - onValueChange = { v -> scope.launch { liquidPreferences.setBlurRadius(v) } }, + onValueChange = { v -> scope.launch { liquidPreferences.setBlurRadius(selectedTarget, v) } }, sliderValue = blurRadius, - onSliderValueChange = { v -> scope.launch { liquidPreferences.setBlurRadius(v) } }, + onSliderValueChange = { v -> scope.launch { liquidPreferences.setBlurRadius(selectedTarget, v) } }, title = { Text("Blur Radius") }, valueRange = 0f..64f, summary = { Text("${blurRadius.roundToInt()} px", color = MaterialTheme.colorScheme.outline) } @@ -219,9 +244,9 @@ object AppearancePreferencesScreen : Screen { SliderPreference( value = refractionHeight, - onValueChange = { v -> scope.launch { liquidPreferences.setRefractionHeight(v) } }, + onValueChange = { v -> scope.launch { liquidPreferences.setRefractionHeight(selectedTarget, v) } }, sliderValue = refractionHeight, - onSliderValueChange = { v -> scope.launch { liquidPreferences.setRefractionHeight(v) } }, + onSliderValueChange = { v -> scope.launch { liquidPreferences.setRefractionHeight(selectedTarget, v) } }, title = { Text("Refraction Height") }, valueRange = 0f..100f, summary = { Text("${refractionHeight.roundToInt()} dp", color = MaterialTheme.colorScheme.outline) } @@ -229,9 +254,9 @@ object AppearancePreferencesScreen : Screen { SliderPreference( value = refractionAmount, - onValueChange = { v -> scope.launch { liquidPreferences.setRefractionAmount(v) } }, + onValueChange = { v -> scope.launch { liquidPreferences.setRefractionAmount(selectedTarget, v) } }, sliderValue = refractionAmount, - onSliderValueChange = { v -> scope.launch { liquidPreferences.setRefractionAmount(v) } }, + onSliderValueChange = { v -> scope.launch { liquidPreferences.setRefractionAmount(selectedTarget, v) } }, title = { Text("Refraction Amount") }, valueRange = 0f..100f, summary = { Text("${refractionAmount.roundToInt()} dp", color = MaterialTheme.colorScheme.outline) } @@ -239,9 +264,9 @@ object AppearancePreferencesScreen : Screen { SliderPreference( value = tintAlpha, - onValueChange = { v -> scope.launch { liquidPreferences.setTintAlpha(v) } }, + onValueChange = { v -> scope.launch { liquidPreferences.setTintAlpha(selectedTarget, v) } }, sliderValue = tintAlpha, - onSliderValueChange = { v -> scope.launch { liquidPreferences.setTintAlpha(v) } }, + onSliderValueChange = { v -> scope.launch { liquidPreferences.setTintAlpha(selectedTarget, v) } }, title = { Text("Glass Opacity (Tint)") }, valueRange = 0.0f..1.0f, summary = { Text("${(tintAlpha * 100).roundToInt()}%", color = MaterialTheme.colorScheme.outline) } @@ -249,24 +274,23 @@ object AppearancePreferencesScreen : Screen { PreferenceDivider() - // --- KYANT BACKDROP LIVE SWITCHES --- LiquidSwitchPreference( value = depthEffect, - onValueChange = { v -> scope.launch { liquidPreferences.setDepthEffect(v) } }, + onValueChange = { v -> scope.launch { liquidPreferences.setDepthEffect(selectedTarget, v) } }, title = { Text("Depth Effect") }, summary = { Text("Enables 3D lens depth calculation", color = MaterialTheme.colorScheme.outline) } ) LiquidSwitchPreference( value = chromaticAberration, - onValueChange = { v -> scope.launch { liquidPreferences.setChromaticAberration(v) } }, + onValueChange = { v -> scope.launch { liquidPreferences.setChromaticAberration(selectedTarget, v) } }, title = { Text("Chromatic Aberration") }, summary = { Text("Enables RGB color-split on glass edges", color = MaterialTheme.colorScheme.outline) } ) LiquidSwitchPreference( value = vibrancyEnabled, - onValueChange = { v -> scope.launch { liquidPreferences.setVibrancyEnabled(v) } }, + onValueChange = { v -> scope.launch { liquidPreferences.setVibrancyEnabled(selectedTarget, v) } }, title = { Text("Vibrancy") }, summary = { Text("Multiplies background saturation by 1.5x", color = MaterialTheme.colorScheme.outline) } ) @@ -274,8 +298,7 @@ object AppearancePreferencesScreen : Screen { } PreferenceDivider() - // ------------------------------------- - + LiquidSwitchPreference( value = amoledMode, onValueChange = { newValue -> preferences.amoledMode.set(newValue) }, From e982c79d712fb1779b0190c70cb395ffcf8009c7 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 21:09:29 +0530 Subject: [PATCH 135/194] Update AppearancePreferencesScreen.kt From 75fb6d31c899ff50852eb17f7af6e5dc6d7496de Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 21:33:12 +0530 Subject: [PATCH 136/194] Update LiquidUIPreferences.kt --- .../mpvex/preferences/LiquidUIPreferences.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt b/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt index cb8b896c2..4e5e61410 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt @@ -29,6 +29,7 @@ class LiquidUIPreferences(context: Context) { // Legacy Keys to prevent crashes in other screens val LIQUID_BLUR_ENABLED = booleanPreferencesKey("liquid_blur_enabled") val LIQUID_LENS_ENABLED = booleanPreferencesKey("liquid_lens_enabled") + val LIQUID_VIBRANCY_ENABLED_LEGACY = booleanPreferencesKey("liquid_vibrancy_enabled_legacy") } // Master Toggles @@ -59,4 +60,15 @@ class LiquidUIPreferences(context: Context) { suspend fun setChromaticAberration(target: LiquidTarget, enabled: Boolean) { dataStore.edit { it[booleanPreferencesKey("${target.id}_chromatic")] = enabled } } suspend fun setDepthEffect(target: LiquidTarget, enabled: Boolean) { dataStore.edit { it[booleanPreferencesKey("${target.id}_depth")] = enabled } } suspend fun setVibrancyEnabled(target: LiquidTarget, enabled: Boolean) { dataStore.edit { it[booleanPreferencesKey("${target.id}_vibrancy")] = enabled } } + + // --- LEGACY FALLBACKS (Keeps LiquidUISettingsScreen.kt from crashing!) --- + val liquidBlurEnabledFlow: Flow = dataStore.data.map { it[LIQUID_BLUR_ENABLED] ?: true } + val liquidLensEnabledFlow: Flow = dataStore.data.map { it[LIQUID_LENS_ENABLED] ?: true } + val liquidVibrancyEnabledFlow: Flow = dataStore.data.map { it[LIQUID_VIBRANCY_ENABLED_LEGACY] ?: true } + + suspend fun setBlurEnabled(enabled: Boolean) { dataStore.edit { it[LIQUID_BLUR_ENABLED] = enabled } } + suspend fun setLensEnabled(enabled: Boolean) { dataStore.edit { it[LIQUID_LENS_ENABLED] = enabled } } + + // This perfectly satisfies the old screen while leaving the new engine untouched! + suspend fun setVibrancyEnabled(enabled: Boolean) { dataStore.edit { it[LIQUID_VIBRANCY_ENABLED_LEGACY] = enabled } } } From e9a31970070227bd9d92b91cd4a3af9f82819cd0 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 22:28:47 +0530 Subject: [PATCH 137/194] Update LiquidComponents.kt --- .../ui/components/liquid/LiquidComponents.kt | 65 ++++++++++++------- 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt index 268a4fd99..8b8d597e5 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt @@ -1,58 +1,77 @@ package app.marlboroadvance.mpvex.ui.components.liquid -import androidx.compose.foundation.clickable +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Card -import androidx.compose.material3.IconButton -import androidx.compose.material3.Surface +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.dp import com.kyant.backdrop.Backdrop import com.kyant.backdrop.drawBackdrop import app.marlboroadvance.mpvex.ui.theme.LiquidUIEffects -import com.kyant.backdrop.drawBackdrop +import app.marlboroadvance.mpvex.preferences.LiquidTarget /** - * Reusable Transparent Liquid Button - * Perfect for Play/Pause/Seek controls over the video player + * UPGRADED: Reusable Transparent Liquid Button + * Now supports Long-Presses and Dynamic Target Settings! */ +@OptIn(ExperimentalFoundationApi::class) @Composable fun TransparentLiquidButton( modifier: Modifier = Modifier, backdrop: Backdrop?, + shape: Shape = CircleShape, + target: LiquidTarget = LiquidTarget.BUTTON, // Listens to your new Buttons Tab! onClick: () -> Unit, + onLongClick: (() -> Unit)? = null, content: @Composable () -> Unit ) { - // If liquid UI is disabled or backdrop is missing, show standard button + // If liquid UI is disabled or backdrop is missing, show standard clickable box if (backdrop == null) { - IconButton(onClick = onClick, modifier = modifier) { content() } + Box( + modifier = modifier + .clip(shape) + .combinedClickable( + onClick = onClick, + onLongClick = onLongClick + ), + contentAlignment = Alignment.Center + ) { content() } return } - Surface( + val interactionSource = remember { MutableInteractionSource() } + + // Wraps our master glass engine with long-press physics! + LiquidGlassSurface( + backdrop = backdrop, + target = target, + shape = shape, modifier = modifier - .drawBackdrop( - backdrop = backdrop, - shape = { CircleShape }, - // Calls the lambda from your fixed LiquidUIEffects.kt - effects = LiquidUIEffects.glassButtonEffects(), - // Using an ultra-light overlay to ensure it remains a truly transparent liquid button - onDrawSurface = { drawRect(Color.White.copy(alpha = 0.1f)) } - ), - shape = CircleShape, - // Critical: The Surface itself must be transparent so the backdrop shows through - color = Color.Transparent + .clip(shape) + .combinedClickable( + interactionSource = interactionSource, + indication = ripple(), + onClick = onClick, + onLongClick = onLongClick + ) ) { - IconButton(onClick = onClick) { - content() + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + content() } } } + /** * Reusable Liquid Glass Card * Perfect for the Browser Screen and video list items From 87f018178c68e31f223382800799f1ed8595263d Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 22:44:17 +0530 Subject: [PATCH 138/194] Update PlayerControls.kt --- .../ui/player/controls/PlayerControls.kt | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt index 61e996a07..b4cc5b662 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt @@ -1501,3 +1501,61 @@ fun PlayerControls( ) } } + +@OptIn(androidx.compose.foundation.ExperimentalFoundationApi::class) +@Composable +fun LiquidCustomButton( + button: app.marlboroadvance.mpvex.preferences.PlayerButton, + liquidUIEnabled: Boolean, + backdrop: com.kyant.backdrop.Backdrop, + viewModel: app.marlboroadvance.mpvex.ui.player.PlayerViewModel, + haptic: androidx.compose.ui.hapticfeedback.HapticFeedback, + onInteract: () -> Unit +) { + if (liquidUIEnabled) { + app.marlboroadvance.mpvex.ui.components.liquid.TransparentLiquidButton( + backdrop = backdrop, + onClick = { + onInteract() + viewModel.callCustomButton(button.id) + }, + onLongClick = { + haptic.performHapticFeedback(androidx.compose.ui.hapticfeedback.HapticFeedbackType.TextHandleMove) + onInteract() + viewModel.callCustomButtonLongPress(button.id) + } + ) { + androidx.compose.material3.Text( + text = button.label, + modifier = androidx.compose.ui.Modifier.padding(horizontal = 16.dp, vertical = 8.dp).androidx.compose.foundation.basicMarquee(), + style = androidx.compose.material3.MaterialTheme.typography.labelLarge.copy(fontWeight = androidx.compose.ui.text.font.FontWeight.Bold), + maxLines = 1, + softWrap = false, + color = androidx.compose.ui.graphics.Color.White + ) + } + } else { + val interactionSource = androidx.compose.runtime.remember { androidx.compose.foundation.interaction.MutableInteractionSource() } + androidx.compose.material3.Surface( + shape = androidx.compose.foundation.shape.CircleShape, + color = androidx.compose.material3.MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.85f), + contentColor = androidx.compose.material3.MaterialTheme.colorScheme.onSecondaryContainer, + border = androidx.compose.foundation.BorderStroke(1.dp, androidx.compose.material3.MaterialTheme.colorScheme.outline.copy(alpha = 0.1f)), + modifier = androidx.compose.ui.Modifier.clip(androidx.compose.foundation.shape.CircleShape).combinedClickable( + interactionSource = interactionSource, + indication = androidx.compose.material3.ripple(), + onClick = { onInteract(); viewModel.callCustomButton(button.id) }, + onLongClick = { haptic.performHapticFeedback(androidx.compose.ui.hapticfeedback.HapticFeedbackType.TextHandleMove); onInteract(); viewModel.callCustomButtonLongPress(button.id) } + ) + ) { + androidx.compose.material3.Text( + text = button.label, + modifier = androidx.compose.ui.Modifier.padding(horizontal = 12.dp, vertical = 6.dp).androidx.compose.foundation.basicMarquee(), + style = androidx.compose.material3.MaterialTheme.typography.labelLarge.copy(fontWeight = androidx.compose.ui.text.font.FontWeight.Bold), + maxLines = 1, + softWrap = false + ) + } + } +} + From 6546299f39b452884fcaf75db40978b1cebb66c8 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 23:23:13 +0530 Subject: [PATCH 139/194] Update PlayerControls.kt --- .../ui/player/controls/PlayerControls.kt | 99 ++----------------- 1 file changed, 6 insertions(+), 93 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt index b4cc5b662..31527ba9a 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt @@ -629,37 +629,8 @@ fun PlayerControls( .horizontalScroll(rememberScrollState()) ) { customButtons.filter { it.isLeft }.forEach { button -> - val buttonInteractionSource = remember { MutableInteractionSource() } - Surface( - shape = CircleShape, - color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.85f), - contentColor = MaterialTheme.colorScheme.onSecondaryContainer, - border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline.copy(alpha = 0.1f)), - modifier = Modifier - .clip(CircleShape) - .combinedClickable( - interactionSource = buttonInteractionSource, - indication = ripple(), - onClick = { - resetControlsTimestamp = System.currentTimeMillis() - viewModel.callCustomButton(button.id) - }, - onLongClick = { - haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove) - resetControlsTimestamp = System.currentTimeMillis() - viewModel.callCustomButtonLongPress(button.id) - } - ) - ) { - Text( - text = button.label, - modifier = Modifier - .padding(horizontal = 12.dp, vertical = 6.dp) - .basicMarquee(), - style = MaterialTheme.typography.labelLarge.copy(fontWeight = FontWeight.Bold), - maxLines = 1, - softWrap = false - ) + LiquidCustomButton(button, liquidUIEnabled, backdrop, viewModel, haptic) { + resetControlsTimestamp = System.currentTimeMillis() } } } @@ -686,37 +657,8 @@ fun PlayerControls( .horizontalScroll(rememberScrollState(), reverseScrolling = true) ) { customButtons.filter { !it.isLeft }.forEach { button -> - val buttonInteractionSource = remember { MutableInteractionSource() } - Surface( - shape = CircleShape, - color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.85f), - contentColor = MaterialTheme.colorScheme.onSecondaryContainer, - border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline.copy(alpha = 0.1f)), - modifier = Modifier - .clip(CircleShape) - .combinedClickable( - interactionSource = buttonInteractionSource, - indication = ripple(), - onClick = { - resetControlsTimestamp = System.currentTimeMillis() - viewModel.callCustomButton(button.id) - }, - onLongClick = { - haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove) - resetControlsTimestamp = System.currentTimeMillis() - viewModel.callCustomButtonLongPress(button.id) - } - ) - ) { - Text( - text = button.label, - modifier = Modifier - .padding(horizontal = 12.dp, vertical = 6.dp) - .basicMarquee(), - style = MaterialTheme.typography.labelLarge.copy(fontWeight = FontWeight.Bold), - maxLines = 1, - softWrap = false - ) + LiquidCustomButton(button, liquidUIEnabled, backdrop, viewModel, haptic) { + resetControlsTimestamp = System.currentTimeMillis() } } } @@ -742,37 +684,8 @@ fun PlayerControls( .horizontalScroll(rememberScrollState()) ) { customButtons.forEach { button -> - val buttonInteractionSource = remember { MutableInteractionSource() } - Surface( - shape = CircleShape, - color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.85f), - contentColor = MaterialTheme.colorScheme.onSecondaryContainer, - border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline.copy(alpha = 0.1f)), - modifier = Modifier - .clip(CircleShape) - .combinedClickable( - interactionSource = buttonInteractionSource, - indication = ripple(), - onClick = { - resetControlsTimestamp = System.currentTimeMillis() - viewModel.callCustomButton(button.id) - }, - onLongClick = { - haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove) - resetControlsTimestamp = System.currentTimeMillis() - viewModel.callCustomButtonLongPress(button.id) - } - ) - ) { - Text( - text = button.label, - modifier = Modifier - .padding(horizontal = 12.dp, vertical = 6.dp) - .basicMarquee(), - style = MaterialTheme.typography.labelLarge.copy(fontWeight = FontWeight.Bold), - maxLines = 1, - softWrap = false - ) + LiquidCustomButton(button, liquidUIEnabled, backdrop, viewModel, haptic) { + resetControlsTimestamp = System.currentTimeMillis() } } } From eb6550cff3e638d053568a14d5f54624b688c21f Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Mon, 9 Mar 2026 23:39:07 +0530 Subject: [PATCH 140/194] Update PlayerControls.kt --- .../ui/player/controls/PlayerControls.kt | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt index 31527ba9a..35e4a4a3e 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt @@ -1418,15 +1418,15 @@ fun PlayerControls( @OptIn(androidx.compose.foundation.ExperimentalFoundationApi::class) @Composable fun LiquidCustomButton( - button: app.marlboroadvance.mpvex.preferences.PlayerButton, + button: PlayerViewModel.CustomButtonState, // FIXED: Correctly matching your ViewModel's button state! liquidUIEnabled: Boolean, backdrop: com.kyant.backdrop.Backdrop, - viewModel: app.marlboroadvance.mpvex.ui.player.PlayerViewModel, + viewModel: PlayerViewModel, haptic: androidx.compose.ui.hapticfeedback.HapticFeedback, onInteract: () -> Unit ) { if (liquidUIEnabled) { - app.marlboroadvance.mpvex.ui.components.liquid.TransparentLiquidButton( + TransparentLiquidButton( backdrop = backdrop, onClick = { onInteract() @@ -1438,37 +1438,38 @@ fun LiquidCustomButton( viewModel.callCustomButtonLongPress(button.id) } ) { - androidx.compose.material3.Text( + Text( text = button.label, - modifier = androidx.compose.ui.Modifier.padding(horizontal = 16.dp, vertical = 8.dp).androidx.compose.foundation.basicMarquee(), - style = androidx.compose.material3.MaterialTheme.typography.labelLarge.copy(fontWeight = androidx.compose.ui.text.font.FontWeight.Bold), + // FIXED: Cleaned up the modifier syntax! + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp).basicMarquee(), + style = MaterialTheme.typography.labelLarge.copy(fontWeight = FontWeight.Bold), maxLines = 1, softWrap = false, - color = androidx.compose.ui.graphics.Color.White + color = Color.White ) } } else { - val interactionSource = androidx.compose.runtime.remember { androidx.compose.foundation.interaction.MutableInteractionSource() } - androidx.compose.material3.Surface( - shape = androidx.compose.foundation.shape.CircleShape, - color = androidx.compose.material3.MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.85f), - contentColor = androidx.compose.material3.MaterialTheme.colorScheme.onSecondaryContainer, - border = androidx.compose.foundation.BorderStroke(1.dp, androidx.compose.material3.MaterialTheme.colorScheme.outline.copy(alpha = 0.1f)), - modifier = androidx.compose.ui.Modifier.clip(androidx.compose.foundation.shape.CircleShape).combinedClickable( + val interactionSource = remember { androidx.compose.foundation.interaction.MutableInteractionSource() } + Surface( + shape = CircleShape, + color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.85f), + contentColor = MaterialTheme.colorScheme.onSecondaryContainer, + border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline.copy(alpha = 0.1f)), + modifier = Modifier.clip(CircleShape).combinedClickable( interactionSource = interactionSource, - indication = androidx.compose.material3.ripple(), + indication = ripple(), onClick = { onInteract(); viewModel.callCustomButton(button.id) }, onLongClick = { haptic.performHapticFeedback(androidx.compose.ui.hapticfeedback.HapticFeedbackType.TextHandleMove); onInteract(); viewModel.callCustomButtonLongPress(button.id) } ) ) { - androidx.compose.material3.Text( + Text( text = button.label, - modifier = androidx.compose.ui.Modifier.padding(horizontal = 12.dp, vertical = 6.dp).androidx.compose.foundation.basicMarquee(), - style = androidx.compose.material3.MaterialTheme.typography.labelLarge.copy(fontWeight = androidx.compose.ui.text.font.FontWeight.Bold), + // FIXED: Cleaned up the modifier syntax! + modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp).basicMarquee(), + style = MaterialTheme.typography.labelLarge.copy(fontWeight = FontWeight.Bold), maxLines = 1, softWrap = false ) } } } - From 2d934d34383cd823383ed6e10687549ec290049e Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 00:13:02 +0530 Subject: [PATCH 141/194] Update LiquidComponents.kt --- .../ui/components/liquid/LiquidComponents.kt | 43 +++++++------------ 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt index 8b8d597e5..2dd9d7edb 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt @@ -12,17 +12,14 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.dp import com.kyant.backdrop.Backdrop -import com.kyant.backdrop.drawBackdrop -import app.marlboroadvance.mpvex.ui.theme.LiquidUIEffects import app.marlboroadvance.mpvex.preferences.LiquidTarget /** * UPGRADED: Reusable Transparent Liquid Button - * Now supports Long-Presses and Dynamic Target Settings! + * Now wired directly to the 'Buttons' Tab in your Settings! */ @OptIn(ExperimentalFoundationApi::class) @Composable @@ -30,20 +27,16 @@ fun TransparentLiquidButton( modifier: Modifier = Modifier, backdrop: Backdrop?, shape: Shape = CircleShape, - target: LiquidTarget = LiquidTarget.BUTTON, // Listens to your new Buttons Tab! + target: LiquidTarget = LiquidTarget.BUTTON, // Listens to the Buttons Settings! onClick: () -> Unit, onLongClick: (() -> Unit)? = null, content: @Composable () -> Unit ) { - // If liquid UI is disabled or backdrop is missing, show standard clickable box if (backdrop == null) { - Box( + androidx.compose.foundation.layout.Box( modifier = modifier .clip(shape) - .combinedClickable( - onClick = onClick, - onLongClick = onLongClick - ), + .combinedClickable(onClick = onClick, onLongClick = onLongClick), contentAlignment = Alignment.Center ) { content() } return @@ -51,10 +44,9 @@ fun TransparentLiquidButton( val interactionSource = remember { MutableInteractionSource() } - // Wraps our master glass engine with long-press physics! LiquidGlassSurface( backdrop = backdrop, - target = target, + target = target, // Connects to the lens engine! shape = shape, modifier = modifier .clip(shape) @@ -65,25 +57,26 @@ fun TransparentLiquidButton( onLongClick = onLongClick ) ) { - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + androidx.compose.foundation.layout.Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { content() } } } - /** - * Reusable Liquid Glass Card - * Perfect for the Browser Screen and video list items + * UPGRADED: Reusable Liquid Glass Card + * Now wired directly to the 'Dialogs' Tab in your Settings! */ @Composable fun LiquidGlassCard( modifier: Modifier = Modifier, - backdrop: com.kyant.backdrop.Backdrop?, + backdrop: Backdrop?, onClick: () -> Unit = {}, content: @Composable () -> Unit ) { - // If liquid UI is off, just draw a transparent box so the inner card works normally if (backdrop == null) { androidx.compose.foundation.layout.Box(modifier = modifier) { content() @@ -91,15 +84,11 @@ fun LiquidGlassCard( return } - // If liquid UI is on, apply the effects engine to a transparent box - androidx.compose.foundation.layout.Box( + LiquidGlassSurface( + backdrop = backdrop, + target = LiquidTarget.DIALOG, // Connects to the Dialogs Settings! + shape = RoundedCornerShape(12.dp), modifier = modifier - .drawBackdrop( - backdrop = backdrop, - shape = { RoundedCornerShape(12.dp) }, - effects = LiquidUIEffects.glassCardEffects(enableBlur = true), - onDrawSurface = { drawRect(LiquidUIEffects.glassSurfaceColor) } - ) ) { content() } From baa0dcd116fa3b1dfaf8adf6b577ffa56bb6a53e Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 00:31:38 +0530 Subject: [PATCH 142/194] Update LiquidComponents.kt --- .../ui/components/liquid/LiquidComponents.kt | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt index 2dd9d7edb..0dc02d0cc 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt @@ -12,22 +12,17 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.dp import com.kyant.backdrop.Backdrop -import app.marlboroadvance.mpvex.preferences.LiquidTarget -/** - * UPGRADED: Reusable Transparent Liquid Button - * Now wired directly to the 'Buttons' Tab in your Settings! - */ @OptIn(ExperimentalFoundationApi::class) @Composable fun TransparentLiquidButton( modifier: Modifier = Modifier, backdrop: Backdrop?, shape: Shape = CircleShape, - target: LiquidTarget = LiquidTarget.BUTTON, // Listens to the Buttons Settings! onClick: () -> Unit, onLongClick: (() -> Unit)? = null, content: @Composable () -> Unit @@ -44,9 +39,9 @@ fun TransparentLiquidButton( val interactionSource = remember { MutableInteractionSource() } + // Calls the engine directly—it automatically reads your live DataStore flows! LiquidGlassSurface( backdrop = backdrop, - target = target, // Connects to the lens engine! shape = shape, modifier = modifier .clip(shape) @@ -66,28 +61,21 @@ fun TransparentLiquidButton( } } -/** - * UPGRADED: Reusable Liquid Glass Card - * Now wired directly to the 'Dialogs' Tab in your Settings! - */ @Composable fun LiquidGlassCard( modifier: Modifier = Modifier, backdrop: Backdrop?, - onClick: () -> Unit = {}, + shape: Shape = RoundedCornerShape(12.dp), content: @Composable () -> Unit ) { if (backdrop == null) { - androidx.compose.foundation.layout.Box(modifier = modifier) { - content() - } + androidx.compose.foundation.layout.Box(modifier = modifier) { content() } return } LiquidGlassSurface( backdrop = backdrop, - target = LiquidTarget.DIALOG, // Connects to the Dialogs Settings! - shape = RoundedCornerShape(12.dp), + shape = shape, modifier = modifier ) { content() From a8984e3519ceac878da924539a35dc0d20fd5e8a Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 01:07:00 +0530 Subject: [PATCH 143/194] Update LiquidComponents.kt --- .../mpvex/ui/components/liquid/LiquidComponents.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt index 0dc02d0cc..d5ed289f7 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt @@ -24,6 +24,7 @@ fun TransparentLiquidButton( backdrop: Backdrop?, shape: Shape = CircleShape, onClick: () -> Unit, + target: LiquidTarget = LiquidTarget.BUTTON, onLongClick: (() -> Unit)? = null, content: @Composable () -> Unit ) { From 988f4cc38eb81f7b466ca7cb6fd447308225ba53 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 01:09:01 +0530 Subject: [PATCH 144/194] Update LiquidComponents.kt --- .../mpvex/ui/components/liquid/LiquidComponents.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt index d5ed289f7..3f4a0769a 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt @@ -45,6 +45,7 @@ fun TransparentLiquidButton( backdrop = backdrop, shape = shape, modifier = modifier + target = target, // Connects to the lens engine! .clip(shape) .combinedClickable( interactionSource = interactionSource, From 5f30aadccaa7b24fea1d7add13cec5aacb1b0fc6 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 01:21:38 +0530 Subject: [PATCH 145/194] Update LiquidComponents.kt --- .../mpvex/ui/components/liquid/LiquidComponents.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt index 3f4a0769a..6f1010e49 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt @@ -16,6 +16,9 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.dp import com.kyant.backdrop.Backdrop +// Broadcasts the glass camera to any button that wants it! +val LocalLiquidBackdrop = androidx.compose.runtime.staticCompositionLocalOf { null } + @OptIn(ExperimentalFoundationApi::class) @Composable From 37a0e3dc74b551558f6b0c8016f8df84be41cc1f Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 01:26:01 +0530 Subject: [PATCH 146/194] Update PlayerControls.kt --- .../mpvex/ui/player/controls/PlayerControls.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt index 35e4a4a3e..98a745b3f 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt @@ -167,8 +167,14 @@ fun PlayerControls( val liquidUIEnabled by liquidUIPreferences.liquidUIEnabledFlow.collectAsState(initial = false) // The engine that captures the screen for the blur/lens effects - val backdrop = rememberLayerBackdrop { - drawContent() + val backdrop = rememberLayerBackdrop { drawContent() } + + // Turn on the broadcast tower! + androidx.compose.runtime.CompositionLocalProvider( + app.marlboroadvance.mpvex.ui.components.liquid.LocalLiquidBackdrop provides (if (liquidUIEnabled) backdrop else null) + ) { + Box(modifier = modifier.fillMaxSize()) { + if (controlsShown) { } val hideBackground by appearancePreferences.hidePlayerButtonsBackground.collectAsState() val playerPreferences = koinInject() From ba86eb73fdd23215fd00d991339dd44067ea77ef Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 01:30:38 +0530 Subject: [PATCH 147/194] Update ControlsButton.kt --- .../controls/components/ControlsButton.kt | 126 ++++++++++-------- 1 file changed, 69 insertions(+), 57 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/ControlsButton.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/ControlsButton.kt index 0386edf66..2df4cfdc4 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/ControlsButton.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/ControlsButton.kt @@ -9,81 +9,102 @@ import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.CatchingPokemon import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.ripple import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import app.marlboroadvance.mpvex.preferences.AppearancePreferences -import app.marlboroadvance.mpvex.preferences.preference.collectAsState import app.marlboroadvance.mpvex.ui.player.controls.LocalPlayerButtonsClickEvent import app.marlboroadvance.mpvex.ui.theme.spacing -import org.koin.compose.koinInject -@Suppress("ModifierClickableOrder") +// --- NEW LIQUID IMPORTS --- +import app.marlboroadvance.mpvex.ui.components.liquid.LocalLiquidBackdrop +import app.marlboroadvance.mpvex.ui.components.liquid.TransparentLiquidButton + @OptIn(ExperimentalFoundationApi::class) @Composable fun ControlsButton( icon: ImageVector, - onClick: () -> Unit, modifier: Modifier = Modifier, - onLongClick: () -> Unit = {}, - title: String? = null, color: Color? = null, + title: String? = null, + hideBackground: Boolean = false, + onClick: () -> Unit, + onLongClick: (() -> Unit)? = null, ) { + val clickEvent = LocalPlayerButtonsClickEvent.current val interactionSource = remember { MutableInteractionSource() } - val appearancePreferences = koinInject() - val hideBackground by appearancePreferences.hidePlayerButtonsBackground.collectAsState() + + // 1. TUNE INTO THE BROADCAST TOWER! + val backdrop = LocalLiquidBackdrop.current - val clickEvent = LocalPlayerButtonsClickEvent.current - Surface( - modifier = - modifier - .clip(CircleShape) - .combinedClickable( + // 2. IF LIQUID UI IS ON, DRAW THE GLASS BUTTON! + if (backdrop != null && !hideBackground) { + TransparentLiquidButton( + backdrop = backdrop, + modifier = modifier.size(40.dp), // Match standard sizing onClick = { - clickEvent() - onClick() + clickEvent() + onClick() + }, + onLongClick = onLongClick + ) { + Icon( + imageVector = icon, + contentDescription = title, + tint = color ?: Color.White, // Glass buttons look best with white icons + modifier = Modifier + .padding(MaterialTheme.spacing.small) + .size(20.dp), + ) + } + } else { + // 3. OTHERWISE, FALLBACK TO THE STANDARD BUTTON + Surface( + modifier = + modifier + .clip(CircleShape) + .combinedClickable( + onClick = { + clickEvent() + onClick() + }, + onLongClick = onLongClick, + interactionSource = interactionSource, + indication = ripple(), + ), + shape = CircleShape, + color = if (hideBackground) Color.Transparent else MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), + contentColor = color ?: MaterialTheme.colorScheme.onSurface, + tonalElevation = 0.dp, + shadowElevation = 0.dp, + border = + if (hideBackground) { + null + } else { + BorderStroke( + 1.dp, + MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f), + ) }, - onLongClick = onLongClick, - interactionSource = interactionSource, - indication = ripple(), - ), - shape = CircleShape, - color = if (hideBackground) Color.Transparent else MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), - contentColor = color ?: MaterialTheme.colorScheme.onSurface, - tonalElevation = 0.dp, - shadowElevation = 0.dp, - border = - if (hideBackground) { - null - } else { - BorderStroke( - 1.dp, - MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f), + ) { + Icon( + imageVector = icon, + contentDescription = title, + tint = color ?: MaterialTheme.colorScheme.onSurface, + modifier = + Modifier + .padding(MaterialTheme.spacing.small) + .size(20.dp), ) - }, - ) { - Icon( - imageVector = icon, - contentDescription = title, - tint = color ?: MaterialTheme.colorScheme.onSurface, - modifier = - Modifier - .padding(MaterialTheme.spacing.small) - .size(20.dp), - ) + } } } @@ -103,12 +124,3 @@ fun ControlsGroup( content = content, ) } - -@Preview -@Composable -private fun PreviewControlsButton() { - ControlsButton( - Icons.Default.CatchingPokemon, - onClick = {}, - ) -} From 09bfcd4d06c4cefbf26470714b066bf3095ce495 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 01:37:33 +0530 Subject: [PATCH 148/194] Update LiquidComponents.kt --- .../mpvex/ui/components/liquid/LiquidComponents.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt index 6f1010e49..db97e9e42 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt @@ -27,7 +27,6 @@ fun TransparentLiquidButton( backdrop: Backdrop?, shape: Shape = CircleShape, onClick: () -> Unit, - target: LiquidTarget = LiquidTarget.BUTTON, onLongClick: (() -> Unit)? = null, content: @Composable () -> Unit ) { @@ -48,7 +47,6 @@ fun TransparentLiquidButton( backdrop = backdrop, shape = shape, modifier = modifier - target = target, // Connects to the lens engine! .clip(shape) .combinedClickable( interactionSource = interactionSource, From 9198ac606f4a19428090cd982a818594538b1d19 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 01:52:56 +0530 Subject: [PATCH 149/194] Update PlayerControls.kt --- .../mpvex/ui/player/controls/PlayerControls.kt | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt index 98a745b3f..6ccc3e15e 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt @@ -169,9 +169,6 @@ fun PlayerControls( // The engine that captures the screen for the blur/lens effects val backdrop = rememberLayerBackdrop { drawContent() } - // Turn on the broadcast tower! - androidx.compose.runtime.CompositionLocalProvider( - app.marlboroadvance.mpvex.ui.components.liquid.LocalLiquidBackdrop provides (if (liquidUIEnabled) backdrop else null) ) { Box(modifier = modifier.fillMaxSize()) { if (controlsShown) { @@ -283,15 +280,18 @@ fun PlayerControls( GestureHandler( viewModel = viewModel, interactionSource = interactionSource, - ) + ) { DoubleTapToSeekOvals(doubleTapSeekAmount, seekText, showDoubleTapOvals, showSeekTime, showSeekTime, interactionSource) - CompositionLocalProvider( + CompositionLocalProvider( LocalRippleConfiguration provides playerRippleConfiguration, LocalPlayerButtonsClickEvent provides { resetControlsTimestamp = System.currentTimeMillis() }, LocalContentColor provides Color.White, - ) { + // THE CLEAN BROADCAST TOWER! + app.marlboroadvance.mpvex.ui.components.liquid.LocalLiquidBackdrop provides (if (liquidUIEnabled) backdrop else null), + ) + CompositionLocalProvider( LocalLayoutDirection provides LayoutDirection.Ltr, ) { @@ -1417,10 +1417,11 @@ fun PlayerControls( PlayerPanels( panelShown = panel, onDismissRequest = { onOpenPanel(Panels.None) }, - ) + ) + } } } - + @OptIn(androidx.compose.foundation.ExperimentalFoundationApi::class) @Composable fun LiquidCustomButton( From 515fec898d42c1039c20fa7dd5d8adfb35a3da4e Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 01:54:59 +0530 Subject: [PATCH 150/194] Update ControlsButton.kt From 59bcf350c69d1f352a1b32573bbeeb59770c602c Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 01:57:19 +0530 Subject: [PATCH 151/194] Update ControlsButton.kt From e1bb05395ba67717105381e03fc834bb6cfab82d Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 02:12:39 +0530 Subject: [PATCH 152/194] Update PlayerControls.kt --- .../mpvex/ui/player/controls/PlayerControls.kt | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt index 6ccc3e15e..038732eb9 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt @@ -169,10 +169,6 @@ fun PlayerControls( // The engine that captures the screen for the blur/lens effects val backdrop = rememberLayerBackdrop { drawContent() } - ) { - Box(modifier = modifier.fillMaxSize()) { - if (controlsShown) { - } val hideBackground by appearancePreferences.hidePlayerButtonsBackground.collectAsState() val playerPreferences = koinInject() val audioPreferences = koinInject() @@ -277,21 +273,20 @@ fun PlayerControls( label = "controls_transparent_overlay", ) - GestureHandler( + GestureHandler( viewModel = viewModel, interactionSource = interactionSource, - ) { + ) DoubleTapToSeekOvals(doubleTapSeekAmount, seekText, showDoubleTapOvals, showSeekTime, showSeekTime, interactionSource) - CompositionLocalProvider( + CompositionLocalProvider( LocalRippleConfiguration provides playerRippleConfiguration, LocalPlayerButtonsClickEvent provides { resetControlsTimestamp = System.currentTimeMillis() }, LocalContentColor provides Color.White, // THE CLEAN BROADCAST TOWER! app.marlboroadvance.mpvex.ui.components.liquid.LocalLiquidBackdrop provides (if (liquidUIEnabled) backdrop else null), - ) - + ) { CompositionLocalProvider( LocalLayoutDirection provides LayoutDirection.Ltr, ) { @@ -1420,7 +1415,6 @@ fun PlayerControls( ) } } -} @OptIn(androidx.compose.foundation.ExperimentalFoundationApi::class) @Composable From 12920a32c92324b69a3486ebedc0f4c8910126a8 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 03:07:30 +0530 Subject: [PATCH 153/194] Update LiquidComponents.kt --- .../mpvex/ui/components/liquid/LiquidComponents.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt index db97e9e42..ad062d909 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.dp import com.kyant.backdrop.Backdrop +inport app.marlboroadvance.npvex.preferences.liquidTarget // Broadcasts the glass camera to any button that wants it! val LocalLiquidBackdrop = androidx.compose.runtime.staticCompositionLocalOf { null } @@ -46,6 +47,7 @@ fun TransparentLiquidButton( LiquidGlassSurface( backdrop = backdrop, shape = shape, + target = target, modifier = modifier .clip(shape) .combinedClickable( From 51689c24e744110cd7f74a5f5b800c481a51a050 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 03:09:18 +0530 Subject: [PATCH 154/194] Update LiquidComponents.kt --- .../mpvex/ui/components/liquid/LiquidComponents.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt index ad062d909..07ae0a122 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt @@ -27,6 +27,7 @@ fun TransparentLiquidButton( modifier: Modifier = Modifier, backdrop: Backdrop?, shape: Shape = CircleShape, + target: LiquidTarget = LiquidTarget.BUTTON, onClick: () -> Unit, onLongClick: (() -> Unit)? = null, content: @Composable () -> Unit From 4a6b90c808beb92689c96830e95fb728bb7b65e2 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 10:14:22 +0530 Subject: [PATCH 155/194] Update PlayerUpdates.kt --- .../controls/components/PlayerUpdates.kt | 134 +++++++++++++----- 1 file changed, 102 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/PlayerUpdates.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/PlayerUpdates.kt index 8a33ecd8b..6c9c92c34 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/PlayerUpdates.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/PlayerUpdates.kt @@ -29,35 +29,51 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import app.marlboroadvance.mpvex.R import app.marlboroadvance.mpvex.ui.theme.spacing +import app.marlboroadvance.mpvex.ui.components.liquid.LocalLiquidBackdrop +import app.marlboroadvance.mpvex.ui.components.liquid.LiquidGlassSurface +import app.marlboroadvance.mpvex.preferences.LiquidTarget @Composable fun PlayerUpdate( modifier: Modifier = Modifier, - content: @Composable () -> Unit = {}, + content: @Composable () -> Unit, ) { - Surface( - shape = CircleShape, - color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), - contentColor = MaterialTheme.colorScheme.onSurface, - tonalElevation = 0.dp, - shadowElevation = 0.dp, - border = BorderStroke( - 1.dp, - MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f), - ), - modifier = modifier - .height(45.dp) - .animateContentSize(), - ) { - Box( - modifier = Modifier.padding( - vertical = MaterialTheme.spacing.small, - horizontal = MaterialTheme.spacing.medium, - ), - contentAlignment = Alignment.Center, - ) { - content() - } + val backdrop = LocalLiquidBackdrop.current + if (backdrop != null) { + LiquidGlassSurface( + backdrop = backdrop, + target = LiquidTarget.BUTTON, + shape = CircleShape, + modifier = modifier + ) { + Box( + modifier = Modifier + .padding(vertical = 4.dp, horizontal = 12.dp) + .height(24.dp) + .widthIn(min = 24.dp), + contentAlignment = Alignment.Center, + ) { + content() + } + } + } else { + Surface( + modifier = modifier, + shape = CircleShape, + color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), + contentColor = MaterialTheme.colorScheme.onSurface, + border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), + ) { + Box( + modifier = Modifier + .padding(vertical = 4.dp, horizontal = 12.dp) + .height(24.dp) + .widthIn(min = 24.dp), + contentAlignment = Alignment.Center, + ) { + content() + } + } } } @@ -67,13 +83,14 @@ fun TextPlayerUpdate( text: String, modifier: Modifier = Modifier, ) { + val backdrop = LocalLiquidBackdrop.current PlayerUpdate(modifier) { Text( text = text, fontFamily = FontFamily.Monospace, fontWeight = FontWeight.Bold, textAlign = TextAlign.Center, - color = MaterialTheme.colorScheme.onSurface, + color = if (backdrop != null) Color.White else MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.bodyMedium, ) } @@ -87,17 +104,13 @@ fun MultipleSpeedPlayerUpdate( CompactSpeedIndicator(currentSpeed = currentSpeed, modifier = modifier) } -@Composable -@Preview -private fun PreviewMultipleSpeedPlayerUpdate() { - MultipleSpeedPlayerUpdate(currentSpeed = 2f) -} @Composable fun SeekPlayerUpdate( currentTime: String, seekDelta: String, modifier: Modifier = Modifier, ) { + val backdrop = LocalLiquidBackdrop.current PlayerUpdate(modifier) { Row( verticalAlignment = Alignment.CenterVertically, @@ -107,7 +120,7 @@ fun SeekPlayerUpdate( fontFamily = FontFamily.Monospace, fontWeight = FontWeight.Bold, textAlign = TextAlign.Center, - color = MaterialTheme.colorScheme.onSurface, + color = if (backdrop != null) Color.White else MaterialTheme.colorScheme.onSurface, ) Text( @@ -116,8 +129,65 @@ fun SeekPlayerUpdate( fontWeight = FontWeight.Normal, textAlign = TextAlign.Center, style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), + color = if (backdrop != null) Color.White else MaterialTheme.colorScheme.primary, ) } } } + +@Composable +fun DoubleTapSeekPlayerUpdate( + isRight: Boolean, + seekAmount: Int, + seekText: String?, + modifier: Modifier = Modifier, +) { + val backdrop = LocalLiquidBackdrop.current + val directionText = if (isRight) { + stringResource(R.string.player_double_tap_seek_forward) + } else { + stringResource(R.string.player_double_tap_seek_rewind) + } + + PlayerUpdate(modifier.animateContentSize()) { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + if (!isRight) { + Icon( + imageVector = Icons.Default.DoubleArrow, + contentDescription = null, + tint = if (backdrop != null) Color.White else MaterialTheme.colorScheme.onSurface, + modifier = + Modifier + .clip(CircleShape) + .background(if (backdrop != null) Color.White.copy(alpha = 0.2f) else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f)) + .padding(4.dp) + .Modifier.rotate(180f), + ) + } + + Text( + text = seekText ?: "$directionText $seekAmount ${stringResource(R.string.player_double_tap_seek_seconds)}", + fontFamily = FontFamily.Monospace, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + color = if (backdrop != null) Color.White else MaterialTheme.colorScheme.onSurface, + modifier = Modifier.padding(horizontal = 8.dp), + ) + + if (isRight) { + Icon( + imageVector = Icons.Default.DoubleArrow, + contentDescription = null, + tint = if (backdrop != null) Color.White else MaterialTheme.colorScheme.onSurface, + modifier = + Modifier + .clip(CircleShape) + .background(if (backdrop != null) Color.White.copy(alpha = 0.2f) else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f)) + .padding(4.dp), + ) + } + } + } +} From 073fa956fbef1d71e17367ab296ca519ff66d5fd Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 10:16:01 +0530 Subject: [PATCH 156/194] Update SpeedControlSlider.kt --- .../controls/components/SpeedControlSlider.kt | 318 +++++++++--------- 1 file changed, 150 insertions(+), 168 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/SpeedControlSlider.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/SpeedControlSlider.kt index 0e585c8ae..f8b2be84d 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/SpeedControlSlider.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/SpeedControlSlider.kt @@ -47,191 +47,173 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import app.marlboroadvance.mpvex.ui.theme.spacing import kotlinx.coroutines.delay +import app.marlboroadvance.mpvex.ui.components.liquid.LocalLiquidBackdrop +import app.marlboroadvance.mpvex.ui.components.liquid.LiquidGlassSurface +import app.marlboroadvance.mpvex.preferences.LiquidTarget -/** - * A compact speed control display that shows available speed options (0.25x to 4x) - * with an indicator showing the current speed. Styled to match the zoom overlay. - * - * @param currentSpeed The current playback speed (0.25f to 4.0f) - * @param modifier Optional modifier for the container - */ @Composable -fun SpeedControlSlider( +fun CompactSpeedIndicator( currentSpeed: Float, modifier: Modifier = Modifier, ) { - // Speed presets from 0.25x to 4x - val speedPresets = listOf(0.25f, 0.5f, 1.0f, 1.5f, 2.0f, 2.5f, 3.0f, 4.0f) - - // Find the index of current speed in presets - val currentIndex = speedPresets.indexOfFirst { - kotlin.math.abs(it - currentSpeed) < 0.05f - }.coerceIn(0, speedPresets.size - 1) - - val primaryColor = MaterialTheme.colorScheme.primary - val onSurfaceColor = MaterialTheme.colorScheme.onSurface - // Use a Surface with less rounded corners instead of CircleShape - Surface( - shape = RoundedCornerShape(12.dp), - color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), - contentColor = MaterialTheme.colorScheme.onSurface, - tonalElevation = 0.dp, - shadowElevation = 0.dp, - border = BorderStroke( - 1.dp, - MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f), - ), - modifier = modifier.animateContentSize(), - ) { - Box( - modifier = Modifier.padding( - vertical = MaterialTheme.spacing.small, - horizontal = MaterialTheme.spacing.medium, - ), - contentAlignment = Alignment.Center, - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(8.dp), + val backdrop = LocalLiquidBackdrop.current + + if (backdrop != null) { + LiquidGlassSurface( + backdrop = backdrop, + target = LiquidTarget.BUTTON, + shape = RoundedCornerShape(100.dp), + modifier = modifier ) { - // Speed labels - compact version - Row( - modifier = Modifier.width(280.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - speedPresets.forEach { speed -> - val isCurrentSpeed = kotlin.math.abs(currentSpeed - speed) < 0.05f - Text( - text = "${speed.format()}x", - fontSize = if (isCurrentSpeed) 13.sp else 10.sp, - fontWeight = if (isCurrentSpeed) FontWeight.Bold else FontWeight.Normal, - color = if (isCurrentSpeed) { - primaryColor - } else { - onSurfaceColor.copy(alpha = 0.7f) - }, - ) - } - } - - // Compact slider track - Canvas( - modifier = Modifier - .width(280.dp) - .height(3.dp), - ) { - val trackWidth = size.width - val trackHeight = 3.dp.toPx() - val centerY = size.height / 2 - val segmentWidth = trackWidth / (speedPresets.size - 1) - - // Background track - drawLine( - color = onSurfaceColor.copy(alpha = 0.35f), - start = Offset(0f, centerY), - end = Offset(trackWidth, centerY), - strokeWidth = trackHeight, - cap = StrokeCap.Round, - ) - - // Progress track (filled portion up to current speed) - val progressX = currentIndex * segmentWidth - drawLine( - color = primaryColor, - start = Offset(0f, centerY), - end = Offset(progressX, centerY), - strokeWidth = trackHeight, - cap = StrokeCap.Round, - ) - - // Draw tick marks for each speed preset - speedPresets.forEachIndexed { index, _ -> - val tickX = index * segmentWidth - drawCircle( - color = if (index <= currentIndex) { - primaryColor - } else { - onSurfaceColor.copy(alpha = 0.7f) - }, - radius = 2.5.dp.toPx(), - center = Offset(tickX, centerY), - ) - } - } - - // Current speed display - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center, - ) { - Icon( - imageVector = Icons.Filled.FastForward, - contentDescription = null, - modifier = Modifier.size(16.dp), + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + modifier = Modifier.padding(horizontal = MaterialTheme.spacing.medium, vertical = MaterialTheme.spacing.small) + ) { + Icon( + imageVector = Icons.Filled.FastForward, + contentDescription = null, + modifier = Modifier.size(16.dp), + tint = Color.White + ) + Text( + text = "${currentSpeed.format()}x", + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + style = MaterialTheme.typography.bodyLarge, + modifier = Modifier.padding(start = 4.dp), + color = Color.White + ) + } + } + } else { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + modifier = modifier + .background( + color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), + shape = RoundedCornerShape(100.dp) ) - Text( - text = "${currentSpeed.format()}x Speed Playing", - fontSize = 14.sp, - fontWeight = FontWeight.Bold, - style = MaterialTheme.typography.bodyLarge, - modifier = Modifier.padding(start = 4.dp), + .border( + BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), + shape = RoundedCornerShape(100.dp) ) - } + .padding(horizontal = MaterialTheme.spacing.medium, vertical = MaterialTheme.spacing.small) + ) { + Icon( + imageVector = Icons.Filled.FastForward, + contentDescription = null, + modifier = Modifier.size(16.dp), + tint = MaterialTheme.colorScheme.onSurface + ) + Text( + text = "${currentSpeed.format()}x", + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + style = MaterialTheme.typography.bodyLarge, + modifier = Modifier.padding(start = 4.dp), + color = MaterialTheme.colorScheme.onSurface + ) } - } } } -/** - * A compact speed indicator that shows just the icon and speed value. - * Used when dynamic speed overlay is collapsed or disabled. - * - * @param currentSpeed The current playback speed - * @param modifier Optional modifier for the container - */ +private fun Float.format(): String { + return when { + this % 1.0f == 0.0f -> String.format("%.0f", this) + this % 0.5f == 0.0f -> String.format("%.1f", this) + else -> String.format("%.2f", this) + } +} + @Composable -fun CompactSpeedIndicator( +fun SpeedControlSlider( currentSpeed: Float, + speedPresets: List = listOf(0.25f, 0.5f, 1.0f, 1.25f, 1.5f, 2.0f, 3.0f, 4.0f), modifier: Modifier = Modifier, ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center, - modifier = modifier - .background( - color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), - shape = RoundedCornerShape(100.dp) - ) - .border( - BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), - shape = RoundedCornerShape(100.dp) - ) - .padding(horizontal = MaterialTheme.spacing.medium, vertical = MaterialTheme.spacing.small) - ) { - Icon( - imageVector = Icons.Filled.FastForward, - contentDescription = null, - modifier = Modifier.size(16.dp), - tint = MaterialTheme.colorScheme.onSurface - ) - Text( - text = "${currentSpeed.format()}x", - fontSize = 14.sp, - fontWeight = FontWeight.Bold, - style = MaterialTheme.typography.bodyLarge, - modifier = Modifier.padding(start = 4.dp), - color = MaterialTheme.colorScheme.onSurface // Explicitly set color - ) + var isExpanded by remember { mutableStateOf(false) } + + LaunchedEffect(currentSpeed) { + if (!isExpanded) { + isExpanded = true + } + delay(1500) + isExpanded = false } -} -/** - * Format float speed value to display with minimal decimal places - */ -private fun Float.format(): String { - return when { - this % 1.0f == 0.0f -> this.toInt().toString() - else -> String.format("%.2f", this).trimEnd('0').trimEnd('.') + Box( + modifier = modifier.height(36.dp), + contentAlignment = Alignment.Center + ) { + AnimatedVisibility( + visible = isExpanded, + enter = fadeIn() + expandHorizontally( + expandFrom = Alignment.CenterHorizontally, + animationSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy, stiffness = Spring.StiffnessMedium) + ), + exit = fadeOut(animationSpec = tween(300)) + shrinkHorizontally( + shrinkTowards = Alignment.CenterHorizontally, + animationSpec = tween(300, delayMillis = 100) + ) + ) { + val primaryColor = MaterialTheme.colorScheme.primary + val onSurfaceColor = MaterialTheme.colorScheme.onSurface + + Row( + modifier = Modifier + .background( + color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.85f), + shape = RoundedCornerShape(100.dp) + ) + .border( + BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), + shape = RoundedCornerShape(100.dp) + ) + .padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + Canvas(modifier = Modifier.width(100.dp).height(24.dp)) { + val trackWidth = size.width + val trackHeight = 4.dp.toPx() + val centerY = size.height / 2 + val segmentWidth = trackWidth / (speedPresets.size - 1) + + drawLine( + color = onSurfaceColor.copy(alpha = 0.35f), + start = Offset(0f, centerY), + end = Offset(trackWidth, centerY), + strokeWidth = trackHeight, + cap = StrokeCap.Round, + ) + + val progressX = (currentSpeed - speedPresets.first()) / (speedPresets.last() - speedPresets.first()) * trackWidth + + drawLine( + color = primaryColor, + start = Offset(0f, centerY), + end = Offset(progressX.coerceIn(0f, trackWidth), centerY), + strokeWidth = trackHeight, + cap = StrokeCap.Round, + ) + + drawCircle( + color = primaryColor, + radius = 6.dp.toPx(), + center = Offset(progressX.coerceIn(0f, trackWidth), centerY) + ) + } + + Text( + text = "${currentSpeed.format()}x", + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurface + ) + } + } } } From 317847327fbae8f99729a986a49f9b9110ab0a23 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 10:26:55 +0530 Subject: [PATCH 157/194] Update CurrentChapter.kt --- .../controls/components/CurrentChapter.kt | 212 ++++++++++++------ 1 file changed, 140 insertions(+), 72 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/CurrentChapter.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/CurrentChapter.kt index 660dc7a69..a443ece32 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/CurrentChapter.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/CurrentChapter.kt @@ -34,91 +34,159 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import app.marlboroadvance.mpvex.preferences.AppearancePreferences -import app.marlboroadvance.mpvex.preferences.preference.collectAsState -import app.marlboroadvance.mpvex.ui.theme.controlColor import app.marlboroadvance.mpvex.ui.theme.spacing import dev.vivvvek.seeker.Segment import `is`.xyz.mpv.Utils -import org.koin.compose.koinInject +import app.marlboroadvance.mpvex.ui.components.liquid.LocalLiquidBackdrop +import app.marlboroadvance.mpvex.ui.components.liquid.LiquidGlassSurface +import app.marlboroadvance.mpvex.preferences.LiquidTarget @Composable fun CurrentChapter( chapter: Segment, + onClick: () -> Unit, modifier: Modifier = Modifier, - onClick: () -> Unit = {}, ) { - val appearancePreferences = koinInject() + val backdrop = LocalLiquidBackdrop.current - Surface( - modifier = - modifier - .height(45.dp) - .widthIn(max = 220.dp) - .clip(RoundedCornerShape(50)) - .clickable(onClick = onClick), - shape = RoundedCornerShape(50), - color = - MaterialTheme.colorScheme.surfaceContainer.copy( - alpha = 0.55f, - ), - contentColor = MaterialTheme.colorScheme.onSurface, - tonalElevation = 0.dp, - border = - BorderStroke( - 1.dp, - MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f), - ), - ) { - AnimatedContent( - targetState = chapter, - modifier = Modifier.padding(horizontal = MaterialTheme.spacing.medium, vertical = MaterialTheme.spacing.small), - transitionSpec = { - if (targetState.start > initialState.start) { - (slideInVertically { height -> height } + fadeIn()) - .togetherWith(slideOutVertically { height -> -height } + fadeOut()) - } else { - (slideInVertically { height -> -height } + fadeIn()) - .togetherWith(slideOutVertically { height -> height } + fadeOut()) - }.using( - SizeTransform(clip = false), - ) - }, - label = "Chapter", - ) { currentChapter -> - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.extraSmall), + if (backdrop != null) { + LiquidGlassSurface( + backdrop = backdrop, + target = LiquidTarget.BUTTON, + shape = RoundedCornerShape(100.dp), + modifier = modifier + .clip(RoundedCornerShape(100.dp)) + .clickable(onClick = onClick) ) { - Text( - text = Utils.prettyTime(currentChapter.start.toInt()), - fontWeight = FontWeight.Bold, - style = MaterialTheme.typography.bodyMedium, - maxLines = 1, - overflow = TextOverflow.Clip, - color = MaterialTheme.colorScheme.primary, - ) - currentChapter.name.let { - Text( - text = Typography.bullet.toString(), - textAlign = TextAlign.Center, - style = MaterialTheme.typography.bodyMedium, - maxLines = 1, - color = MaterialTheme.colorScheme.onSurface, - overflow = TextOverflow.Clip, - ) - Text( - text = it, - textAlign = TextAlign.Center, - style = MaterialTheme.typography.bodyMedium, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - fontWeight = FontWeight.ExtraBold, - color = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.basicMarquee(), + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(horizontal = MaterialTheme.spacing.medium, vertical = 6.dp), + horizontalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.small), + ) { + Icon( + imageVector = Icons.Default.Bookmarks, + contentDescription = "Chapter", + tint = Color.White, + modifier = Modifier.size(16.dp), + ) + AnimatedContent( + targetState = chapter, + transitionSpec = { + if (targetState.start > initialState.start) { + (slideInVertically { height -> height } + fadeIn()) + .togetherWith(slideOutVertically { height -> -height } + fadeOut()) + } else { + (slideInVertically { height -> -height } + fadeIn()) + .togetherWith(slideOutVertically { height -> height } + fadeOut()) + }.using(SizeTransform(clip = false)) + }, + label = "Chapter", + ) { currentChapter -> + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.extraSmall), + ) { + Text( + text = Utils.prettyTime(currentChapter.start.toInt()), + fontWeight = FontWeight.Bold, + style = MaterialTheme.typography.bodyMedium, + maxLines = 1, + overflow = TextOverflow.Clip, + color = MaterialTheme.colorScheme.primary, + ) + currentChapter.name.let { + Text( + text = "•", + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyMedium, + maxLines = 1, + color = Color.White, + overflow = TextOverflow.Clip, + ) + Text( + text = it, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyMedium, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontWeight = FontWeight.ExtraBold, + color = Color.White, + modifier = Modifier.basicMarquee(), + ) + } + } + } + } + } + } else { + Surface( + modifier = modifier + .clip(RoundedCornerShape(100.dp)) + .clickable(onClick = onClick), + shape = RoundedCornerShape(100.dp), + color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), + contentColor = MaterialTheme.colorScheme.onSurface, + border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(horizontal = MaterialTheme.spacing.medium, vertical = 6.dp), + horizontalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.small), + ) { + Icon( + imageVector = Icons.Default.Bookmarks, + contentDescription = "Chapter", + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.size(16.dp), ) + AnimatedContent( + targetState = chapter, + transitionSpec = { + if (targetState.start > initialState.start) { + (slideInVertically { height -> height } + fadeIn()) + .togetherWith(slideOutVertically { height -> -height } + fadeOut()) + } else { + (slideInVertically { height -> -height } + fadeIn()) + .togetherWith(slideOutVertically { height -> height } + fadeOut()) + }.using(SizeTransform(clip = false)) + }, + label = "Chapter", + ) { currentChapter -> + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.extraSmall), + ) { + Text( + text = Utils.prettyTime(currentChapter.start.toInt()), + fontWeight = FontWeight.Bold, + style = MaterialTheme.typography.bodyMedium, + maxLines = 1, + overflow = TextOverflow.Clip, + color = MaterialTheme.colorScheme.primary, + ) + currentChapter.name.let { + Text( + text = "•", + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyMedium, + maxLines = 1, + color = MaterialTheme.colorScheme.onSurface, + overflow = TextOverflow.Clip, + ) + Text( + text = it, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyMedium, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontWeight = FontWeight.ExtraBold, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.basicMarquee(), + ) + } + } + } } } - } } } From d3ed86e6367873467241038afdb64a920be56669 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 10:32:17 +0530 Subject: [PATCH 158/194] Update SlideToUnlock.kt --- .../controls/components/SlideToUnlock.kt | 124 ++++++++++-------- 1 file changed, 66 insertions(+), 58 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/SlideToUnlock.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/SlideToUnlock.kt index 3cc4eda95..998822fa2 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/SlideToUnlock.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/SlideToUnlock.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.gestures.detectHorizontalDragGestures import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding @@ -19,6 +20,7 @@ import androidx.compose.material.icons.filled.Lock import androidx.compose.material.icons.filled.LockOpen import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -33,13 +35,14 @@ import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.layout.onSizeChanged -import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import kotlinx.coroutines.launch import kotlin.math.roundToInt +import app.marlboroadvance.mpvex.ui.components.liquid.LocalLiquidBackdrop +import app.marlboroadvance.mpvex.ui.components.liquid.LiquidGlassSurface +import app.marlboroadvance.mpvex.preferences.LiquidTarget @Composable fun SlideToUnlock( @@ -47,58 +50,75 @@ fun SlideToUnlock( modifier: Modifier = Modifier, onDraggingChanged: (Boolean) -> Unit = {}, ) { - val coroutineScope = rememberCoroutineScope() - - var containerWidthPx by remember { mutableFloatStateOf(0f) } - val sliderSize = 56.dp - + val maxOffset = with(LocalDensity.current) { (200.dp - 64.dp).toPx() } val offsetX = remember { Animatable(0f) } + val coroutineScope = rememberCoroutineScope() var isDragging by remember { mutableStateOf(false) } - + + val showUnlockIcon = offsetX.value > maxOffset * 0.8f + Box( - modifier = modifier - .width(200.dp) - .height(64.dp) - .clip(RoundedCornerShape(32.dp)) - .background(Color.Black.copy(alpha = 0.6f)) - .padding(4.dp) - .onSizeChanged { size -> - containerWidthPx = size.width.toFloat() - }, + modifier = modifier.width(200.dp).height(64.dp), + contentAlignment = Alignment.CenterStart, ) { - val sliderSizePx = containerWidthPx * (56f / 192f) // Accounting for padding (200 - 8) - val maxOffset = if (containerWidthPx > 0f) containerWidthPx - sliderSizePx else 0f - val unlockThreshold = if (maxOffset > 0f) maxOffset * 0.85f else Float.MAX_VALUE - - // Background text - slightly to the right - Box( - modifier = Modifier - .matchParentSize() - .padding(start = 55.dp) - .alpha(if (maxOffset > 0f) 1f - (offsetX.value / maxOffset).coerceIn(0f, 1f) else 1f), - contentAlignment = Alignment.Center, + val backdrop = LocalLiquidBackdrop.current + if (backdrop != null) { + LiquidGlassSurface( + modifier = Modifier.width(200.dp).height(64.dp), + backdrop = backdrop, + target = LiquidTarget.BUTTON, + shape = RoundedCornerShape(100.dp), + ) { + Box( + modifier = Modifier + .fillMaxSize() + .background( + MaterialTheme.colorScheme.primary.copy( + alpha = (offsetX.value / maxOffset).coerceIn(0f, 0.5f) + ) + ) + ) + } + } else { + Surface( + modifier = Modifier.width(200.dp).height(64.dp).alpha(0.5f), + shape = RoundedCornerShape(100.dp), + color = MaterialTheme.colorScheme.surfaceContainer, + border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), + ) { + Box( + modifier = Modifier + .fillMaxSize() + .background( + MaterialTheme.colorScheme.primary.copy( + alpha = (offsetX.value / maxOffset).coerceIn(0f, 0.5f) + ) + ) + ) + } + } + + Row( + modifier = Modifier.fillMaxSize(), + verticalAlignment = Alignment.CenterVertically, ) { + Spacer(modifier = Modifier.width(72.dp)) Text( - text = "Slide to Unlock", - color = Color.White.copy(alpha = 0.7f), - fontSize = 16.sp, - fontWeight = FontWeight.Medium, + text = "Slide to unlock", + style = MaterialTheme.typography.bodyMedium, + color = if (backdrop != null) Color.White.copy(alpha = 0.7f) else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), + modifier = Modifier.alpha(1f - (offsetX.value / (maxOffset * 0.5f)).coerceIn(0f, 1f)), ) } - - // Slider button - val progress = if (maxOffset > 0f) (offsetX.value / maxOffset).coerceIn(0f, 1f) else 0f - val showUnlockIcon = progress > 0.5f - + Box( modifier = Modifier .offset { IntOffset(offsetX.value.roundToInt(), 0) } - .size(sliderSize) + .padding(4.dp) + .size(56.dp) .clip(CircleShape) .background(MaterialTheme.colorScheme.primary) - .pointerInput(containerWidthPx) { - if (containerWidthPx <= 0f) return@pointerInput - + .pointerInput(Unit) { detectHorizontalDragGestures( onDragStart = { isDragging = true @@ -107,16 +127,11 @@ fun SlideToUnlock( onDragEnd = { isDragging = false onDraggingChanged(false) - if (offsetX.value >= unlockThreshold) { - // Unlock triggered - instantly unlock without animation + if (offsetX.value >= maxOffset * 0.9f) { onUnlock() } else { - // Snap back coroutineScope.launch { - offsetX.animateTo( - targetValue = 0f, - animationSpec = tween(durationMillis = 300), - ) + offsetX.animateTo(targetValue = 0f, animationSpec = tween(durationMillis = 300)) } } }, @@ -124,10 +139,7 @@ fun SlideToUnlock( isDragging = false onDraggingChanged(false) coroutineScope.launch { - offsetX.animateTo( - targetValue = 0f, - animationSpec = tween(durationMillis = 300), - ) + offsetX.animateTo(targetValue = 0f, animationSpec = tween(durationMillis = 300)) } }, onHorizontalDrag = { _, dragAmount -> @@ -140,11 +152,7 @@ fun SlideToUnlock( }, contentAlignment = Alignment.Center, ) { - // Crossfade between lock and unlock icons - androidx.compose.animation.Crossfade( - targetState = showUnlockIcon, - animationSpec = tween(durationMillis = 200), - ) { showUnlock -> + androidx.compose.animation.Crossfade(targetState = showUnlockIcon, animationSpec = tween(durationMillis = 200)) { showUnlock -> Icon( imageVector = if (showUnlock) Icons.Filled.LockOpen else Icons.Filled.Lock, contentDescription = "Slide to unlock", From 36266009cb206f9484f765bc85186dafa1847e87 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 10:34:22 +0530 Subject: [PATCH 159/194] Update PlayerControlsShared.kt --- .../player/controls/PlayerControlsShared.kt | 824 +++++------------- 1 file changed, 227 insertions(+), 597 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControlsShared.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControlsShared.kt index 4e29b1add..b8ef628e3 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControlsShared.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControlsShared.kt @@ -11,6 +11,7 @@ import androidx.compose.animation.shrinkHorizontally import androidx.compose.animation.togetherWith import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -83,6 +84,12 @@ import app.marlboroadvance.mpvex.ui.theme.controlColor import app.marlboroadvance.mpvex.ui.theme.spacing import dev.vivvvek.seeker.Segment +// --- NEW LIQUID IMPORTS --- +import app.marlboroadvance.mpvex.ui.components.liquid.LocalLiquidBackdrop +import app.marlboroadvance.mpvex.ui.components.liquid.TransparentLiquidButton +import app.marlboroadvance.mpvex.ui.components.liquid.LiquidGlassSurface +import app.marlboroadvance.mpvex.preferences.LiquidTarget + @Composable fun RenderPlayerButton( button: PlayerButton, @@ -104,6 +111,8 @@ fun RenderPlayerButton( buttonSize: Dp = 40.dp, ) { val clickEvent = LocalPlayerButtonsClickEvent.current + val backdrop = LocalLiquidBackdrop.current + when (button) { PlayerButton.BACK_ARROW -> { ControlsButton( @@ -116,70 +125,49 @@ fun RenderPlayerButton( PlayerButton.VIDEO_TITLE -> { val playlistModeEnabled = viewModel.hasPlaylistSupport() - val titleInteractionSource = remember { MutableInteractionSource() } - Surface( - shape = CircleShape, - color = - if (hideBackground) { - Color.Transparent - } else { - MaterialTheme.colorScheme.surfaceContainer.copy( - alpha = 0.55f, - ) - }, - contentColor = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, - tonalElevation = 0.dp, - shadowElevation = 0.dp, - border = - if (hideBackground) { - null - } else { - BorderStroke( - 1.dp, - MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f), - ) - }, - modifier = - Modifier - .height(buttonSize) - .clip(CircleShape) - .clickable( - interactionSource = titleInteractionSource, - indication = ripple( - bounded = true, - ), - enabled = playlistModeEnabled, - onClick = { - clickEvent() - onOpenSheet(Sheets.Playlist) - }, - ), - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = - Modifier - .padding( - horizontal = MaterialTheme.spacing.extraSmall, - vertical = MaterialTheme.spacing.small, - ), + if (backdrop != null && !hideBackground) { + TransparentLiquidButton( + backdrop = backdrop, + modifier = Modifier.height(buttonSize), + shape = CircleShape, + onClick = { clickEvent(); onOpenSheet(Sheets.Playlist) } ) { - Text( - mediaTitle ?: "", - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.weight(1f, fill = false), - ) - viewModel.getPlaylistInfo()?.let { playlistInfo -> + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(horizontal = MaterialTheme.spacing.medium, vertical = MaterialTheme.spacing.small) + ) { Text( - " • $playlistInfo", - maxLines = 1, - overflow = TextOverflow.Visible, - style = MaterialTheme.typography.bodySmall, + mediaTitle ?: "", maxLines = 1, overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.bodyMedium, color = Color.White, + modifier = Modifier.weight(1f, fill = false), ) + viewModel.getPlaylistInfo()?.let { playlistInfo -> + Text(" • $playlistInfo", maxLines = 1, overflow = TextOverflow.Visible, color = Color.White, style = MaterialTheme.typography.bodySmall) + } + } + } + } else { + Surface( + shape = CircleShape, + color = if (hideBackground) Color.Transparent else MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), + contentColor = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, + tonalElevation = 0.dp, shadowElevation = 0.dp, + border = if (hideBackground) null else BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), + modifier = Modifier.height(buttonSize).clip(CircleShape).clickable( + interactionSource = titleInteractionSource, indication = ripple(bounded = true), + enabled = playlistModeEnabled, onClick = { clickEvent(); onOpenSheet(Sheets.Playlist) } + ) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(horizontal = MaterialTheme.spacing.extraSmall, vertical = MaterialTheme.spacing.small) + ) { + Text(mediaTitle ?: "", maxLines = 1, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.weight(1f, fill = false)) + viewModel.getPlaylistInfo()?.let { playlistInfo -> + Text(" • $playlistInfo", maxLines = 1, overflow = TextOverflow.Visible, style = MaterialTheme.typography.bodySmall) + } } } } @@ -188,8 +176,7 @@ fun RenderPlayerButton( PlayerButton.BOOKMARKS_CHAPTERS -> { if (chapters.isNotEmpty()) { ControlsButton( - Icons.Default.Bookmarks, - onClick = { onOpenSheet(Sheets.Chapters) }, + Icons.Default.Bookmarks, onClick = { onOpenSheet(Sheets.Chapters) }, color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(buttonSize), ) @@ -198,120 +185,74 @@ fun RenderPlayerButton( PlayerButton.PLAYBACK_SPEED -> { if (isSpeedNonOne) { - Surface( - shape = CircleShape, - color = if (hideBackground) Color.Transparent else MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), - contentColor = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, - tonalElevation = 0.dp, - shadowElevation = 0.dp, - border = if (hideBackground) null else BorderStroke( - 1.dp, - MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f), - ), - modifier = Modifier - .height(buttonSize) - .clip(CircleShape) - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = ripple(bounded = true), - onClick = { - clickEvent() - onOpenSheet(Sheets.PlaybackSpeed) - }, - ), - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.extraSmall), - modifier = Modifier.padding( - horizontal = MaterialTheme.spacing.small, - vertical = MaterialTheme.spacing.small, - ), + if (backdrop != null && !hideBackground) { + TransparentLiquidButton( + backdrop = backdrop, modifier = Modifier.height(buttonSize), shape = CircleShape, + onClick = { clickEvent(); onOpenSheet(Sheets.PlaybackSpeed) } ) { - Icon( - imageVector = Icons.Default.Speed, - contentDescription = "Playback Speed", - tint = MaterialTheme.colorScheme.primary, - modifier = Modifier.size(20.dp), - ) - Text( - text = String.format("%.2fx", playbackSpeed), - maxLines = 1, - style = MaterialTheme.typography.bodyMedium, + Row( + verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.extraSmall), + modifier = Modifier.padding(horizontal = MaterialTheme.spacing.small, vertical = MaterialTheme.spacing.small) + ) { + Icon(Icons.Default.Speed, contentDescription = "Playback Speed", tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(20.dp)) + Text(String.format("%.2fx", playbackSpeed), maxLines = 1, color = Color.White, style = MaterialTheme.typography.bodyMedium) + } + } + } else { + Surface( + shape = CircleShape, + color = if (hideBackground) Color.Transparent else MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), + contentColor = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, + border = if (hideBackground) null else BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), + modifier = Modifier.height(buttonSize).clip(CircleShape).clickable( + interactionSource = remember { MutableInteractionSource() }, indication = ripple(bounded = true), + onClick = { clickEvent(); onOpenSheet(Sheets.PlaybackSpeed) } ) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.extraSmall), + modifier = Modifier.padding(horizontal = MaterialTheme.spacing.small, vertical = MaterialTheme.spacing.small) + ) { + Icon(Icons.Default.Speed, contentDescription = "Playback Speed", tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(20.dp)) + Text(String.format("%.2fx", playbackSpeed), maxLines = 1, style = MaterialTheme.typography.bodyMedium) + } } } } else { - ControlsButton( - icon = Icons.Default.Speed, - onClick = { onOpenSheet(Sheets.PlaybackSpeed) }, - color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, - modifier = Modifier.size(buttonSize), - ) + ControlsButton(icon = Icons.Default.Speed, onClick = { onOpenSheet(Sheets.PlaybackSpeed) }, color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(buttonSize)) } } PlayerButton.DECODER -> { - Surface( - shape = CircleShape, - color = - if (hideBackground) { - Color.Transparent - } else { - MaterialTheme.colorScheme.surfaceContainer.copy( - alpha = 0.55f, - ) - }, - contentColor = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, - tonalElevation = 0.dp, - shadowElevation = 0.dp, - border = - if (hideBackground) { - null - } else { - BorderStroke( - 1.dp, - MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f), - ) - }, - modifier = Modifier - .height(buttonSize) - .clip(CircleShape) - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = ripple(bounded = true), - onClick = { - clickEvent() - onOpenSheet(Sheets.Decoders) - }, - ), - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = - Modifier - .padding( - horizontal = MaterialTheme.spacing.medium, - vertical = MaterialTheme.spacing.small, - ), - ) { - Text( - text = decoder.title, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.bodyMedium, + if (backdrop != null && !hideBackground) { + TransparentLiquidButton( + backdrop = backdrop, modifier = Modifier.height(buttonSize), shape = CircleShape, + onClick = { clickEvent(); onOpenSheet(Sheets.Decoders) } + ) { + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = MaterialTheme.spacing.medium, vertical = MaterialTheme.spacing.small)) { + Text(decoder.title, maxLines = 1, color = Color.White, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodyMedium) + } + } + } else { + Surface( + shape = CircleShape, + color = if (hideBackground) Color.Transparent else MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), + contentColor = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, + border = if (hideBackground) null else BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), + modifier = Modifier.height(buttonSize).clip(CircleShape).clickable( + interactionSource = remember { MutableInteractionSource() }, indication = ripple(bounded = true), + onClick = { clickEvent(); onOpenSheet(Sheets.Decoders) } ) + ) { + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = MaterialTheme.spacing.medium, vertical = MaterialTheme.spacing.small)) { + Text(text = decoder.title, maxLines = 1, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodyMedium) + } } } } PlayerButton.SCREEN_ROTATION -> { - ControlsButton( - icon = Icons.Default.ScreenRotation, - onClick = viewModel::cycleScreenRotations, - color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, - modifier = Modifier.size(buttonSize), - ) + ControlsButton(icon = Icons.Default.ScreenRotation, onClick = viewModel::cycleScreenRotations, color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(buttonSize)) } PlayerButton.FRAME_NAVIGATION -> { @@ -321,326 +262,132 @@ fun RenderPlayerButton( AnimatedContent( targetState = isExpanded, - transitionSpec = { - (fadeIn(animationSpec = tween(200)) + expandHorizontally(animationSpec = tween(250))) - .togetherWith(fadeOut(animationSpec = tween(200)) + shrinkHorizontally(animationSpec = tween(250))) - .using(SizeTransform(clip = false)) - }, + transitionSpec = { (fadeIn(animationSpec = tween(200)) + expandHorizontally(animationSpec = tween(250))).togetherWith(fadeOut(animationSpec = tween(200)) + shrinkHorizontally(animationSpec = tween(250))).using(SizeTransform(clip = false)) }, label = "FrameNavExpandCollapse", ) { expanded -> if (expanded) { - Surface( - shape = MaterialTheme.shapes.extraLarge, - color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), - border = if (hideBackground) null else BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), - modifier = Modifier.height(buttonSize), - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(2.dp), - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(horizontal = 4.dp), - ) { - // Previous frame button - Surface( - shape = CircleShape, - color = Color.Transparent, - modifier = Modifier - .size(buttonSize - 4.dp) - .clip(CircleShape) - .clickable(onClick = { - viewModel.frameStepBackward() - viewModel.resetFrameNavigationTimer() - }), - ) { - Box(contentAlignment = Alignment.Center) { - Icon( - imageVector = Icons.Default.FastRewind, - contentDescription = "Previous Frame", - tint = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, - modifier = Modifier.size(20.dp), - ) - } - } - - // Camera / Loading button - if (isSnapshotLoading) { - Surface( - shape = CircleShape, - color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), - border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), - modifier = Modifier.size(buttonSize - 4.dp), - ) { - Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) { - CircularProgressIndicator( - modifier = Modifier.size(16.dp), - strokeWidth = 2.dp, - color = if (hideBackground) controlColor else MaterialTheme.colorScheme.primary, - ) - } + if (backdrop != null && !hideBackground) { + LiquidGlassSurface( + backdrop = backdrop, target = LiquidTarget.BUTTON, shape = MaterialTheme.shapes.extraLarge, modifier = Modifier.height(buttonSize) + ) { + Row(horizontalArrangement = Arrangement.spacedBy(2.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 4.dp)) { + Box(modifier = Modifier.size(buttonSize - 4.dp).clip(CircleShape).clickable(onClick = { viewModel.frameStepBackward(); viewModel.resetFrameNavigationTimer() }), contentAlignment = Alignment.Center) { Icon(Icons.Default.FastRewind, contentDescription = "Previous Frame", tint = Color.White, modifier = Modifier.size(20.dp)) } + if (isSnapshotLoading) { + Box(modifier = Modifier.size(buttonSize - 4.dp), contentAlignment = Alignment.Center) { CircularProgressIndicator(modifier = Modifier.size(16.dp), strokeWidth = 2.dp, color = MaterialTheme.colorScheme.primary) } + } else { + @OptIn(ExperimentalFoundationApi::class) + Box(modifier = Modifier.size(buttonSize - 4.dp).clip(CircleShape).combinedClickable(onClick = { viewModel.takeSnapshot(context); viewModel.resetFrameNavigationTimer() }, onLongClick = { onOpenSheet(Sheets.FrameNavigation) }), contentAlignment = Alignment.Center) { Icon(Icons.Default.CameraAlt, contentDescription = "Take Screenshot", tint = Color.White, modifier = Modifier.size(20.dp)) } + } + Box(modifier = Modifier.size(buttonSize - 4.dp).clip(CircleShape).clickable(onClick = { viewModel.frameStepForward(); viewModel.resetFrameNavigationTimer() }), contentAlignment = Alignment.Center) { Icon(Icons.Default.FastForward, contentDescription = "Next Frame", tint = Color.White, modifier = Modifier.size(20.dp)) } } - } else { - @OptIn(ExperimentalFoundationApi::class) - Surface( - shape = CircleShape, - color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), - border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), - modifier = Modifier - .size(buttonSize - 4.dp) - .clip(CircleShape) - .combinedClickable( - onClick = { - viewModel.takeSnapshot(context) - viewModel.resetFrameNavigationTimer() - }, - onLongClick = { onOpenSheet(Sheets.FrameNavigation) }, - ), - ) { - Box(contentAlignment = Alignment.Center) { - Icon( - imageVector = Icons.Default.CameraAlt, - contentDescription = "Take Screenshot", - tint = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, - modifier = Modifier.size(20.dp), - ) - } - } - } - - // Next frame button - Surface( - shape = CircleShape, - color = Color.Transparent, - modifier = Modifier - .size(buttonSize - 4.dp) - .clip(CircleShape) - .clickable(onClick = { - viewModel.frameStepForward() - viewModel.resetFrameNavigationTimer() - }), - ) { - Box(contentAlignment = Alignment.Center) { - Icon( - imageVector = Icons.Default.FastForward, - contentDescription = "Next Frame", - tint = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, - modifier = Modifier.size(20.dp), - ) + } + } else { + Surface( + shape = MaterialTheme.shapes.extraLarge, + color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), + border = if (hideBackground) null else BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), + modifier = Modifier.height(buttonSize) + ) { + Row(horizontalArrangement = Arrangement.spacedBy(2.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 4.dp)) { + Surface(shape = CircleShape, color = Color.Transparent, modifier = Modifier.size(buttonSize - 4.dp).clip(CircleShape).clickable(onClick = { viewModel.frameStepBackward(); viewModel.resetFrameNavigationTimer() })) { Box(contentAlignment = Alignment.Center) { Icon(Icons.Default.FastRewind, contentDescription = "Previous Frame", tint = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(20.dp)) } } + if (isSnapshotLoading) { + Surface(shape = CircleShape, color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), modifier = Modifier.size(buttonSize - 4.dp)) { Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) { CircularProgressIndicator(modifier = Modifier.size(16.dp), strokeWidth = 2.dp, color = if (hideBackground) controlColor else MaterialTheme.colorScheme.primary) } } + } else { + @OptIn(ExperimentalFoundationApi::class) + Surface(shape = CircleShape, color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), modifier = Modifier.size(buttonSize - 4.dp).clip(CircleShape).combinedClickable(onClick = { viewModel.takeSnapshot(context); viewModel.resetFrameNavigationTimer() }, onLongClick = { onOpenSheet(Sheets.FrameNavigation) })) { Box(contentAlignment = Alignment.Center) { Icon(Icons.Default.CameraAlt, contentDescription = "Take Screenshot", tint = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(20.dp)) } } } + Surface(shape = CircleShape, color = Color.Transparent, modifier = Modifier.size(buttonSize - 4.dp).clip(CircleShape).clickable(onClick = { viewModel.frameStepForward(); viewModel.resetFrameNavigationTimer() })) { Box(contentAlignment = Alignment.Center) { Icon(Icons.Default.FastForward, contentDescription = "Next Frame", tint = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(20.dp)) } } } } } } else { - // Collapsed: Show camera icon button - ControlsButton( - icon = Icons.Default.Camera, - onClick = viewModel::toggleFrameNavigationExpanded, - onLongClick = { onOpenSheet(Sheets.FrameNavigation) }, - color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, - modifier = Modifier.size(buttonSize), - ) + ControlsButton(icon = Icons.Default.Camera, onClick = viewModel::toggleFrameNavigationExpanded, onLongClick = { onOpenSheet(Sheets.FrameNavigation) }, color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(buttonSize)) } } } PlayerButton.VIDEO_ZOOM -> { if (kotlin.math.abs(currentZoom) >= 0.005f) { - @OptIn(ExperimentalFoundationApi::class) - Surface( - shape = CircleShape, - color = if (hideBackground) Color.Transparent else MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), - contentColor = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, - tonalElevation = 0.dp, - shadowElevation = 0.dp, - border = if (hideBackground) null else BorderStroke( - 1.dp, - MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f), - ), - modifier = Modifier - .height(buttonSize) - .clip(CircleShape) - .combinedClickable( - interactionSource = remember { MutableInteractionSource() }, - indication = ripple(bounded = true), - onClick = { - clickEvent() - onOpenSheet(Sheets.VideoZoom) - }, - onLongClick = { - clickEvent() - viewModel.resetVideoZoom() - }, - ), - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.extraSmall), - modifier = Modifier.padding( - horizontal = MaterialTheme.spacing.small, - vertical = MaterialTheme.spacing.small, - ), + if (backdrop != null && !hideBackground) { + TransparentLiquidButton( + backdrop = backdrop, modifier = Modifier.height(buttonSize), shape = CircleShape, + onClick = { clickEvent(); onOpenSheet(Sheets.VideoZoom) }, + onLongClick = { clickEvent(); viewModel.resetVideoZoom() } + ) { + Row( + verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.extraSmall), + modifier = Modifier.padding(horizontal = MaterialTheme.spacing.small, vertical = MaterialTheme.spacing.small) + ) { + Icon(Icons.Default.ZoomIn, contentDescription = "Video Zoom", tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(20.dp)) + Text(String.format("%.0f%%", currentZoom * 100), maxLines = 1, color = Color.White, style = MaterialTheme.typography.bodyMedium) + } + } + } else { + @OptIn(ExperimentalFoundationApi::class) + Surface( + shape = CircleShape, color = if (hideBackground) Color.Transparent else MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), + contentColor = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, border = if (hideBackground) null else BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), + modifier = Modifier.height(buttonSize).clip(CircleShape).combinedClickable(interactionSource = remember { MutableInteractionSource() }, indication = ripple(bounded = true), onClick = { clickEvent(); onOpenSheet(Sheets.VideoZoom) }, onLongClick = { clickEvent(); viewModel.resetVideoZoom() }) ) { - Icon( - imageVector = Icons.Default.ZoomIn, - contentDescription = "Video Zoom", - tint = MaterialTheme.colorScheme.primary, - modifier = Modifier.size(20.dp), - ) - Text( - text = String.format("%.0f%%", currentZoom * 100), - maxLines = 1, - style = MaterialTheme.typography.bodyMedium, - ) + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.extraSmall), modifier = Modifier.padding(horizontal = MaterialTheme.spacing.small, vertical = MaterialTheme.spacing.small)) { + Icon(Icons.Default.ZoomIn, contentDescription = "Video Zoom", tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(20.dp)) + Text(String.format("%.0f%%", currentZoom * 100), maxLines = 1, style = MaterialTheme.typography.bodyMedium) + } } } } else { - ControlsButton( - Icons.Default.ZoomIn, - onClick = { - clickEvent() - onOpenSheet(Sheets.VideoZoom) - }, - onLongClick = { viewModel.resetVideoZoom() }, - color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, - modifier = Modifier.size(buttonSize), - ) + ControlsButton(Icons.Default.ZoomIn, onClick = { clickEvent(); onOpenSheet(Sheets.VideoZoom) }, onLongClick = { viewModel.resetVideoZoom() }, color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(buttonSize)) } } - PlayerButton.PICTURE_IN_PICTURE -> { - ControlsButton( - Icons.Default.PictureInPictureAlt, - onClick = { activity.enterPipModeHidingOverlay() }, - color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, - modifier = Modifier.size(buttonSize), - ) - } + PlayerButton.PICTURE_IN_PICTURE -> { ControlsButton(Icons.Default.PictureInPictureAlt, onClick = { activity.enterPipModeHidingOverlay() }, color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(buttonSize)) } PlayerButton.ASPECT_RATIO -> { ControlsButton( - icon = - when (aspect) { - VideoAspect.Fit -> Icons.Default.AspectRatio - VideoAspect.Stretch -> Icons.Default.ZoomOutMap - VideoAspect.Crop -> Icons.Default.FitScreen - }, - onClick = { - when (aspect) { - VideoAspect.Fit -> viewModel.changeVideoAspect(VideoAspect.Stretch) - VideoAspect.Stretch -> viewModel.changeVideoAspect(VideoAspect.Crop) - VideoAspect.Crop -> viewModel.changeVideoAspect(VideoAspect.Fit) - } - }, - onLongClick = { onOpenSheet(Sheets.AspectRatios) }, - color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, - modifier = Modifier.size(buttonSize), + icon = when (aspect) { VideoAspect.Fit -> Icons.Default.AspectRatio; VideoAspect.Stretch -> Icons.Default.ZoomOutMap; VideoAspect.Crop -> Icons.Default.FitScreen }, + onClick = { when (aspect) { VideoAspect.Fit -> viewModel.changeVideoAspect(VideoAspect.Stretch); VideoAspect.Stretch -> viewModel.changeVideoAspect(VideoAspect.Crop); VideoAspect.Crop -> viewModel.changeVideoAspect(VideoAspect.Fit) } }, + onLongClick = { onOpenSheet(Sheets.AspectRatios) }, color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(buttonSize) ) } - PlayerButton.LOCK_CONTROLS -> { - ControlsButton( - Icons.Default.LockOpen, - onClick = viewModel::lockControls, - color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, - modifier = Modifier.size(buttonSize), - ) - } + PlayerButton.LOCK_CONTROLS -> { ControlsButton(Icons.Default.LockOpen, onClick = viewModel::lockControls, color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(buttonSize)) } - PlayerButton.AUDIO_TRACK -> { - ControlsButton( - Icons.Default.Audiotrack, - onClick = { onOpenSheet(Sheets.AudioTracks) }, - onLongClick = { onOpenPanel(Panels.AudioDelay) }, - color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, - modifier = Modifier.size(buttonSize), - ) - } + PlayerButton.AUDIO_TRACK -> { ControlsButton(Icons.Default.Audiotrack, onClick = { onOpenSheet(Sheets.AudioTracks) }, onLongClick = { onOpenPanel(Panels.AudioDelay) }, color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(buttonSize)) } - PlayerButton.SUBTITLES -> { - ControlsButton( - Icons.Default.Subtitles, - onClick = { onOpenSheet(Sheets.SubtitleTracks) }, - onLongClick = { onOpenPanel(Panels.SubtitleDelay) }, - color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, - modifier = Modifier.size(buttonSize), - ) - } + PlayerButton.SUBTITLES -> { ControlsButton(Icons.Default.Subtitles, onClick = { onOpenSheet(Sheets.SubtitleTracks) }, onLongClick = { onOpenPanel(Panels.SubtitleDelay) }, color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(buttonSize)) } - PlayerButton.MORE_OPTIONS -> { - ControlsButton( - Icons.Default.MoreVert, - onClick = { onOpenSheet(Sheets.More) }, - onLongClick = { onOpenPanel(Panels.VideoFilters) }, - color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, - modifier = Modifier.size(buttonSize), - ) - } + PlayerButton.MORE_OPTIONS -> { ControlsButton(Icons.Default.MoreVert, onClick = { onOpenSheet(Sheets.More) }, onLongClick = { onOpenPanel(Panels.VideoFilters) }, color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(buttonSize)) } PlayerButton.CURRENT_CHAPTER -> { - if (isPortrait) { - } else { - AnimatedVisibility( - chapters.getOrNull(currentChapter ?: 0) != null, - enter = fadeIn(), - exit = fadeOut(), - ) { - chapters.getOrNull(currentChapter ?: 0)?.let { chapter -> - CurrentChapter( - chapter = chapter, - onClick = { onOpenSheet(Sheets.Chapters) }, - ) - } + if (!isPortrait) { + AnimatedVisibility(chapters.getOrNull(currentChapter ?: 0) != null, enter = fadeIn(), exit = fadeOut()) { + chapters.getOrNull(currentChapter ?: 0)?.let { chapter -> CurrentChapter(chapter = chapter, onClick = { onOpenSheet(Sheets.Chapters) }) } } } } PlayerButton.REPEAT_MODE -> { val repeatMode by viewModel.repeatMode.collectAsState() - val icon = when (repeatMode) { - app.marlboroadvance.mpvex.ui.player.RepeatMode.OFF -> Icons.Default.Repeat - app.marlboroadvance.mpvex.ui.player.RepeatMode.ONE -> Icons.Default.RepeatOne - app.marlboroadvance.mpvex.ui.player.RepeatMode.ALL -> Icons.Default.RepeatOn - } + val icon = when (repeatMode) { app.marlboroadvance.mpvex.ui.player.RepeatMode.OFF -> Icons.Default.Repeat; app.marlboroadvance.mpvex.ui.player.RepeatMode.ONE -> Icons.Default.RepeatOne; app.marlboroadvance.mpvex.ui.player.RepeatMode.ALL -> Icons.Default.RepeatOn } ControlsButton( - icon = icon, - onClick = viewModel::cycleRepeatMode, - color = if (hideBackground) { - when (repeatMode) { - app.marlboroadvance.mpvex.ui.player.RepeatMode.OFF -> controlColor - else -> MaterialTheme.colorScheme.primary - } - } else { - when (repeatMode) { - app.marlboroadvance.mpvex.ui.player.RepeatMode.OFF -> MaterialTheme.colorScheme.onSurface - else -> MaterialTheme.colorScheme.primary - } - }, - modifier = Modifier.size(buttonSize), + icon = icon, onClick = viewModel::cycleRepeatMode, + color = if (hideBackground) { when (repeatMode) { app.marlboroadvance.mpvex.ui.player.RepeatMode.OFF -> controlColor; else -> MaterialTheme.colorScheme.primary } } else { when (repeatMode) { app.marlboroadvance.mpvex.ui.player.RepeatMode.OFF -> MaterialTheme.colorScheme.onSurface; else -> MaterialTheme.colorScheme.primary } }, + modifier = Modifier.size(buttonSize) ) } PlayerButton.CUSTOM_SKIP -> { val playerPreferences = org.koin.compose.koinInject() - ControlsButton( - icon = Icons.Default.FastForward, - onClick = { viewModel.seekBy(playerPreferences.customSkipDuration.get()) }, - color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, - modifier = Modifier.size(buttonSize), - ) + ControlsButton(icon = Icons.Default.FastForward, onClick = { viewModel.seekBy(playerPreferences.customSkipDuration.get()) }, color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(buttonSize)) } PlayerButton.SHUFFLE -> { - // Only show shuffle button if there's a playlist (more than one video) if (viewModel.hasPlaylistSupport()) { val shuffleEnabled by viewModel.shuffleEnabled.collectAsState() ControlsButton( - icon = if (shuffleEnabled) Icons.Default.ShuffleOn else Icons.Default.Shuffle, - onClick = viewModel::toggleShuffle, - color = if (hideBackground) { - if (shuffleEnabled) MaterialTheme.colorScheme.primary else controlColor - } else { - if (shuffleEnabled) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface - }, - modifier = Modifier.size(buttonSize), + icon = if (shuffleEnabled) Icons.Default.ShuffleOn else Icons.Default.Shuffle, onClick = viewModel::toggleShuffle, + color = if (hideBackground) { if (shuffleEnabled) MaterialTheme.colorScheme.primary else controlColor } else { if (shuffleEnabled) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface }, + modifier = Modifier.size(buttonSize) ) } } @@ -648,44 +395,23 @@ fun RenderPlayerButton( PlayerButton.MIRROR -> { val isMirrored by viewModel.isMirrored.collectAsState() ControlsButton( - icon = Icons.Default.Flip, - onClick = viewModel::toggleMirroring, - color = if (hideBackground) { - if (isMirrored) MaterialTheme.colorScheme.primary else controlColor - } else { - if (isMirrored) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface - }, - modifier = Modifier.size(buttonSize), + icon = Icons.Default.Flip, onClick = viewModel::toggleMirroring, + color = if (hideBackground) { if (isMirrored) MaterialTheme.colorScheme.primary else controlColor } else { if (isMirrored) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface }, + modifier = Modifier.size(buttonSize) ) } PlayerButton.VERTICAL_FLIP -> { val isVerticalFlipped by viewModel.isVerticalFlipped.collectAsState() - val vFlipColor = if (hideBackground) { - if (isVerticalFlipped) MaterialTheme.colorScheme.primary else controlColor + val vFlipColor = if (hideBackground) { if (isVerticalFlipped) MaterialTheme.colorScheme.primary else controlColor } else { if (isVerticalFlipped) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface } + + if (backdrop != null && !hideBackground) { + TransparentLiquidButton(backdrop = backdrop, onClick = viewModel::toggleVerticalFlip, modifier = Modifier.size(buttonSize)) { + Icon(imageVector = Icons.Default.Flip, contentDescription = "Vertical Flip", tint = if (isVerticalFlipped) MaterialTheme.colorScheme.primary else Color.White, modifier = Modifier.padding(MaterialTheme.spacing.small).size(20.dp).rotate(90f)) + } } else { - if (isVerticalFlipped) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface - } - Surface( - shape = CircleShape, - color = if (hideBackground) Color.Transparent else MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), - contentColor = vFlipColor, - border = if (hideBackground) null else BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), - modifier = Modifier - .size(buttonSize) - .clip(CircleShape) - .clickable(onClick = viewModel::toggleVerticalFlip), - ) { - Box(contentAlignment = Alignment.Center) { - Icon( - imageVector = Icons.Default.Flip, - contentDescription = "Vertical Flip", - tint = vFlipColor, - modifier = Modifier - .padding(MaterialTheme.spacing.small) - .size(20.dp) - .rotate(90f), - ) + Surface(shape = CircleShape, color = if (hideBackground) Color.Transparent else MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), contentColor = vFlipColor, border = if (hideBackground) null else BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), modifier = Modifier.size(buttonSize).clip(CircleShape).clickable(onClick = viewModel::toggleVerticalFlip)) { + Box(contentAlignment = Alignment.Center) { Icon(imageVector = Icons.Default.Flip, contentDescription = "Vertical Flip", tint = vFlipColor, modifier = Modifier.padding(MaterialTheme.spacing.small).size(20.dp).rotate(90f)) } } } } @@ -697,162 +423,66 @@ fun RenderPlayerButton( AnimatedContent( targetState = isExpanded, - transitionSpec = { - (fadeIn(animationSpec = tween(200)) + expandHorizontally(animationSpec = tween(250))) - .togetherWith(fadeOut(animationSpec = tween(200)) + shrinkHorizontally(animationSpec = tween(250))) - .using(SizeTransform(clip = false)) - }, + transitionSpec = { (fadeIn(animationSpec = tween(200)) + expandHorizontally(animationSpec = tween(250))).togetherWith(fadeOut(animationSpec = tween(200)) + shrinkHorizontally(animationSpec = tween(250))).using(SizeTransform(clip = false)) }, label = "ABLoopExpandCollapse", ) { expanded -> if (expanded) { - Surface( - shape = MaterialTheme.shapes.extraLarge, - color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), - border = if (hideBackground) null else BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), - modifier = Modifier.height(buttonSize), - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(2.dp), - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(horizontal = 4.dp), - ) { - // Point A Button - always transparent background - Surface( - shape = CircleShape, - color = if (loopA != null) MaterialTheme.colorScheme.tertiaryContainer else Color.Transparent, - modifier = Modifier - .height(buttonSize - 4.dp) - .widthIn(min = buttonSize - 4.dp) - .clip(CircleShape) - .clickable(onClick = { viewModel.setLoopA() }), - ) { - Box(contentAlignment = Alignment.Center) { - Text( - text = if (loopA != null) viewModel.formatTimestamp(loopA!!) else "A", - style = MaterialTheme.typography.labelLarge, - color = if (loopA != null) MaterialTheme.colorScheme.onTertiaryContainer else MaterialTheme.colorScheme.onSurface, - modifier = Modifier.padding(horizontal = if (loopA != null) 8.dp else 0.dp), - ) - } - } - - // Clear/Close Button - always has background - Surface( - shape = CircleShape, - color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), - border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), - modifier = Modifier - .size(buttonSize - 4.dp) - .clip(CircleShape) - .clickable(onClick = { - viewModel.clearABLoop() - viewModel.toggleABLoopExpanded() - }), - ) { - Box(contentAlignment = Alignment.Center) { - Icon( - imageVector = Icons.Default.Close, - contentDescription = "Clear Loop", - tint = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.size(16.dp), - ) - } - } - - // Point B Button - always transparent background - Surface( - shape = CircleShape, - color = if (loopB != null) MaterialTheme.colorScheme.tertiaryContainer else Color.Transparent, - modifier = Modifier - .height(buttonSize - 4.dp) - .widthIn(min = buttonSize - 4.dp) - .clip(CircleShape) - .clickable(onClick = { viewModel.setLoopB() }), - ) { - Box(contentAlignment = Alignment.Center) { - Text( - text = if (loopB != null) viewModel.formatTimestamp(loopB!!) else "B", - style = MaterialTheme.typography.labelLarge, - color = if (loopB != null) MaterialTheme.colorScheme.onTertiaryContainer else MaterialTheme.colorScheme.onSurface, - modifier = Modifier.padding(horizontal = if (loopB != null) 8.dp else 0.dp), - ) + if (backdrop != null && !hideBackground) { + LiquidGlassSurface(backdrop = backdrop, target = LiquidTarget.BUTTON, shape = MaterialTheme.shapes.extraLarge, modifier = Modifier.height(buttonSize)) { + Row(horizontalArrangement = Arrangement.spacedBy(2.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 4.dp)) { + Box(modifier = Modifier.height(buttonSize - 4.dp).widthIn(min = buttonSize - 4.dp).clip(CircleShape).background(if (loopA != null) MaterialTheme.colorScheme.primary.copy(alpha = 0.5f) else Color.Transparent).clickable(onClick = { viewModel.setLoopA() }), contentAlignment = Alignment.Center) { Text(text = if (loopA != null) viewModel.formatTimestamp(loopA!!) else "A", style = MaterialTheme.typography.labelLarge, color = Color.White, modifier = Modifier.padding(horizontal = if (loopA != null) 8.dp else 0.dp)) } + Box(modifier = Modifier.size(buttonSize - 4.dp).clip(CircleShape).background(Color.White.copy(alpha = 0.2f)).clickable(onClick = { viewModel.clearABLoop(); viewModel.toggleABLoopExpanded() }), contentAlignment = Alignment.Center) { Icon(imageVector = Icons.Default.Close, contentDescription = "Clear Loop", tint = Color.White, modifier = Modifier.size(16.dp)) } + Box(modifier = Modifier.height(buttonSize - 4.dp).widthIn(min = buttonSize - 4.dp).clip(CircleShape).background(if (loopB != null) MaterialTheme.colorScheme.primary.copy(alpha = 0.5f) else Color.Transparent).clickable(onClick = { viewModel.setLoopB() }), contentAlignment = Alignment.Center) { Text(text = if (loopB != null) viewModel.formatTimestamp(loopB!!) else "B", style = MaterialTheme.typography.labelLarge, color = Color.White, modifier = Modifier.padding(horizontal = if (loopB != null) 8.dp else 0.dp)) } } + } + } else { + Surface(shape = MaterialTheme.shapes.extraLarge, color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), border = if (hideBackground) null else BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), modifier = Modifier.height(buttonSize)) { + Row(horizontalArrangement = Arrangement.spacedBy(2.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 4.dp)) { + Surface(shape = CircleShape, color = if (loopA != null) MaterialTheme.colorScheme.tertiaryContainer else Color.Transparent, modifier = Modifier.height(buttonSize - 4.dp).widthIn(min = buttonSize - 4.dp).clip(CircleShape).clickable(onClick = { viewModel.setLoopA() })) { Box(contentAlignment = Alignment.Center) { Text(text = if (loopA != null) viewModel.formatTimestamp(loopA!!) else "A", style = MaterialTheme.typography.labelLarge, color = if (loopA != null) MaterialTheme.colorScheme.onTertiaryContainer else MaterialTheme.colorScheme.onSurface, modifier = Modifier.padding(horizontal = if (loopA != null) 8.dp else 0.dp)) } } + Surface(shape = CircleShape, color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), modifier = Modifier.size(buttonSize - 4.dp).clip(CircleShape).clickable(onClick = { viewModel.clearABLoop(); viewModel.toggleABLoopExpanded() })) { Box(contentAlignment = Alignment.Center) { Icon(imageVector = Icons.Default.Close, contentDescription = "Clear Loop", tint = MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(16.dp)) } } + Surface(shape = CircleShape, color = if (loopB != null) MaterialTheme.colorScheme.tertiaryContainer else Color.Transparent, modifier = Modifier.height(buttonSize - 4.dp).widthIn(min = buttonSize - 4.dp).clip(CircleShape).clickable(onClick = { viewModel.setLoopB() })) { Box(contentAlignment = Alignment.Center) { Text(text = if (loopB != null) viewModel.formatTimestamp(loopB!!) else "B", style = MaterialTheme.typography.labelLarge, color = if (loopB != null) MaterialTheme.colorScheme.onTertiaryContainer else MaterialTheme.colorScheme.onSurface, modifier = Modifier.padding(horizontal = if (loopB != null) 8.dp else 0.dp)) } } } } } } else { - // Collapsed: Show "AB" text button - Surface( - shape = CircleShape, - color = if (hideBackground) Color.Transparent else MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), - border = if (hideBackground) null else BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), - modifier = Modifier - .size(buttonSize) - .clip(CircleShape) - .clickable(onClick = viewModel::toggleABLoopExpanded), - ) { - Box(contentAlignment = Alignment.Center) { - Text( - text = "AB", - style = MaterialTheme.typography.labelLarge, - fontWeight = androidx.compose.ui.text.font.FontWeight.Bold, - color = if (loopA != null && loopB != null) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface, - ) + if (backdrop != null && !hideBackground) { + TransparentLiquidButton(backdrop = backdrop, onClick = viewModel::toggleABLoopExpanded, modifier = Modifier.size(buttonSize)) { + Text(text = "AB", style = MaterialTheme.typography.labelLarge, fontWeight = androidx.compose.ui.text.font.FontWeight.Bold, color = if (loopA != null && loopB != null) MaterialTheme.colorScheme.primary else Color.White) + } + } else { + Surface(shape = CircleShape, color = if (hideBackground) Color.Transparent else MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), border = if (hideBackground) null else BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), modifier = Modifier.size(buttonSize).clip(CircleShape).clickable(onClick = viewModel::toggleABLoopExpanded)) { + Box(contentAlignment = Alignment.Center) { Text(text = "AB", style = MaterialTheme.typography.labelLarge, fontWeight = androidx.compose.ui.text.font.FontWeight.Bold, color = if (loopA != null && loopB != null) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface) } } } } } } - PlayerButton.BACKGROUND_PLAYBACK -> { - ControlsButton( - icon = Icons.Default.Headset, - onClick = { activity.triggerBackgroundPlayback() }, - color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, - modifier = Modifier.size(buttonSize), - ) - } + PlayerButton.BACKGROUND_PLAYBACK -> { ControlsButton(icon = Icons.Default.Headset, onClick = { activity.triggerBackgroundPlayback() }, color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(buttonSize)) } PlayerButton.AMBIENT_MODE -> { val isAmbientEnabled by viewModel.isAmbientEnabled.collectAsState() - @OptIn(ExperimentalFoundationApi::class) - Surface( - shape = CircleShape, - color = if (hideBackground) Color.Transparent else MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), - contentColor = if (isAmbientEnabled) { - MaterialTheme.colorScheme.primary - } else { - if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface - }, - border = if (hideBackground) null else BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), - modifier = Modifier - .size(buttonSize) - .clip(CircleShape) - .combinedClickable( - interactionSource = remember { MutableInteractionSource() }, - indication = ripple(bounded = true), - onClick = { - clickEvent() - viewModel.toggleAmbientMode() - }, - onLongClick = { - clickEvent() - onOpenSheet(Sheets.AmbientConfig) - } - ), - ) { - Box(contentAlignment = Alignment.Center) { - Icon( - imageVector = if (isAmbientEnabled) Icons.Filled.BlurOn else Icons.Outlined.BlurOn, - contentDescription = "Ambience Mode", - tint = if (isAmbientEnabled) MaterialTheme.colorScheme.primary else (if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface), - modifier = Modifier.size(24.dp) - ) + if (backdrop != null && !hideBackground) { + TransparentLiquidButton( + backdrop = backdrop, modifier = Modifier.size(buttonSize), + onClick = { clickEvent(); viewModel.toggleAmbientMode() }, + onLongClick = { clickEvent(); onOpenSheet(Sheets.AmbientConfig) } + ) { + Icon(imageVector = if (isAmbientEnabled) Icons.Filled.BlurOn else Icons.Outlined.BlurOn, contentDescription = "Ambience Mode", tint = if (isAmbientEnabled) MaterialTheme.colorScheme.primary else Color.White, modifier = Modifier.size(24.dp)) + } + } else { + @OptIn(ExperimentalFoundationApi::class) + Surface( + shape = CircleShape, color = if (hideBackground) Color.Transparent else MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), + contentColor = if (isAmbientEnabled) { MaterialTheme.colorScheme.primary } else { if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface }, + border = if (hideBackground) null else BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), + modifier = Modifier.size(buttonSize).clip(CircleShape).combinedClickable(interactionSource = remember { MutableInteractionSource() }, indication = ripple(bounded = true), onClick = { clickEvent(); viewModel.toggleAmbientMode() }, onLongClick = { clickEvent(); onOpenSheet(Sheets.AmbientConfig) }) + ) { + Box(contentAlignment = Alignment.Center) { Icon(imageVector = if (isAmbientEnabled) Icons.Filled.BlurOn else Icons.Outlined.BlurOn, contentDescription = "Ambience Mode", tint = if (isAmbientEnabled) MaterialTheme.colorScheme.primary else (if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface), modifier = Modifier.size(24.dp)) } } } } - PlayerButton.NONE -> { /* Do nothing */ - } + PlayerButton.NONE -> {} } } From 71c1c751ae1c9d825545d22f81dd0d3f5fc4fa17 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 10:40:06 +0530 Subject: [PATCH 160/194] Update VerticalSliders.kt --- .../controls/components/VerticalSliders.kt | 284 +++++++++++------- 1 file changed, 181 insertions(+), 103 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/VerticalSliders.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/VerticalSliders.kt index e066e6d87..f62c9ee99 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/VerticalSliders.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/VerticalSliders.kt @@ -31,6 +31,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -38,6 +39,11 @@ import app.marlboroadvance.mpvex.R import app.marlboroadvance.mpvex.ui.theme.spacing import kotlin.math.roundToInt +// --- NEW LIQUID IMPORTS --- +import app.marlboroadvance.mpvex.ui.components.liquid.LocalLiquidBackdrop +import app.marlboroadvance.mpvex.ui.components.liquid.LiquidGlassSurface +import app.marlboroadvance.mpvex.preferences.LiquidTarget + fun percentage( value: Float, range: ClosedFloatingPointRange, @@ -57,32 +63,35 @@ fun VerticalSlider( overflowRange: ClosedFloatingPointRange? = null, ) { val coercedValue = value.coerceIn(range) - Box( - modifier = - modifier - .height(120.dp) - .aspectRatio(0.2f) - .clip(RoundedCornerShape(16.dp)) - .background(MaterialTheme.colorScheme.background) - .border( - width = 1.dp, - color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f), - shape = RoundedCornerShape(16.dp), - ), - contentAlignment = Alignment.BottomCenter, - ) { + val backdrop = LocalLiquidBackdrop.current + + val baseModifier = modifier + .height(120.dp) + .aspectRatio(0.2f) + .clip(RoundedCornerShape(16.dp)) + + val finalModifier = if (backdrop != null) { + baseModifier.background(Color.White.copy(alpha = 0.2f)) + } else { + baseModifier + .background(MaterialTheme.colorScheme.background) + .border( + width = 1.dp, + color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f), + shape = RoundedCornerShape(16.dp), + ) + } + + Box(modifier = finalModifier, contentAlignment = Alignment.BottomCenter) { val targetHeight by animateFloatAsState(percentage(coercedValue, range), label = "vsliderheight") Box( Modifier .fillMaxWidth() .fillMaxHeight(targetHeight) - .background(MaterialTheme.colorScheme.tertiary), + .background(if (backdrop != null) Color.White else MaterialTheme.colorScheme.tertiary), ) if (overflowRange != null && overflowValue != null) { - val overflowHeight by animateFloatAsState( - percentage(overflowValue, overflowRange), - label = "vslideroverflowheight", - ) + val overflowHeight by animateFloatAsState(percentage(overflowValue, overflowRange), label = "vslideroverflowheight") Box( Modifier .fillMaxWidth() @@ -102,32 +111,35 @@ fun VerticalSlider( overflowRange: ClosedRange? = null, ) { val coercedValue = value.coerceIn(range) - Box( - modifier = - modifier - .height(120.dp) - .aspectRatio(0.2f) - .clip(RoundedCornerShape(16.dp)) - .background(MaterialTheme.colorScheme.background) - .border( - width = 1.dp, - color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f), - shape = RoundedCornerShape(16.dp), - ), - contentAlignment = Alignment.BottomCenter, - ) { + val backdrop = LocalLiquidBackdrop.current + + val baseModifier = modifier + .height(120.dp) + .aspectRatio(0.2f) + .clip(RoundedCornerShape(16.dp)) + + val finalModifier = if (backdrop != null) { + baseModifier.background(Color.White.copy(alpha = 0.2f)) + } else { + baseModifier + .background(MaterialTheme.colorScheme.background) + .border( + width = 1.dp, + color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f), + shape = RoundedCornerShape(16.dp), + ) + } + + Box(modifier = finalModifier, contentAlignment = Alignment.BottomCenter) { val targetHeight by animateFloatAsState(percentage(coercedValue, range), label = "vsliderheight") Box( Modifier .fillMaxWidth() .fillMaxHeight(targetHeight) - .background(MaterialTheme.colorScheme.tertiary), + .background(if (backdrop != null) Color.White else MaterialTheme.colorScheme.tertiary), ) if (overflowRange != null && overflowValue != null) { - val overflowHeight by animateFloatAsState( - percentage(overflowValue, overflowRange), - label = "vslideroverflowheight", - ) + val overflowHeight by animateFloatAsState(percentage(overflowValue, overflowRange), label = "vslideroverflowheight") Box( Modifier .fillMaxWidth() @@ -145,37 +157,61 @@ fun BrightnessSlider( modifier: Modifier = Modifier, ) { val coercedBrightness = brightness.coerceIn(range) - Surface( - modifier = modifier, - shape = RoundedCornerShape(20.dp), - color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), - contentColor = MaterialTheme.colorScheme.onSurface, - tonalElevation = 0.dp, - shadowElevation = 0.dp, - border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), - ) { - Column( - modifier = Modifier.padding(horizontal = 12.dp, vertical = 16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.smaller), + val backdrop = LocalLiquidBackdrop.current + + if (backdrop != null) { + LiquidGlassSurface( + backdrop = backdrop, + target = LiquidTarget.BUTTON, // Connects to the Buttons Settings tab! + shape = RoundedCornerShape(20.dp), + modifier = modifier ) { - Text( - (coercedBrightness * 100).toInt().toString(), - style = MaterialTheme.typography.bodySmall, - ) - VerticalSlider( - coercedBrightness, - range, - ) - Icon( - when (percentage(coercedBrightness, range)) { - in 0f..0.3f -> Icons.Default.BrightnessLow - in 0.3f..0.6f -> Icons.Default.BrightnessMedium - in 0.6f..1f -> Icons.Default.BrightnessHigh - else -> Icons.Default.BrightnessMedium - }, - contentDescription = null, - ) + Column( + modifier = Modifier.padding(horizontal = 12.dp, vertical = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.smaller), + ) { + Text((coercedBrightness * 100).toInt().toString(), style = MaterialTheme.typography.bodySmall, color = Color.White) + VerticalSlider(coercedBrightness, range) + Icon( + when (percentage(coercedBrightness, range)) { + in 0f..0.3f -> Icons.Default.BrightnessLow + in 0.3f..0.6f -> Icons.Default.BrightnessMedium + in 0.6f..1f -> Icons.Default.BrightnessHigh + else -> Icons.Default.BrightnessMedium + }, + contentDescription = null, + tint = Color.White + ) + } + } + } else { + Surface( + modifier = modifier, + shape = RoundedCornerShape(20.dp), + color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), + contentColor = MaterialTheme.colorScheme.onSurface, + tonalElevation = 0.dp, + shadowElevation = 0.dp, + border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), + ) { + Column( + modifier = Modifier.padding(horizontal = 12.dp, vertical = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.smaller), + ) { + Text((coercedBrightness * 100).toInt().toString(), style = MaterialTheme.typography.bodySmall) + VerticalSlider(coercedBrightness, range) + Icon( + when (percentage(coercedBrightness, range)) { + in 0f..0.3f -> Icons.Default.BrightnessLow + in 0.3f..0.6f -> Icons.Default.BrightnessMedium + in 0.6f..1f -> Icons.Default.BrightnessHigh + else -> Icons.Default.BrightnessMedium + }, + contentDescription = null, + ) + } } } } @@ -190,42 +226,84 @@ fun VolumeSlider( displayAsPercentage: Boolean = false, ) { val percentage = (percentage(volume, range) * 100).roundToInt() - Surface( - modifier = modifier, - shape = RoundedCornerShape(20.dp), - color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), - contentColor = MaterialTheme.colorScheme.onSurface, - tonalElevation = 0.dp, - shadowElevation = 0.dp, - border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), - ) { - Column( - modifier = Modifier.padding(horizontal = 12.dp, vertical = 16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.smaller), + val backdrop = LocalLiquidBackdrop.current + + if (backdrop != null) { + LiquidGlassSurface( + backdrop = backdrop, + target = LiquidTarget.BUTTON, // Connects to the Buttons Settings tab! + shape = RoundedCornerShape(20.dp), + modifier = modifier ) { - val boostVolume = mpvVolume - 100 - Text( - getVolumeSliderText(volume, mpvVolume, boostVolume, percentage, displayAsPercentage), - style = MaterialTheme.typography.bodySmall, - textAlign = TextAlign.Center, - ) - VerticalSlider( - if (displayAsPercentage) percentage else volume, - if (displayAsPercentage) 0..100 else range, - overflowValue = boostVolume, - overflowRange = boostRange, - ) - Icon( - when (percentage) { - 0 -> Icons.AutoMirrored.Default.VolumeOff - in 0..30 -> Icons.AutoMirrored.Default.VolumeMute - in 30..60 -> Icons.AutoMirrored.Default.VolumeDown - in 60..100 -> Icons.AutoMirrored.Default.VolumeUp - else -> Icons.AutoMirrored.Default.VolumeOff - }, - contentDescription = null, - ) + Column( + modifier = Modifier.padding(horizontal = 12.dp, vertical = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.smaller), + ) { + val boostVolume = mpvVolume - 100 + Text( + getVolumeSliderText(volume, mpvVolume, boostVolume, percentage, displayAsPercentage), + style = MaterialTheme.typography.bodySmall, + textAlign = TextAlign.Center, + color = Color.White + ) + VerticalSlider( + if (displayAsPercentage) percentage else volume, + if (displayAsPercentage) 0..100 else range, + overflowValue = boostVolume, + overflowRange = boostRange, + ) + Icon( + when (percentage) { + 0 -> Icons.AutoMirrored.Default.VolumeOff + in 0..30 -> Icons.AutoMirrored.Default.VolumeMute + in 30..60 -> Icons.AutoMirrored.Default.VolumeDown + in 60..100 -> Icons.AutoMirrored.Default.VolumeUp + else -> Icons.AutoMirrored.Default.VolumeOff + }, + contentDescription = null, + tint = Color.White + ) + } + } + } else { + Surface( + modifier = modifier, + shape = RoundedCornerShape(20.dp), + color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), + contentColor = MaterialTheme.colorScheme.onSurface, + tonalElevation = 0.dp, + shadowElevation = 0.dp, + border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), + ) { + Column( + modifier = Modifier.padding(horizontal = 12.dp, vertical = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.smaller), + ) { + val boostVolume = mpvVolume - 100 + Text( + getVolumeSliderText(volume, mpvVolume, boostVolume, percentage, displayAsPercentage), + style = MaterialTheme.typography.bodySmall, + textAlign = TextAlign.Center, + ) + VerticalSlider( + if (displayAsPercentage) percentage else volume, + if (displayAsPercentage) 0..100 else range, + overflowValue = boostVolume, + overflowRange = boostRange, + ) + Icon( + when (percentage) { + 0 -> Icons.AutoMirrored.Default.VolumeOff + in 0..30 -> Icons.AutoMirrored.Default.VolumeMute + in 30..60 -> Icons.AutoMirrored.Default.VolumeDown + in 60..100 -> Icons.AutoMirrored.Default.VolumeUp + else -> Icons.AutoMirrored.Default.VolumeOff + }, + contentDescription = null, + ) + } } } } From 98fca22291698d460aa89264faa8b437bc3a4b87 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 10:52:48 +0530 Subject: [PATCH 161/194] Update LiquidComponents.kt --- .../mpvex/ui/components/liquid/LiquidComponents.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt index 07ae0a122..d64e47462 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt @@ -16,7 +16,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.dp import com.kyant.backdrop.Backdrop -inport app.marlboroadvance.npvex.preferences.liquidTarget +import app.marlboroadvance.mpvex.preferences.liquidTarget // Broadcasts the glass camera to any button that wants it! val LocalLiquidBackdrop = androidx.compose.runtime.staticCompositionLocalOf { null } From 7c934edfbcaa1f03c5c181612be43fee38102176 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 10:54:17 +0530 Subject: [PATCH 162/194] Update SlideToUnlock.kt --- .../mpvex/ui/player/controls/components/SlideToUnlock.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/SlideToUnlock.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/SlideToUnlock.kt index 998822fa2..08dfad08d 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/SlideToUnlock.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/SlideToUnlock.kt @@ -2,6 +2,7 @@ package app.marlboroadvance.mpvex.ui.player.controls.components import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.tween +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectHorizontalDragGestures import androidx.compose.foundation.layout.Box From 1bf7b0ad1be348aef9b58f8c49bef7940f422316 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 10:58:45 +0530 Subject: [PATCH 163/194] Update SlideToUnlock.kt --- .../mpvex/ui/player/controls/components/SlideToUnlock.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/SlideToUnlock.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/SlideToUnlock.kt index 08dfad08d..99a57ec75 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/SlideToUnlock.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/SlideToUnlock.kt @@ -34,7 +34,9 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.IntOffset From 25fe66f94907526ef60087040916e554354a404a Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 11:00:35 +0530 Subject: [PATCH 164/194] Update SlideToUnlock.kt --- .../mpvex/ui/player/controls/components/SlideToUnlock.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/SlideToUnlock.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/SlideToUnlock.kt index 99a57ec75..08dfad08d 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/SlideToUnlock.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/SlideToUnlock.kt @@ -34,9 +34,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.IntOffset From cd9f1ac8a3091dcf74eadc0f83215cfde67f6481 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 11:02:10 +0530 Subject: [PATCH 165/194] Update PlayerUpdates.kt --- .../mpvex/ui/player/controls/components/PlayerUpdates.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/PlayerUpdates.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/PlayerUpdates.kt index 6c9c92c34..69eb735f7 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/PlayerUpdates.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/PlayerUpdates.kt @@ -20,6 +20,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontFamily From a2b13b2a5353a358db6c76c8439cf392e044f9ea Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 11:06:15 +0530 Subject: [PATCH 166/194] Update PlayerUpdates.kt --- .../ui/player/controls/components/PlayerUpdates.kt | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/PlayerUpdates.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/PlayerUpdates.kt index 69eb735f7..f7de0f553 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/PlayerUpdates.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/PlayerUpdates.kt @@ -144,12 +144,7 @@ fun DoubleTapSeekPlayerUpdate( modifier: Modifier = Modifier, ) { val backdrop = LocalLiquidBackdrop.current - val directionText = if (isRight) { - stringResource(R.string.player_double_tap_seek_forward) - } else { - stringResource(R.string.player_double_tap_seek_rewind) - } - + val directionText = if (isRight) "Forward" else "Rewind" PlayerUpdate(modifier.animateContentSize()) { Row( verticalAlignment = Alignment.CenterVertically, @@ -164,12 +159,12 @@ fun DoubleTapSeekPlayerUpdate( .clip(CircleShape) .background(if (backdrop != null) Color.White.copy(alpha = 0.2f) else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f)) .padding(4.dp) - .Modifier.rotate(180f), + .rotate(180f), ) } Text( - text = seekText ?: "$directionText $seekAmount ${stringResource(R.string.player_double_tap_seek_seconds)}", + text = seekText ?: "$directionText $seekAmount seconds", fontFamily = FontFamily.Monospace, fontWeight = FontWeight.Bold, textAlign = TextAlign.Center, From c59540e545f6c359f845ece498a43b507dc4bf1b Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 11:14:06 +0530 Subject: [PATCH 167/194] Update LiquidComponents.kt --- .../mpvex/ui/components/liquid/LiquidComponents.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt index d64e47462..2de4cbeaf 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt @@ -16,11 +16,11 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.dp import com.kyant.backdrop.Backdrop -import app.marlboroadvance.mpvex.preferences.liquidTarget +import app.marlboroadvance.mpvex.preferences.LiquidTarget + // Broadcasts the glass camera to any button that wants it! val LocalLiquidBackdrop = androidx.compose.runtime.staticCompositionLocalOf { null } - @OptIn(ExperimentalFoundationApi::class) @Composable fun TransparentLiquidButton( @@ -82,6 +82,7 @@ fun LiquidGlassCard( LiquidGlassSurface( backdrop = backdrop, shape = shape, + target = LiquidTarget.DIALOG, modifier = modifier ) { content() From d9f1cf1b6dc1c82658fa5cf69ed511d039bacc6a Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 12:38:05 +0530 Subject: [PATCH 168/194] Update PlayerControlsShared.kt --- .../mpvex/ui/player/controls/PlayerControlsShared.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControlsShared.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControlsShared.kt index b8ef628e3..992f98e0d 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControlsShared.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControlsShared.kt @@ -229,7 +229,8 @@ fun RenderPlayerButton( backdrop = backdrop, modifier = Modifier.height(buttonSize), shape = CircleShape, onClick = { clickEvent(); onOpenSheet(Sheets.Decoders) } ) { - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = MaterialTheme.spacing.medium, vertical = MaterialTheme.spacing.small)) { + // SHRUNK: horizontal padding is now 8.dp to make it a compact badge! + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 8.dp, vertical = MaterialTheme.spacing.small)) { Text(decoder.title, maxLines = 1, color = Color.White, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodyMedium) } } @@ -244,7 +245,8 @@ fun RenderPlayerButton( onClick = { clickEvent(); onOpenSheet(Sheets.Decoders) } ) ) { - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = MaterialTheme.spacing.medium, vertical = MaterialTheme.spacing.small)) { + // SHRUNK: horizontal padding is now 8.dp here too! + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 8.dp, vertical = MaterialTheme.spacing.small)) { Text(text = decoder.title, maxLines = 1, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodyMedium) } } From ad79255bc2ce158ce41709bb9c84c97db391beca Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 13:36:10 +0530 Subject: [PATCH 169/194] Update PlayerControlsShared.kt --- .../player/controls/PlayerControlsShared.kt | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControlsShared.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControlsShared.kt index 992f98e0d..fb5494e21 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControlsShared.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControlsShared.kt @@ -136,7 +136,7 @@ fun RenderPlayerButton( ) { Row( verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(horizontal = MaterialTheme.spacing.medium, vertical = MaterialTheme.spacing.small) + modifier = Modifier.padding(horizontal = MaterialTheme.spacing.extraSmall, vertical = MaterialTheme.spacing.small) ) { Text( mediaTitle ?: "", maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -203,6 +203,7 @@ fun RenderPlayerButton( shape = CircleShape, color = if (hideBackground) Color.Transparent else MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), contentColor = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, + tonalElevation = 0.dp, shadowElevation = 0.dp, border = if (hideBackground) null else BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), modifier = Modifier.height(buttonSize).clip(CircleShape).clickable( interactionSource = remember { MutableInteractionSource() }, indication = ripple(bounded = true), @@ -229,8 +230,7 @@ fun RenderPlayerButton( backdrop = backdrop, modifier = Modifier.height(buttonSize), shape = CircleShape, onClick = { clickEvent(); onOpenSheet(Sheets.Decoders) } ) { - // SHRUNK: horizontal padding is now 8.dp to make it a compact badge! - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 8.dp, vertical = MaterialTheme.spacing.small)) { + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = MaterialTheme.spacing.medium, vertical = MaterialTheme.spacing.small)) { Text(decoder.title, maxLines = 1, color = Color.White, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodyMedium) } } @@ -239,14 +239,14 @@ fun RenderPlayerButton( shape = CircleShape, color = if (hideBackground) Color.Transparent else MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), contentColor = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, + tonalElevation = 0.dp, shadowElevation = 0.dp, border = if (hideBackground) null else BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), modifier = Modifier.height(buttonSize).clip(CircleShape).clickable( interactionSource = remember { MutableInteractionSource() }, indication = ripple(bounded = true), onClick = { clickEvent(); onOpenSheet(Sheets.Decoders) } ) ) { - // SHRUNK: horizontal padding is now 8.dp here too! - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 8.dp, vertical = MaterialTheme.spacing.small)) { + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = MaterialTheme.spacing.medium, vertical = MaterialTheme.spacing.small)) { Text(text = decoder.title, maxLines = 1, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodyMedium) } } @@ -361,7 +361,8 @@ fun RenderPlayerButton( PlayerButton.MORE_OPTIONS -> { ControlsButton(Icons.Default.MoreVert, onClick = { onOpenSheet(Sheets.More) }, onLongClick = { onOpenPanel(Panels.VideoFilters) }, color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(buttonSize)) } PlayerButton.CURRENT_CHAPTER -> { - if (!isPortrait) { + if (isPortrait) { + } else { AnimatedVisibility(chapters.getOrNull(currentChapter ?: 0) != null, enter = fadeIn(), exit = fadeOut()) { chapters.getOrNull(currentChapter ?: 0)?.let { chapter -> CurrentChapter(chapter = chapter, onClick = { onOpenSheet(Sheets.Chapters) }) } } @@ -464,11 +465,11 @@ fun RenderPlayerButton( PlayerButton.AMBIENT_MODE -> { val isAmbientEnabled by viewModel.isAmbientEnabled.collectAsState() + if (backdrop != null && !hideBackground) { TransparentLiquidButton( backdrop = backdrop, modifier = Modifier.size(buttonSize), - onClick = { clickEvent(); viewModel.toggleAmbientMode() }, - onLongClick = { clickEvent(); onOpenSheet(Sheets.AmbientConfig) } + onClick = { clickEvent(); viewModel.toggleAmbientMode() } ) { Icon(imageVector = if (isAmbientEnabled) Icons.Filled.BlurOn else Icons.Outlined.BlurOn, contentDescription = "Ambience Mode", tint = if (isAmbientEnabled) MaterialTheme.colorScheme.primary else Color.White, modifier = Modifier.size(24.dp)) } @@ -478,13 +479,13 @@ fun RenderPlayerButton( shape = CircleShape, color = if (hideBackground) Color.Transparent else MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), contentColor = if (isAmbientEnabled) { MaterialTheme.colorScheme.primary } else { if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface }, border = if (hideBackground) null else BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), - modifier = Modifier.size(buttonSize).clip(CircleShape).combinedClickable(interactionSource = remember { MutableInteractionSource() }, indication = ripple(bounded = true), onClick = { clickEvent(); viewModel.toggleAmbientMode() }, onLongClick = { clickEvent(); onOpenSheet(Sheets.AmbientConfig) }) + modifier = Modifier.size(buttonSize).clip(CircleShape).clickable(interactionSource = remember { MutableInteractionSource() }, indication = ripple(bounded = true), onClick = { clickEvent(); viewModel.toggleAmbientMode() }) ) { Box(contentAlignment = Alignment.Center) { Icon(imageVector = if (isAmbientEnabled) Icons.Filled.BlurOn else Icons.Outlined.BlurOn, contentDescription = "Ambience Mode", tint = if (isAmbientEnabled) MaterialTheme.colorScheme.primary else (if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface), modifier = Modifier.size(24.dp)) } } } } - PlayerButton.NONE -> {} + PlayerButton.NONE -> { /* Do nothing */ } } } From edb00f088b69b87df6ba5dbd46cd408053304f65 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 14:09:22 +0530 Subject: [PATCH 170/194] Enhance player functionality with ambient mode improvements and fixes (#671) (#7) * chore: bump version to 1.2.9 (129) * feat(player): improve background playback and notification handling * ui(player): fix AB loop control visibility when background is hidden * Revert "feat(player): improve background playback and notification handling" This reverts commit 0ea65e99161910d83cf41ef0b80306ec20823b3d. * ui(player): cleanup and icon updates * fix(ambient): ensure seamless transition and persistent state across playbacks (#663) * Update PlayerViewModel.kt : fix(playback): reset ambient and visual states and reapply ambient mode calculations on file transition Fix visual properties bleeding into subsequent files during playback This commit addresses edge cases where video properties (ambient shaders, aspect ratio, zoom, and pan) incorrectly persist when transitioning to a new media file (e.g., clicking Next or auto-playing a playlist) breaking the ambient mode function by carrying the stretch properties into the next/previous file. Changes in `PlayerViewModel.kt`: * Fixed a bug where keeping Ambient Mode ON during a file transition would apply the previous video's dimension math to the new video. The shader now forces a recalculation (`lastAmbientScaleX = -1.0`) when a new duration is detected. * Updated `setMediaTitle` to strictly reset UI states and MPV engine properties for Video Aspect Ratio (forces `Fit`), Video Zoom, and Video Pan. This guarantees every new file starts with a clean visual slate. * Update resetAmbientMode for Persistent Status on Next/Previous file Updated resetAmbientMode() with for enabling Persistent Status on Next/Previous file. * Add ambient mode preference to PlayerPreferences Added a new persistant preference for enabling ambient mode. * Implement persistent ambient mode state Added persistent state management for ambient mode in PlayerViewModel. * refactor(player): simplify Ambient Mode and extract to `AmbientModeManager` * fix(player): dynamically adjust subtitle positions for dual-subtitles - Add `updateSubtitlePositions()` to `MPVView` to manage subtitle placement based on active tracks. - Automatically move secondary subtitles to the top (position 10) when primary subtitles are also active. - Revert secondary subtitles to the bottom (user-defined `subPos`) when they are the only active track. - Observe `sid` and `secondary-sid` properties in `PlayerActivity` and `MPVView` to trigger position updates on track changes. * Anime4k fix (#664) --------- Co-authored-by: MarlboroAdvance Co-authored-by: Chinna95P <140423321+Chinna95P@users.noreply.github.com> Co-authored-by: Ritesh Pandit <87899750+Riteshp2001@users.noreply.github.com> --- app/build.gradle.kts | 4 +- .../mpvex/preferences/PlayerButton.kt | 4 +- .../mpvex/preferences/PlayerPreferences.kt | 13 +- .../mpvex/ui/player/AmbientModeManager.kt | 271 ++++++++++ .../mpvex/ui/player/MPVView.kt | 16 +- .../mpvex/ui/player/PlayerEnums.kt | 1 - .../mpvex/ui/player/PlayerViewModel.kt | 510 ++---------------- .../mpvex/ui/player/controls/PlayerSheets.kt | 8 - .../components/sheets/AmbientSheet.kt | 372 ------------- .../components/sheets/AudioTracksSheet.kt | 54 +- .../controls/components/sheets/MoreSheet.kt | 61 +-- .../preferences/ControlLayoutEditorScreen.kt | 59 +- 12 files changed, 387 insertions(+), 986 deletions(-) create mode 100644 app/src/main/java/app/marlboroadvance/mpvex/ui/player/AmbientModeManager.kt delete mode 100644 app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/sheets/AmbientSheet.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 54eea5803..bafac34ae 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -17,8 +17,8 @@ android { applicationId = "app.marlboroadvance.mpvex" minSdk = 26 targetSdk = 36 - versionCode = 128 - versionName = "1.2.8" + versionCode = 129 + versionName = "1.2.9" vectorDrawables { useSupportLibrary = true diff --git a/app/src/main/java/app/marlboroadvance/mpvex/preferences/PlayerButton.kt b/app/src/main/java/app/marlboroadvance/mpvex/preferences/PlayerButton.kt index a0cd1b378..600f590e3 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/preferences/PlayerButton.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/preferences/PlayerButton.kt @@ -2,6 +2,7 @@ package app.marlboroadvance.mpvex.preferences import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.ArrowBack +import androidx.compose.material.icons.automirrored.outlined.Segment import androidx.compose.material.icons.outlined.AspectRatio import androidx.compose.material.icons.outlined.Audiotrack import androidx.compose.material.icons.outlined.Bookmarks @@ -16,6 +17,7 @@ import androidx.compose.material.icons.outlined.Subtitles import androidx.compose.material.icons.outlined.Title import androidx.compose.material.icons.outlined.Flip import androidx.compose.material.icons.outlined.Repeat +import androidx.compose.material.icons.outlined.Segment import androidx.compose.material.icons.outlined.ZoomIn import androidx.compose.material.icons.outlined.FastForward import androidx.compose.material.icons.outlined.Shuffle @@ -52,7 +54,7 @@ enum class PlayerButton( SHUFFLE(Icons.Outlined.Shuffle), MIRROR(Icons.Outlined.Flip), VERTICAL_FLIP(Icons.Outlined.Flip), - AB_LOOP(Icons.Outlined.Repeat), + AB_LOOP(Icons.AutoMirrored.Outlined.Segment), CUSTOM_SKIP(Icons.Outlined.FastForward), BACKGROUND_PLAYBACK(Icons.Outlined.Headset), AMBIENT_MODE(Icons.Outlined.BlurOn), diff --git a/app/src/main/java/app/marlboroadvance/mpvex/preferences/PlayerPreferences.kt b/app/src/main/java/app/marlboroadvance/mpvex/preferences/PlayerPreferences.kt index 6e2fcdc76..cebd75ca0 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/preferences/PlayerPreferences.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/preferences/PlayerPreferences.kt @@ -74,15 +74,6 @@ class PlayerPreferences( val customButtons = preferenceStore.getString("custom_buttons_json", "[]") // Ambience Mode - val ambientBlurSamples = preferenceStore.getInt("ambient_blur_samples", 24) - val ambientMaxRadius = preferenceStore.getFloat("ambient_max_radius", 0.18f) - val ambientGlowIntensity = preferenceStore.getFloat("ambient_glow_intensity", 1.4f) - val ambientSatBoost = preferenceStore.getFloat("ambient_sat_boost", 1.2f) - val ambientDitherNoise = preferenceStore.getFloat("ambient_dither_noise", 0.0f) - val ambientBezelDepth = preferenceStore.getFloat("ambient_bezel_depth", 0.0f) - val ambientVignetteStrength = preferenceStore.getFloat("ambient_vignette_strength", 0.5f) - val ambientWarmth = preferenceStore.getFloat("ambient_warmth", 0.0f) - val ambientEdgeSmooth = preferenceStore.getFloat("ambient_edge_smooth", 0.02f) - val ambientFadeCurve = preferenceStore.getFloat("ambient_fade_curve", 1.5f) - val ambientOpacity = preferenceStore.getFloat("ambient_opacity", 1.0f) + val isAmbientEnabled = preferenceStore.getBoolean("ambient_enabled", false) + } diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/AmbientModeManager.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/AmbientModeManager.kt new file mode 100644 index 000000000..a72f0e1f4 --- /dev/null +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/AmbientModeManager.kt @@ -0,0 +1,271 @@ +package app.marlboroadvance.mpvex.ui.player + +import android.util.Log +import app.marlboroadvance.mpvex.preferences.PlayerPreferences +import `is`.xyz.mpv.MPVLib +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import java.io.File + +/** + * Manages ambient mode functionality for the video player. + * Handles shader generation, video scaling, and parameter management. + */ +class AmbientModeManager( + private val playerPreferences: PlayerPreferences, + private val cacheDir: File, + private val scope: CoroutineScope, + private val onShowText: (String) -> Unit +) { + companion object { + private const val TAG = "AmbientModeManager" + } + + // ==================== State Management ==================== + + private val _isAmbientEnabled = MutableStateFlow(playerPreferences.isAmbientEnabled.get()) + val isAmbientEnabled: StateFlow = _isAmbientEnabled.asStateFlow() + + private val _isAmbientLoading = MutableStateFlow(false) + val isAmbientLoading: StateFlow = _isAmbientLoading.asStateFlow() + + private var lastAmbientScaleX = -1.0 + private var lastAmbientScaleY = -1.0 + private var ambientDebounceJob: Job? = null + private var ambientShaderSeq = 0 + private var ambientShaderFile: File? = null + + // ==================== Public API ==================== + + fun toggleAmbientMode() { + _isAmbientEnabled.value = !_isAmbientEnabled.value + + // Save the Ambient Mode ON/OFF state permanently to preferences + playerPreferences.isAmbientEnabled.set(_isAmbientEnabled.value) + if (_isAmbientEnabled.value) { + lastAmbientScaleX = -1.0 // Force rewrite + updateAmbientStretch() + onShowText("Ambience Mode: ON") + } else { + disableAmbientShader() + onShowText("Ambience Mode: OFF") + } + } + + /** Called when the device orientation changes. Refreshes ambient shader for new dimensions. */ + fun onOrientationChanged(isPortrait: Boolean) { + if (_isAmbientEnabled.value) { + // Force shader refresh to adapt to new screen dimensions + lastAmbientScaleX = -1.0 + lastAmbientScaleY = -1.0 + // Small delay to let the new OSD dimensions settle + ambientDebounceJob?.cancel() + ambientDebounceJob = scope.launch { + delay(200) + updateAmbientStretch() + } + } + } + + /** Resets ambient mode to OFF when a new video file is loaded. */ + fun resetAmbientMode() { + if (!_isAmbientEnabled.value) return + + // Ambient Mode Persistent Fix for Next/Previous files + // DO NOT set _isAmbientEnabled.value = false + // Just temporarily remove the old shader and reset the scale + // so the new video starts with a clean slate before recalculating. + disableAmbientShader() + lastAmbientScaleX = -1.0 + lastAmbientScaleY = -1.0 + } + + /** + * Re-injects the ambient shader if ambient mode is currently ON. + * Called after Anime4K shader changes, since setPropertyString("glsl-shaders", ...) + * wipes ALL glsl-shaders including the ambient one. + */ + fun restartAmbientIfActive() { + if (!_isAmbientEnabled.value) return + // The old ambient shader file was wiped by the glsl-shaders reset. + // Clean up our local reference without trying to remove from MPV. + ambientShaderFile?.delete() + ambientShaderFile = null + lastAmbientScaleX = -1.0 // Force rewrite + // Small delay to let Anime4K shaders settle + ambientDebounceJob?.cancel() + ambientDebounceJob = scope.launch { + delay(200) + updateAmbientStretch() + } + } + + + + fun updateAmbientStretch() { + if (!_isAmbientEnabled.value) return + + runCatching { + val osdW = MPVLib.getPropertyInt("osd-width") ?: 1920 + val osdH = MPVLib.getPropertyInt("osd-height") ?: 1080 + + // Portrait mode: ambient glow goes on top/bottom (letterbox) + // Landscape mode: ambient glow goes on left/right (pillarbox) + // Both are handled by the same scaleX/scaleY math below + + var vidW = (MPVLib.getPropertyInt("video-params/w") ?: 1920).toDouble() + var vidH = (MPVLib.getPropertyInt("video-params/h") ?: 1080).toDouble() + val par = MPVLib.getPropertyDouble("video-params/par") ?: 1.0 + val rot = MPVLib.getPropertyInt("video-params/rotate") ?: 0 + + // Intercept autocrop boundaries — if a crop is active, use the cropped dimensions + // so the shader's aspect-ratio math matches the actual visible video area + val crop = MPVLib.getPropertyString("video-crop") ?: "" + val cropMatch = Regex("""^(\d+)x(\d+)""").find(crop) + if (cropMatch != null) { + vidW = cropMatch.groupValues[1].toDouble() + vidH = cropMatch.groupValues[2].toDouble() + } + + if (osdW <= 0 || osdH <= 0 || vidW <= 0.0 || vidH <= 0.0) return + + // Apply pixel aspect ratio (non-square pixels) + vidW *= par + // Swap dimensions for 90°/270° rotated videos (portrait shot stored as landscape) + if (rot == 90 || rot == 270) { val tmp = vidW; vidW = vidH; vidH = tmp } + + val screenAr = osdW.toDouble() / osdH.toDouble() + val vidAr = vidW / vidH + + // Scale the video to fill the screen — the shader remaps it back to the + // correct aspect ratio, so only the "overflow" area receives ambient glow. + val scaleX = if (screenAr > vidAr) screenAr / vidAr else 1.0 + val scaleY = if (vidAr > screenAr) vidAr / screenAr else 1.0 + + if (Math.abs(scaleX - lastAmbientScaleX) > 0.001 || + Math.abs(scaleY - lastAmbientScaleY) > 0.001) { + lastAmbientScaleX = scaleX + lastAmbientScaleY = scaleY + MPVLib.setPropertyDouble("video-scale-x", scaleX) + MPVLib.setPropertyDouble("video-scale-y", scaleY) + } + + // ── Generate GLSL shader ─────────────────────────────────────────────── + val shaderCode = buildAmbientShader( + sx = lastAmbientScaleX, + sy = lastAmbientScaleY + ) + + // Each reload gets a unique filename so MPV never reuses a cached + // compiled shader — incrementing seq guarantees a fresh compile every time. + val newFile = File(cacheDir, "ambient_${++ambientShaderSeq}.glsl") + newFile.writeText(shaderCode) + ambientShaderFile?.let { oldFile -> + runCatching { MPVLib.command("change-list", "glsl-shaders", "remove", oldFile.absolutePath) } + oldFile.delete() + } + MPVLib.command("change-list", "glsl-shaders", "append", newFile.absolutePath) + ambientShaderFile = newFile + }.onFailure { e -> + Log.e(TAG, "Failed to update ambient stretch", e) + } + } + + // ==================== Private Methods ==================== + + /** Disables the ambient shader and resets video scale. Safe to call from any state. */ + private fun disableAmbientShader() { + ambientDebounceJob?.cancel() + ambientShaderFile?.let { file -> + runCatching { MPVLib.command("change-list", "glsl-shaders", "remove", file.absolutePath) } + file.delete() + } + ambientShaderFile = null + runCatching { + MPVLib.setPropertyDouble("video-scale-x", 1.0) + MPVLib.setPropertyDouble("video-scale-y", 1.0) + } + } + + /** + * Builds a simplified YouTube-style ambient GLSL shader. + * Samples random areas from entire video with temporal stability and debanding. + */ + private fun buildAmbientShader( + sx: Double, + sy: Double + ): String = """ +//!HOOK OUTPUT +//!BIND HOOKED +//!DESC YouTube-Style Ambient Mode + +#define SCALE_X $sx +#define SCALE_Y $sy + +// Simple hash function for pseudo-random sampling +float hash(vec2 p) { + return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); +} + +vec4 hook() { + vec2 uv = HOOKED_pos; + vec2 video_uv = (uv - 0.5) * vec2(SCALE_X, SCALE_Y) + 0.5; + + // Return video pixel if inside video bounds + if (video_uv.x >= 0.0 && video_uv.x <= 1.0 && + video_uv.y >= 0.0 && video_uv.y <= 1.0) { + return HOOKED_tex(video_uv); + } + + // Ambient region - sample random areas from entire video + // Use fixed seed for temporal stability (color doesn't flicker) + vec3 avg_color = vec3(0.0); + int samples = 20; + float base_seed = 42.0; // Fixed seed for stability + + // Sample random positions across the entire video + for (int i = 0; i < samples; i++) { + float seed = base_seed + float(i) * 0.618034; + float x = hash(vec2(seed, 0.123)); + float y = hash(vec2(seed, 0.456)); + + vec2 sample_pos = vec2(x, y); + avg_color += HOOKED_tex(sample_pos).rgb; + } + + avg_color /= float(samples); + + // Boost saturation slightly for more vibrant glow + float luma = dot(avg_color, vec3(0.2126, 0.7152, 0.0722)); + avg_color = mix(vec3(luma), avg_color, 1.3); // 30% saturation boost + + // Increased brightness for more visible glow (30% instead of 20%) + avg_color *= 0.30; + + // Smooth fade based on distance from video edge + vec2 edge_uv = clamp(video_uv, 0.0, 1.0); + float dist = length(video_uv - edge_uv); + float fade = exp(-dist * 2.5); + avg_color *= fade; + + // Debanding: add subtle dither noise to eliminate color banding + float dither = hash(uv * 1000.0) * 0.004 - 0.002; // ±0.002 range + avg_color = clamp(avg_color + dither, 0.0, 1.0); + + return vec4(avg_color, 1.0); +} + """.trimIndent() + + // ==================== Cleanup ==================== + + fun cleanup() { + ambientDebounceJob?.cancel() + ambientShaderFile?.delete() + ambientShaderFile = null + } +} diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/MPVView.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/MPVView.kt index 93f057ace..9a73d8567 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/MPVView.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/MPVView.kt @@ -310,7 +310,8 @@ class MPVView( MPVLib.setOptionString("secondary-sub-border-style", borderStyle) MPVLib.setOptionString("secondary-sub-shadow-offset", shadowOffset) MPVLib.setOptionString("secondary-sub-scale", subScale) - MPVLib.setOptionString("secondary-sub-pos", subPos) + // Position secondary subtitle at top (10) instead of bottom to avoid overlap with primary + MPVLib.setOptionString("secondary-sub-pos", "10") val scaleByWindow = if (subtitlesPreferences.scaleByWindow.get()) "yes" else "no" MPVLib.setOptionString("sub-scale-by-window", scaleByWindow) @@ -327,9 +328,10 @@ class MPVView( return } - // DEFENSIVE CHECK: Ensure mutual exclusion at initialization time + // Anime4K requires the legacy GPU path unless gpu-next is running on Vulkan. val gpuNextActive = decoderPreferences.gpuNext.get() - if (gpuNextActive) { + val useVulkan = decoderPreferences.useVulkan.get() + if (gpuNextActive && !useVulkan) { return // Abort shader loading to prevent incompatible state } @@ -364,10 +366,12 @@ class MPVView( val shaderChain = anime4kManager.getShaderChain(mode, quality) if (shaderChain.isNotEmpty()) { - // GPU optimizations for better performance - MPVLib.setOptionString("opengl-pbo", "yes") + // OpenGL-only tuning should not be pushed onto the Vulkan backend. + if (!useVulkan) { + MPVLib.setOptionString("opengl-pbo", "yes") + MPVLib.setOptionString("opengl-early-flush", "no") + } MPVLib.setOptionString("vd-lavc-dr", "yes") - MPVLib.setOptionString("opengl-early-flush", "no") // Apply shaders (MUST use setOptionString in initOptions!) MPVLib.setOptionString("glsl-shaders", shaderChain) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/PlayerEnums.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/PlayerEnums.kt index 3548096ac..8441e7919 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/PlayerEnums.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/PlayerEnums.kt @@ -101,7 +101,6 @@ enum class Sheets { VideoZoom, AspectRatios, Playlist, - AmbientConfig, FrameNavigation, } diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/PlayerViewModel.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/PlayerViewModel.kt index 6a26ac85c..a81782975 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/PlayerViewModel.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/PlayerViewModel.kt @@ -38,13 +38,11 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.coroutines.sync.Mutex @@ -52,9 +50,7 @@ import kotlinx.coroutines.sync.withLock import kotlinx.serialization.json.Json import org.koin.core.component.KoinComponent import org.koin.core.component.inject -import app.marlboroadvance.mpvex.ui.preferences.CustomButton import java.io.File -import androidx.core.net.toUri import androidx.documentfile.provider.DocumentFile import app.marlboroadvance.mpvex.preferences.AdvancedPreferences import kotlin.properties.ReadOnlyProperty @@ -185,6 +181,15 @@ class PlayerViewModel( val dur = MPVLib.getPropertyDouble("duration") if (dur != null && dur > 0) { _preciseDuration.value = dur.toFloat() + + // --- AMBIENT FIX: Adapt shader to new file dimensions --- + ambientModeManager.resetAmbientMode() + viewModelScope.launch { + // Slight delay ensures MPV's video-params (w/h/crop) are fully populated + delay(250) + ambientModeManager.updateAmbientStretch() + } + // -------------------------------------------------------- } } } @@ -316,44 +321,16 @@ class PlayerViewModel( val isVerticalFlipped: StateFlow = _isVerticalFlipped.asStateFlow() // ==================== Ambience Mode ====================================== - private val _isAmbientEnabled = MutableStateFlow(false) - val isAmbientEnabled: StateFlow = _isAmbientEnabled.asStateFlow() - - private val _ambientBlurSamples = MutableStateFlow(playerPreferences.ambientBlurSamples.get()) - val ambientBlurSamples: StateFlow = _ambientBlurSamples.asStateFlow() - - private val _ambientMaxRadius = MutableStateFlow(playerPreferences.ambientMaxRadius.get()) - val ambientMaxRadius: StateFlow = _ambientMaxRadius.asStateFlow() - - private val _ambientGlowIntensity = MutableStateFlow(playerPreferences.ambientGlowIntensity.get()) - val ambientGlowIntensity: StateFlow = _ambientGlowIntensity.asStateFlow() - - private val _ambientSatBoost = MutableStateFlow(playerPreferences.ambientSatBoost.get()) - val ambientSatBoost: StateFlow = _ambientSatBoost.asStateFlow() - - private val _ambientDitherNoise = MutableStateFlow(playerPreferences.ambientDitherNoise.get()) - val ambientDitherNoise: StateFlow = _ambientDitherNoise.asStateFlow() - - private val _ambientBezelDepth = MutableStateFlow(playerPreferences.ambientBezelDepth.get()) - val ambientBezelDepth: StateFlow = _ambientBezelDepth.asStateFlow() - - private val _ambientVignetteStrength = MutableStateFlow(playerPreferences.ambientVignetteStrength.get()) - val ambientVignetteStrength: StateFlow = _ambientVignetteStrength.asStateFlow() - - private val _ambientWarmth = MutableStateFlow(playerPreferences.ambientWarmth.get()) - val ambientWarmth: StateFlow = _ambientWarmth.asStateFlow() - - private val _ambientFadeCurve = MutableStateFlow(playerPreferences.ambientFadeCurve.get()) - val ambientFadeCurve: StateFlow = _ambientFadeCurve.asStateFlow() - - private val _ambientOpacity = MutableStateFlow(playerPreferences.ambientOpacity.get()) - val ambientOpacity: StateFlow = _ambientOpacity.asStateFlow() + // Ambient mode manager handles all ambient mode functionality + private val ambientModeManager = AmbientModeManager( + playerPreferences = playerPreferences, + cacheDir = host.context.cacheDir, + scope = viewModelScope, + onShowText = { text -> playerUpdate.value = PlayerUpdates.ShowText(text) } + ) - private var lastAmbientScaleX = -1.0 - private var lastAmbientScaleY = -1.0 - private var ambientDebounceJob: kotlinx.coroutines.Job? = null - private var ambientShaderSeq = 0 - private var ambientShaderFile: java.io.File? = null + // Expose ambient mode state through the manager + val isAmbientEnabled: StateFlow = ambientModeManager.isAmbientEnabled init { // Track selection is now handled by TrackSelector in PlayerActivity @@ -787,6 +764,33 @@ class PlayerViewModel( _externalSubtitles.clear() // Scan for previously downloaded/added subtitles scanLocalSubtitles(mediaTitle) + + // --- ADDED: Reset visual states for the new file for Ambient Mode Function by @Chinna95P --- + + // 1. Reset Aspect Ratio UI state and MPV properties to "Fit" + _videoAspect.value = VideoAspect.Fit + _currentAspectRatio.value = -1.0 + runCatching { + MPVLib.setPropertyDouble("panscan", 0.0) + MPVLib.setPropertyDouble("video-aspect-override", -1.0) + } + + // 2. Reset Video Zoom + if (_videoZoom.value != 0f) { + _videoZoom.value = 0f + runCatching { MPVLib.setPropertyDouble("video-zoom", 0.0) } + } + + // 3. Reset Video Pan + if (_videoPanX.value != 0f || _videoPanY.value != 0f) { + _videoPanX.value = 0f + _videoPanY.value = 0f + runCatching { + MPVLib.setPropertyDouble("video-pan-x", 0.0) + MPVLib.setPropertyDouble("video-pan-y", 0.0) + } + } + // --------------------------------------------------- } } @@ -2150,430 +2154,26 @@ class PlayerViewModel( // ==================== Ambient Mode Integration ==================== - fun toggleAmbientMode() { - _isAmbientEnabled.value = !_isAmbientEnabled.value - if (_isAmbientEnabled.value) { - lastAmbientScaleX = -1.0 // Force rewrite - updateAmbientStretch() - playerUpdate.value = PlayerUpdates.ShowText("Ambience Mode: ON") - } else { - disableAmbientShader() - playerUpdate.value = PlayerUpdates.ShowText("Ambience Mode: OFF") - } - } + fun toggleAmbientMode() = ambientModeManager.toggleAmbientMode() - /** Disables the ambient shader and resets video scale. Safe to call from any state. */ - private fun disableAmbientShader() { - ambientDebounceJob?.cancel() - ambientShaderFile?.let { file -> - runCatching { MPVLib.command("change-list", "glsl-shaders", "remove", file.absolutePath) } - file.delete() - } - ambientShaderFile = null - runCatching { - MPVLib.setPropertyDouble("video-scale-x", 1.0) - MPVLib.setPropertyDouble("video-scale-y", 1.0) - } - } + fun onOrientationChanged(isPortrait: Boolean) = ambientModeManager.onOrientationChanged(isPortrait) - /** Called when the device orientation changes. Refreshes ambient shader for new dimensions. */ - fun onOrientationChanged(isPortrait: Boolean) { - if (_isAmbientEnabled.value) { - // Force shader refresh to adapt to new screen dimensions - lastAmbientScaleX = -1.0 - lastAmbientScaleY = -1.0 - // Small delay to let the new OSD dimensions settle - ambientDebounceJob?.cancel() - ambientDebounceJob = viewModelScope.launch { - delay(200) - updateAmbientStretch() - } - } - } - - /** Resets ambient mode to OFF when a new video file is loaded. */ - fun resetAmbientMode() { - if (!_isAmbientEnabled.value) return - _isAmbientEnabled.value = false - disableAmbientShader() - } - - /** - * Re-injects the ambient shader if ambient mode is currently ON. - * Called after Anime4K shader changes, since setPropertyString("glsl-shaders", ...) - * wipes ALL glsl-shaders including the ambient one. - */ - fun restartAmbientIfActive() { - if (!_isAmbientEnabled.value) return - // The old ambient shader file was wiped by the glsl-shaders reset. - // Clean up our local reference without trying to remove from MPV. - ambientShaderFile?.delete() - ambientShaderFile = null - lastAmbientScaleX = -1.0 // Force rewrite - // Small delay to let Anime4K shaders settle - ambientDebounceJob?.cancel() - ambientDebounceJob = viewModelScope.launch { - delay(200) - updateAmbientStretch() - } - } - - fun updateAmbientParams( - blurSamples: Int = _ambientBlurSamples.value, - maxRadius: Float = _ambientMaxRadius.value, - glowIntensity: Float = _ambientGlowIntensity.value, - satBoost: Float = _ambientSatBoost.value, - ditherNoise: Float = _ambientDitherNoise.value, - bezelDepth: Float = _ambientBezelDepth.value, - vignetteStrength: Float = _ambientVignetteStrength.value, - warmth: Float = _ambientWarmth.value, - fadeCurve: Float = _ambientFadeCurve.value, - opacity: Float = _ambientOpacity.value - ) { - _ambientBlurSamples.value = blurSamples - _ambientMaxRadius.value = maxRadius - _ambientGlowIntensity.value = glowIntensity - _ambientSatBoost.value = satBoost - _ambientDitherNoise.value = ditherNoise - _ambientBezelDepth.value = bezelDepth - _ambientVignetteStrength.value = vignetteStrength - _ambientWarmth.value = warmth - _ambientFadeCurve.value = fadeCurve - _ambientOpacity.value = opacity - - // Persist to preferences - playerPreferences.ambientBlurSamples.set(blurSamples) - playerPreferences.ambientMaxRadius.set(maxRadius) - playerPreferences.ambientGlowIntensity.set(glowIntensity) - playerPreferences.ambientSatBoost.set(satBoost) - playerPreferences.ambientDitherNoise.set(ditherNoise) - playerPreferences.ambientBezelDepth.set(bezelDepth) - playerPreferences.ambientVignetteStrength.set(vignetteStrength) - playerPreferences.ambientWarmth.set(warmth) - playerPreferences.ambientFadeCurve.set(fadeCurve) - playerPreferences.ambientOpacity.set(opacity) - - // Debounce shader re-injection to avoid excessive GPU reloads - if (_isAmbientEnabled.value) { - ambientDebounceJob?.cancel() - ambientDebounceJob = viewModelScope.launch { - delay(150) - updateAmbientStretch() - } - } - } - - /** Fast profile — low GPU cost, still visually solid. */ - fun applyAmbientProfileFast() { - updateAmbientParams( - blurSamples = 16, maxRadius = 0.22f, glowIntensity = 1.4f, - satBoost = 1.2f, ditherNoise = 0.0f, bezelDepth = 0.0f, - vignetteStrength = 0.4f, warmth = 0.0f, fadeCurve = 1.6f, opacity = 1.0f - ) - } + fun resetAmbientMode() = ambientModeManager.resetAmbientMode() - /** Balanced profile — good quality/performance trade-off for most devices. */ - fun applyAmbientProfileBalanced() { - updateAmbientParams( - blurSamples = 24, maxRadius = 0.28f, glowIntensity = 1.45f, - satBoost = 1.25f, ditherNoise = 0.0f, bezelDepth = 0.0f, - vignetteStrength = 0.55f, warmth = 0.0f, fadeCurve = 1.7f, opacity = 1.0f - ) - } - - /** High Quality profile — maximum visual fidelity for high-end devices. */ - fun applyAmbientProfileHighQuality() { - updateAmbientParams( - blurSamples = 48, maxRadius = 0.35f, glowIntensity = 1.5f, - satBoost = 1.3f, ditherNoise = 0.0f, bezelDepth = 0.0f, - vignetteStrength = 0.7f, warmth = 0.0f, fadeCurve = 1.8f, opacity = 1.0f - ) - } - - fun updateAmbientStretch() { - if (!_isAmbientEnabled.value) return - - runCatching { - val osdW = MPVLib.getPropertyInt("osd-width") ?: 1920 - val osdH = MPVLib.getPropertyInt("osd-height") ?: 1080 - - // Portrait mode: ambient glow goes on top/bottom (letterbox) - // Landscape mode: ambient glow goes on left/right (pillarbox) - // Both are handled by the same scaleX/scaleY math below + fun restartAmbientIfActive() = ambientModeManager.restartAmbientIfActive() - var vidW = (MPVLib.getPropertyInt("video-params/w") ?: 1920).toDouble() - var vidH = (MPVLib.getPropertyInt("video-params/h") ?: 1080).toDouble() - val par = MPVLib.getPropertyDouble("video-params/par") ?: 1.0 - val rot = MPVLib.getPropertyInt("video-params/rotate") ?: 0 - - // Intercept autocrop boundaries — if a crop is active, use the cropped dimensions - // so the shader's aspect-ratio math matches the actual visible video area - val crop = MPVLib.getPropertyString("video-crop") ?: "" - val cropMatch = Regex("""^(\d+)x(\d+)""").find(crop) - if (cropMatch != null) { - vidW = cropMatch.groupValues[1].toDouble() - vidH = cropMatch.groupValues[2].toDouble() - } - - if (osdW <= 0 || osdH <= 0 || vidW <= 0.0 || vidH <= 0.0) return - - // Apply pixel aspect ratio (non-square pixels) - vidW *= par - // Swap dimensions for 90°/270° rotated videos (portrait shot stored as landscape) - if (rot == 90 || rot == 270) { val tmp = vidW; vidW = vidH; vidH = tmp } - - val screenAr = osdW.toDouble() / osdH.toDouble() - val vidAr = vidW / vidH - - // Scale the video to fill the screen — the shader remaps it back to the - // correct aspect ratio, so only the "overflow" area receives ambient glow. - val scaleX = if (screenAr > vidAr) screenAr / vidAr else 1.0 - val scaleY = if (vidAr > screenAr) vidAr / screenAr else 1.0 - - if (Math.abs(scaleX - lastAmbientScaleX) > 0.001 || - Math.abs(scaleY - lastAmbientScaleY) > 0.001) { - lastAmbientScaleX = scaleX - lastAmbientScaleY = scaleY - MPVLib.setPropertyDouble("video-scale-x", scaleX) - MPVLib.setPropertyDouble("video-scale-y", scaleY) - } - - // ── Snapshot current parameter values ───────────────────────────────── - val sx = lastAmbientScaleX - val sy = lastAmbientScaleY - val samples = _ambientBlurSamples.value - val radius = _ambientMaxRadius.value - val glow = _ambientGlowIntensity.value - val sat = _ambientSatBoost.value - val dither = _ambientDitherNoise.value - val bezel = _ambientBezelDepth.value - val vignette= _ambientVignetteStrength.value - val warmth = _ambientWarmth.value - val curve = _ambientFadeCurve.value - val opacity = _ambientOpacity.value - - // ── Generate GLSL shader ─────────────────────────────────────────────── - val shaderCode = buildAmbientShader( - sx = sx, sy = sy, - blurSamples = samples, maxRadius = radius, - glowIntensity = glow, satBoost = sat, - ditherNoise = dither, bezelDepth = bezel, - vignetteStrength = vignette, warmth = warmth, - fadeCurve = curve, opacity = opacity - ) - - // Each reload gets a unique filename so MPV never reuses a cached - // compiled shader — incrementing seq guarantees a fresh compile every time. - val newFile = File(host.context.cacheDir, "ambient_${++ambientShaderSeq}.glsl") - newFile.writeText(shaderCode) - ambientShaderFile?.let { oldFile -> - runCatching { MPVLib.command("change-list", "glsl-shaders", "remove", oldFile.absolutePath) } - oldFile.delete() - } - MPVLib.command("change-list", "glsl-shaders", "append", newFile.absolutePath) - ambientShaderFile = newFile - }.onFailure { e -> - Log.e(TAG, "Failed to update ambient stretch", e) - } - } - - /** - * Builds the True Ambient GLSL shader string with all parameters baked in - * as `#define` constants. The shader: - * 1. Detects the video region using aspect-ratio correction (SCALE_X/Y). - * 2. For interior pixels — returns the original (unscaled) video pixel. - * 3. For ambient pixels — samples the nearest video-edge with a - * Fibonacci-spiral blur kernel and composites the glowing result. - */ - private fun buildAmbientShader( - sx: Double, sy: Double, - blurSamples: Int, maxRadius: Float, - glowIntensity: Float, satBoost: Float, - ditherNoise: Float, bezelDepth: Float, - vignetteStrength: Float, warmth: Float, - fadeCurve: Float, opacity: Float - ): String = """ -//!HOOK OUTPUT -//!BIND HOOKED -//!DESC True Ambient Mode - -// ───────────────────────────────────────────────────────────────────────────── -// CONFIGURATION (all values injected at runtime — do not hand-edit) -// ───────────────────────────────────────────────────────────────────────────── - -// Blur quality: number of spiral samples (higher = smoother, more GPU cost) -#define BLUR_SAMPLES $blurSamples - -// Maximum blur spread radius in normalised UV coordinates -#define MAX_RADIUS $maxRadius - -// Ambient brightness multiplier (1.0 = neutral) -#define GLOW_INTENSITY $glowIntensity - -// Saturation boost applied to the ambient glow (1.0 = neutral) -#define SAT_BOOST $satBoost - -// Width of the soft blend zone at the video edge (0 = hard cut) -#define BEZEL_DEPTH $bezelDepth - -// Anti-banding dither noise amplitude -#define DITHER_NOISE $ditherNoise - -// Corner vignette strength (0.0 = none, 1.0 = full darkening) -#define VIGNETTE_STR $vignetteStrength - -// Color temperature shift (-1.0 = cooler/blue, 0.0 = neutral, +1.0 = warmer/orange) -#define WARMTH $warmth - -// Distance falloff power (1.0 = linear, 2.0 = quadratic, higher = tighter glow) -#define FADE_CURVE $fadeCurve - -// Overall ambient opacity multiplier -#define OPACITY $opacity - -// Aspect-ratio correction factors derived from video / screen dimensions -#define SCALE_X $sx -#define SCALE_Y $sy - -// ───────────────────────────────────────────────────────────────────────────── -// CONSTANTS -// ───────────────────────────────────────────────────────────────────────────── - -const float PI = 3.14159265358979; -const float PHI = 1.61803398874989; // Golden ratio — drives Fibonacci spiral - -// ───────────────────────────────────────────────────────────────────────────── -// UTILITY FUNCTIONS -// ───────────────────────────────────────────────────────────────────────────── - -// Hash-based pseudo-random scalar in [0, 1] -float rand(vec2 seed) { - return fract(sin(dot(seed, vec2(12.9898, 78.233))) * 43758.5453); -} - -// BT.709 perceptual luminance -float luma(vec3 rgb) { - return dot(rgb, vec3(0.2126, 0.7152, 0.0722)); -} - -// Luma-preserving saturation adjustment -vec3 adjust_saturation(vec3 rgb, float amount) { - return mix(vec3(luma(rgb)), rgb, amount); -} - -// Kelvin-style warm / cool color temperature shift -vec3 apply_warmth(vec3 rgb, float amount) { - rgb.r = clamp(rgb.r + amount * 0.060, 0.0, 1.0); - rgb.g = clamp(rgb.g + amount * 0.025, 0.0, 1.0); - rgb.b = clamp(rgb.b - amount * 0.080, 0.0, 1.0); - return rgb; -} - -// ───────────────────────────────────────────────────────────────────────────── -// MAIN HOOK -// ───────────────────────────────────────────────────────────────────────────── - -vec4 hook() { - // Current pixel position in normalised screen space [0, 1] - vec2 uv = HOOKED_pos; - - // Remap screen UV → original (pre-scale) video UV space. - // Pixels outside [0, 1] × [0, 1] are in the letterbox / pillarbox region. - vec2 video_uv = (uv - 0.5) * vec2(SCALE_X, SCALE_Y) + 0.5; - - // ── Hard boundary: return video pixel directly — zero ambient bleeds in ── - if (video_uv.x >= 0.0 && video_uv.x <= 1.0 && - video_uv.y >= 0.0 && video_uv.y <= 1.0) { - return HOOKED_tex(video_uv); - } - - // ── Ambient region: compute edge-directed glow ──────────────────────────── - - // Nearest point on the video border (clamped to [0, 1]) - vec2 edge_origin = clamp(video_uv, 0.0, 1.0); - - // Euclidean distance from this pixel to the video edge (used for fade-out). - // Constant 3.0 gives ~22 % brightness at half-radius and ~5 % at full-radius — - // visible glow across the whole pillarbox / letterbox area. - float edge_dist = length(video_uv - edge_origin); - float edge_fade = exp(-edge_dist * (3.0 / max(MAX_RADIUS, 0.001))); - - // Per-pixel rotation jitter to avoid banding in the spiral pattern - float jitter = rand(uv * HOOKED_size) * (PI * 2.0); - float angle_inc = PI * 2.0 / (PHI * PHI); // ~2.399 rad — golden angle - float inv_n = 1.0 / float(BLUR_SAMPLES); - - // Aspect correction keeps the blur kernel circular in screen space - vec2 aspect_fix = vec2(HOOKED_size.y / HOOKED_size.x, 1.0); - - vec3 acc_color = vec3(0.0); - float acc_weight = 0.0; - - // ── Fibonacci-spiral blur kernel ────────────────────────────────────────── - for (int i = 0; i < BLUR_SAMPLES; i++) { - float fi = float(i) + 0.5; - float r = sqrt(fi * inv_n) * MAX_RADIUS; - float theta = fi * angle_inc + jitter; - - // Sample from the nearest video-edge origin, spreading outward. - // This ensures ambient colours come from the actual video edge. - vec2 offset = vec2(cos(theta), sin(theta)) * r * aspect_fix; - vec2 sample_uv = clamp(edge_origin + offset, 0.0, 1.0); - vec3 sample_rgb = HOOKED_tex(sample_uv).rgb; - - // Weight = distance falloff × luminance bloom - // (brighter video pixels contribute more to the glow) - float dist_w = pow(max(1.0 / (1.0 + r * 40.0), 0.0), FADE_CURVE); - float luma_w = 1.0 + luma(sample_rgb) * 2.0; - float w = dist_w * luma_w; - - acc_color += sample_rgb * w; - acc_weight += w; - } - - // Normalise and apply global brightness - vec3 glow = (acc_color / max(acc_weight, 1e-5)) * GLOW_INTENSITY; - - // ── Post-processing ─────────────────────────────────────────────────────── - - glow = adjust_saturation(glow, SAT_BOOST); - glow = apply_warmth(glow, WARMTH); - - // Fade the glow out as distance from the video edge increases - glow *= edge_fade; - - // Radial vignette: corners receive less ambient light - float vig_r = length(uv - 0.5) * 2.0; - glow *= mix(1.0, smoothstep(1.3, 0.1, vig_r), VIGNETTE_STR); - - // Dither noise — breaks up colour banding in the gradient - float noise = rand(uv + vec2(fract(uv.x * 127.1), fract(uv.y * 311.7))); - glow = clamp(glow + DITHER_NOISE * (noise - 0.5), 0.0, 1.0); - - // ── Compositing ─────────────────────────────────────────────────────────── - // Bezel blend: transition from the nearest video-edge pixel outward into - // the ambient glow. BEZEL_DEPTH is in video-UV units; the blend lives - // entirely in the ambient region so no glow ever bleeds into the video. - float bezel = max(BEZEL_DEPTH, 0.001); - vec2 outside_dist = max(max(-video_uv, video_uv - vec2(1.0)), vec2(0.0)); - float dist_to_edge = max(outside_dist.x, outside_dist.y); - float bezel_alpha = smoothstep(0.0, bezel, dist_to_edge); - - // At dist=0 (right at edge): show the video edge pixel → seamless join. - // At dist≥bezel: show full ambient glow. - // OPACITY scales only rgb; alpha stays 1.0 so the output stays opaque. - vec4 edge_pixel = HOOKED_tex(edge_origin); - vec4 ambient_out = vec4(glow * OPACITY, 1.0); - - return mix(edge_pixel, ambient_out, bezel_alpha); -} - """.trimIndent() + fun updateAmbientStretch() = ambientModeManager.updateAmbientStretch() // ==================== Utility ==================== fun showToast(message: String) { Toast.makeText(host.context, message, Toast.LENGTH_SHORT).show() } + + override fun onCleared() { + super.onCleared() + ambientModeManager.cleanup() + } } // Extension functions diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerSheets.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerSheets.kt index 6a24429d8..f805a32fb 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerSheets.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerSheets.kt @@ -23,7 +23,6 @@ import app.marlboroadvance.mpvex.ui.player.controls.components.sheets.PlaylistSh import app.marlboroadvance.mpvex.ui.player.controls.components.sheets.SubtitlesSheet import app.marlboroadvance.mpvex.ui.player.controls.components.sheets.OnlineSubtitleSearchSheet import app.marlboroadvance.mpvex.ui.player.controls.components.sheets.VideoZoomSheet -import app.marlboroadvance.mpvex.ui.player.controls.components.sheets.AmbientSheet import app.marlboroadvance.mpvex.utils.media.MediaInfoParser import dev.vivvvek.seeker.Segment import kotlinx.collections.immutable.ImmutableList @@ -353,12 +352,5 @@ fun PlayerSheets( ) } } - - Sheets.AmbientConfig -> { - AmbientSheet( - viewModel = viewModel, - onDismissRequest = onDismissRequest - ) - } } } diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/sheets/AmbientSheet.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/sheets/AmbientSheet.kt deleted file mode 100644 index c0fdb53eb..000000000 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/sheets/AmbientSheet.kt +++ /dev/null @@ -1,372 +0,0 @@ -package app.marlboroadvance.mpvex.ui.player.controls.components.sheets - -import android.content.res.Configuration -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.BlurOn -import androidx.compose.material.icons.outlined.Brightness6 -import androidx.compose.material.icons.outlined.Grain -import androidx.compose.material.icons.outlined.Gradient -import androidx.compose.material.icons.outlined.Opacity -import androidx.compose.material.icons.outlined.Palette -import androidx.compose.material.icons.outlined.RoundedCorner -import androidx.compose.material.icons.outlined.Thermostat -import androidx.compose.material.icons.outlined.Vignette -import androidx.compose.material.icons.outlined.WbSunny -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.FilledTonalButton -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import app.marlboroadvance.mpvex.presentation.components.PlayerSheet -import app.marlboroadvance.mpvex.presentation.components.SliderItem -import app.marlboroadvance.mpvex.ui.player.PlayerViewModel -import app.marlboroadvance.mpvex.ui.theme.spacing - -@Composable -fun AmbientSheet( - viewModel: PlayerViewModel, - onDismissRequest: () -> Unit -) { - // ── Collect all state flows ────────────────────────────────────────────── - val blurSamples by viewModel.ambientBlurSamples.collectAsState() - val maxRadius by viewModel.ambientMaxRadius.collectAsState() - val glowIntensity by viewModel.ambientGlowIntensity.collectAsState() - val satBoost by viewModel.ambientSatBoost.collectAsState() - val vignetteStrength by viewModel.ambientVignetteStrength.collectAsState() - val warmth by viewModel.ambientWarmth.collectAsState() - val fadeCurve by viewModel.ambientFadeCurve.collectAsState() - val opacity by viewModel.ambientOpacity.collectAsState() - val bezelDepth by viewModel.ambientBezelDepth.collectAsState() - val ditherNoise by viewModel.ambientDitherNoise.collectAsState() - - // Profile detection (all conditions must match for the button to highlight) - val isFast = blurSamples == 16 && maxRadius == 0.17f && - glowIntensity == 1.4f && satBoost == 1.2f && - fadeCurve == 1.6f && opacity == 1.0f - val isBalanced = blurSamples == 24 && maxRadius == 0.20f && - glowIntensity == 1.45f && satBoost == 1.25f && - fadeCurve == 1.7f && opacity == 1.0f - val isHQ = blurSamples == 48 && maxRadius == 0.25f && - glowIntensity == 1.5f && satBoost == 1.3f && - fadeCurve == 1.8f && opacity == 1.0f - - val configuration = LocalConfiguration.current - val customMaxHeight = if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { - (configuration.screenHeightDp * 0.5f).dp - } else { - null - } - - PlayerSheet( - onDismissRequest = onDismissRequest, - customMaxHeight = customMaxHeight - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .verticalScroll(rememberScrollState()) - .padding(vertical = MaterialTheme.spacing.medium), - verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.small), - ) { - - // ── Title ──────────────────────────────────────────────────────── - Text( - text = "Ambience Mode", - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.onSurface, - textAlign = TextAlign.Center, - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 4.dp), - ) - - // ── Quality Presets ────────────────────────────────────────────── - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = MaterialTheme.spacing.medium), - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - FilledTonalButton( - onClick = { viewModel.applyAmbientProfileFast() }, - modifier = Modifier.weight(1f), - colors = if (isFast) ButtonDefaults.filledTonalButtonColors( - containerColor = MaterialTheme.colorScheme.primary, - contentColor = MaterialTheme.colorScheme.onPrimary, - ) else ButtonDefaults.filledTonalButtonColors(), - ) { - Text("Fast", fontWeight = FontWeight.Bold) - } - FilledTonalButton( - onClick = { viewModel.applyAmbientProfileBalanced() }, - modifier = Modifier.weight(1f), - colors = if (isBalanced) ButtonDefaults.filledTonalButtonColors( - containerColor = MaterialTheme.colorScheme.primary, - contentColor = MaterialTheme.colorScheme.onPrimary, - ) else ButtonDefaults.filledTonalButtonColors(), - ) { - Text("Balanced", fontWeight = FontWeight.Bold) - } - FilledTonalButton( - onClick = { viewModel.applyAmbientProfileHighQuality() }, - modifier = Modifier.weight(1f), - colors = if (isHQ) ButtonDefaults.filledTonalButtonColors( - containerColor = MaterialTheme.colorScheme.primary, - contentColor = MaterialTheme.colorScheme.onPrimary, - ) else ButtonDefaults.filledTonalButtonColors(), - ) { - Text("HQ", fontWeight = FontWeight.Bold) - } - } - - HorizontalDivider( - modifier = Modifier.padding(horizontal = MaterialTheme.spacing.medium), - color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.3f), - ) - - // ── Section: Glow ──────────────────────────────────────────────── - SectionLabel("Glow") - - SliderItem( - label = "Blur Samples", - valueText = "$blurSamples", - value = blurSamples, - onChange = { viewModel.updateAmbientParams(blurSamples = it) }, - min = 5, - max = 64, - icon = { - Icon( - imageVector = Icons.Outlined.BlurOn, - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.size(20.dp), - ) - }, - ) - - SliderItem( - label = "Spread", - valueText = "%.2f".format(maxRadius), - value = maxRadius, - onChange = { viewModel.updateAmbientParams(maxRadius = it) }, - min = 0.05f, - max = 0.80f, - steps = 75, - icon = { - Icon( - imageVector = Icons.Outlined.Gradient, - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.size(20.dp), - ) - }, - ) - - SliderItem( - label = "Glow Intensity", - valueText = "%.1f".format(glowIntensity), - value = glowIntensity, - onChange = { viewModel.updateAmbientParams(glowIntensity = it) }, - min = 0.5f, - max = 3.0f, - steps = 25, - icon = { - Icon( - imageVector = Icons.Outlined.Brightness6, - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.size(20.dp), - ) - }, - ) - - SliderItem( - label = "Fade Curve", - valueText = "%.1f".format(fadeCurve), - value = fadeCurve, - onChange = { viewModel.updateAmbientParams(fadeCurve = it) }, - min = 0.5f, - max = 3.0f, - steps = 25, - icon = { - Icon( - imageVector = Icons.Outlined.WbSunny, - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.size(20.dp), - ) - }, - ) - - HorizontalDivider( - modifier = Modifier.padding(horizontal = MaterialTheme.spacing.medium), - color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.3f), - ) - - // ── Section: Color ─────────────────────────────────────────────── - SectionLabel("Color") - - SliderItem( - label = "Saturation", - valueText = "%.1f".format(satBoost), - value = satBoost, - onChange = { viewModel.updateAmbientParams(satBoost = it) }, - min = 0.0f, - max = 3.0f, - steps = 30, - icon = { - Icon( - imageVector = Icons.Outlined.Palette, - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.size(20.dp), - ) - }, - ) - - SliderItem( - label = "Warmth", - valueText = if (warmth == 0f) "0" else "%.2f".format(warmth), - value = warmth, - onChange = { viewModel.updateAmbientParams(warmth = it) }, - min = -1.0f, - max = 1.0f, - steps = 40, - icon = { - Icon( - imageVector = Icons.Outlined.Thermostat, - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.size(20.dp), - ) - }, - ) - - HorizontalDivider( - modifier = Modifier.padding(horizontal = MaterialTheme.spacing.medium), - color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.3f), - ) - - // ── Section: Compositing ───────────────────────────────────────── - SectionLabel("Compositing") - - SliderItem( - label = "Opacity", - valueText = "%.2f".format(opacity), - value = opacity, - onChange = { viewModel.updateAmbientParams(opacity = it) }, - min = 0.0f, - max = 1.0f, - steps = 20, - icon = { - Icon( - imageVector = Icons.Outlined.Opacity, - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.size(20.dp), - ) - }, - ) - - SliderItem( - label = "Vignette", - valueText = "%.1f".format(vignetteStrength), - value = vignetteStrength, - onChange = { viewModel.updateAmbientParams(vignetteStrength = it) }, - min = 0.0f, - max = 1.0f, - steps = 10, - icon = { - Icon( - imageVector = Icons.Outlined.Vignette, - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.size(20.dp), - ) - }, - ) - - HorizontalDivider( - modifier = Modifier.padding(horizontal = MaterialTheme.spacing.medium), - color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.3f), - ) - - // ── Section: Advanced ──────────────────────────────────────────── - SectionLabel("Advanced") - - SliderItem( - label = "Bezel", - valueText = "%.3f".format(bezelDepth), - value = bezelDepth, - onChange = { viewModel.updateAmbientParams(bezelDepth = it) }, - min = 0.0f, - max = 0.1f, - steps = 50, - icon = { - Icon( - imageVector = Icons.Outlined.RoundedCorner, - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.size(20.dp), - ) - }, - ) - - SliderItem( - label = "Dither", - valueText = "%.3f".format(ditherNoise), - value = ditherNoise, - onChange = { viewModel.updateAmbientParams(ditherNoise = it) }, - min = 0.0f, - max = 0.05f, - steps = 50, - icon = { - Icon( - imageVector = Icons.Outlined.Grain, - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.size(20.dp), - ) - }, - ) - - Spacer(modifier = Modifier.height(8.dp)) - } - } -} - -// ── Helper: section label ──────────────────────────────────────────────────── -@Composable -private fun SectionLabel(text: String) { - Text( - text = text, - style = MaterialTheme.typography.labelMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.padding( - horizontal = MaterialTheme.spacing.medium, - vertical = 2.dp, - ), - ) -} diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/sheets/AudioTracksSheet.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/sheets/AudioTracksSheet.kt index bc445f597..85d50ba76 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/sheets/AudioTracksSheet.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/sheets/AudioTracksSheet.kt @@ -8,13 +8,10 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.MoreTime -import androidx.compose.material.icons.outlined.Info import androidx.compose.material3.FilterChip import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -23,12 +20,8 @@ import androidx.compose.material3.RadioButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight @@ -54,21 +47,6 @@ fun AudioTracksSheet( ) { val audioPreferences = koinInject() val audioChannels by audioPreferences.audioChannels.collectAsState() - val context = LocalContext.current - var infoDialogData by remember { mutableStateOf?>(null) } - - if (infoDialogData != null) { - androidx.compose.material3.AlertDialog( - onDismissRequest = { infoDialogData = null }, - title = { Text(infoDialogData!!.first) }, - text = { Text(infoDialogData!!.second) }, - confirmButton = { - androidx.compose.material3.TextButton(onClick = { infoDialogData = null }) { - Text(stringResource(R.string.generic_ok)) - } - } - ) - } GenericTracksSheet( tracks, @@ -98,33 +76,11 @@ fun AudioTracksSheet( .padding(MaterialTheme.spacing.medium) ) { Spacer(modifier = Modifier.height(MaterialTheme.spacing.medium)) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Start, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = stringResource(id = R.string.pref_audio_channels), - style = MaterialTheme.typography.titleMedium, - color = MaterialTheme.colorScheme.primary - ) - Spacer(modifier = Modifier.width(8.dp)) - IconButton(onClick = { - val descResName = "pref_audio_channels_${audioChannels.value.replace("-safe", "_safe").replace("-", "_")}_desc" - // Special handling for reversed stereo if value string doesn't match resource convention - val finalDescResName = if (audioChannels.name == "ReverseStereo") "pref_audio_channels_reverse_stereo_desc" else descResName - val resId = context.resources.getIdentifier(finalDescResName, "string", context.packageName) - val description = if (resId != 0) context.getString(resId) else "" - infoDialogData = Pair(context.getString(audioChannels.title), description) - }, modifier = Modifier.size(24.dp)) { - Icon( - imageVector = Icons.Outlined.Info, - contentDescription = "Info", - tint = MaterialTheme.colorScheme.primary, - modifier = Modifier.size(16.dp) - ) - } - } + Text( + text = stringResource(id = R.string.pref_audio_channels), + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.primary + ) Spacer(modifier = Modifier.height(MaterialTheme.spacing.smaller)) LazyRow( horizontalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.smaller), diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/sheets/MoreSheet.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/sheets/MoreSheet.kt index cca32cf15..5bc79647f 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/sheets/MoreSheet.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/sheets/MoreSheet.kt @@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items @@ -19,7 +18,6 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Tune -import androidx.compose.material.icons.outlined.Info import androidx.compose.material.icons.outlined.Timer import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button @@ -83,20 +81,6 @@ fun MoreSheet( val context = LocalContext.current val scope = rememberCoroutineScope() -var infoDialogData by remember { mutableStateOf?>(null) } - -if (infoDialogData != null) { - AlertDialog( - onDismissRequest = { }, - title = { Text(infoDialogData!!.first) }, - text = { Text(infoDialogData!!.second) }, - confirmButton = { - TextButton(onClick = { infoDialogData = null }) { - Text(stringResource(R.string.generic_ok)) - } - } - ) -} PlayerSheet( onDismissRequest, @@ -160,19 +144,10 @@ if (infoDialogData != null) { } } } - SectionHeaderWithInfo( - title = stringResource(R.string.player_sheets_stats_page_title), - onInfoClick = { - val descResName = "player_sheets_stats_page_${statisticsPage}_desc" - val resId = context.resources.getIdentifier(descResName, "string", context.packageName) - val description = if (resId != 0) context.getString(resId) else "" - - // Title for dialog: "Page X" or "Direct Title" - val titleRes = if (statisticsPage == 0) R.string.player_sheets_tracks_off else R.string.player_sheets_stats_page_chip - val title = if (statisticsPage == 0) context.getString(titleRes) else context.getString(titleRes, statisticsPage) - - infoDialogData = Pair(context.getString(R.string.player_sheets_stats_page_title), "$title: $description") - } + Text( + text = stringResource(R.string.player_sheets_stats_page_title), + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.primary ) LazyRow( horizontalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.smaller), @@ -436,31 +411,5 @@ fun TimePickerDialog( } -@Composable -fun SectionHeaderWithInfo( - title: String, - onInfoClick: () -> Unit, - modifier: Modifier = Modifier -) { - Row( - modifier = modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Start, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = title, - style = MaterialTheme.typography.titleMedium, - color = MaterialTheme.colorScheme.primary - ) - Spacer(modifier = Modifier.width(8.dp)) - IconButton(onClick = onInfoClick, modifier = Modifier.size(24.dp)) { - Icon( - imageVector = Icons.Outlined.Info, - contentDescription = "Info", - tint = MaterialTheme.colorScheme.primary, - modifier = Modifier.size(16.dp) - ) - } - } -} + diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/ControlLayoutEditorScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/ControlLayoutEditorScreen.kt index 14464e037..048ced515 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/ControlLayoutEditorScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/ControlLayoutEditorScreen.kt @@ -406,19 +406,12 @@ private fun IconsLegend() { verticalArrangement = Arrangement.spacedBy(16.dp) ) { // Header - androidx.compose.foundation.layout.Column { - Text( - text = "Icons Legend", - style = MaterialTheme.typography.titleMedium, - fontWeight = androidx.compose.ui.text.font.FontWeight.Bold, - color = MaterialTheme.colorScheme.onSurface, - ) - Text( - text = "What is each icon for?", - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } + Text( + text = "Icons Legend", + style = MaterialTheme.typography.titleMedium, + fontWeight = androidx.compose.ui.text.font.FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface, + ) // FlowRow grid of icons FlowRow( @@ -433,20 +426,36 @@ private fun IconsLegend() { horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.wrapContentWidth() ) { - val modifier = if (button == PlayerButton.VERTICAL_FLIP) { - Modifier.rotate(90f) + if (button == PlayerButton.AB_LOOP) { + // Show "AB" text instead of icon for AB_LOOP + Box( + modifier = Modifier.size(20.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "AB", + style = MaterialTheme.typography.labelMedium, + fontWeight = androidx.compose.ui.text.font.FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurfaceVariant, + textAlign = androidx.compose.ui.text.style.TextAlign.Center + ) + } } else { - Modifier - } + val modifier = if (button == PlayerButton.VERTICAL_FLIP) { + Modifier.rotate(90f) + } else { + Modifier + } - Icon( - imageVector = button.icon, - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier - .size(20.dp) - .then(modifier) - ) + Icon( + imageVector = button.icon, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier + .size(20.dp) + .then(modifier) + ) + } Text( text = app.marlboroadvance.mpvex.preferences.getPlayerButtonLabel(button), From 9bd93cb399c8f407b423b2a0321ca285eac67dde Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 14:25:45 +0530 Subject: [PATCH 171/194] Merge From 305bd9d8522b979b242a919ea2a2086634f15440 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 14:34:03 +0530 Subject: [PATCH 172/194] Update PlayerControlsShared.kt --- .../mpvex/ui/player/controls/PlayerControlsShared.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControlsShared.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControlsShared.kt index fb5494e21..dfb789103 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControlsShared.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControlsShared.kt @@ -126,7 +126,6 @@ fun RenderPlayerButton( PlayerButton.VIDEO_TITLE -> { val playlistModeEnabled = viewModel.hasPlaylistSupport() val titleInteractionSource = remember { MutableInteractionSource() } - if (backdrop != null && !hideBackground) { TransparentLiquidButton( backdrop = backdrop, @@ -230,7 +229,7 @@ fun RenderPlayerButton( backdrop = backdrop, modifier = Modifier.height(buttonSize), shape = CircleShape, onClick = { clickEvent(); onOpenSheet(Sheets.Decoders) } ) { - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = MaterialTheme.spacing.medium, vertical = MaterialTheme.spacing.small)) { + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = MaterialTheme.spacing.small, vertical = MaterialTheme.spacing.small)) { Text(decoder.title, maxLines = 1, color = Color.White, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodyMedium) } } From 3140f26836ec95f13794b8425ed77638237d1ffa Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 15:02:27 +0530 Subject: [PATCH 173/194] Update PlayerControlsShared.kt --- .../player/controls/PlayerControlsShared.kt | 73 +++---------------- 1 file changed, 11 insertions(+), 62 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControlsShared.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControlsShared.kt index dfb789103..71fb57b9e 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControlsShared.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControlsShared.kt @@ -226,11 +226,14 @@ fun RenderPlayerButton( PlayerButton.DECODER -> { if (backdrop != null && !hideBackground) { TransparentLiquidButton( - backdrop = backdrop, modifier = Modifier.height(buttonSize), shape = CircleShape, + backdrop = backdrop, + // FORCE the width to 56.dp so the Liquid Engine cannot stretch it! + modifier = Modifier.width(56.dp).height(buttonSize), + shape = CircleShape, onClick = { clickEvent(); onOpenSheet(Sheets.Decoders) } ) { - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = MaterialTheme.spacing.small, vertical = MaterialTheme.spacing.small)) { - Text(decoder.title, maxLines = 1, color = Color.White, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodyMedium) + Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) { + Text(decoder.title, maxLines = 1, color = Color.White, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodyMedium, fontWeight = androidx.compose.ui.text.font.FontWeight.Bold) } } } else { @@ -240,73 +243,19 @@ fun RenderPlayerButton( contentColor = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, tonalElevation = 0.dp, shadowElevation = 0.dp, border = if (hideBackground) null else BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), - modifier = Modifier.height(buttonSize).clip(CircleShape).clickable( + modifier = Modifier.width(56.dp).height(buttonSize).clip(CircleShape).clickable( interactionSource = remember { MutableInteractionSource() }, indication = ripple(bounded = true), onClick = { clickEvent(); onOpenSheet(Sheets.Decoders) } ) ) { - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = MaterialTheme.spacing.medium, vertical = MaterialTheme.spacing.small)) { - Text(text = decoder.title, maxLines = 1, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodyMedium) - } - } - } - } - - PlayerButton.SCREEN_ROTATION -> { - ControlsButton(icon = Icons.Default.ScreenRotation, onClick = viewModel::cycleScreenRotations, color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(buttonSize)) - } - - PlayerButton.FRAME_NAVIGATION -> { - val isExpanded by viewModel.isFrameNavigationExpanded.collectAsState() - val isSnapshotLoading by viewModel.isSnapshotLoading.collectAsState() - val context = LocalContext.current - - AnimatedContent( - targetState = isExpanded, - transitionSpec = { (fadeIn(animationSpec = tween(200)) + expandHorizontally(animationSpec = tween(250))).togetherWith(fadeOut(animationSpec = tween(200)) + shrinkHorizontally(animationSpec = tween(250))).using(SizeTransform(clip = false)) }, - label = "FrameNavExpandCollapse", - ) { expanded -> - if (expanded) { - if (backdrop != null && !hideBackground) { - LiquidGlassSurface( - backdrop = backdrop, target = LiquidTarget.BUTTON, shape = MaterialTheme.shapes.extraLarge, modifier = Modifier.height(buttonSize) - ) { - Row(horizontalArrangement = Arrangement.spacedBy(2.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 4.dp)) { - Box(modifier = Modifier.size(buttonSize - 4.dp).clip(CircleShape).clickable(onClick = { viewModel.frameStepBackward(); viewModel.resetFrameNavigationTimer() }), contentAlignment = Alignment.Center) { Icon(Icons.Default.FastRewind, contentDescription = "Previous Frame", tint = Color.White, modifier = Modifier.size(20.dp)) } - if (isSnapshotLoading) { - Box(modifier = Modifier.size(buttonSize - 4.dp), contentAlignment = Alignment.Center) { CircularProgressIndicator(modifier = Modifier.size(16.dp), strokeWidth = 2.dp, color = MaterialTheme.colorScheme.primary) } - } else { - @OptIn(ExperimentalFoundationApi::class) - Box(modifier = Modifier.size(buttonSize - 4.dp).clip(CircleShape).combinedClickable(onClick = { viewModel.takeSnapshot(context); viewModel.resetFrameNavigationTimer() }, onLongClick = { onOpenSheet(Sheets.FrameNavigation) }), contentAlignment = Alignment.Center) { Icon(Icons.Default.CameraAlt, contentDescription = "Take Screenshot", tint = Color.White, modifier = Modifier.size(20.dp)) } - } - Box(modifier = Modifier.size(buttonSize - 4.dp).clip(CircleShape).clickable(onClick = { viewModel.frameStepForward(); viewModel.resetFrameNavigationTimer() }), contentAlignment = Alignment.Center) { Icon(Icons.Default.FastForward, contentDescription = "Next Frame", tint = Color.White, modifier = Modifier.size(20.dp)) } - } - } - } else { - Surface( - shape = MaterialTheme.shapes.extraLarge, - color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), - border = if (hideBackground) null else BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), - modifier = Modifier.height(buttonSize) - ) { - Row(horizontalArrangement = Arrangement.spacedBy(2.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 4.dp)) { - Surface(shape = CircleShape, color = Color.Transparent, modifier = Modifier.size(buttonSize - 4.dp).clip(CircleShape).clickable(onClick = { viewModel.frameStepBackward(); viewModel.resetFrameNavigationTimer() })) { Box(contentAlignment = Alignment.Center) { Icon(Icons.Default.FastRewind, contentDescription = "Previous Frame", tint = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(20.dp)) } } - if (isSnapshotLoading) { - Surface(shape = CircleShape, color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), modifier = Modifier.size(buttonSize - 4.dp)) { Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) { CircularProgressIndicator(modifier = Modifier.size(16.dp), strokeWidth = 2.dp, color = if (hideBackground) controlColor else MaterialTheme.colorScheme.primary) } } - } else { - @OptIn(ExperimentalFoundationApi::class) - Surface(shape = CircleShape, color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), modifier = Modifier.size(buttonSize - 4.dp).clip(CircleShape).combinedClickable(onClick = { viewModel.takeSnapshot(context); viewModel.resetFrameNavigationTimer() }, onLongClick = { onOpenSheet(Sheets.FrameNavigation) })) { Box(contentAlignment = Alignment.Center) { Icon(Icons.Default.CameraAlt, contentDescription = "Take Screenshot", tint = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(20.dp)) } } - } - Surface(shape = CircleShape, color = Color.Transparent, modifier = Modifier.size(buttonSize - 4.dp).clip(CircleShape).clickable(onClick = { viewModel.frameStepForward(); viewModel.resetFrameNavigationTimer() })) { Box(contentAlignment = Alignment.Center) { Icon(Icons.Default.FastForward, contentDescription = "Next Frame", tint = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(20.dp)) } } - } - } + Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) { + Text(text = decoder.title, maxLines = 1, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodyMedium, fontWeight = androidx.compose.ui.text.font.FontWeight.Bold) } - } else { - ControlsButton(icon = Icons.Default.Camera, onClick = viewModel::toggleFrameNavigationExpanded, onLongClick = { onOpenSheet(Sheets.FrameNavigation) }, color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(buttonSize)) } } } - + } +} PlayerButton.VIDEO_ZOOM -> { if (kotlin.math.abs(currentZoom) >= 0.005f) { if (backdrop != null && !hideBackground) { From 2bd35df8ebc6ae5597b767597782730f73866100 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 15:34:23 +0530 Subject: [PATCH 174/194] Update VerticalSliders.kt --- .../ui/player/controls/components/VerticalSliders.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/VerticalSliders.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/VerticalSliders.kt index f62c9ee99..2b33abfae 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/VerticalSliders.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/VerticalSliders.kt @@ -88,8 +88,10 @@ fun VerticalSlider( Modifier .fillMaxWidth() .fillMaxHeight(targetHeight) - .background(if (backdrop != null) Color.White else MaterialTheme.colorScheme.tertiary), + // Now it uses your dynamic primary theme color instead of hardcoded white! + .background(if (backdrop != null) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.tertiary), ) + if (overflowRange != null && overflowValue != null) { val overflowHeight by animateFloatAsState(percentage(overflowValue, overflowRange), label = "vslideroverflowheight") Box( @@ -136,8 +138,10 @@ fun VerticalSlider( Modifier .fillMaxWidth() .fillMaxHeight(targetHeight) - .background(if (backdrop != null) Color.White else MaterialTheme.colorScheme.tertiary), + // Now it uses your dynamic primary theme color instead of hardcoded white! + .background(if (backdrop != null) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.tertiary), ) + if (overflowRange != null && overflowValue != null) { val overflowHeight by animateFloatAsState(percentage(overflowValue, overflowRange), label = "vslideroverflowheight") Box( From e935046c325fa3bea28f80f0c157c934edf6d79a Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 15:45:37 +0530 Subject: [PATCH 175/194] Update PlayerControlsShared.kt --- .../player/controls/PlayerControlsShared.kt | 61 ++++++++++++++++++- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControlsShared.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControlsShared.kt index 71fb57b9e..8757f23f0 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControlsShared.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControlsShared.kt @@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons @@ -126,6 +127,7 @@ fun RenderPlayerButton( PlayerButton.VIDEO_TITLE -> { val playlistModeEnabled = viewModel.hasPlaylistSupport() val titleInteractionSource = remember { MutableInteractionSource() } + if (backdrop != null && !hideBackground) { TransparentLiquidButton( backdrop = backdrop, @@ -227,7 +229,6 @@ fun RenderPlayerButton( if (backdrop != null && !hideBackground) { TransparentLiquidButton( backdrop = backdrop, - // FORCE the width to 56.dp so the Liquid Engine cannot stretch it! modifier = Modifier.width(56.dp).height(buttonSize), shape = CircleShape, onClick = { clickEvent(); onOpenSheet(Sheets.Decoders) } @@ -254,8 +255,62 @@ fun RenderPlayerButton( } } } - } -} + + PlayerButton.SCREEN_ROTATION -> { + ControlsButton(icon = Icons.Default.ScreenRotation, onClick = viewModel::cycleScreenRotations, color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(buttonSize)) + } + + PlayerButton.FRAME_NAVIGATION -> { + val isExpanded by viewModel.isFrameNavigationExpanded.collectAsState() + val isSnapshotLoading by viewModel.isSnapshotLoading.collectAsState() + val context = LocalContext.current + + AnimatedContent( + targetState = isExpanded, + transitionSpec = { (fadeIn(animationSpec = tween(200)) + expandHorizontally(animationSpec = tween(250))).togetherWith(fadeOut(animationSpec = tween(200)) + shrinkHorizontally(animationSpec = tween(250))).using(SizeTransform(clip = false)) }, + label = "FrameNavExpandCollapse", + ) { expanded -> + if (expanded) { + if (backdrop != null && !hideBackground) { + LiquidGlassSurface( + backdrop = backdrop, target = LiquidTarget.BUTTON, shape = MaterialTheme.shapes.extraLarge, modifier = Modifier.height(buttonSize) + ) { + Row(horizontalArrangement = Arrangement.spacedBy(2.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 4.dp)) { + Box(modifier = Modifier.size(buttonSize - 4.dp).clip(CircleShape).clickable(onClick = { viewModel.frameStepBackward(); viewModel.resetFrameNavigationTimer() }), contentAlignment = Alignment.Center) { Icon(Icons.Default.FastRewind, contentDescription = "Previous Frame", tint = Color.White, modifier = Modifier.size(20.dp)) } + if (isSnapshotLoading) { + Box(modifier = Modifier.size(buttonSize - 4.dp), contentAlignment = Alignment.Center) { CircularProgressIndicator(modifier = Modifier.size(16.dp), strokeWidth = 2.dp, color = MaterialTheme.colorScheme.primary) } + } else { + @OptIn(ExperimentalFoundationApi::class) + Box(modifier = Modifier.size(buttonSize - 4.dp).clip(CircleShape).combinedClickable(onClick = { viewModel.takeSnapshot(context); viewModel.resetFrameNavigationTimer() }, onLongClick = { onOpenSheet(Sheets.FrameNavigation) }), contentAlignment = Alignment.Center) { Icon(Icons.Default.CameraAlt, contentDescription = "Take Screenshot", tint = Color.White, modifier = Modifier.size(20.dp)) } + } + Box(modifier = Modifier.size(buttonSize - 4.dp).clip(CircleShape).clickable(onClick = { viewModel.frameStepForward(); viewModel.resetFrameNavigationTimer() }), contentAlignment = Alignment.Center) { Icon(Icons.Default.FastForward, contentDescription = "Next Frame", tint = Color.White, modifier = Modifier.size(20.dp)) } + } + } + } else { + Surface( + shape = MaterialTheme.shapes.extraLarge, + color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), + border = if (hideBackground) null else BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), + modifier = Modifier.height(buttonSize) + ) { + Row(horizontalArrangement = Arrangement.spacedBy(2.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 4.dp)) { + Surface(shape = CircleShape, color = Color.Transparent, modifier = Modifier.size(buttonSize - 4.dp).clip(CircleShape).clickable(onClick = { viewModel.frameStepBackward(); viewModel.resetFrameNavigationTimer() })) { Box(contentAlignment = Alignment.Center) { Icon(Icons.Default.FastRewind, contentDescription = "Previous Frame", tint = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(20.dp)) } } + if (isSnapshotLoading) { + Surface(shape = CircleShape, color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), modifier = Modifier.size(buttonSize - 4.dp)) { Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) { CircularProgressIndicator(modifier = Modifier.size(16.dp), strokeWidth = 2.dp, color = if (hideBackground) controlColor else MaterialTheme.colorScheme.primary) } } + } else { + @OptIn(ExperimentalFoundationApi::class) + Surface(shape = CircleShape, color = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f), border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)), modifier = Modifier.size(buttonSize - 4.dp).clip(CircleShape).combinedClickable(onClick = { viewModel.takeSnapshot(context); viewModel.resetFrameNavigationTimer() }, onLongClick = { onOpenSheet(Sheets.FrameNavigation) })) { Box(contentAlignment = Alignment.Center) { Icon(Icons.Default.CameraAlt, contentDescription = "Take Screenshot", tint = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(20.dp)) } } + } + Surface(shape = CircleShape, color = Color.Transparent, modifier = Modifier.size(buttonSize - 4.dp).clip(CircleShape).clickable(onClick = { viewModel.frameStepForward(); viewModel.resetFrameNavigationTimer() })) { Box(contentAlignment = Alignment.Center) { Icon(Icons.Default.FastForward, contentDescription = "Next Frame", tint = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(20.dp)) } } + } + } + } + } else { + ControlsButton(icon = Icons.Default.Camera, onClick = viewModel::toggleFrameNavigationExpanded, onLongClick = { onOpenSheet(Sheets.FrameNavigation) }, color = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, modifier = Modifier.size(buttonSize)) + } + } + } + PlayerButton.VIDEO_ZOOM -> { if (kotlin.math.abs(currentZoom) >= 0.005f) { if (backdrop != null && !hideBackground) { From d2afc093f032c1a36189d28aa8d1bb5a1a96c34e Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 19:31:33 +0530 Subject: [PATCH 176/194] Update DraggablePanel.kt --- .../components/panels/DraggablePanel.kt | 70 +++++++++++-------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/panels/DraggablePanel.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/panels/DraggablePanel.kt index 3751ed677..89bee2d7d 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/panels/DraggablePanel.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/panels/DraggablePanel.kt @@ -30,22 +30,20 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp - import kotlin.math.roundToInt -/** - * A draggable panel with an optional fixed header and scrollable content. - * - * @param modifier Modifier for the panel - * @param header Optional composable for the fixed header that stays constant during scroll - * @param content The scrollable content of the panel - */ +// --- NEW LIQUID IMPORTS --- +import app.marlboroadvance.mpvex.ui.components.liquid.LocalLiquidBackdrop +import app.marlboroadvance.mpvex.ui.components.liquid.LiquidGlassSurface +import app.marlboroadvance.mpvex.preferences.LiquidTarget + @Composable fun DraggablePanel( modifier: Modifier = Modifier, @@ -65,33 +63,26 @@ fun DraggablePanel( val density = LocalDensity.current val parentWidthPx = with(density) { maxWidth.toPx() } - // Calculate bounds for horizontal drag - // Panel is aligned to CenterEnd (Right), so offset 0 is the default rightmost position. val freeSpace = (parentWidthPx - panelWidth).coerceAtLeast(0f) val maxOffset = 0f val minOffset = -freeSpace - - // In portrait, cap panel height to 50% of available height val panelMaxHeight = if (isPortrait) maxHeight * 0.5f else maxHeight val colors = panelCardsColors() - Surface( - modifier = Modifier - .offset { IntOffset(offsetX.roundToInt(), 0) } - .onSizeChanged { panelWidth = it.width } - .widthIn(max = 380.dp) - .heightIn(max = panelMaxHeight), - shape = MaterialTheme.shapes.extraLarge, - color = colors.containerColor, - contentColor = colors.contentColor, - tonalElevation = 0.dp, - ) { + val backdrop = LocalLiquidBackdrop.current + + val panelModifier = Modifier + .offset { IntOffset(offsetX.roundToInt(), 0) } + .onSizeChanged { panelWidth = it.width } + .widthIn(max = 380.dp) + .heightIn(max = panelMaxHeight) + + val panelContent: @Composable () -> Unit = { Column { - // Drag Handle & Indicator Box( modifier = Modifier .fillMaxWidth() - .height(18.dp) // Good touch target size + .height(18.dp) .pointerInput(maxOffset, minOffset) { detectDragGestures { change, dragAmount -> change.consume() @@ -106,24 +97,41 @@ fun DraggablePanel( .width(32.dp) .height(4.dp) .background( - color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.4f), + color = if (backdrop != null) Color.White.copy(alpha = 0.5f) else MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.4f), shape = RoundedCornerShape(2.dp) ) ) } - // Fixed header (if provided) - stays constant if (header != null) { header() } - // Scrollable content - Column( - modifier = Modifier.verticalScroll(rememberScrollState()) - ) { + Column(modifier = Modifier.verticalScroll(rememberScrollState())) { content() } } } + + if (backdrop != null) { + LiquidGlassSurface( + backdrop = backdrop, + target = LiquidTarget.DIALOG, + shape = MaterialTheme.shapes.extraLarge, + modifier = panelModifier + ) { + panelContent() + } + } else { + Surface( + modifier = panelModifier, + shape = MaterialTheme.shapes.extraLarge, + color = colors.containerColor, + contentColor = colors.contentColor, + tonalElevation = 0.dp, + ) { + panelContent() + } + } } } From 026c2e7c51f2e37e7c50b796f458e7518ed8744d Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 19:32:09 +0530 Subject: [PATCH 177/194] Update PlayerSheet.kt --- .../presentation/components/PlayerSheet.kt | 96 +++++++++++-------- 1 file changed, 58 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/presentation/components/PlayerSheet.kt b/app/src/main/java/app/marlboroadvance/mpvex/presentation/components/PlayerSheet.kt index 93ebc9a19..997380843 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/presentation/components/PlayerSheet.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/presentation/components/PlayerSheet.kt @@ -60,6 +60,11 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch import kotlin.math.roundToInt +// --- NEW LIQUID IMPORTS --- +import app.marlboroadvance.mpvex.ui.components.liquid.LocalLiquidBackdrop +import app.marlboroadvance.mpvex.ui.components.liquid.LiquidGlassSurface +import app.marlboroadvance.mpvex.preferences.LiquidTarget + private val sheetAnimationSpec = tween(350) @SuppressLint("ConfigurationScreenWidthHeight") @@ -135,45 +140,60 @@ fun PlayerSheet( }, contentAlignment = Alignment.BottomCenter, ) { - Surface( - modifier = - Modifier - .sizeIn(maxWidth = maxWidth, maxHeight = maxHeight) - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null, - onClick = {}, - ).nestedScroll( - remember(anchoredDraggableState) { - anchoredDraggableState.preUpPostDownNestedScrollConnection() - }, - ).then(modifier) - .offset { - IntOffset( - 0, - anchoredDraggableState.offset - .takeIf { it.isFinite() } - ?.roundToInt() - ?: 0, - ) - }.anchoredDraggable( - state = anchoredDraggableState, - orientation = Orientation.Vertical, - ).windowInsetsPadding( - WindowInsets.systemBars - .only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal), - ).imePadding(), - shape = MaterialTheme.shapes.extraLarge.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize), - color = surfaceColor ?: MaterialTheme.colorScheme.surface, - tonalElevation = tonalElevation, - content = { - BackHandler( - enabled = anchoredDraggableState.targetValue == 0, - onBack = internalOnDismissRequest, + val backdrop = LocalLiquidBackdrop.current + val sheetModifier = Modifier + .sizeIn(maxWidth = maxWidth, maxHeight = maxHeight) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = {}, + ).nestedScroll( + remember(anchoredDraggableState) { + anchoredDraggableState.preUpPostDownNestedScrollConnection() + }, + ).then(modifier) + .offset { + IntOffset( + 0, + anchoredDraggableState.offset + .takeIf { it.isFinite() } + ?.roundToInt() + ?: 0, ) - content() - }, - ) + }.anchoredDraggable( + state = anchoredDraggableState, + orientation = Orientation.Vertical, + ).windowInsetsPadding( + WindowInsets.systemBars + .only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal), + ).imePadding() + + val sheetContent: @Composable () -> Unit = { + BackHandler( + enabled = anchoredDraggableState.targetValue == 0, + onBack = internalOnDismissRequest, + ) + content() + } + + if (backdrop != null) { + LiquidGlassSurface( + backdrop = backdrop, + target = LiquidTarget.DIALOG, + shape = MaterialTheme.shapes.extraLarge.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize), + modifier = sheetModifier + ) { + sheetContent() + } + } else { + Surface( + modifier = sheetModifier, + shape = MaterialTheme.shapes.extraLarge.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize), + color = surfaceColor ?: MaterialTheme.colorScheme.surface, + tonalElevation = tonalElevation, + content = sheetContent, + ) + } LaunchedEffect(true) { backgroundAlpha = 0.5f From 9f84457b8f88095c9c80870fdddc62e4a8198e2b Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 20:25:45 +0530 Subject: [PATCH 178/194] Update PlayerPanels.kt --- .../mpvex/ui/player/controls/PlayerPanels.kt | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerPanels.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerPanels.kt index 943fc10ea..6011c539a 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerPanels.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerPanels.kt @@ -14,6 +14,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import app.marlboroadvance.mpvex.ui.player.Panels import app.marlboroadvance.mpvex.ui.player.controls.components.panels.AudioDelayPanel @@ -21,6 +22,9 @@ import app.marlboroadvance.mpvex.ui.player.controls.components.panels.SubtitleDe import app.marlboroadvance.mpvex.ui.player.controls.components.panels.SubtitleSettingsPanel import app.marlboroadvance.mpvex.ui.player.controls.components.panels.VideoSettingsPanel +// --- NEW LIQUID IMPORT --- +import app.marlboroadvance.mpvex.ui.components.liquid.LocalLiquidBackdrop + @Composable fun PlayerPanels( panelShown: Panels, @@ -58,14 +62,27 @@ fun PlayerPanels( } val CARDS_MAX_WIDTH = 420.dp + val panelCardsColors: @Composable () -> CardColors = { - // Higher alpha for better readability in panels (less transparent) - val alpha = 0.85f + val backdrop = LocalLiquidBackdrop.current - CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = alpha), - contentColor = MaterialTheme.colorScheme.onSurface, - disabledContainerColor = MaterialTheme.colorScheme.surfaceContainerHighest.copy(alpha = alpha), - disabledContentColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f), - ) + if (backdrop != null) { + // LIQUID UI MODE: Nested Glass Effect! + // We use a highly transparent white so the parent panel's blur shines right through! + CardDefaults.cardColors( + containerColor = Color.White.copy(alpha = 0.15f), + contentColor = Color.White, + disabledContainerColor = Color.White.copy(alpha = 0.05f), + disabledContentColor = Color.White.copy(alpha = 0.38f), + ) + } else { + // STANDARD MODE: The original solid grey colors + val alpha = 0.85f + CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceContainer.copy(alpha = alpha), + contentColor = MaterialTheme.colorScheme.onSurface, + disabledContainerColor = MaterialTheme.colorScheme.surfaceContainerHighest.copy(alpha = alpha), + disabledContentColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f), + ) + } } From 68b043d195c0e34b65bdabda58ec4be3ce242565 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 20:53:22 +0530 Subject: [PATCH 179/194] Create LiquidAlertDialog.kt --- .../ui/components/liquid/LiquidAlertDialog.kt | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidAlertDialog.kt diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidAlertDialog.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidAlertDialog.kt new file mode 100644 index 000000000..4c776e250 --- /dev/null +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidAlertDialog.kt @@ -0,0 +1,41 @@ +package app.marlboroadvance.mpvex.ui.components.liquid + +import androidx.compose.foundation.background +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp + +@Composable +fun LiquidAlertDialog( + onDismissRequest: () -> Unit, + confirmButton: @Composable () -> Unit, + modifier: Modifier = Modifier, + dismissButton: @Composable (() -> Unit)? = null, + icon: @Composable (() -> Unit)? = null, + title: @Composable (() -> Unit)? = null, + text: @Composable (() -> Unit)? = null, +) { + val backdrop = LocalLiquidBackdrop.current + + AlertDialog( + onDismissRequest = onDismissRequest, + confirmButton = confirmButton, + dismissButton = dismissButton, + icon = icon, + title = title, + text = text, + // The Magic Sauce: If Liquid is enabled, it strips the grey background and makes it a transparent glass pane! + modifier = if (backdrop != null) { + modifier.background( + color = Color.White.copy(alpha = 0.15f), + shape = MaterialTheme.shapes.extraLarge + ) + } else modifier, + containerColor = if (backdrop != null) Color.Transparent else MaterialTheme.colorScheme.surfaceContainerHigh, + tonalElevation = if (backdrop != null) 0.dp else 6.dp, + shape = MaterialTheme.shapes.extraLarge + ) +} From 5c84f6599e5136285ee8c5b5dbe679a32cef69e8 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 20:57:35 +0530 Subject: [PATCH 180/194] Create LiquidCard.kt --- .../mpvex/ui/components/liquid/LiquidCard.kt | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidCard.kt diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidCard.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidCard.kt new file mode 100644 index 000000000..817fef288 --- /dev/null +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidCard.kt @@ -0,0 +1,50 @@ +package app.marlboroadvance.mpvex.ui.components.liquid + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.material3.Card +import androidx.compose.material3.CardColors +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.CardElevation +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Shape +import app.marlboroadvance.mpvex.preferences.LiquidTarget + +@Composable +fun LiquidCard( + modifier: Modifier = Modifier, + shape: Shape = CardDefaults.shape, + colors: CardColors = CardDefaults.cardColors(), + elevation: CardElevation = CardDefaults.cardElevation(), + border: BorderStroke? = null, + target: LiquidTarget = LiquidTarget.DIALOG, + content: @Composable ColumnScope.() -> Unit +) { + val backdrop = LocalLiquidBackdrop.current + + if (backdrop != null) { + // LIQUID UI MODE: Swaps the standard Card for a beautiful glass surface! + LiquidGlassSurface( + backdrop = backdrop, + target = target, + shape = shape, + modifier = modifier + ) { + // A standard Material Card naturally acts like a Column, so we wrap the content here + // to ensure your app's layouts don't break when switching to Glass. + Column(content = content) + } + } else { + // STANDARD MODE: Uses the exact original Material 3 Card + Card( + modifier = modifier, + shape = shape, + colors = colors, + elevation = elevation, + border = border, + content = content + ) + } +} From 80dc0f246baa0465ee5dc5cc97e59675525aa51d Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 22:44:06 +0530 Subject: [PATCH 181/194] Update MainActivity.kt --- .../app/marlboroadvance/mpvex/MainActivity.kt | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/MainActivity.kt b/app/src/main/java/app/marlboroadvance/mpvex/MainActivity.kt index de64dbab9..0dd55c2eb 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/MainActivity.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/MainActivity.kt @@ -50,6 +50,10 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.withContext import org.koin.android.ext.android.inject +// --- NEW LIQUID IMPORTS --- +import com.kyant.backdrop.Backdrop +import app.marlboroadvance.mpvex.ui.components.liquid.LocalLiquidBackdrop + /** * Main entry point for the application */ @@ -94,7 +98,14 @@ class MainActivity : ComponentActivity() { MpvexTheme { Surface { - Navigator() + // THE MAGIC SAUCE: We turn the Liquid Camera on at the root of the app! + Backdrop { backdrop -> + CompositionLocalProvider( + LocalLiquidBackdrop provides backdrop + ) { + Navigator() + } + } } } } @@ -137,7 +148,7 @@ class MainActivity : ComponentActivity() { } } catch (e: Exception) { withContext(Dispatchers.Main) { - Log.e("MainActivity", "Error during auto-connect", e) + Log.e("MainActivity", "Error during auto-connect", e) } } } @@ -186,26 +197,26 @@ class MainActivity : ComponentActivity() { transitionSpec = { ( fadeIn(animationSpec = tween(220)) + - slideIn(animationSpec = tween(220)) { IntOffset(it.width / 2, 0) } + slideIn(animationSpec = tween(220)) { IntOffset(it.width / 2, 0) } ) togetherWith ( fadeOut(animationSpec = tween(220)) + slideOut(animationSpec = tween(220)) { IntOffset(-it.width / 2, 0) } ) }, predictivePopTransitionSpec = { - ( + ( fadeIn(animationSpec = tween(220)) + scaleIn( animationSpec = tween(220, delayMillis = 30), initialScale = .9f, TransformOrigin(-1f, .5f), - ) + ) ) togetherWith ( fadeOut(animationSpec = tween(220)) + scaleOut( animationSpec = tween(220, delayMillis = 30), targetScale = .9f, - TransformOrigin(-1f, .5f), + TransformOrigin(-1f, .5f), ) ) }, @@ -214,7 +225,7 @@ class MainActivity : ComponentActivity() { // Display Update Dialog when appropriate (only if update feature is enabled) if (BuildConfig.ENABLE_UPDATE_FEATURE && updateViewModel != null) { when (updateState) { - is UpdateViewModel.UpdateState.Available -> { + is UpdateViewModel.UpdateState.Available -> { val release = (updateState as UpdateViewModel.UpdateState.Available).release UpdateDialog( release = release, @@ -227,7 +238,7 @@ class MainActivity : ComponentActivity() { onIgnore = { updateViewModel.ignoreVersion(release.tagName.removePrefix("v")) } ) } - is UpdateViewModel.UpdateState.ReadyToInstall -> { + is UpdateViewModel.UpdateState.ReadyToInstall -> { val release = (updateState as UpdateViewModel.UpdateState.ReadyToInstall).release UpdateDialog( release = release, @@ -240,7 +251,7 @@ class MainActivity : ComponentActivity() { onIgnore = { updateViewModel.ignoreVersion(release.tagName.removePrefix("v")) } ) } - else -> {} + else -> {} } } } From eedc588b267c44dfe27360e8cadf83515edceefa Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 22:57:28 +0530 Subject: [PATCH 182/194] Update MainActivity.kt --- .../app/marlboroadvance/mpvex/MainActivity.kt | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/MainActivity.kt b/app/src/main/java/app/marlboroadvance/mpvex/MainActivity.kt index 0dd55c2eb..7784d11bf 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/MainActivity.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/MainActivity.kt @@ -22,6 +22,7 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.toArgb @@ -51,7 +52,8 @@ import kotlinx.coroutines.withContext import org.koin.android.ext.android.inject // --- NEW LIQUID IMPORTS --- -import com.kyant.backdrop.Backdrop +import com.kyant.backdrop.backdrops.layerBackdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop import app.marlboroadvance.mpvex.ui.components.liquid.LocalLiquidBackdrop /** @@ -97,14 +99,14 @@ class MainActivity : ComponentActivity() { } MpvexTheme { - Surface { - // THE MAGIC SAUCE: We turn the Liquid Camera on at the root of the app! - Backdrop { backdrop -> - CompositionLocalProvider( - LocalLiquidBackdrop provides backdrop - ) { - Navigator() - } + // THE MAGIC SAUCE: We initialize the Liquid Camera properly! + val backdrop = rememberLayerBackdrop() + + Surface(modifier = Modifier.layerBackdrop(backdrop)) { + CompositionLocalProvider( + LocalLiquidBackdrop provides backdrop + ) { + Navigator() } } } From c584e6d2170e0819c7347b8ac8c58a5682fb51b5 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 23:19:46 +0530 Subject: [PATCH 183/194] Update MainActivity.kt --- app/src/main/java/app/marlboroadvance/mpvex/MainActivity.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/MainActivity.kt b/app/src/main/java/app/marlboroadvance/mpvex/MainActivity.kt index 7784d11bf..2ea21e150 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/MainActivity.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/MainActivity.kt @@ -99,10 +99,10 @@ class MainActivity : ComponentActivity() { } MpvexTheme { - // THE MAGIC SAUCE: We initialize the Liquid Camera properly! + // THE MAGIC SAUCE: We turn the camera on, but let the dialogs handle the drawing! val backdrop = rememberLayerBackdrop() - Surface(modifier = Modifier.layerBackdrop(backdrop)) { + Surface { // Modifier completely removed! CompositionLocalProvider( LocalLiquidBackdrop provides backdrop ) { @@ -112,6 +112,7 @@ class MainActivity : ComponentActivity() { } } } +} override fun onDestroy() { try { From 9aa1b06333f01b32ff0c75ef70ae4617009d1a9b Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 23:29:52 +0530 Subject: [PATCH 184/194] Update MainActivity.kt --- .../app/marlboroadvance/mpvex/MainActivity.kt | 22 ++----------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/MainActivity.kt b/app/src/main/java/app/marlboroadvance/mpvex/MainActivity.kt index 2ea21e150..247f2d995 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/MainActivity.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/MainActivity.kt @@ -22,7 +22,6 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.toArgb @@ -63,10 +62,8 @@ class MainActivity : ComponentActivity() { private val appearancePreferences by inject() private val networkRepository by inject() - // Create a coroutine scope tied to the activity lifecycle private val activityScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) - // Register the ActivityResultLauncher at class level private val mediaAccessLauncher = registerForActivityResult( ActivityResultContracts.StartIntentSenderForResult() ) { result -> @@ -78,11 +75,9 @@ class MainActivity : ComponentActivity() { PermissionUtils.setMediaAccessLauncher(mediaAccessLauncher) - // Register proxy lifecycle observer for network streaming lifecycle.addObserver(app.marlboroadvance.mpvex.ui.browser.networkstreaming.proxy.ProxyLifecycleObserver()) setContent { - // Set up theme and edge-to-edge display val dark by appearancePreferences.darkMode.collectAsState() val isSystemInDarkTheme = isSystemInDarkTheme() val isDarkMode = dark == DarkMode.Dark || (dark == DarkMode.System && isSystemInDarkTheme) @@ -93,16 +88,15 @@ class MainActivity : ComponentActivity() { ) { isDarkMode }, ) - // Auto-connect to saved network connections LaunchedEffect(Unit) { autoConnectToNetworks() } MpvexTheme { - // THE MAGIC SAUCE: We turn the camera on, but let the dialogs handle the drawing! + // Correct initialization of the backdrop engine val backdrop = rememberLayerBackdrop() - Surface { // Modifier completely removed! + Surface { CompositionLocalProvider( LocalLiquidBackdrop provides backdrop ) { @@ -112,7 +106,6 @@ class MainActivity : ComponentActivity() { } } } -} override fun onDestroy() { try { @@ -122,14 +115,9 @@ class MainActivity : ComponentActivity() { } } - /** - * Auto-connect to network connections that are marked for auto-connection - */ private suspend fun autoConnectToNetworks() { - // Delay auto-connect to let UI settle first kotlinx.coroutines.delay(500) - // Use coroutineScope for properly structured concurrency withContext(Dispatchers.IO) { try { val autoConnectConnections = networkRepository.getAutoConnectConnections() @@ -157,9 +145,6 @@ class MainActivity : ComponentActivity() { } } - /** - * Navigator that handles screen transitions and provides shared states - */ @Composable fun Navigator() { val backstack = rememberNavBackStack(MainScreen) @@ -170,7 +155,6 @@ class MainActivity : ComponentActivity() { val context = LocalContext.current val currentVersion = BuildConfig.VERSION_NAME.replace("-dev", "") - // Conditionally initialize update feature based on build config val updateViewModel: UpdateViewModel? = if (BuildConfig.ENABLE_UPDATE_FEATURE) { viewModel(context as ComponentActivity) } else { @@ -180,7 +164,6 @@ class MainActivity : ComponentActivity() { val isDownloading by (updateViewModel?.isDownloading ?: MutableStateFlow(false)).collectAsState() val downloadProgress by (updateViewModel?.downloadProgress ?: MutableStateFlow(0f)).collectAsState() - // Provide both LocalBackStack and the LazyList/Grid states to all screens CompositionLocalProvider( LocalBackStack provides typedBackstack ) { @@ -225,7 +208,6 @@ class MainActivity : ComponentActivity() { }, ) - // Display Update Dialog when appropriate (only if update feature is enabled) if (BuildConfig.ENABLE_UPDATE_FEATURE && updateViewModel != null) { when (updateState) { is UpdateViewModel.UpdateState.Available -> { From 97a19ff2410195a70d0c2949ddd1cf4a8f076d18 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Tue, 10 Mar 2026 23:33:14 +0530 Subject: [PATCH 185/194] Update MainActivity.kt --- app/src/main/java/app/marlboroadvance/mpvex/MainActivity.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/MainActivity.kt b/app/src/main/java/app/marlboroadvance/mpvex/MainActivity.kt index 247f2d995..3f2516ec5 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/MainActivity.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/MainActivity.kt @@ -22,6 +22,7 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.toArgb From 048e0bbefdbfb24872c4bfbf980b7b8692a9d83e Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Wed, 11 Mar 2026 00:02:54 +0530 Subject: [PATCH 186/194] Update LiquidUIPreferences.kt --- .../app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt b/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt index 4e5e61410..4fce8a3f1 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt @@ -16,6 +16,7 @@ enum class LiquidTarget(val id: String, val title: String) { NAV("nav", "Navigation"), BUTTON("btn", "Buttons"), DIALOG("dlg", "Dialogs") + CARD("card") } class LiquidUIPreferences(context: Context) { From 0a57a2b6ab3154f6308f4c15c1f3f2129b2a1b76 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Wed, 11 Mar 2026 00:17:48 +0530 Subject: [PATCH 187/194] Update LiquidUIPreferences.kt --- .../marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt b/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt index 4fce8a3f1..0899ac618 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt @@ -15,8 +15,8 @@ private val Context.liquidUIDataStore by preferencesDataStore(name = "liquid_ui_ enum class LiquidTarget(val id: String, val title: String) { NAV("nav", "Navigation"), BUTTON("btn", "Buttons"), - DIALOG("dlg", "Dialogs") - CARD("card") + DIALOG("dlg", "Dialogs"), + CARD("card", "Browser Cards") } class LiquidUIPreferences(context: Context) { From e9e3399f45d0e8c81feff06e1533a3c64eac655a Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Wed, 11 Mar 2026 00:25:30 +0530 Subject: [PATCH 188/194] Update LiquidUIPreferences.kt --- .../marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt b/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt index 0899ac618..cdf3ff71d 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt @@ -12,11 +12,12 @@ import kotlinx.coroutines.flow.map private val Context.liquidUIDataStore by preferencesDataStore(name = "liquid_ui_prefs") // --- THE TARGET ENUM --- +// Adding CARD here automatically generates the entire UI tab in AppearancePreferencesScreen! enum class LiquidTarget(val id: String, val title: String) { NAV("nav", "Navigation"), BUTTON("btn", "Buttons"), DIALOG("dlg", "Dialogs"), - CARD("card", "Browser Cards") + CARD("card", "Browser Cards") } class LiquidUIPreferences(context: Context) { From c0dfecb867bd46df93b16282bc8d1ce6a005ba11 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Wed, 11 Mar 2026 00:33:08 +0530 Subject: [PATCH 189/194] Update LiquidCard.kt --- .../marlboroadvance/mpvex/ui/components/liquid/LiquidCard.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidCard.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidCard.kt index 817fef288..65c348f6f 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidCard.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidCard.kt @@ -19,7 +19,8 @@ fun LiquidCard( colors: CardColors = CardDefaults.cardColors(), elevation: CardElevation = CardDefaults.cardElevation(), border: BorderStroke? = null, - target: LiquidTarget = LiquidTarget.DIALOG, + // The target is securely set to CARD so it listens to the new sliders! + target: LiquidTarget = LiquidTarget.CARD, content: @Composable ColumnScope.() -> Unit ) { val backdrop = LocalLiquidBackdrop.current From db809d331b35f7feae26c9ee2d31de43403f8c81 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Wed, 11 Mar 2026 01:42:41 +0530 Subject: [PATCH 190/194] Update LiquidCard.kt --- .../mpvex/ui/components/liquid/LiquidCard.kt | 53 ++++++++++++++++--- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidCard.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidCard.kt index 65c348f6f..0feec58d3 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidCard.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidCard.kt @@ -1,17 +1,20 @@ package app.marlboroadvance.mpvex.ui.components.liquid import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.material3.Card import androidx.compose.material3.CardColors import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardElevation +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import app.marlboroadvance.mpvex.preferences.LiquidTarget +// --- VERSION 1: STANDARD CARD --- @Composable fun LiquidCard( modifier: Modifier = Modifier, @@ -19,26 +22,21 @@ fun LiquidCard( colors: CardColors = CardDefaults.cardColors(), elevation: CardElevation = CardDefaults.cardElevation(), border: BorderStroke? = null, - // The target is securely set to CARD so it listens to the new sliders! - target: LiquidTarget = LiquidTarget.CARD, + target: LiquidTarget = LiquidTarget.CARD, content: @Composable ColumnScope.() -> Unit ) { val backdrop = LocalLiquidBackdrop.current if (backdrop != null) { - // LIQUID UI MODE: Swaps the standard Card for a beautiful glass surface! LiquidGlassSurface( backdrop = backdrop, target = target, shape = shape, modifier = modifier ) { - // A standard Material Card naturally acts like a Column, so we wrap the content here - // to ensure your app's layouts don't break when switching to Glass. Column(content = content) } } else { - // STANDARD MODE: Uses the exact original Material 3 Card Card( modifier = modifier, shape = shape, @@ -49,3 +47,46 @@ fun LiquidCard( ) } } + +// --- VERSION 2: CLICKABLE CARD (Fixes the onClick error!) --- +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun LiquidCard( + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + shape: Shape = CardDefaults.shape, + colors: CardColors = CardDefaults.cardColors(), + elevation: CardElevation = CardDefaults.cardElevation(), + border: BorderStroke? = null, + target: LiquidTarget = LiquidTarget.CARD, + content: @Composable ColumnScope.() -> Unit +) { + val backdrop = LocalLiquidBackdrop.current + + if (backdrop != null) { + // LIQUID UI MODE WITH CLICK ACTION + LiquidGlassSurface( + backdrop = backdrop, + target = target, + shape = shape, + modifier = modifier.then( + if (enabled) Modifier.clickable(onClick = onClick) else Modifier + ) + ) { + Column(content = content) + } + } else { + // STANDARD CLICKABLE MATERIAL 3 CARD + Card( + onClick = onClick, + modifier = modifier, + enabled = enabled, + shape = shape, + colors = colors, + elevation = elevation, + border = border, + content = content + ) + } +} From 7e994f170146f51439d52205bef8eb91bcfe8276 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Wed, 11 Mar 2026 02:15:05 +0530 Subject: [PATCH 191/194] Update AppearancePreferencesScreen.kt --- .../AppearancePreferencesScreen.kt | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt index b9119ef01..f86857f19 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/preferences/AppearancePreferencesScreen.kt @@ -62,6 +62,9 @@ import me.zhanghai.compose.preference.ProvidePreferenceLocals import me.zhanghai.compose.preference.SliderPreference import org.koin.compose.koinInject import kotlin.math.roundToInt +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.rememberScrollState + @Serializable object AppearancePreferencesScreen : Screen { @@ -208,11 +211,16 @@ object AppearancePreferencesScreen : Screen { PreferenceDivider() - // --- THE NEW PREMIUM TARGET SELECTOR --- + // --- THE NEW PREMIUM TARGET SELECTOR --- Column(modifier = Modifier.fillMaxWidth().padding(vertical = 12.dp), horizontalAlignment = Alignment.CenterHorizontally) { Text("Select UI Layer to Tune:", style = MaterialTheme.typography.labelLarge, color = MaterialTheme.colorScheme.outline, modifier = Modifier.padding(bottom = 8.dp)) + + // The Row is now safely scrollable so it won't break its layout! Row( - modifier = Modifier.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f), CircleShape).padding(4.dp), + modifier = Modifier + .horizontalScroll(rememberScrollState()) + .background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f), CircleShape) + .padding(4.dp), horizontalArrangement = Arrangement.Center ) { LiquidTarget.values().forEach { target -> @@ -223,12 +231,19 @@ object AppearancePreferencesScreen : Screen { color = if (isSelected) MaterialTheme.colorScheme.primary else Color.Transparent, contentColor = if (isSelected) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurfaceVariant ) { - Text(text = target.title, modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal) + // maxLines = 1 completely prevents the "wrapping" bug + Text( + text = target.title, + maxLines = 1, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), + fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal + ) } } } } + // --- THE SLIDERS --- Text("${selectedTarget.title} Tuning", style = MaterialTheme.typography.titleSmall, color = MaterialTheme.colorScheme.primary, modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) From be08fbe629b920b9a2f69765b965455f3a99b59e Mon Sep 17 00:00:00 2001 From: Aditya Date: Sat, 2 May 2026 19:54:34 +0530 Subject: [PATCH 192/194] Liquid (#10) * Change dialog scrim color for better text contrast Updated dialog scrim color to adapt to themes and improve text visibility. * Introduce shared LiquidUIPreferences CompositionLocal Added LocalLiquidPreferences to share LiquidUIPreferences across the component tree. * Refactor LiquidGlassSurface for performance improvements Refactor LiquidGlassSurface to use shared LocalLiquidPreferences for better performance and prevent unnecessary recompositions. Adjust tint alpha handling and improve the draw logic for different API levels. * Change default tint alpha value to improve readability Updated default tint alpha value from 0.15f to 0.5f for better text readability. --- .../mpvex/preferences/LiquidUIPreferences.kt | 4 +- .../ui/components/liquid/LiquidAlertDialog.kt | 9 ++- .../ui/components/liquid/LiquidComponents.kt | 7 ++ .../components/liquid/LiquidGlassSurface.kt | 79 +++++++++++++------ 4 files changed, 70 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt b/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt index cdf3ff71d..f075dc372 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/preferences/LiquidUIPreferences.kt @@ -47,7 +47,9 @@ class LiquidUIPreferences(context: Context) { fun blurRadiusFlow(target: LiquidTarget): Flow = dataStore.data.map { it[floatPreferencesKey("${target.id}_blur")] ?: 0f } fun refractionHeightFlow(target: LiquidTarget): Flow = dataStore.data.map { it[floatPreferencesKey("${target.id}_height")] ?: 40f } fun refractionAmountFlow(target: LiquidTarget): Flow = dataStore.data.map { it[floatPreferencesKey("${target.id}_amount")] ?: 23f } - fun tintAlphaFlow(target: LiquidTarget): Flow = dataStore.data.map { it[floatPreferencesKey("${target.id}_alpha")] ?: 0.15f } + // CHANGED: default raised 0.15f → 0.5f. Old value let backdrop text bleed through navigation/dialog + // glass; 0.5f is the Backdrop docs' recommended balance of "glass look" vs. text readability. + fun tintAlphaFlow(target: LiquidTarget): Flow = dataStore.data.map { it[floatPreferencesKey("${target.id}_alpha")] ?: 0.5f } fun chromaticAberrationFlow(target: LiquidTarget): Flow = dataStore.data.map { it[booleanPreferencesKey("${target.id}_chromatic")] ?: false } fun depthEffectFlow(target: LiquidTarget): Flow = dataStore.data.map { it[booleanPreferencesKey("${target.id}_depth")] ?: true } diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidAlertDialog.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidAlertDialog.kt index 4c776e250..acc8fac5c 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidAlertDialog.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidAlertDialog.kt @@ -20,6 +20,12 @@ fun LiquidAlertDialog( ) { val backdrop = LocalLiquidBackdrop.current + // CHANGED: dialog scrim was previously `Color.White.copy(alpha = 0.15f)`. That flat 15%-opaque white was the + // root cause of the dialog text bleeding into menu/background text — and it didn't adapt to dark theme. + // New scrim: theme color (`surfaceContainerHigh`) at 0.85 alpha. Adapts to light/dark automatically and is + // opaque enough that text behind the dialog can't compete with text inside it. + val scrimColor = MaterialTheme.colorScheme.surfaceContainerHigh.copy(alpha = 0.85f) + AlertDialog( onDismissRequest = onDismissRequest, confirmButton = confirmButton, @@ -27,10 +33,9 @@ fun LiquidAlertDialog( icon = icon, title = title, text = text, - // The Magic Sauce: If Liquid is enabled, it strips the grey background and makes it a transparent glass pane! modifier = if (backdrop != null) { modifier.background( - color = Color.White.copy(alpha = 0.15f), + color = scrimColor, shape = MaterialTheme.shapes.extraLarge ) } else modifier, diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt index 2de4cbeaf..ac41d3156 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidComponents.kt @@ -17,10 +17,17 @@ import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.dp import com.kyant.backdrop.Backdrop import app.marlboroadvance.mpvex.preferences.LiquidTarget +import app.marlboroadvance.mpvex.preferences.LiquidUIPreferences // Broadcasts the glass camera to any button that wants it! val LocalLiquidBackdrop = androidx.compose.runtime.staticCompositionLocalOf { null } +// ADDED: shared LiquidUIPreferences CompositionLocal. +// Why: previously every LiquidGlassSurface built its own DataStore wrapper via remember{ LiquidUIPreferences(context) }. +// With many glass surfaces on screen (nav + buttons + cards) that meant N wrappers all observing the same DataStore. +// MainActivity can now provide one instance for the whole tree; null fallback keeps old behavior working. +val LocalLiquidPreferences = androidx.compose.runtime.staticCompositionLocalOf { null } + @OptIn(ExperimentalFoundationApi::class) @Composable fun TransparentLiquidButton( diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidGlassSurface.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidGlassSurface.kt index 15a48c3cb..83f8aa170 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidGlassSurface.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/components/liquid/LiquidGlassSurface.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import com.kyant.backdrop.Backdrop @@ -28,49 +29,75 @@ import app.marlboroadvance.mpvex.preferences.LiquidTarget @Composable fun LiquidGlassSurface( backdrop: Backdrop, - target: LiquidTarget = LiquidTarget.NAV, // Targets the Nav bar by default + target: LiquidTarget = LiquidTarget.NAV, modifier: Modifier = Modifier, - shape: Shape = RoundedCornerShape(24.dp), - defaultTintColor: Color = Color.White, + shape: Shape = RoundedCornerShape(24.dp), + defaultTintColor: Color = Color.White, content: @Composable () -> Unit ) { val context = LocalContext.current - val liquidPrefs = remember { LiquidUIPreferences(context) } - - // SAFE STATE COLLECTIONS: Only updates when the target actually changes - val blurRadius by remember(target) { liquidPrefs.blurRadiusFlow(target) }.collectAsState(initial = 0f) - val refractionHeight by remember(target) { liquidPrefs.refractionHeightFlow(target) }.collectAsState(initial = 40f) - val refractionAmount by remember(target) { liquidPrefs.refractionAmountFlow(target) }.collectAsState(initial = 23f) - val chromaticAberration by remember(target) { liquidPrefs.chromaticAberrationFlow(target) }.collectAsState(initial = false) - val depthEffect by remember(target) { liquidPrefs.depthEffectFlow(target) }.collectAsState(initial = true) - val vibrancyEnabled by remember(target) { liquidPrefs.vibrancyEnabledFlow(target) }.collectAsState(initial = true) - val tintAlpha by remember(target) { liquidPrefs.tintAlphaFlow(target) }.collectAsState(initial = 0.15f) + // CHANGED: prefer shared LocalLiquidPreferences if MainActivity provided one (perf: one DataStore wrapper for whole tree). + // Fallback to a remembered instance keyed on applicationContext so it survives configuration changes + // and is not rebuilt on every recomposition. + val sharedPrefs = LocalLiquidPreferences.current + val liquidPrefs = sharedPrefs ?: remember(context.applicationContext) { + LiquidUIPreferences(context.applicationContext) + } + + val blurRadius by remember(liquidPrefs, target) { liquidPrefs.blurRadiusFlow(target) }.collectAsState(initial = 0f) + val refractionHeight by remember(liquidPrefs, target) { liquidPrefs.refractionHeightFlow(target) }.collectAsState(initial = 40f) + val refractionAmount by remember(liquidPrefs, target) { liquidPrefs.refractionAmountFlow(target) }.collectAsState(initial = 23f) + val chromaticAberration by remember(liquidPrefs, target) { liquidPrefs.chromaticAberrationFlow(target) }.collectAsState(initial = false) + val depthEffect by remember(liquidPrefs, target) { liquidPrefs.depthEffectFlow(target) }.collectAsState(initial = true) + val vibrancyEnabled by remember(liquidPrefs, target) { liquidPrefs.vibrancyEnabledFlow(target) }.collectAsState(initial = true) + // CHANGED: initial value 0.15f → 0.5f to match the new DataStore default; prevents a flash of see-through glass + // (where backdrop text would bleed through) on first frame before the flow emits. + val tintAlpha by remember(liquidPrefs, target) { liquidPrefs.tintAlphaFlow(target) }.collectAsState(initial = 0.5f) + + val density = LocalDensity.current + // ADDED (perf): hoist dp→px conversions out of the per-frame `effects` draw lambda. + // Previously `refractionHeight.dp.toPx()` etc. ran on every frame inside drawBackdrop's effects block. + // Now they only recompute when density or the underlying pref value actually changes. + val blurPx = remember(density, blurRadius) { with(density) { blurRadius.dp.toPx() } } + val refractionHeightPx = remember(density, refractionHeight) { with(density) { refractionHeight.dp.toPx() } } + val refractionAmountPx = remember(density, refractionAmount) { with(density) { refractionAmount.dp.toPx() } } + // ADDED: clamp tint alpha to [0,1] defensively; a stray out-of-range pref value would otherwise crash drawRect. + val safeTintAlpha = tintAlpha.coerceIn(0f, 1f) - if (Build.VERSION.SDK_INT >= 33) { + if (Build.VERSION.SDK_INT >= 33) { Box( modifier = modifier .drawBackdrop( backdrop = backdrop, shape = { shape }, effects = { + // CHANGED: enforce Backdrop docs' required effect order — color filter (vibrancy) → blur → lens. + // ADDED (perf): skip lens() entirely when refraction params are 0 — the lens shader is + // the most expensive effect in this pipeline, and running it with zero amount is wasted GPU work. if (vibrancyEnabled) vibrancy() - if (blurRadius > 0f) blur(blurRadius.dp.toPx()) - - lens( - refractionHeight = refractionHeight.dp.toPx(), - refractionAmount = refractionAmount.dp.toPx(), - depthEffect = depthEffect, - chromaticAberration = chromaticAberration - ) + if (blurPx > 0f) blur(blurPx) + if (refractionHeightPx > 0f && refractionAmountPx > 0f) { + lens( + refractionHeight = refractionHeightPx, + refractionAmount = refractionAmountPx, + depthEffect = depthEffect, + chromaticAberration = chromaticAberration + ) + } }, - onDrawSurface = { drawRect(defaultTintColor.copy(alpha = tintAlpha)) } + onDrawSurface = { drawRect(defaultTintColor.copy(alpha = safeTintAlpha)) } ) ) { content() } - } else { + } else { + // CHANGED: pre-API-33 fallback alpha is now coerced to ≥ 0.7f. + // Reason: this branch can't run blur/lens shaders (RuntimeShader is API 33+), so the tint is the ONLY + // thing separating foreground text from backdrop content. 0.5f looked transparent here even though it + // works fine on API 33+ where blur/lens further obscure the backdrop. + val fallbackAlpha = safeTintAlpha.coerceAtLeast(0.7f) Box( modifier = modifier - .background(defaultTintColor.copy(alpha = tintAlpha), shape) - .border(1.dp, Color.White.copy(alpha = 0.2f), shape) + .background(defaultTintColor.copy(alpha = fallbackAlpha), shape) + .border(1.dp, Color.White.copy(alpha = 0.2f), shape) .clip(shape) ) { content() } } From d2816b3faa4a3f3f3cd69050950231e494101602 Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 2 May 2026 21:23:55 +0530 Subject: [PATCH 193/194] Refactor PlayerControls with code cleanup and fixes --- .../ui/player/controls/PlayerControls.kt | 162 +++++++++--------- 1 file changed, 81 insertions(+), 81 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt index 2d687850d..a14d0d046 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/PlayerControls.kt @@ -165,8 +165,8 @@ fun PlayerControls( val context = LocalContext.current val liquidUIPreferences = remember { LiquidUIPreferences(context) } // Wired directly to the DataStore, defaults to false until toggled - val liquidUIEnabled by liquidUIPreferences.liquidUIEnabledFlow.collectAsState(initial = false) - + val liquidUIEnabled by liquidUIPreferences.liquidUIEnabledFlow.collectAsState(initial = false) + // The engine that captures the screen for the blur/lens effects val backdrop = rememberLayerBackdrop { drawContent() } @@ -631,8 +631,8 @@ fun PlayerControls( .horizontalScroll(rememberScrollState()) ) { customButtons.filter { it.isLeft }.forEach { button -> - LiquidCustomButton(button, liquidUIEnabled, backdrop, viewModel, haptic) { - resetControlsTimestamp = System.currentTimeMillis() + LiquidCustomButton(button, liquidUIEnabled, backdrop, viewModel, haptic) { + resetControlsTimestamp = System.currentTimeMillis() } } } @@ -659,8 +659,8 @@ fun PlayerControls( .horizontalScroll(rememberScrollState(), reverseScrolling = true) ) { customButtons.filter { !it.isLeft }.forEach { button -> - LiquidCustomButton(button, liquidUIEnabled, backdrop, viewModel, haptic) { - resetControlsTimestamp = System.currentTimeMillis() + LiquidCustomButton(button, liquidUIEnabled, backdrop, viewModel, haptic) { + resetControlsTimestamp = System.currentTimeMillis() } } } @@ -686,8 +686,8 @@ fun PlayerControls( .horizontalScroll(rememberScrollState()) ) { customButtons.forEach { button -> - LiquidCustomButton(button, liquidUIEnabled, backdrop, viewModel, haptic) { - resetControlsTimestamp = System.currentTimeMillis() + LiquidCustomButton(button, liquidUIEnabled, backdrop, viewModel, haptic) { + resetControlsTimestamp = System.currentTimeMillis() } } } @@ -923,71 +923,18 @@ fun PlayerControls( ) } - Surface( - modifier = - Modifier - .size(56.dp) - .clip(CircleShape) - .clickable( - enabled = viewModel.hasNext(), - onClick = { - resetControlsTimestamp = System.currentTimeMillis() - if (viewModel.hasNext()) viewModel.playNext() - }, - ) - .then( - if (hideBackground) { - Modifier.background(brush = buttonShadow, shape = CircleShape) - } else { - Modifier - }, - ), - shape = CircleShape, - color = - if (!hideBackground) { - MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f) - } else { - Color.Transparent - }, - contentColor = MaterialTheme.colorScheme.onSurface, - tonalElevation = 0.dp, - shadowElevation = 0.dp, - border = - if (!hideBackground) { - BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)) - } else { - null - }, - ) { - Icon( - imageVector = Icons.Default.SkipNext, - contentDescription = "Next", - tint = - if (viewModel.hasNext()) { - if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface - } else { - if (hideBackground) { - controlColor.copy(alpha = 0.38f) - } else { - MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) - } - }, - modifier = Modifier - .fillMaxSize() - .padding(MaterialTheme.spacing.small), - ) - } - } - } else { Surface( modifier = Modifier - .size(64.dp) + .size(56.dp) .clip(CircleShape) - .clickable(interaction, ripple(), onClick = { - resetControlsTimestamp = System.currentTimeMillis() - viewModel.pauseUnpause() - }) + .clickable( + enabled = viewModel.hasNext(), + onClick = { + resetControlsTimestamp = System.currentTimeMillis() + if (viewModel.hasNext()) viewModel.playNext() + }, + ) .then( if (hideBackground) { Modifier.background(brush = buttonShadow, shape = CircleShape) @@ -1002,30 +949,83 @@ fun PlayerControls( } else { Color.Transparent }, - contentColor = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, + contentColor = MaterialTheme.colorScheme.onSurface, tonalElevation = 0.dp, shadowElevation = 0.dp, border = - if (!hideBackground) { + if (!hideBackground) { BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)) } else { null }, ) { - Image( - painter = rememberAnimatedVectorPainter(icon, paused == false), + Icon( + imageVector = Icons.Default.SkipNext, + contentDescription = "Next", + tint = + if (viewModel.hasNext()) { + if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface + } else { + if (hideBackground) { + controlColor.copy(alpha = 0.38f) + } else { + MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) + } + }, modifier = Modifier .fillMaxSize() - .padding(MaterialTheme.spacing.medium), - contentDescription = null, - colorFilter = ColorFilter.tint(LocalContentColor.current), + .padding(MaterialTheme.spacing.small), + ) + } + } + } else { + Surface( + modifier = + Modifier + .size(64.dp) + .clip(CircleShape) + .clickable(interaction, ripple(), onClick = { + resetControlsTimestamp = System.currentTimeMillis() + viewModel.pauseUnpause() + }) + .then( + if (hideBackground) { + Modifier.background(brush = buttonShadow, shape = CircleShape) + } else { + Modifier + }, + ), + shape = CircleShape, + color = + if (!hideBackground) { + MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.55f) + } else { + Color.Transparent + }, + contentColor = if (hideBackground) controlColor else MaterialTheme.colorScheme.onSurface, + tonalElevation = 0.dp, + shadowElevation = 0.dp, + border = + if (!hideBackground) { + BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)) + } else { + null + }, + ) { + Image( + painter = rememberAnimatedVectorPainter(icon, paused == false), + modifier = Modifier + .fillMaxSize() + .padding(MaterialTheme.spacing.medium), + contentDescription = null, + colorFilter = ColorFilter.tint(LocalContentColor.current), ) } } } } } - } + } AnimatedVisibility( visible = controlsShown && !areControlsLocked, @@ -1414,14 +1414,14 @@ fun PlayerControls( panelShown = panel, onDismissRequest = { onOpenPanel(Panels.None) }, ) - + // ========================================== // INJECT THE NATIVE PAGE 6 HARDWARE HUD HERE // ========================================== HardwareHudOverlay() - } - + }} + @OptIn(androidx.compose.foundation.ExperimentalFoundationApi::class) @Composable fun LiquidCustomButton( @@ -1448,7 +1448,7 @@ fun LiquidCustomButton( Text( text = button.label, // FIXED: Cleaned up the modifier syntax! - modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp).basicMarquee(), + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp).basicMarquee(), style = MaterialTheme.typography.labelLarge.copy(fontWeight = FontWeight.Bold), maxLines = 1, softWrap = false, From 37c46055e9fe00c8604be7c924f04f1568edaf7d Mon Sep 17 00:00:00 2001 From: SunnyVishnu3 Date: Sat, 2 May 2026 21:25:01 +0530 Subject: [PATCH 194/194] Refactor SectionHeaderWithInfo component Refactor SectionHeaderWithInfo to improve readability and usage. --- .../controls/components/sheets/MoreSheet.kt | 102 +++++++++--------- 1 file changed, 54 insertions(+), 48 deletions(-) diff --git a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/sheets/MoreSheet.kt b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/sheets/MoreSheet.kt index 926e43896..c3665d3ad 100644 --- a/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/sheets/MoreSheet.kt +++ b/app/src/main/java/app/marlboroadvance/mpvex/ui/player/controls/components/sheets/MoreSheet.kt @@ -11,12 +11,14 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.Tune import androidx.compose.material.icons.outlined.Timer import androidx.compose.material3.AlertDialog @@ -81,21 +83,23 @@ fun MoreSheet( val context = LocalContext.current val scope = rememberCoroutineScope() + var infoDialogData by remember { mutableStateOf?>(null) } - - if (infoDialogData != null) { - AlertDialog( - onDismissRequest = { }, - title = { Text(infoDialogData!!.first) }, - text = { Text(infoDialogData!!.second) }, - confirmButton = { - TextButton(onClick = { infoDialogData = null }) { - Text(stringResource(R.string.generic_ok)) - } - } - ) + + infoDialogData?.let { (title, message) -> + AlertDialog( + onDismissRequest = { infoDialogData = null }, + title = { Text(title) }, + text = { Text(message) }, + confirmButton = { + TextButton(onClick = { infoDialogData = null }) { + Text(stringResource(id = R.string.generic_ok)) + } + } + ) } + PlayerSheet( onDismissRequest, modifier, @@ -125,7 +129,7 @@ fun MoreSheet( ) { Icon(imageVector = Icons.Outlined.Timer, contentDescription = null) Text( - text = if (remainingTime == 0) stringResource(R.string.timer_title) + text = if (remainingTime == 0) stringResource(R.string.timer_title) else stringResource(R.string.timer_remaining, DateUtils.formatElapsedTime(remainingTime.toLong())), ) if (isSleepTimerDialogShown) { @@ -148,7 +152,7 @@ fun MoreSheet( } } } - + SectionHeaderWithInfo( title = stringResource(R.string.player_sheets_stats_page_title), onInfoClick = { @@ -159,17 +163,17 @@ fun MoreSheet( val resId = context.resources.getIdentifier(descResName, "string", context.packageName) if (resId != 0) context.getString(resId) else "No description available." } - + val title = when (statisticsPage) { 0 -> context.getString(R.string.player_sheets_tracks_off) 6 -> "Page 6: Hardware HUD" else -> context.getString(R.string.player_sheets_stats_page_chip, statisticsPage) } - + infoDialogData = Pair(context.getString(R.string.player_sheets_stats_page_title), "$title\n\n$description") } ) - + LazyRow(horizontalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.smaller)) { items(7) { page -> FilterChip( @@ -192,7 +196,7 @@ fun MoreSheet( if (page in 1..5) { MPVLib.command("script-binding", "stats/display-page-$page") } - + advancedPreferences.enabledStatisticsPage.set(page) }, selected = statisticsPage == page, @@ -200,7 +204,7 @@ fun MoreSheet( ) } } - + if (enableAnime4K && (!gpuNext || useVulkan)) { val width = MPVLib.getPropertyInt("video-params/w") ?: 0 val height = MPVLib.getPropertyInt("video-params/h") ?: 0 @@ -280,6 +284,36 @@ fun MoreSheet( } } +@Composable +private fun SectionHeaderWithInfo( + title: String, + onInfoClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + text = title, + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.primary, + ) + IconButton( + onClick = onInfoClick, + modifier = Modifier.size(24.dp), + ) { + Icon( + imageVector = Icons.Default.Info, + contentDescription = null, + modifier = Modifier.size(16.dp), + tint = MaterialTheme.colorScheme.primary, + ) + } + } +} + @OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) @Composable fun TimePickerDialog( @@ -331,7 +365,7 @@ fun TimePickerDialog( ) TimeInput(state = state) - + Column( horizontalAlignment = Alignment.Start, modifier = Modifier.fillMaxWidth() @@ -391,31 +425,3 @@ fun TimePickerDialog( } } } - -@Composable -fun SectionHeaderWithInfo( - title: String, - onInfoClick: () -> Unit, - modifier: Modifier = Modifier -) { - Row( - modifier = modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Start, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = title, - style = MaterialTheme.typography.titleMedium, - color = MaterialTheme.colorScheme.primary - ) - Spacer(modifier = Modifier.width(8.dp)) - IconButton(onClick = onInfoClick, modifier = Modifier.size(24.dp)) { - Icon( - imageVector = Icons.Outlined.Info, - contentDescription = "Info", - tint = MaterialTheme.colorScheme.primary, - modifier = Modifier.size(16.dp) - ) - } - } -}