From 6789a6e9ac034d43ba378bd5c4f2ddf9194d3850 Mon Sep 17 00:00:00 2001 From: Isaac Serrano Date: Fri, 23 Oct 2026 21:53:59 -0700 Subject: [PATCH] feat: update island pulse shadow options and rendering --- .../data/repository/SettingsRepository.kt | 22 ++++---- .../island/service/IslandCoordinator.kt | 14 ++--- .../essentials/island/ui/IslandLayoutSpec.kt | 7 +-- .../essentials/island/ui/IslandRoot.kt | 36 ++++++------- .../IslandPulseShadowOptionsBottomSheet.kt | 53 ++++++++++++------- .../essentials/viewmodels/MainViewModel.kt | 29 ++++++---- app/src/main/res/values-es/strings.xml | 2 - app/src/main/res/values-nl/strings.xml | 2 - app/src/main/res/values-vi/strings.xml | 2 - app/src/main/res/values-zh/strings.xml | 1 - app/src/main/res/values/strings.xml | 7 +-- 11 files changed, 96 insertions(+), 79 deletions(-) diff --git a/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt b/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt index be508fa054..7f65e1852e 100644 --- a/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt +++ b/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt @@ -710,9 +710,10 @@ class SettingsRepository( const val KEY_ISLAND_BORDER_OUTLINE_THICKNESS = "island_border_outline_thickness" const val KEY_ISLAND_BORDER_OUTLINE_HIDE_EXPANDED = "island_border_outline_hide_expanded" const val KEY_ISLAND_PULSE_SHADOW_ON_NOTIFICATION = "island_pulse_shadow_on_notification" - const val KEY_ISLAND_PULSE_SHADOW_SIZE = "island_pulse_shadow_size" - const val KEY_ISLAND_PULSE_SHADOW_Y_SHIFT = "island_pulse_shadow_y_shift" - const val KEY_ISLAND_PULSE_SHADOW_SPREAD = "island_pulse_shadow_spread" + const val KEY_ISLAND_PULSE_SHADOW_BLUR = "island_pulse_shadow_blur" + const val KEY_ISLAND_PULSE_SHADOW_SPREAD_DP = "island_pulse_shadow_spread_dp" + const val KEY_ISLAND_PULSE_SHADOW_OFFSET_Y = "island_pulse_shadow_offset_y" + const val KEY_ISLAND_PULSE_SHADOW_OPACITY = "island_pulse_shadow_opacity" const val KEY_ISLAND_PULSE_SHADOW_DURATION_MS = "island_pulse_shadow_duration_ms" const val ISLAND_BORDER_OUTLINE_DEFAULT_COLOR = "#202020" @@ -4078,14 +4079,17 @@ class SettingsRepository( fun isIslandPulseShadowEnabled(): Boolean = getBoolean(KEY_ISLAND_PULSE_SHADOW_ON_NOTIFICATION, false) fun setIslandPulseShadowEnabled(enabled: Boolean) = putBoolean(KEY_ISLAND_PULSE_SHADOW_ON_NOTIFICATION, enabled) - fun getIslandPulseShadowSize(): Float = getFloat(KEY_ISLAND_PULSE_SHADOW_SIZE, 0.5f) - fun setIslandPulseShadowSize(value: Float) = putFloat(KEY_ISLAND_PULSE_SHADOW_SIZE, value) + fun getIslandPulseShadowBlur(): Float = getFloat(KEY_ISLAND_PULSE_SHADOW_BLUR, 20f) + fun setIslandPulseShadowBlur(value: Float) = putFloat(KEY_ISLAND_PULSE_SHADOW_BLUR, value) - fun getIslandPulseShadowYShift(): Float = getFloat(KEY_ISLAND_PULSE_SHADOW_Y_SHIFT, 0.35f) - fun setIslandPulseShadowYShift(value: Float) = putFloat(KEY_ISLAND_PULSE_SHADOW_Y_SHIFT, value) + fun getIslandPulseShadowSpread(): Float = getFloat(KEY_ISLAND_PULSE_SHADOW_SPREAD_DP, 6f) + fun setIslandPulseShadowSpread(value: Float) = putFloat(KEY_ISLAND_PULSE_SHADOW_SPREAD_DP, value) - fun getIslandPulseShadowSpread(): Float = getFloat(KEY_ISLAND_PULSE_SHADOW_SPREAD, 2f) - fun setIslandPulseShadowSpread(value: Float) = putFloat(KEY_ISLAND_PULSE_SHADOW_SPREAD, value) + fun getIslandPulseShadowOffsetY(): Float = getFloat(KEY_ISLAND_PULSE_SHADOW_OFFSET_Y, 1f) + fun setIslandPulseShadowOffsetY(value: Float) = putFloat(KEY_ISLAND_PULSE_SHADOW_OFFSET_Y, value) + + fun getIslandPulseShadowOpacity(): Float = getFloat(KEY_ISLAND_PULSE_SHADOW_OPACITY, 0.35f) + fun setIslandPulseShadowOpacity(value: Float) = putFloat(KEY_ISLAND_PULSE_SHADOW_OPACITY, value) fun getIslandPulseShadowDurationMs(): Float = getFloat(KEY_ISLAND_PULSE_SHADOW_DURATION_MS, 1450f) fun setIslandPulseShadowDurationMs(value: Float) = putFloat(KEY_ISLAND_PULSE_SHADOW_DURATION_MS, value) diff --git a/app/src/main/java/com/sameerasw/essentials/island/service/IslandCoordinator.kt b/app/src/main/java/com/sameerasw/essentials/island/service/IslandCoordinator.kt index 9f5fd291d9..c95765217c 100644 --- a/app/src/main/java/com/sameerasw/essentials/island/service/IslandCoordinator.kt +++ b/app/src/main/java/com/sameerasw/essentials/island/service/IslandCoordinator.kt @@ -702,9 +702,10 @@ class IslandCoordinator( outlineThickness = settings.getIslandBorderOutlineThickness().coerceIn(1f, 5f).dp, outlineHiddenWhenExpanded = settings.isIslandBorderOutlineHiddenWhenExpanded(), pulseShadow = settings.isIslandPulseShadowEnabled(), - pulseSize = settings.getIslandPulseShadowSize().coerceIn(0.2f, 1f), - pulseYShift = settings.getIslandPulseShadowYShift().coerceIn(0f, 1f), - pulseSpread = settings.getIslandPulseShadowSpread().coerceIn(1f, 4f), + pulseBlur = settings.getIslandPulseShadowBlur().coerceIn(0f, 48f).dp, + pulseSpread = settings.getIslandPulseShadowSpread().coerceIn(0f, 24f).dp, + pulseOffsetY = settings.getIslandPulseShadowOffsetY().coerceIn(-8f, 16f).dp, + pulseOpacity = settings.getIslandPulseShadowOpacity().coerceIn(0.1f, 1f), pulseDurationMs = settings.getIslandPulseShadowDurationMs().coerceIn(300f, 4000f).toInt(), queueStyle = when (settings.getIslandNotifQueueStyle()) { SettingsRepository.ISLAND_NOTIF_QUEUE_STYLE_OVERLAP -> QueueStyle.Overlap @@ -841,9 +842,10 @@ class IslandCoordinator( SettingsRepository.KEY_ISLAND_BORDER_OUTLINE_THICKNESS, SettingsRepository.KEY_ISLAND_BORDER_OUTLINE_HIDE_EXPANDED, SettingsRepository.KEY_ISLAND_PULSE_SHADOW_ON_NOTIFICATION, - SettingsRepository.KEY_ISLAND_PULSE_SHADOW_SIZE, - SettingsRepository.KEY_ISLAND_PULSE_SHADOW_Y_SHIFT, - SettingsRepository.KEY_ISLAND_PULSE_SHADOW_SPREAD, + SettingsRepository.KEY_ISLAND_PULSE_SHADOW_BLUR, + SettingsRepository.KEY_ISLAND_PULSE_SHADOW_SPREAD_DP, + SettingsRepository.KEY_ISLAND_PULSE_SHADOW_OFFSET_Y, + SettingsRepository.KEY_ISLAND_PULSE_SHADOW_OPACITY, SettingsRepository.KEY_ISLAND_PULSE_SHADOW_DURATION_MS, SettingsRepository.KEY_ISLAND_EXPANDED_ROUNDNESS, SettingsRepository.KEY_ISLAND_EXPANDED_PADDING, diff --git a/app/src/main/java/com/sameerasw/essentials/island/ui/IslandLayoutSpec.kt b/app/src/main/java/com/sameerasw/essentials/island/ui/IslandLayoutSpec.kt index 36dc9c7edd..046db4583e 100644 --- a/app/src/main/java/com/sameerasw/essentials/island/ui/IslandLayoutSpec.kt +++ b/app/src/main/java/com/sameerasw/essentials/island/ui/IslandLayoutSpec.kt @@ -34,9 +34,10 @@ data class IslandLayoutSpec( val outlineThickness: Dp = 1.dp, val outlineHiddenWhenExpanded: Boolean = false, val pulseShadow: Boolean = false, - val pulseSize: Float = 0.5f, - val pulseYShift: Float = 0.35f, - val pulseSpread: Float = 2f, + val pulseBlur: Dp = 20.dp, + val pulseSpread: Dp = 6.dp, + val pulseOffsetY: Dp = 1.dp, + val pulseOpacity: Float = 0.35f, val pulseDurationMs: Int = 1450, val queueStyle: QueueStyle = QueueStyle.Stack, val queueInline: Boolean = false, diff --git a/app/src/main/java/com/sameerasw/essentials/island/ui/IslandRoot.kt b/app/src/main/java/com/sameerasw/essentials/island/ui/IslandRoot.kt index a54c5eea01..658c436ac8 100644 --- a/app/src/main/java/com/sameerasw/essentials/island/ui/IslandRoot.kt +++ b/app/src/main/java/com/sameerasw/essentials/island/ui/IslandRoot.kt @@ -35,8 +35,6 @@ import androidx.compose.ui.graphics.Outline import androidx.compose.ui.graphics.Shape import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.drawscope.scale import com.sameerasw.essentials.R import androidx.compose.ui.unit.Dp import androidx.compose.ui.res.stringResource @@ -85,6 +83,9 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.animation.animateColorAsState import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.nativeCanvas +import androidx.compose.ui.graphics.toArgb +import android.graphics.BlurMaskFilter import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.util.VelocityTracker @@ -223,6 +224,10 @@ private fun IslandRootContent( ) val pulseAccent = state.items[state.focusedKey]?.accent + val pulsePaint = remember { android.graphics.Paint(android.graphics.Paint.ANTI_ALIAS_FLAG) } + val pulseBlur = remember(spec.pulseBlur, density) { + with(density) { spec.pulseBlur.toPx() }.takeIf { it > 0f }?.let { BlurMaskFilter(it * 0.866f, BlurMaskFilter.Blur.NORMAL) } + } val dynamicOutline: Color? = if (spec.outlineDynamic) { val focused = state.items[state.focusedKey] (focused?.outlineAccent ?: focused?.accent) @@ -241,7 +246,7 @@ private fun IslandRootContent( pulse.snapTo(0f) if (!spec.pulseShadow || state.focusedKey == null) return@LaunchedEffect pulse.animateTo(1f, tween(250, easing = LinearEasing)) - pulse.animateTo(0f, tween(1200, easing = LinearEasing)) + pulse.animateTo(0f, tween((spec.pulseDurationMs - 250).coerceAtLeast(100), easing = LinearEasing)) } val minSurfaceWidth = with(density) { spec.cameraDiameter.roundToPx() } val minSurfaceHeight = with(density) { spec.compactHeight.roundToPx() } @@ -872,23 +877,14 @@ private fun IslandRootContent( .drawBehind { val p = pulse.value if (pulseAccent == null || p <= 0f || !visible) return@drawBehind - val w = size.width * spec.pulseSize - val h = size.height * spec.pulseSize - val center = Offset(size.width / 2f, size.height / 2f + h * spec.pulseYShift) - val radius = h / 2f * spec.pulseSpread - scale(scaleX = w / h, scaleY = 1f, pivot = center) { - drawCircle( - brush = Brush.radialGradient( - 0f to pulseAccent.copy(alpha = 0.95f * p), - 0.45f to pulseAccent.copy(alpha = 0.55f * p), - 1f to Color.Transparent, - center = center, - radius = radius, - ), - radius = radius, - center = center, - ) - } + val spread = spec.pulseSpread.toPx() + val dy = spec.pulseOffsetY.toPx() + val r = surfaceCornerPx(size.height, compactHeightPx, expandedCornerPx) + spread + pulsePaint.color = pulseAccent.copy(alpha = spec.pulseOpacity * p).toArgb() + pulsePaint.maskFilter = pulseBlur + drawContext.canvas.nativeCanvas.drawRoundRect( + -spread, -spread + dy, size.width + spread, size.height + spread + dy, r, r, pulsePaint, + ) } .graphicsLayer { alpha = (if (visible) 1f else 0f) * (1f - if (bubbleOnlyLayout) catchUpAnim.value.coerceIn(0f, 1f) else 0f) diff --git a/app/src/main/java/com/sameerasw/essentials/ui/features/display/sheets/IslandPulseShadowOptionsBottomSheet.kt b/app/src/main/java/com/sameerasw/essentials/ui/features/display/sheets/IslandPulseShadowOptionsBottomSheet.kt index 592d165ba1..a0f3e4580a 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/features/display/sheets/IslandPulseShadowOptionsBottomSheet.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/features/display/sheets/IslandPulseShadowOptionsBottomSheet.kt @@ -18,7 +18,7 @@ import com.sameerasw.essentials.ui.core.containers.RoundedCardContainer import com.sameerasw.essentials.ui.core.sheets.EssentialsBottomSheet import com.sameerasw.essentials.utils.HapticUtil import com.sameerasw.essentials.viewmodels.MainViewModel -import java.util.Locale +import kotlin.math.roundToInt @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -45,42 +45,55 @@ fun IslandPulseShadowOptionsBottomSheet( RoundedCardContainer(spacing = 2.dp, cornerRadius = 24.dp) { ConfigSliderItem( - title = stringResource(R.string.island_pulse_shadow_size_title), - value = viewModel.islandPulseShadowSize.floatValue, + title = stringResource(R.string.island_pulse_shadow_blur_title), + value = viewModel.islandPulseShadowBlur.floatValue, onValueChange = { HapticUtil.performUIHaptic(view) - viewModel.setIslandPulseShadowSize(it) + viewModel.setIslandPulseShadowBlur(it) }, - valueRange = 0.2f..1f, - increment = 0.05f, + valueRange = 0f..48f, + increment = 1f, + iconRes = R.drawable.rounded_blur_on_24, + valueFormatter = { "${it.toInt()} dp" }, + ) + + ConfigSliderItem( + title = stringResource(R.string.island_pulse_shadow_spread_title), + value = viewModel.islandPulseShadowSpread.floatValue, + onValueChange = { + HapticUtil.performUIHaptic(view) + viewModel.setIslandPulseShadowSpread(it) + }, + valueRange = 0f..24f, + increment = 1f, iconRes = R.drawable.rounded_magnify_fullscreen_24, - valueFormatter = { "${(it * 100).toInt()}%" }, + valueFormatter = { "${it.toInt()} dp" }, ) ConfigSliderItem( - title = stringResource(R.string.island_pulse_shadow_y_shift_title), - value = viewModel.islandPulseShadowYShift.floatValue, + title = stringResource(R.string.island_pulse_shadow_offset_y_title), + value = viewModel.islandPulseShadowOffsetY.floatValue, onValueChange = { HapticUtil.performUIHaptic(view) - viewModel.setIslandPulseShadowYShift(it) + viewModel.setIslandPulseShadowOffsetY(it) }, - valueRange = 0f..1f, - increment = 0.05f, + valueRange = -8f..16f, + increment = 1f, iconRes = R.drawable.rounded_screenshot_region_24, - valueFormatter = { "${(it * 100).toInt()}%" }, + valueFormatter = { "${it.toInt()} dp" }, ) ConfigSliderItem( - title = stringResource(R.string.island_pulse_shadow_spread_title), - value = viewModel.islandPulseShadowSpread.floatValue, + title = stringResource(R.string.island_pulse_shadow_opacity_title), + value = viewModel.islandPulseShadowOpacity.floatValue, onValueChange = { HapticUtil.performUIHaptic(view) - viewModel.setIslandPulseShadowSpread(it) + viewModel.setIslandPulseShadowOpacity(it) }, - valueRange = 1f..4f, - increment = 0.1f, - iconRes = R.drawable.rounded_blur_on_24, - valueFormatter = { String.format(Locale.US, "%.1fx", it) }, + valueRange = 0.1f..1f, + increment = 0.02f, + iconRes = R.drawable.rounded_invert_colors_24, + valueFormatter = { "${(it * 100).roundToInt()}%" }, ) ConfigSliderItem( diff --git a/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt b/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt index e99c0317ae..e0427c22f2 100644 --- a/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt +++ b/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt @@ -306,9 +306,10 @@ class MainViewModel : ViewModel() { val islandBorderOutlineThickness = mutableFloatStateOf(1f) val isIslandBorderOutlineHiddenWhenExpanded = mutableStateOf(false) val isIslandPulseShadow = mutableStateOf(false) - val islandPulseShadowSize = mutableFloatStateOf(0.5f) - val islandPulseShadowYShift = mutableFloatStateOf(0.35f) - val islandPulseShadowSpread = mutableFloatStateOf(2f) + val islandPulseShadowBlur = mutableFloatStateOf(20f) + val islandPulseShadowSpread = mutableFloatStateOf(6f) + val islandPulseShadowOffsetY = mutableFloatStateOf(1f) + val islandPulseShadowOpacity = mutableFloatStateOf(0.35f) val islandPulseShadowDurationMs = mutableFloatStateOf(1450f) val isIslandShowMedia = mutableStateOf(true) val isIslandShowCalendar = mutableStateOf(false) @@ -2585,9 +2586,10 @@ class MainViewModel : ViewModel() { islandBorderOutlineThickness.floatValue = settingsRepository.getIslandBorderOutlineThickness() isIslandBorderOutlineHiddenWhenExpanded.value = settingsRepository.isIslandBorderOutlineHiddenWhenExpanded() isIslandPulseShadow.value = settingsRepository.isIslandPulseShadowEnabled() - islandPulseShadowSize.floatValue = settingsRepository.getIslandPulseShadowSize() - islandPulseShadowYShift.floatValue = settingsRepository.getIslandPulseShadowYShift() + islandPulseShadowBlur.floatValue = settingsRepository.getIslandPulseShadowBlur() islandPulseShadowSpread.floatValue = settingsRepository.getIslandPulseShadowSpread() + islandPulseShadowOffsetY.floatValue = settingsRepository.getIslandPulseShadowOffsetY() + islandPulseShadowOpacity.floatValue = settingsRepository.getIslandPulseShadowOpacity() islandPulseShadowDurationMs.floatValue = settingsRepository.getIslandPulseShadowDurationMs() isIslandShowMedia.value = settingsRepository.isIslandShowMediaEnabled() isIslandShowCalendar.value = settingsRepository.isIslandShowCalendarEnabled() @@ -6195,14 +6197,19 @@ class MainViewModel : ViewModel() { settingsRepository.setIslandPulseShadowEnabled(enabled) } - fun setIslandPulseShadowSize(value: Float) { - islandPulseShadowSize.floatValue = value - settingsRepository.setIslandPulseShadowSize(value) + fun setIslandPulseShadowBlur(value: Float) { + islandPulseShadowBlur.floatValue = value + settingsRepository.setIslandPulseShadowBlur(value) } - fun setIslandPulseShadowYShift(value: Float) { - islandPulseShadowYShift.floatValue = value - settingsRepository.setIslandPulseShadowYShift(value) + fun setIslandPulseShadowOffsetY(value: Float) { + islandPulseShadowOffsetY.floatValue = value + settingsRepository.setIslandPulseShadowOffsetY(value) + } + + fun setIslandPulseShadowOpacity(value: Float) { + islandPulseShadowOpacity.floatValue = value + settingsRepository.setIslandPulseShadowOpacity(value) } fun setIslandPulseShadowSpread(value: Float) { diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 2463dc523f..67c5b38dcd 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -1806,8 +1806,6 @@ Requiere permiso de administrador del dispositivo si se utiliza el bloqueo.Ocultar al Expandir Solo delinear la isla compacta y de vista previa Muestra una línea alrededor de la isla - Tamaño del Brillo - Desplazamiento vertical Ondas de Brillo Duración de la Animación Número máximo de elementos a mostrar diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index de5744571e..d764d71f7f 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -1656,8 +1656,6 @@ Toon accentkleurige gloed Toon puls schaduw tijdens meldingen Toont een gloed achter het eiland in de appkleuren - Gloed grootte - Verticale positie Snelheid gloed Puls duur Duur diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 26eedb5667..0d8d25f067 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -1916,8 +1916,6 @@ Nút Tiếp tục sẽ bị vô hiệu hóa trong vài giây để bạn có th Chỉ cần phác thảo đường viền nhỏ gọn và nhìn thoáng qua island Độ dày đường viền Ẩn nội dung thông báo - Kích thước phát sáng - Dịch chuyển theo chiều dọc Ánh sáng lan tỏa Thời lượng xung Bật Duo diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index aa99f88eb5..030f12be69 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -1645,7 +1645,6 @@ 自适应(随播放专辑主题色同步变化) 灵动岛展开后隐藏边框 边框加粗 - 边框发散光影 持续时间 扩散范围 平移 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0f50484bf9..c5360e7222 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -3224,9 +3224,10 @@ Only outline the compact and peek island Show pulse shadow on notification Briefly glows behind the island in the app color - Glow size - Vertical shift - Glow spread + Blur + Vertical offset + Spread + Opacity Pulse duration App based color glow Expanded width