Skip to content
Merged
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 @@ -624,6 +624,7 @@ class SettingsRepository(
const val KEY_ISLAND_MEDIA_SHOW_PREVIOUS = "island_media_show_previous"
const val KEY_ISLAND_MEDIA_SHOW_LIKE = "island_media_show_like"
const val KEY_ISLAND_NOTIF_COMPACT_HEADS_UP = "island_notif_compact_heads_up"
const val KEY_ISLAND_NOTIF_CONDENSED_INFO = "island_notif_condensed_info"
const val KEY_ISLAND_NOTIF_KEEP_PROGRESS = "island_notif_keep_progress"
const val KEY_ISLAND_NOTIF_QUEUE = "island_notif_queue"
const val KEY_ISLAND_NOTIF_SKIP_SILENT = "island_notif_skip_silent"
Expand Down Expand Up @@ -4178,6 +4179,9 @@ class SettingsRepository(
fun isIslandNotifCompactHeadsUpEnabled(): Boolean = getBoolean(KEY_ISLAND_NOTIF_COMPACT_HEADS_UP, true)
fun setIslandNotifCompactHeadsUpEnabled(enabled: Boolean) = putBoolean(KEY_ISLAND_NOTIF_COMPACT_HEADS_UP, enabled)

fun isIslandNotifCondensedInfoEnabled(): Boolean = getBoolean(KEY_ISLAND_NOTIF_CONDENSED_INFO, false)
fun setIslandNotifCondensedInfoEnabled(enabled: Boolean) = putBoolean(KEY_ISLAND_NOTIF_CONDENSED_INFO, enabled)

fun isIslandNotifKeepProgressEnabled(): Boolean = getBoolean(KEY_ISLAND_NOTIF_KEEP_PROGRESS, true)
fun setIslandNotifKeepProgressEnabled(enabled: Boolean) = putBoolean(KEY_ISLAND_NOTIF_KEEP_PROGRESS, enabled)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ package com.sameerasw.essentials.domain.registry

import com.sameerasw.essentials.ui.features.display.ISLAND_BEHAVIOR_FEATURE_ID
import com.sameerasw.essentials.ui.features.display.ISLAND_DURATION_FEATURE_ID
import com.sameerasw.essentials.ui.features.display.ISLAND_NOTIFICATIONS_FEATURE_ID
import com.sameerasw.essentials.ui.features.display.ISLAND_PLACEMENT_FEATURE_ID
import com.sameerasw.essentials.ui.features.display.ISLAND_VISUALS_FEATURE_ID
import android.content.Context
Expand Down Expand Up @@ -819,26 +820,6 @@ object FeatureRegistry {
R.string.island_call_incoming,
"island_show_calls",
),
SearchSetting(
R.string.island_notif_queue_title,
R.string.island_notif_queue_title,
"island_notif_queue",
),
SearchSetting(
R.string.island_notif_compact_heads_up_title,
R.string.island_notif_compact_heads_up_desc,
"island_notif_compact_heads_up",
),
SearchSetting(
R.string.island_notif_skip_silent_title,
R.string.island_notification_options_title,
"island_notif_skip_silent",
),
SearchSetting(
R.string.island_notif_filter_apps_title,
R.string.island_notification_options_title,
"island_notif_filter_apps",
),
SearchSetting(
R.string.island_media_peek_song_change_title,
R.string.island_media_peek_song_change_desc,
Expand Down Expand Up @@ -976,6 +957,67 @@ object FeatureRegistry {
enabled: Boolean,
) {}
},
object : Feature(
id = ISLAND_NOTIFICATIONS_FEATURE_ID,
title = R.string.island_section_notifications,
iconRes = R.drawable.rounded_notifications_unread_24,
category = R.string.cat_interface,
description = R.string.island_notification_options_title,
showToggle = false,
parentFeatureId = "Island",
isVisibleInMain = false,
searchableSettings =
listOf(
SearchSetting(
R.string.island_notif_filter_apps_title,
R.string.island_notif_filter_apps_desc,
"island_notif_filter_apps",
),
SearchSetting(
R.string.island_notif_skip_silent_title,
R.string.island_notif_skip_silent_desc,
"island_notif_skip_silent",
),
SearchSetting(
R.string.island_notif_keep_progress_title,
R.string.island_notif_keep_progress_desc,
"island_notif_keep_progress",
),
SearchSetting(
R.string.island_notif_compact_heads_up_title,
R.string.island_notif_compact_heads_up_desc,
"island_notif_compact_heads_up",
),
SearchSetting(
R.string.island_notif_condensed_info_title,
R.string.island_notif_condensed_info_desc,
"island_notif_condensed_info",
),
SearchSetting(
R.string.island_notif_queue_title,
R.string.island_notif_queue_desc,
"island_notif_queue",
),
SearchSetting(
R.string.island_notif_tap_to_open_title,
R.string.island_notif_tap_to_open_desc,
"island_notif_tap_to_open",
),
SearchSetting(
R.string.island_catch_up_title,
R.string.island_catch_up_desc,
"island_catch_up",
),
),
) {
override fun isEnabled(viewModel: MainViewModel) = viewModel.isIslandEnabled.value

override fun onToggle(
viewModel: MainViewModel,
context: Context,
enabled: Boolean,
) {}
},
object : Feature(
id = "Maps power saving mode",
title = R.string.feat_maps_power_saving_title,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ class LineContent(
val end: String,
val endSlot: (@Composable () -> Unit)? = null,
val showLeftIcon: Boolean = true,
// Icon + title only on line peek
val condensed: Boolean = false,
)

interface IslandExpandedScope {
Expand Down Expand Up @@ -123,6 +125,8 @@ class IslandItem(
val sourcePackage: String? = null,
// Temporary priority while the item's state is urgent; null falls back to `priority`.
val priorityOverride: Int? = null,
// Identifies the content currently shown, so a new one pulses even when the item key is unchanged.
val contentId: String? = null,

val compactVisible: Boolean = true,
val needsCompanyAtCenter: Boolean = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ class NotificationsPlugin : BaseIslandPlugin() {
override val settingKeys = setOf(
SettingsRepository.KEY_ISLAND_CATCH_UP_ENABLED,
SettingsRepository.KEY_ISLAND_NOTIF_COMPACT_HEADS_UP,
SettingsRepository.KEY_ISLAND_NOTIF_CONDENSED_INFO,
SettingsRepository.KEY_ISLAND_LINE_STAGE_ENABLED,
SettingsRepository.KEY_ISLAND_SHOW_GLOW,
SettingsRepository.KEY_ISLAND_NOTIF_QUEUE,
SettingsRepository.KEY_ISLAND_NOTIF_SKIP_SILENT,
Expand Down Expand Up @@ -333,6 +335,9 @@ class NotificationsPlugin : BaseIslandPlugin() {

private fun queueEnabled() = settings.isIslandNotifQueueEnabled()

private fun condensedLine() = settings.isIslandNotifCondensedInfoEnabled() &&
settings.isIslandLineStageEnabled() && settings.isIslandNotifCompactHeadsUpEnabled()

private fun itemFor(alert: ActiveNotificationAlert): IslandItem {
if (concealed()) return concealedItemFor(alert)
val (sender, message) = senderAndMessage(context, alert)
Expand All @@ -359,6 +364,7 @@ class NotificationsPlugin : BaseIslandPlugin() {
icon = { IslandBitmap(icon, 24.dp, fallbackRes = R.drawable.rounded_notifications_unread_24) },
start = sender,
end = message,
condensed = condensedLine(),
),
expanded = ExpandedContent { scope ->
NotificationExpanded(
Expand All @@ -383,6 +389,7 @@ class NotificationsPlugin : BaseIslandPlugin() {
popCurrent(reExpand = false)
},
sourcePackage = alert.packageName,
contentId = alert.key,
)
}

Expand Down Expand Up @@ -414,6 +421,7 @@ class NotificationsPlugin : BaseIslandPlugin() {
icon = { IslandBitmap(icon, 24.dp, fallbackRes = R.drawable.rounded_notifications_unread_24) },
start = appNameFor(context, alert),
end = "",
condensed = condensedLine(),
),
accent = alert.appColor?.let { Color(soften(it)) },
dismissible = true,
Expand All @@ -424,6 +432,7 @@ class NotificationsPlugin : BaseIslandPlugin() {
onOpen = open,
interactions = InteractionOverrides(onTap = { open(); true }),
sourcePackage = alert.packageName,
contentId = alert.key,
)
}

Expand All @@ -448,6 +457,7 @@ class NotificationsPlugin : BaseIslandPlugin() {
interactions = interactions,
queue = queue,
sourcePackage = sourcePackage,
contentId = contentId,
stack = stack,
compactVisible = sideBubble == null,
sideBubble = sideBubble,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ fun IslandRoot(
?.takeIf { outlineAlpha > 0f && fade > 0f }
?.let { it.copy(alpha = it.alpha * outlineAlpha * fade) }
val pulse = remember { Animatable(0f) }
LaunchedEffect(state.focusedKey, spec.pulseShadow) {
LaunchedEffect(state.focusedKey, state.items[state.focusedKey]?.contentId, spec.pulseShadow) {
pulse.snapTo(0f)
if (!spec.pulseShadow || state.focusedKey == null) return@LaunchedEffect
pulse.animateTo(1f, tween(250, easing = LinearEasing))
Expand Down Expand Up @@ -410,11 +410,9 @@ fun IslandRoot(
if (spec.growDirection == 0 && hasCompactCells) catchUpAnim.value.coerceAtLeast(0f) * (bubbleSizePx + bubbleGapPx) / 2f else 0f
val lineShift = remember { Animatable(0f) }
val liveSurfaceHeight = remember { mutableIntStateOf(0) }
fun surfaceLeft(width: Int): Int = windowWidth / 2 + lineShift.value.roundToInt() + catchShiftPx().roundToInt() - when {
spec.growDirection > 0 -> cameraSlotPx / 2
spec.growDirection < 0 -> width - cameraSlotPx / 2
else -> width / 2
}
fun surfaceShiftFor(width: Int): Int = spec.growDirection * (width - cameraSlotPx) / 2
fun surfaceLeft(width: Int): Int = windowWidth / 2 - width / 2 + surfaceShiftFor(width) +
lineShift.value.roundToInt() + catchShiftPx().roundToInt()
fun bubbleX(width: Int): Int =
if (spec.growDirection > 0) surfaceLeft(width) + width + bubbleGapPx else surfaceLeft(width) - bubbleGapPx - bubbleSizePx
val focusedItem = state.focused
Expand All @@ -433,8 +431,13 @@ fun IslandRoot(
val lineInsetActive = queueShown && stage == IslandStage.Line
val lineInset = if (lineInsetActive) spec.compactHeight + spec.cameraGap else 0.dp
val lineInsets = if (spec.growDirection > 0) 0.dp to lineInset else lineInset to 0.dp
LaunchedEffect(lineInsetActive, spec.growDirection) {
val shift = if (lineInsetActive && spec.growDirection == 0) (bubbleSizePx + bubbleGapPx) / 2f else 0f
val condensedLineActive = spec.growDirection == 0 && stage == IslandStage.Line && focusedItem?.line?.condensed == true
LaunchedEffect(lineInsetActive, spec.growDirection, condensedLineActive) {
val shift = if (lineInsetActive && spec.growDirection == 0 && !condensedLineActive) {
(bubbleSizePx + bubbleGapPx) / 2f
} else {
0f
}
lineShift.animateTo(shift, IslandMotion.float())
}
var lastQueuedIcons by remember { mutableStateOf(emptyList<StackIcon>()) }
Expand All @@ -459,15 +462,18 @@ fun IslandRoot(
}
}

LaunchedEffect(target, windowWidth, stage, queueShown, pillSize, lineInsetActive, catchUpShown, hasCompactCells, bubbleOnlyLayout) {
LaunchedEffect(
target, windowWidth, stage, queueShown, pillSize, lineInsetActive, catchUpShown, hasCompactCells,
bubbleOnlyLayout, condensedLineActive,
) {
if (target == IntSize.Zero || windowWidth == 0) return@LaunchedEffect
val g = if (stage == IslandStage.Expanded) outsetPx.roundToInt() else 0
val shift = if ((lineInsetActive || (catchUpShown && hasCompactCells)) && spec.growDirection == 0) (bubbleSizePx + bubbleGapPx) / 2 else 0
val left = windowWidth / 2 + shift - when {
spec.growDirection > 0 -> cameraSlotPx / 2
spec.growDirection < 0 -> target.width - cameraSlotPx / 2
else -> target.width / 2
val shift = if ((lineInsetActive || (catchUpShown && hasCompactCells)) && spec.growDirection == 0 && !condensedLineActive) {
(bubbleSizePx + bubbleGapPx) / 2
} else {
0
}
val left = windowWidth / 2 - target.width / 2 + surfaceShiftFor(target.width) + shift
val top = (surfaceTopPx - g).coerceAtLeast(0)
var bounds = IntRect(left, top, left + target.width, surfaceTopPx - g + target.height)
if (queueShown && stage == IslandStage.Line) {
Expand Down Expand Up @@ -635,7 +641,7 @@ fun IslandRoot(
.padding(top = spec.surfaceTop)
.layout { measurable, constraints ->
val p = measurable.measure(constraints)
val dx = spec.growDirection * (p.width - cameraSlotPx) / 2 + lineShift.value.roundToInt() + catchShiftPx().roundToInt()
val dx = surfaceShiftFor(p.width) + lineShift.value.roundToInt() + catchShiftPx().roundToInt()
layout(p.width, p.height) { p.place(dx, 0) }
}
.offset { IntOffset(0, (squashPx / 2f * squash.value - edgeShift.floatValue - surfaceTopPx * bond).roundToInt()) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontVariation
Expand All @@ -29,6 +32,10 @@ import com.sameerasw.essentials.island.model.LineContent

@Composable
fun LineTemplate(line: LineContent, spec: IslandLayoutSpec, startInset: Dp = 0.dp, endInset: Dp = 0.dp) {
if (line.condensed) {
CondensedLine(line, spec, startInset, endInset)
return
}
val textEndInset = if (line.endSlot == null && spec.growDirection >= 0) spec.compactHeight * OPTICAL_INSET_RATIO else 0.dp
Row(
modifier = Modifier
Expand Down Expand Up @@ -78,6 +85,77 @@ fun LineTemplate(line: LineContent, spec: IslandLayoutSpec, startInset: Dp = 0.d
}
}

internal fun condensedTitleWidth(
natural: Dp,
lineWidth: Dp,
cameraSlotWidth: Dp,
cellSize: Dp,
edgePad: Dp,
endInset: Dp,
): Dp {
val roomBesideCamera = ((lineWidth - edgePad * 2 - cameraSlotWidth - endInset) / 2).coerceAtLeast(cellSize)
return natural.coerceIn(cellSize, roomBesideCamera)
}

@Composable
private fun CondensedLine(line: LineContent, spec: IslandLayoutSpec, startInset: Dp, endInset: Dp) {
val style = IslandTextStyles.line.copy(fontWeight = FontWeight.SemiBold)
val measurer = rememberTextMeasurer()
val naturalPx = remember(line.start, style, measurer) {
measurer.measure(line.start, style, maxLines = 1, softWrap = false).size.width
}
val natural = with(LocalDensity.current) { (naturalPx + 1).toDp() }
val icon: @Composable () -> Unit = {
Box(Modifier.squareFit(spec.cellSize), contentAlignment = Alignment.Center) { line.icon() }
}
val title: @Composable (Dp) -> Unit = { width ->
MarqueeText(text = line.start, style = style, textAlign = TextAlign.Start, modifier = Modifier.width(width))
}

if (spec.growDirection == 0) {
val edgePad = spec.cameraGap + spec.compactHeight * OPTICAL_INSET_RATIO
val titleWidth = condensedTitleWidth(
natural = natural,
lineWidth = spec.lineWidth,
cameraSlotWidth = spec.cameraSlotWidth,
cellSize = spec.cellSize,
edgePad = edgePad,
endInset = endInset,
)
val side = titleWidth + edgePad
Row(
modifier = Modifier.height(spec.compactHeight),
verticalAlignment = Alignment.CenterVertically,
) {
Box(
modifier = Modifier.width(side).padding(start = edgePad),
contentAlignment = Alignment.CenterStart,
) { icon() }
Spacer(Modifier.width(spec.cameraSlotWidth))
Box(
modifier = Modifier.width(side).padding(end = edgePad),
contentAlignment = Alignment.CenterEnd,
) { title(titleWidth) }
}
} else {
val opticalInset = if (spec.growDirection > 0) spec.compactHeight * OPTICAL_INSET_RATIO else 0.dp
val fixed = spec.cameraGap + opticalInset + spec.cameraSlotWidth + spec.cellSize + spec.cellSpacing * 2
val maxText = (spec.lineWidth - startInset - endInset - fixed).coerceAtLeast(0.dp)
Row(
modifier = Modifier
.height(spec.compactHeight)
.padding(start = spec.cameraGap, end = spec.cameraGap + opticalInset),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(spec.cellSpacing),
) {
if (spec.growDirection > 0) Spacer(Modifier.width(spec.cameraSlotWidth - spec.cameraGap))
icon()
title(natural.coerceAtMost(maxText))
if (spec.growDirection < 0) Spacer(Modifier.width(spec.cameraSlotWidth - spec.cameraGap))
}
}
}

@Composable
private fun LineText(text: String, align: TextAlign, weight: FontWeight, modifier: Modifier) {
MarqueeText(text = text, style = IslandTextStyles.line.copy(fontWeight = weight), textAlign = align, modifier = modifier)
Expand Down
Loading
Loading