Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,7 @@ class SettingsRepository(
const val KEY_ISLAND_BATTERY_HIDE_LOCKED = "island_battery_hide_locked"
const val KEY_ISLAND_TIMEOUT_MS = "island_timeout_ms"
const val KEY_ISLAND_SHOW_GLOW = "island_show_glow"
const val KEY_ISLAND_MONOCHROME = "island_monochrome"
const val KEY_ISLAND_EXPANDED_WIDTH = "island_expanded_width"
const val KEY_ISLAND_EXPANDED_ROUNDNESS = "island_expanded_roundness"
const val KEY_ISLAND_EXPANDED_PADDING = "island_expanded_padding"
Expand Down Expand Up @@ -710,9 +711,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"

Expand Down Expand Up @@ -4078,21 +4080,27 @@ 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)

fun isIslandShowGlowEnabled(): Boolean = getBoolean(KEY_ISLAND_SHOW_GLOW, true)
fun setIslandShowGlowEnabled(enabled: Boolean) = putBoolean(KEY_ISLAND_SHOW_GLOW, enabled)

fun isIslandMonochromeEnabled(): Boolean = getBoolean(KEY_ISLAND_MONOCHROME, false)
fun setIslandMonochromeEnabled(enabled: Boolean) = putBoolean(KEY_ISLAND_MONOCHROME, enabled)

