From fc0fcc7c9c0c12afbfc1ccd7b8ab4822c1e648fa Mon Sep 17 00:00:00 2001 From: Freek van de Ven Date: Wed, 24 Jul 2024 11:52:29 +0200 Subject: [PATCH] fix: change the template selection to use light and dark checkmark --- .../lib/src/config/availability_options.dart | 10 ++ .../lib/src/ui/widgets/color_selection.dart | 113 ++++++++++++++---- 2 files changed, 98 insertions(+), 25 deletions(-) diff --git a/packages/flutter_availability/lib/src/config/availability_options.dart b/packages/flutter_availability/lib/src/config/availability_options.dart index 4a8d7c2..72f24b1 100644 --- a/packages/flutter_availability/lib/src/config/availability_options.dart +++ b/packages/flutter_availability/lib/src/config/availability_options.dart @@ -144,6 +144,8 @@ class AvailabilityColors { this.textDarkColor, this.textLightColor, this.templateWeekOverviewBackgroundColor, + this.templateColorLightCheckmarkColor, + this.templateColorDarkCheckmarkColor, this.templateColors = const [ Color(0xFF9bb8f2), Color(0xFF4b77d0), @@ -179,6 +181,14 @@ class AvailabilityColors { /// If not provided the text color will be the theme's text color final Color? textDarkColor; + /// The light color variant of the checkmark icon when selecting a color for + /// a template + final Color? templateColorLightCheckmarkColor; + + /// The dark color variant of the checkmark icon when selecting a color for + /// a template + final Color? templateColorDarkCheckmarkColor; + /// The color of the background in the template week overview that creates a /// layered effect by interchanging a color and a transparent color /// If not provided the color will be the theme's [ColorScheme.surface] diff --git a/packages/flutter_availability/lib/src/ui/widgets/color_selection.dart b/packages/flutter_availability/lib/src/ui/widgets/color_selection.dart index af6ced9..0be6254 100644 --- a/packages/flutter_availability/lib/src/ui/widgets/color_selection.dart +++ b/packages/flutter_availability/lib/src/ui/widgets/color_selection.dart @@ -1,3 +1,5 @@ +import "dart:math"; + import "package:flutter/material.dart"; import "package:flutter_availability/src/util/scope.dart"; @@ -38,35 +40,96 @@ class TemplateColorSelection extends StatelessWidget { spacing: 8, runSpacing: 8, children: [ - for (var color in colors.templateColors) - GestureDetector( - onTap: () => _onColorClick(color), - child: Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: color, - borderRadius: options.borderRadius, - border: Border.all( - color: color.value == selectedColor - ? Colors.black - : Colors.transparent, - width: 1, - ), - ), - child: selectedColor == color.value - ? const Icon(Icons.check) - : null, - ), + for (var color in colors.templateColors) ...[ + _TemplateColorItem( + color: color, + selectedColor: selectedColor, + onColorSelected: onColorSelected, ), + ], ], ), ], ); } - - /// If the color is selected, deselect it, otherwise select it - void _onColorClick(Color color) => onColorSelected( - color.value == selectedColor ? null : color.value, - ); +} + +class _TemplateColorItem extends StatelessWidget { + const _TemplateColorItem({ + required this.color, + required this.selectedColor, + required this.onColorSelected, + }); + + /// Callback for when a color is selected or deselected + final void Function(int?) onColorSelected; + + final Color color; + + final int? selectedColor; + + @override + Widget build(BuildContext context) { + var availabilityScope = AvailabilityScope.of(context); + var options = availabilityScope.options; + var colors = options.colors; + + /// If the color is selected, deselect it, otherwise select it + void onColorClick(Color color) => onColorSelected( + color.value == selectedColor ? null : color.value, + ); + + var checkMarkColor = _hasHighContrast(color) + ? colors.templateColorLightCheckmarkColor + : colors.templateColorDarkCheckmarkColor; + + var icon = selectedColor == color.value + ? Icon(Icons.check, color: checkMarkColor) + : null; + + return GestureDetector( + onTap: () => onColorClick(color), + child: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: color, + borderRadius: options.borderRadius, + ), + child: icon, + ), + ); + } +} + +/// Computes the relative luminance of a color +/// This is following the formula from the WCAG guidelines +double _relativeLuminance(Color color) { + double channelLuminance(int channel) { + var c = channel / 255.0; + return c <= 0.03928 ? c / 12.92 : pow((c + 0.055) / 1.055, 2.4).toDouble(); + } + + return 0.2126 * channelLuminance(color.red) + + 0.7152 * channelLuminance(color.green) + + 0.0722 * channelLuminance(color.blue); +} + +/// Computes the contrast ratio between two colors +/// This is following the formula from the WCAG guidelines +double _contrastRatio(Color color1, Color color2) { + var luminance1 = _relativeLuminance(color1); + var luminance2 = _relativeLuminance(color2); + if (luminance1 > luminance2) { + return (luminance1 + 0.05) / (luminance2 + 0.05); + } else { + return (luminance2 + 0.05) / (luminance1 + 0.05); + } +} + +/// Returns true if the color has high contrast with white +/// This is following the WCAG guidelines +bool _hasHighContrast(Color color) { + const white = Color(0xFFFFFFFF); + return _contrastRatio(color, white) > 4.5; }