Custom Notification Dot Counter Color Support (#2977)

* Add `notificationDotTextColor` to `PreferenceManager2`
* Add `notificationDotTextColor` to preferences UI
* Pass counter color to `DotRenderer`
* Convert `LauncherDefault` to `Default`
* Optimize imports
* Rename "Default" to "Managed by Lawnchair"
* Use the correct `ColorDot` on `ColorPreference`
* Show a contrast warning for dot colors
* Show a special icon on `ColorDot` when the color is 0
* Make `ColorDot(Color)` private to avoid accidental usages
* Create `WarningPreference` as a reusable composable for warnings
* Use Rounded icon on `WarningPreference`
* Fix `ColorContrastWarning.kt`'s file name
* Update platform_frameworks_libs_systemui

Co-authored-by: Daria Hamrah Paytakht <info@dariarnd.ir>
This commit is contained in:
Yasan
2022-09-28 09:03:08 +03:30
committed by GitHub
parent 088cfe48df
commit d75e31c021
13 changed files with 216 additions and 29 deletions

View File

@@ -48,11 +48,12 @@
<string name="config_default_smart_space_calendar" translatable="false">gregorian</string>
<!-- which accent color to use by default -->
<string name="config_default_accent_color" translatable="false">default</string>
<string name="config_default_accent_color" translatable="false">no_default</string>
<!-- which notification dot color to use by default -->
<string name="config_default_notification_dot_color" translatable="false">app_icon</string>
<string name="config_default_folder_color" translatable="false">launcher_default</string>
<string name="config_default_notification_dot_color" translatable="false">default</string>
<string name="config_default_notification_dot_text_color" translatable="false">default</string>
<string name="config_default_folder_color" translatable="false">default</string>
<!-- which time format to use by default on smartspace -->
<string name="config_default_smartspace_time_format" translatable="false">system</string>

View File

@@ -75,6 +75,9 @@
<!-- <string name="notification_dots" />-->
<string name="show_notification_count">Show Notification Count</string>
<string name="notification_dots_color">Notification Dot Color</string>
<string name="notification_dots_text_color">Notification Count Color</string>
<string name="notification_dots_color_contrast_warning_always">Warning: Notification Dot &amp; Counter colors do not have enough contrast with each other</string>
<string name="notification_dots_color_contrast_warning_sometimes">Warning: Notification Dot &amp; Counter colors might not always have enough contrast with each other</string>
<!-- Notification popup -->
<string name="missing_notification_access_label">Notification Access Needed</string>
@@ -156,7 +159,7 @@
<string name="action_paste">Paste</string>
<string name="copied_toast">Copied to clipboard.</string>
<string name="invalid_color">Invalid Color</string>
<string name="launcher_default_color">Default</string>
<string name="launcher_default_color">Managed by Lawnchair</string>
<!-- HomeScreenPreferences -->
<!-- <string name="general_label" /> -->

View File

@@ -100,6 +100,14 @@ class PreferenceManager2(private val context: Context) : PreferenceManager {
defaultValue = ColorOption.fromString(context.getString(R.string.config_default_notification_dot_color)),
)
val notificationDotTextColor = preference(
key = stringPreferencesKey(name = "notification_dot_text_color"),
parse = ColorOption::fromString,
save = ColorOption::toString,
onSet = { reloadHelper.reloadGrid() },
defaultValue = ColorOption.fromString(context.getString(R.string.config_default_notification_dot_text_color)),
)
val folderColor = preference(
key = stringPreferencesKey(name = "folder_color"),
parse = ColorOption::fromString,

View File

@@ -1,7 +1,6 @@
package app.lawnchair.theme.color
import android.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import app.lawnchair.ui.preferences.components.colorpreference.ColorPreferenceEntry
import app.lawnchair.ui.theme.getSystemAccent
@@ -61,7 +60,7 @@ sealed class ColorOption {
override fun toString() = "custom|#${String.format("%08x", color)}"
}
object LauncherDefault : ColorOption() {
object Default : ColorOption() {
override val isSupported = false
override val colorPreferenceEntry = ColorPreferenceEntry<ColorOption>(
@@ -70,7 +69,7 @@ sealed class ColorOption {
{ 0 }
)
override fun toString() = "launcher_default"
override fun toString() = "default"
}
companion object {
@@ -79,7 +78,7 @@ sealed class ColorOption {
fun fromString(stringValue: String) = when (stringValue) {
"system_accent" -> SystemAccent
"wallpaper_primary" -> WallpaperPrimary
"launcher_default" -> LauncherDefault
"default" -> Default
else -> instantiateCustomColor(stringValue)
}

View File

@@ -27,6 +27,8 @@ import app.lawnchair.preferences.getAdapter
import app.lawnchair.preferences.preferenceManager
import app.lawnchair.preferences2.asState
import app.lawnchair.preferences2.preferenceManager2
import app.lawnchair.theme.color.ColorOption
import app.lawnchair.ui.preferences.components.DividerColumn
import app.lawnchair.ui.preferences.components.ExpandAndShrink
import app.lawnchair.ui.preferences.components.FontPreference
import app.lawnchair.ui.preferences.components.IconShapePreview
@@ -37,6 +39,8 @@ import app.lawnchair.ui.preferences.components.PreferenceLayout
import app.lawnchair.ui.preferences.components.SliderPreference
import app.lawnchair.ui.preferences.components.SwitchPreference
import app.lawnchair.ui.preferences.components.ThemePreference
import app.lawnchair.ui.preferences.components.WarningPreference
import app.lawnchair.ui.preferences.components.colorpreference.ColorContrastWarning
import app.lawnchair.ui.preferences.components.colorpreference.ColorPreference
import app.lawnchair.ui.preferences.components.iconShapeEntries
import app.lawnchair.ui.preferences.components.iconShapeGraph
@@ -99,7 +103,7 @@ fun GeneralPreferences() {
)
}
}
val wrapAdaptiveIcons = prefs.wrapAdaptiveIcons.getAdapter()
PreferenceGroup(
heading = stringResource(id = R.string.icons),
@@ -148,15 +152,52 @@ fun GeneralPreferences() {
val serviceEnabled = notificationServiceEnabled()
NotificationDotsPreference(enabled = enabled, serviceEnabled = serviceEnabled)
if (enabled && serviceEnabled) {
SwitchPreference(
adapter = prefs2.showNotificationCount.getAdapter(),
label = stringResource(id = R.string.show_notification_count),
)
val showNotificationCountAdapter = prefs2.showNotificationCount.getAdapter()
ColorPreference(
preference = prefs2.notificationDotColor,
label = stringResource(id = R.string.notification_dots_color),
)
SwitchPreference(
adapter = showNotificationCountAdapter,
label = stringResource(id = R.string.show_notification_count),
)
ExpandAndShrink(visible = showNotificationCountAdapter.state.value) {
DividerColumn {
ColorPreference(
preference = prefs2.notificationDotTextColor,
label = stringResource(id = R.string.notification_dots_text_color),
)
NotificationDotColorContrastWarnings(
dotColor = prefs2.notificationDotColor.asState().value,
dotTextColor = prefs2.notificationDotTextColor.asState().value,
)
}
}
}
}
}
}
@Composable
private fun NotificationDotColorContrastWarnings(
dotColor: ColorOption,
dotTextColor: ColorOption,
) {
val dotColorIsDynamic = when (dotColor) {
is ColorOption.SystemAccent,
is ColorOption.WallpaperPrimary,
is ColorOption.Default -> true
else -> false
}
if (dotColorIsDynamic && dotTextColor !is ColorOption.Default) {
WarningPreference(text = stringResource(id = R.string.notification_dots_color_contrast_warning_sometimes))
} else {
ColorContrastWarning(
foregroundColor = dotTextColor,
backgroundColor = dotColor,
text = stringResource(id = R.string.notification_dots_color_contrast_warning_always),
)
}
}

View File

@@ -0,0 +1,33 @@
package app.lawnchair.ui.preferences.components
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Warning
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@Composable
fun WarningPreference(
modifier: Modifier = Modifier,
text: String,
) {
PreferenceTemplate(
modifier = modifier,
title = {},
description = {
Text(
text = text,
color = MaterialTheme.colorScheme.error,
)
},
startWidget = {
Icon(
imageVector = Icons.Rounded.Warning,
tint = MaterialTheme.colorScheme.error,
contentDescription = null,
)
},
)
}

View File

@@ -0,0 +1,63 @@
package app.lawnchair.ui.preferences.components.colorpreference
import androidx.annotation.ColorInt
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.core.graphics.ColorUtils
import app.lawnchair.theme.color.ColorOption
import app.lawnchair.ui.preferences.components.WarningPreference
private const val CONTRAST_THRESHOLD = 1.5
/**
* Displays [WarningPreference] when [foregroundColor] & [backgroundColor] have less than optimal contrast with each other.
*
* @see CONTRAST_THRESHOLD
*/
@Composable
fun ColorContrastWarning(
modifier: Modifier = Modifier,
foregroundColor: ColorOption,
backgroundColor: ColorOption,
text: String,
) {
val context = LocalContext.current
val foregroundColorInt = foregroundColor.colorPreferenceEntry.lightColor(context)
val backgroundColorInt = backgroundColor.colorPreferenceEntry.lightColor(context)
ColorContrastWarning(
modifier = modifier,
foregroundColor = foregroundColorInt,
backgroundColor = backgroundColorInt,
text = text,
)
}
/**
* Displays [WarningPreference] when [foregroundColor] & [backgroundColor] have less than optimal contrast with each other.
*
* @see CONTRAST_THRESHOLD
*/
@Composable
fun ColorContrastWarning(
modifier: Modifier = Modifier,
@ColorInt foregroundColor: Int,
@ColorInt backgroundColor: Int,
text: String,
) {
val enoughContrast = if (foregroundColor != 0 && backgroundColor != 0) {
ColorUtils.calculateContrast(
foregroundColor,
backgroundColor,
) >= CONTRAST_THRESHOLD
} else true
if (!enoughContrast) {
WarningPreference(
modifier = modifier,
text = text,
)
}
}

View File

@@ -5,7 +5,11 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.HdrAuto
import androidx.compose.material3.Icon
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.graphics.Color
@@ -17,20 +21,27 @@ fun <T> ColorDot(
entry: ColorPreferenceEntry<T>,
modifier: Modifier = Modifier
) {
val context = LocalContext.current
val colorLight = entry.lightColor(context)
val colorDark = entry.darkColor(context)
val color = if (MaterialTheme.colors.isLight) {
entry.lightColor(LocalContext.current)
colorLight
} else {
entry.darkColor(LocalContext.current)
colorDark
}
ColorDot(
color = Color(color),
modifier = modifier
)
if (colorLight != 0) {
ColorDot(
color = Color(color),
modifier = modifier
)
} else DefaultColorDot(modifier = modifier)
}
@Composable
fun ColorDot(
private fun ColorDot(
color: Color,
modifier: Modifier = Modifier
) {
@@ -38,6 +49,25 @@ fun ColorDot(
modifier = modifier
.size(30.dp)
.clip(CircleShape)
.background(color = color)
.background(color = color),
)
}
@Composable
fun DefaultColorDot(
modifier: Modifier = Modifier,
) {
Box(
modifier = modifier
.size(30.dp)
.clip(CircleShape)
.background(color = androidx.compose.material3.MaterialTheme.colorScheme.surfaceVariant),
contentAlignment = Alignment.Center,
) {
Icon(
imageVector = Icons.Rounded.HdrAuto,
contentDescription = null,
tint = androidx.compose.material3.MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}

View File

@@ -22,4 +22,4 @@ val dynamicColors = listOf(ColorOption.SystemAccent, ColorOption.WallpaperPrimar
.filter(ColorOption::isSupported)
.map(ColorOption::colorPreferenceEntry)
val dynamicColorsWithDefault = dynamicColors + listOf(ColorOption.LauncherDefault.colorPreferenceEntry)
val dynamicColorsWithDefault = dynamicColors + listOf(ColorOption.Default.colorPreferenceEntry)

View File

@@ -44,7 +44,9 @@ fun ColorPreference(
val navController = LocalNavController.current
PreferenceTemplate(
title = { Text(text = label) },
endWidget = { ColorDot(Color(adapter.state.value.colorPreferenceEntry.lightColor(LocalContext.current))) },
endWidget = {
ColorDot(adapter.state.value.colorPreferenceEntry)
},
description = {
Text(text = adapter.state.value.colorPreferenceEntry.label())
},

View File

@@ -45,18 +45,21 @@ fun NavGraphBuilder.colorSelectionGraph(route: String) {
val pref = when (prefKey) {
preferenceManager2.accentColor.key.name -> preferenceManager2.accentColor
preferenceManager2.notificationDotColor.key.name -> preferenceManager2.notificationDotColor
preferenceManager2.notificationDotTextColor.key.name -> preferenceManager2.notificationDotTextColor
preferenceManager2.folderColor.key.name -> preferenceManager2.folderColor
else -> return@composable
}
val label = when (prefKey) {
preferenceManager2.accentColor.key.name -> stringResource(id = R.string.accent_color)
preferenceManager2.notificationDotColor.key.name -> stringResource(id = R.string.notification_dots_color)
preferenceManager2.notificationDotTextColor.key.name -> stringResource(id = R.string.notification_dots_text_color)
preferenceManager2.folderColor.key.name -> stringResource(id = R.string.folder_preview_bg_color_label)
else -> return@composable
}
val dynamicEntries = when (prefKey) {
preferenceManager2.folderColor.key.name,
preferenceManager2.notificationDotColor.key.name -> dynamicColorsWithDefault
preferenceManager2.notificationDotColor.key.name,
preferenceManager2.notificationDotTextColor.key.name -> dynamicColorsWithDefault
else -> dynamicColors
}
ColorSelection(

View File

@@ -504,14 +504,18 @@ public class DeviceProfile {
}
// Load dot color
ColorOption colorOption = PreferenceExtensionsKt.firstBlocking(preferenceManager2.getNotificationDotColor());
int color = colorOption.getColorPreferenceEntry().getLightColor().invoke(context);
ColorOption dotColorOption = PreferenceExtensionsKt.firstBlocking(preferenceManager2.getNotificationDotColor());
int dotColor = dotColorOption.getColorPreferenceEntry().getLightColor().invoke(context);
// Load counter color
ColorOption counterColorOption = PreferenceExtensionsKt.firstBlocking(preferenceManager2.getNotificationDotTextColor());
int countColor = counterColorOption.getColorPreferenceEntry().getLightColor().invoke(context);
// This is done last, after iconSizePx is calculated above.
Path dotPath = GraphicsUtils.getShapePath(DEFAULT_DOT_SIZE);
mDotRendererWorkSpace = new DotRenderer(iconSizePx, dotPath, DEFAULT_DOT_SIZE, showNotificationCount, typeface, color);
mDotRendererWorkSpace = new DotRenderer(iconSizePx, dotPath, DEFAULT_DOT_SIZE, showNotificationCount, typeface, dotColor, countColor);
mDotRendererAllApps = iconSizePx == allAppsIconSizePx ? mDotRendererWorkSpace :
new DotRenderer(allAppsIconSizePx, dotPath, DEFAULT_DOT_SIZE, showNotificationCount, typeface, color);
new DotRenderer(allAppsIconSizePx, dotPath, DEFAULT_DOT_SIZE, showNotificationCount, typeface, dotColor, countColor);
}
private int getHorizontalMarginPx(InvariantDeviceProfile idp, Resources res) {