Skip to content

Commit cd70b36

Browse files
neobuddy89joeyhuab
authored andcommitted
SystemUI: Add QS tile gradient customization
Signed-off-by: Pranav Vashi <neobuddy89@gmail.com>
1 parent b5031d7 commit cd70b36

3 files changed

Lines changed: 109 additions & 11 deletions

File tree

core/java/android/provider/Settings.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7711,6 +7711,12 @@ public static void setShowGTalkServiceStatusForUser(ContentResolver cr, boolean
77117711
*/
77127712
public static final String QS_FOOTER_SHOW_POWER_MENU = "qs_footer_show_power_menu";
77137713

7714+
/**
7715+
* Gradient on QS tiles
7716+
* @hide
7717+
*/
7718+
public static final String QS_TILE_GRADIENT = "qs_tile_gradient";
7719+
77147720
/**
77157721
* Keys we no longer back up under the current schema, but want to continue to
77167722
* process when restoring historical backup datasets.

packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,14 @@ fun LargeTileContent(
148148
Modifier.borderOnFocus(color = focusBorderColor, iconShape.topEnd)
149149
.clip(iconShape)
150150
.verticalSquish(squishiness)
151-
.drawBehind { drawRect(animatedBackgroundColor) }
151+
.drawBehind {
152+
val brush = colors.iconBackgroundGradient
153+
if (brush != null) {
154+
drawRect(brush = brush)
155+
} else {
156+
drawRect(color = animatedBackgroundColor)
157+
}
158+
}
152159
.combinedClickable(
153160
onClick = toggleClick!!,
154161
onLongClick = onLongClick,

packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt

Lines changed: 95 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,13 @@ import androidx.compose.runtime.setValue
6868
import androidx.compose.ui.Alignment
6969
import androidx.compose.ui.Modifier
7070
import androidx.compose.ui.draw.clip
71+
import androidx.compose.ui.draw.drawBehind
72+
import androidx.compose.ui.geometry.Offset
7173
import androidx.compose.ui.geometry.Size
74+
import androidx.compose.ui.graphics.Brush
7275
import androidx.compose.ui.graphics.Color
7376
import androidx.compose.ui.graphics.Shape
77+
import androidx.compose.ui.graphics.SolidColor
7478
import androidx.compose.ui.platform.LocalConfiguration
7579
import androidx.compose.ui.platform.LocalContext
7680
import androidx.compose.ui.platform.LocalDensity
@@ -339,7 +343,14 @@ fun ContentScope.Tile(
339343
.size(CommonTileDefaults.TileHeight)
340344
.align(Alignment.Center)
341345
.clip(CircleShape)
342-
.background(animatedColor)
346+
.drawBehind {
347+
val brush = colors.iconBackgroundGradient
348+
if (brush != null) {
349+
drawRect(brush = brush)
350+
} else {
351+
drawRect(color = animatedColor)
352+
}
353+
}
343354
.indication(interaction, LocalIndication.current)
344355
.tileCombinedClickable(
345356
onClick = { click?.invoke() ?: Unit },
@@ -371,6 +382,7 @@ fun ContentScope.Tile(
371382
iconOnly = iconOnly,
372383
isDualTarget = isDualTarget,
373384
modifier = contentRevealModifier,
385+
colors = colors,
374386
) {
375387
val iconProvider: Context.() -> Icon = { getTileIcon(icon = icon) }
376388
if (iconOnly) {
@@ -442,6 +454,7 @@ fun TileContainer(
442454
isDualTarget: Boolean,
443455
interactionSource: MutableInteractionSource?,
444456
modifier: Modifier = Modifier,
457+
colors: TileColors,
445458
content: @Composable BoxScope.() -> Unit,
446459
) {
447460
Box(
@@ -457,7 +470,16 @@ fun TileContainer(
457470
isDualTarget = isDualTarget,
458471
interactionSource = interactionSource,
459472
)
460-
.tileTestTag(iconOnly),
473+
.tileTestTag(iconOnly)
474+
.thenIf(!isDualTarget || iconOnly) {
475+
Modifier
476+
.drawBehind {
477+
val brush = colors.iconBackgroundGradient
478+
if (brush != null) {
479+
drawRect(brush = brush)
480+
}
481+
}
482+
},
461483
content = content,
462484
)
463485
}
@@ -475,7 +497,14 @@ fun LargeStaticTile(
475497
Box(
476498
modifier
477499
.clip(TileDefaults.animateTileShapeAsState(state = uiState.state, shapeMode = shapeMode).value)
478-
.background(colors.background)
500+
.drawBehind {
501+
val brush = colors.iconBackgroundGradient
502+
if (brush != null) {
503+
drawRect(brush = brush)
504+
} else {
505+
drawRect(color = colors.background)
506+
}
507+
}
479508
.height(TileHeight)
480509
.largeTilePadding()
481510
) {
@@ -547,6 +576,7 @@ data class TileColors(
547576
val label: Color,
548577
val secondaryLabel: Color,
549578
val icon: Color,
579+
val iconBackgroundGradient: Brush? = null,
550580
)
551581

552582
@Composable
@@ -629,32 +659,74 @@ fun rememberTileHaptic(): Boolean {
629659
return hapticEnabled
630660
}
631661

662+
@Composable
663+
fun rememberQsGradient(): Boolean {
664+
val context = LocalContext.current
665+
val contentResolver = context.contentResolver
666+
667+
fun readEnabled(): Boolean {
668+
return try {
669+
Settings.System.getIntForUser(
670+
contentResolver, Settings.System.QS_TILE_GRADIENT, 0,
671+
UserHandle.USER_CURRENT
672+
) != 0
673+
} catch (_: Throwable) {
674+
false
675+
}
676+
}
677+
678+
var enabled by remember { mutableStateOf(readEnabled()) }
679+
680+
DisposableEffect(contentResolver) {
681+
val observer = object : ContentObserver(null) {
682+
override fun onChange(selfChange: Boolean) {
683+
enabled = readEnabled()
684+
}
685+
}
686+
contentResolver.registerContentObserver(
687+
Settings.System.getUriFor(Settings.System.QS_TILE_GRADIENT),
688+
false, observer, UserHandle.USER_ALL
689+
)
690+
onDispose { contentResolver.unregisterContentObserver(observer) }
691+
}
692+
693+
return enabled
694+
}
695+
632696
private object TileDefaults {
633697
val ActiveIconCornerRadius = 16.dp
634698

635699
/** An active tile uses the active color as background */
636700
@Composable
637-
@ReadOnlyComposable
638-
fun activeTileColors(): TileColors =
639-
TileColors(
701+
fun activeTileColors(): TileColors {
702+
val gradientEnabled = rememberQsGradient()
703+
val gradient = qsTileBackgroundBrush(gradientEnabled)
704+
705+
return TileColors(
640706
background = MaterialTheme.colorScheme.primary,
641707
iconBackground = MaterialTheme.colorScheme.primary,
642708
label = MaterialTheme.colorScheme.onPrimary,
643709
secondaryLabel = MaterialTheme.colorScheme.onPrimary,
644710
icon = MaterialTheme.colorScheme.onPrimary,
711+
iconBackgroundGradient = gradient,
645712
)
713+
}
646714

647715
/** An active tile with dual target only show the active color on the icon */
648716
@Composable
649-
@ReadOnlyComposable
650-
fun activeDualTargetTileColors(): TileColors =
651-
TileColors(
717+
fun activeDualTargetTileColors(): TileColors {
718+
val gradientEnabled = rememberQsGradient()
719+
val gradient = qsTileBackgroundBrush(gradientEnabled)
720+
721+
return TileColors(
652722
background = LocalAndroidColorScheme.current.surfaceEffect1,
653723
iconBackground = MaterialTheme.colorScheme.primary,
654724
label = MaterialTheme.colorScheme.onSurface,
655725
secondaryLabel = MaterialTheme.colorScheme.onSurface,
656726
icon = MaterialTheme.colorScheme.onPrimary,
727+
iconBackgroundGradient = gradient,
657728
)
729+
}
658730

659731
@Composable
660732
@ReadOnlyComposable
@@ -693,7 +765,6 @@ private object TileDefaults {
693765
}
694766

695767
@Composable
696-
@ReadOnlyComposable
697768
fun getColorForState(uiState: TileUiState, iconOnly: Boolean): TileColors {
698769
return when (uiState.state) {
699770
STATE_ACTIVE -> {
@@ -765,6 +836,20 @@ private object TileDefaults {
765836
mutableStateOf(RoundedCornerShape(corner))
766837
}
767838
}
839+
840+
@Composable
841+
fun qsTileBackgroundBrush(enabled: Boolean): Brush? {
842+
if (!enabled) return null
843+
844+
return Brush.linearGradient(
845+
colors = listOf(
846+
MaterialTheme.colorScheme.primary,
847+
MaterialTheme.colorScheme.secondary
848+
),
849+
start = Offset(0f, 0f),
850+
end = Offset.Infinite
851+
)
852+
}
768853
}
769854

770855
/**

0 commit comments

Comments
 (0)