diff --git a/lawnchair/res/values/config.xml b/lawnchair/res/values/config.xml index a73927291a..1db5dca5eb 100644 --- a/lawnchair/res/values/config.xml +++ b/lawnchair/res/values/config.xml @@ -48,11 +48,12 @@ gregorian - default + no_default - app_icon - launcher_default + default + default + default system diff --git a/lawnchair/res/values/strings.xml b/lawnchair/res/values/strings.xml index 6776b46329..75ec576054 100644 --- a/lawnchair/res/values/strings.xml +++ b/lawnchair/res/values/strings.xml @@ -75,6 +75,9 @@ Show Notification Count Notification Dot Color + Notification Count Color + Warning: Notification Dot & Counter colors do not have enough contrast with each other + Warning: Notification Dot & Counter colors might not always have enough contrast with each other Notification Access Needed @@ -156,7 +159,7 @@ Paste Copied to clipboard. Invalid Color - Default + Managed by Lawnchair diff --git a/lawnchair/src/app/lawnchair/preferences2/PreferenceManager2.kt b/lawnchair/src/app/lawnchair/preferences2/PreferenceManager2.kt index 843319bd95..ff9021500c 100644 --- a/lawnchair/src/app/lawnchair/preferences2/PreferenceManager2.kt +++ b/lawnchair/src/app/lawnchair/preferences2/PreferenceManager2.kt @@ -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, diff --git a/lawnchair/src/app/lawnchair/theme/color/ColorOption.kt b/lawnchair/src/app/lawnchair/theme/color/ColorOption.kt index 6e22857e15..de54d3c734 100644 --- a/lawnchair/src/app/lawnchair/theme/color/ColorOption.kt +++ b/lawnchair/src/app/lawnchair/theme/color/ColorOption.kt @@ -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( @@ -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) } diff --git a/lawnchair/src/app/lawnchair/ui/preferences/GeneralPreferences.kt b/lawnchair/src/app/lawnchair/ui/preferences/GeneralPreferences.kt index 8adea1a267..a8573d6099 100644 --- a/lawnchair/src/app/lawnchair/ui/preferences/GeneralPreferences.kt +++ b/lawnchair/src/app/lawnchair/ui/preferences/GeneralPreferences.kt @@ -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), + ) + } +} diff --git a/lawnchair/src/app/lawnchair/ui/preferences/components/WarningPreference.kt b/lawnchair/src/app/lawnchair/ui/preferences/components/WarningPreference.kt new file mode 100644 index 0000000000..40cb911e4c --- /dev/null +++ b/lawnchair/src/app/lawnchair/ui/preferences/components/WarningPreference.kt @@ -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, + ) + }, + ) +} diff --git a/lawnchair/src/app/lawnchair/ui/preferences/components/colorpreference/ColorContrastWarning.kt b/lawnchair/src/app/lawnchair/ui/preferences/components/colorpreference/ColorContrastWarning.kt new file mode 100644 index 0000000000..04fc385597 --- /dev/null +++ b/lawnchair/src/app/lawnchair/ui/preferences/components/colorpreference/ColorContrastWarning.kt @@ -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, + ) + } + +} diff --git a/lawnchair/src/app/lawnchair/ui/preferences/components/colorpreference/ColorDot.kt b/lawnchair/src/app/lawnchair/ui/preferences/components/colorpreference/ColorDot.kt index 5f60281327..265d800b31 100644 --- a/lawnchair/src/app/lawnchair/ui/preferences/components/colorpreference/ColorDot.kt +++ b/lawnchair/src/app/lawnchair/ui/preferences/components/colorpreference/ColorDot.kt @@ -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 ColorDot( entry: ColorPreferenceEntry, 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, + ) + } +} diff --git a/lawnchair/src/app/lawnchair/ui/preferences/components/colorpreference/ColorOptions.kt b/lawnchair/src/app/lawnchair/ui/preferences/components/colorpreference/ColorOptions.kt index f89a2023bd..6808cfe512 100644 --- a/lawnchair/src/app/lawnchair/ui/preferences/components/colorpreference/ColorOptions.kt +++ b/lawnchair/src/app/lawnchair/ui/preferences/components/colorpreference/ColorOptions.kt @@ -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) diff --git a/lawnchair/src/app/lawnchair/ui/preferences/components/colorpreference/ColorPreference.kt b/lawnchair/src/app/lawnchair/ui/preferences/components/colorpreference/ColorPreference.kt index 8f6f1f55a5..17d80d5fad 100644 --- a/lawnchair/src/app/lawnchair/ui/preferences/components/colorpreference/ColorPreference.kt +++ b/lawnchair/src/app/lawnchair/ui/preferences/components/colorpreference/ColorPreference.kt @@ -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()) }, diff --git a/lawnchair/src/app/lawnchair/ui/preferences/components/colorpreference/ColorSelectionPreference.kt b/lawnchair/src/app/lawnchair/ui/preferences/components/colorpreference/ColorSelectionPreference.kt index c66ee37f09..ef3a91bcd1 100644 --- a/lawnchair/src/app/lawnchair/ui/preferences/components/colorpreference/ColorSelectionPreference.kt +++ b/lawnchair/src/app/lawnchair/ui/preferences/components/colorpreference/ColorSelectionPreference.kt @@ -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( diff --git a/platform_frameworks_libs_systemui b/platform_frameworks_libs_systemui index b634f78d6c..6c2c80fa8c 160000 --- a/platform_frameworks_libs_systemui +++ b/platform_frameworks_libs_systemui @@ -1 +1 @@ -Subproject commit b634f78d6c0189dd21a9709cd40933978ac417bf +Subproject commit 6c2c80fa8c7cd9a1caf8182f06d866cece593b5d diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 5fda036e70..45f93dda5e 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -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) {