fun getIslandExpandedWidth(): Float = getFloat(KEY_ISLAND_EXPANDED_WIDTH, 360f)
fun setIslandExpandedWidth(value: Float) = putFloat(KEY_ISLAND_EXPANDED_WIDTH, value)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,7 @@ data class ActiveNotificationAlert(
val ongoing: Boolean = false,
val isConversation: Boolean = false,
val hubOnly: Boolean = false,
// The status bar glyph (Notification.smallIcon), used by the monochrome island
val smallIcon: Bitmap? = null,
)

Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import android.app.KeyguardManager
import android.content.Context
import android.os.PowerManager
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.runtime.Composable
import android.graphics.Bitmap
import androidx.compose.ui.unit.dp
import com.sameerasw.essentials.R
import com.sameerasw.essentials.data.repository.SettingsRepository
Expand All @@ -32,6 +35,7 @@ import com.sameerasw.essentials.island.plugins.soften
import com.sameerasw.essentials.island.ui.IslandStackBubble
import com.sameerasw.essentials.island.ui.IslandStackOverlap
import com.sameerasw.essentials.island.ui.components.IslandBitmap
import com.sameerasw.essentials.island.ui.components.NotificationGlyph
import com.sameerasw.essentials.island.ui.components.IslandIcon
import com.sameerasw.essentials.services.NotificationListener
import com.sameerasw.essentials.utils.HapticUtil
Expand All @@ -46,6 +50,7 @@ class NotificationsPlugin : BaseIslandPlugin() {
SettingsRepository.KEY_ISLAND_NOTIF_QUEUE_INLINE,
SettingsRepository.KEY_ISLAND_LINE_STAGE_ENABLED,
SettingsRepository.KEY_ISLAND_SHOW_GLOW,
SettingsRepository.KEY_ISLAND_MONOCHROME,
SettingsRepository.KEY_ISLAND_NOTIF_QUEUE,
SettingsRepository.KEY_ISLAND_NOTIF_SKIP_SILENT,
SettingsRepository.KEY_ISLAND_NOTIF_FOLLOW_DND,
Expand Down Expand Up @@ -540,7 +545,7 @@ class NotificationsPlugin : BaseIslandPlugin() {
if (!entry.grouped) return stackIconFor(entry.lead, current = false, highlight = highlight)
val icon = entry.lead.appIcon ?: entry.lead.icon
return StackIcon(entry.key, onSelect = { select(entry.lead.key) }, highlight = highlight) { size ->
IslandBitmap(icon, size, circle = true, fallbackRes = R.drawable.rounded_notifications_unread_24)
AlertIcon(entry.lead, icon, size, circle = true)
}
}

Expand Down Expand Up @@ -715,16 +720,16 @@ class NotificationsPlugin : BaseIslandPlugin() {
compact = buildList {
add(
CompactCell("notif.icon") {
IslandBitmap(icon, 22.dp, fallbackRes = R.drawable.rounded_notifications_unread_24)
AlertIcon(alert, icon, 22.dp)
},
)
val appIcon = alert.appIcon
if (!minimal() && alert.chatIcon != null && appIcon != null) {
if (!minimal() && !monochrome() && alert.chatIcon != null && appIcon != null) {
add(CompactCell("notif.app", soloOnly = true) { IslandBitmap(appIcon, 22.dp, circle = true) })
}
},
line = LineContent(
icon = { IslandBitmap(icon, 24.dp, fallbackRes = R.drawable.rounded_notifications_unread_24) },
icon = { AlertIcon(alert, icon, 24.dp) },
start = sender,
end = message,
condensed = condensedLine(),
Expand Down Expand Up @@ -772,24 +777,24 @@ class NotificationsPlugin : BaseIslandPlugin() {
compact = buildList {
add(
CompactCell("notif.icon") {
IslandBitmap(icon, 22.dp, fallbackRes = R.drawable.rounded_notifications_unread_24)
AlertIcon(alert, icon, 22.dp)
},
)
val appIcon = alert.appIcon
if (!minimal() && !hideChat && alert.chatIcon != null && appIcon != null) {
if (!minimal() && !monochrome() && !hideChat && alert.chatIcon != null && appIcon != null) {
add(CompactCell("notif.app", soloOnly = true) { IslandBitmap(appIcon, 22.dp, circle = true) })
}
},
line = if (isOtp(alert)) {
LineContent(
icon = { IslandBitmap(alert.appIcon ?: alert.icon, 24.dp, fallbackRes = R.drawable.rounded_lock_24) },
icon = { AlertIcon(alert, alert.appIcon ?: alert.icon, 24.dp, fallbackRes = R.drawable.rounded_lock_24) },
start = "${appNameFor(context, alert)} · ${context.getString(R.string.island_otp_label)}",
end = alert.otpCode?.let(::maskOtp).orEmpty(),
endSlot = { IslandIcon(R.drawable.rounded_lock_24, size = 18.dp) },
)
} else {
LineContent(
icon = { IslandBitmap(icon, 24.dp, fallbackRes = R.drawable.rounded_notifications_unread_24) },
icon = { AlertIcon(alert, icon, 24.dp) },
start = appNameFor(context, alert),
end = "",
condensed = condensedLine(),
Expand Down Expand Up @@ -823,11 +828,11 @@ class NotificationsPlugin : BaseIslandPlugin() {
placement = CompactPlacement.Dynamic,
compact = listOf(
CompactCell("notif.icon") {
IslandBitmap(icon, 22.dp, fallbackRes = R.drawable.rounded_notifications_unread_24)
AlertIcon(alert, icon, 22.dp)
},
),
line = LineContent(
icon = { IslandBitmap(icon, 24.dp, fallbackRes = R.drawable.rounded_notifications_unread_24) },
icon = { AlertIcon(alert, icon, 24.dp) },
start = sender,
end = formatOtp(code),
endSlot = { IslandIcon(R.drawable.rounded_content_copy_24, size = 18.dp) },
Expand Down Expand Up @@ -913,7 +918,25 @@ class NotificationsPlugin : BaseIslandPlugin() {
private fun stackIconFor(alert: ActiveNotificationAlert, current: Boolean, highlight: Boolean = false): StackIcon {
val icon = if (concealed() && hidesChatPictures()) alert.appIcon ?: alert.icon else alert.chatIcon ?: alert.appIcon ?: alert.icon
return StackIcon(alert.key, current = current, onSelect = { select(alert.key) }, highlight = highlight) { size ->
IslandBitmap(icon, size, circle = true, fallbackRes = R.drawable.rounded_notifications_unread_24)
AlertIcon(alert, icon, size, circle = true)
}
}

private fun monochrome() = settings.isIslandMonochromeEnabled()

// Monochrome swaps the picture for the app's notification glyph on the island's softened accent
@Composable
private fun AlertIcon(
alert: ActiveNotificationAlert,
bitmap: Bitmap?,
size: Dp,
circle: Boolean = false,
fallbackRes: Int = R.drawable.rounded_notifications_unread_24,
) {
if (monochrome()) {
NotificationGlyph(alert.smallIcon, alert.appColor?.let { Color(soften(it)) }, size, fallbackRes = fallbackRes)
} else {
IslandBitmap(bitmap, size, circle = circle, fallbackRes = fallbackRes)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -728,7 +729,6 @@ class IslandCoordinator(
controller.maxCells = settings.getIslandMaxItems() * 2
controller.balanceCompact = settings.isIslandBalanceCompactEnabled()
controller.secondaryBubbleEnabled = settings.isIslandSecondaryBubbleEnabled()
controller.secondaryBubbleOverlap = settings.getIslandNotifQueueStyle() == SettingsRepository.ISLAND_NOTIF_QUEUE_STYLE_OVERLAP
controller.relayout()
controller.expandedTimeoutMs = settings.getIslandExpandedTimeoutMs()
applyPreviewRing()
Expand Down Expand Up @@ -822,6 +822,7 @@ class IslandCoordinator(
"alarm" to SettingsRepository.KEY_ISLAND_ALARM_LAUNCHER_ONLY,
)
val CONFIG_KEYS = setOf(
SettingsRepository.KEY_ISLAND_MONOCHROME,
SettingsRepository.KEY_ISLAND_NOTIF_QUEUE_STYLE,
SettingsRepository.KEY_ISLAND_NOTIF_QUEUE_INLINE,
SettingsRepository.KEY_ISLAND_USE_AUTO_DETECT,
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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() }
Expand Down Expand Up @@ -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)
Expand Down
Loading
Loading