Last active
January 15, 2026 09:19
-
-
Save AndrewDongminYoo/dc7a8b87d6cc85f576062c7942800253 to your computer and use it in GitHub Desktop.
Debug helpers for theme inspection.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 🐦 Flutter imports: | |
| import 'package:flutter/foundation.dart'; | |
| import 'package:flutter/material.dart'; | |
| // 🌎 Project imports: | |
| import 'package:mirae/gen/colors.gen.dart'; | |
| extension ColorSchemeDebugExtension on ColorScheme { | |
| /// Human-friendly dump of the ColorScheme. | |
| /// | |
| /// - Debug: prints each field using Color.debug (optionally with aliases). | |
| /// - Release/Profile: returns a lightweight identifier only. | |
| String get debug { | |
| if (!kDebugMode) { | |
| return 'ColorScheme(${brightness.name})'; | |
| } | |
| return _debugImpl(); | |
| } | |
| String _debugImpl() { | |
| final items = <(String, Color)>[ | |
| ('error', error), | |
| ('errorContainer', errorContainer), | |
| ('onError', onError), | |
| ('onErrorContainer', onErrorContainer), | |
| ('onInverseSurface', onInverseSurface), | |
| ('outline', outline), | |
| ('outlineVariant', outlineVariant), | |
| ('primary', primary), | |
| ('inversePrimary', inversePrimary), | |
| ('primaryContainer', primaryContainer), | |
| ('primaryFixed', primaryFixed), | |
| ('primaryFixedDim', primaryFixedDim), | |
| ('onPrimary', onPrimary), | |
| ('onPrimaryContainer', onPrimaryContainer), | |
| ('onPrimaryFixed', onPrimaryFixed), | |
| ('onPrimaryFixedVariant', onPrimaryFixedVariant), | |
| ('scrim', scrim), | |
| ('secondary', secondary), | |
| ('secondaryContainer', secondaryContainer), | |
| ('secondaryFixed', secondaryFixed), | |
| ('secondaryFixedDim', secondaryFixedDim), | |
| ('onSecondary', onSecondary), | |
| ('onSecondaryContainer', onSecondaryContainer), | |
| ('onSecondaryFixed', onSecondaryFixed), | |
| ('onSecondaryFixedVariant', onSecondaryFixedVariant), | |
| ('shadow', shadow), | |
| ('surface', surface), | |
| ('inverseSurface', inverseSurface), | |
| ('surfaceBright', surfaceBright), | |
| ('surfaceContainer', surfaceContainer), | |
| ('surfaceContainerHigh', surfaceContainerHigh), | |
| ('surfaceContainerHighest', surfaceContainerHighest), | |
| ('surfaceContainerLow', surfaceContainerLow), | |
| ('surfaceContainerLowest', surfaceContainerLowest), | |
| ('surfaceDim', surfaceDim), | |
| ('surfaceTint', surfaceTint), | |
| ('onSurface', onSurface), | |
| ('onSurfaceVariant', onSurfaceVariant), | |
| ('tertiary', tertiary), | |
| ('tertiaryContainer', tertiaryContainer), | |
| ('tertiaryFixed', tertiaryFixed), | |
| ('tertiaryFixedDim', tertiaryFixedDim), | |
| ('onTertiary', onTertiary), | |
| ('onTertiaryContainer', onTertiaryContainer), | |
| ('onTertiaryFixed', onTertiaryFixed), | |
| ('onTertiaryFixedVariant', onTertiaryFixedVariant), | |
| ]; | |
| final b = StringBuffer()..writeln('ColorScheme.${brightness.name}('); | |
| for (final (name, color) in items) { | |
| b.writeln(' $name: ${color.debug},'); | |
| } | |
| b.writeln(')'); | |
| return b.toString(); | |
| } | |
| } | |
| extension ColorDebugExtension on Color { | |
| /// Debug output verbosity toggle. | |
| /// - true: `Primary /* aka: a, b, c (+N more) */` | |
| /// - false: `Primary` | |
| static const bool _includeAliasesInDebugString = true; | |
| /// Limits alias spam for very-collided colors (e.g., pure white used everywhere). | |
| static const int _maxAliasesToShow = 5; | |
| static const Map<String, Color> _appColors = { | |
| 'black': AppColors.black, | |
| 'white': AppColors.white, | |
| 'transparent': AppColors.transparent, | |
| 'primary': AppColors.primary, | |
| 'primaryLight': AppColors.primaryLight, | |
| 'primaryDark': AppColors.primaryDark, | |
| 'secondary': AppColors.secondary, | |
| 'secondaryLight': AppColors.secondaryLight, | |
| 'secondaryDark': AppColors.secondaryDark, | |
| 'background': AppColors.background, | |
| 'backgroundDark': AppColors.backgroundDark, | |
| 'surface': AppColors.surface, | |
| 'surfaceDark': AppColors.surfaceDark, | |
| 'textPrimary': AppColors.textPrimary, | |
| 'textSecondary': AppColors.textSecondary, | |
| 'textTertiary': AppColors.textTertiary, | |
| 'textOnPrimary': AppColors.textOnPrimary, | |
| 'textDark': AppColors.textDark, | |
| 'accent': AppColors.accent, | |
| 'accentLight': AppColors.accentLight, | |
| 'success': AppColors.success, | |
| 'warning': AppColors.warning, | |
| 'error': AppColors.error, | |
| 'info': AppColors.info, | |
| 'divider': AppColors.divider, | |
| 'dividerDark': AppColors.dividerDark, | |
| 'disabled': AppColors.disabled, | |
| 'disabledDark': AppColors.disabledDark, | |
| 'shadow': AppColors.shadow, | |
| 'shadowDark': AppColors.shadowDark, | |
| 'gradientStart': AppColors.gradientStart, | |
| 'gradientMiddle': AppColors.gradientMiddle, | |
| 'gradientEnd': AppColors.gradientEnd, | |
| 'recording': AppColors.recording, | |
| 'playing': AppColors.playing, | |
| 'paused': AppColors.paused, | |
| 'cardLight': AppColors.cardLight, | |
| 'cardDark': AppColors.cardDark, | |
| 'cardElevated': AppColors.cardElevated, | |
| 'cardElevatedDark': AppColors.cardElevatedDark, | |
| 'overlay': AppColors.overlay, | |
| 'scrim': AppColors.scrim, | |
| 'alarmActive': AppColors.alarmActive, | |
| 'alarmInactive': AppColors.alarmInactive, | |
| 'todayHighlight': AppColors.todayHighlight, | |
| }; | |
| static const Map<String, Color> _colorless = { | |
| 'transparent': Color(0x00000000), | |
| 'black': Color(0xFF000000), | |
| 'black87': Color(0xDD000000), | |
| 'black54': Color(0x8A000000), | |
| 'black45': Color(0x73000000), | |
| 'black38': Color(0x61000000), | |
| 'black26': Color(0x42000000), | |
| 'black12': Color(0x1F000000), | |
| 'white': Color(0xFFFFFFFF), | |
| 'white70': Color(0xB3FFFFFF), | |
| 'white60': Color(0x99FFFFFF), | |
| 'white54': Color(0x8AFFFFFF), | |
| 'white38': Color(0x62FFFFFF), | |
| 'white30': Color(0x4DFFFFFF), | |
| 'white24': Color(0x3DFFFFFF), | |
| 'white12': Color(0x1FFFFFFF), | |
| 'white10': Color(0x1AFFFFFF), | |
| }; | |
| /// The Material Design primary color swatches. | |
| static const Map<String, MaterialColor> _primaries = { | |
| 'red': Colors.red, | |
| 'pink': Colors.pink, | |
| 'purple': Colors.purple, | |
| 'deepPurple': Colors.deepPurple, | |
| 'indigo': Colors.indigo, | |
| 'blue': Colors.blue, | |
| 'lightBlue': Colors.lightBlue, | |
| 'cyan': Colors.cyan, | |
| 'teal': Colors.teal, | |
| 'green': Colors.green, | |
| 'lightGreen': Colors.lightGreen, | |
| 'lime': Colors.lime, | |
| 'yellow': Colors.yellow, | |
| 'amber': Colors.amber, | |
| 'orange': Colors.orange, | |
| 'deepOrange': Colors.deepOrange, | |
| 'brown': Colors.brown, | |
| 'grey': Colors.grey, | |
| 'blueGrey': Colors.blueGrey, | |
| }; | |
| /// The Material Design accent color swatches. | |
| static const Map<String, MaterialAccentColor> _accents = { | |
| 'redAccent': Colors.redAccent, | |
| 'pinkAccent': Colors.pinkAccent, | |
| 'purpleAccent': Colors.purpleAccent, | |
| 'deepPurpleAccent': Colors.deepPurpleAccent, | |
| 'indigoAccent': Colors.indigoAccent, | |
| 'blueAccent': Colors.blueAccent, | |
| 'lightBlueAccent': Colors.lightBlueAccent, | |
| 'cyanAccent': Colors.cyanAccent, | |
| 'tealAccent': Colors.tealAccent, | |
| 'greenAccent': Colors.greenAccent, | |
| 'lightGreenAccent': Colors.lightGreenAccent, | |
| 'limeAccent': Colors.limeAccent, | |
| 'yellowAccent': Colors.yellowAccent, | |
| 'amberAccent': Colors.amberAccent, | |
| 'orangeAccent': Colors.orangeAccent, | |
| 'deepOrangeAccent': Colors.deepOrangeAccent, | |
| }; | |
| /// Precomputed reverse mapping: | |
| /// - primaryByArgb: the representative name chosen by the *original priority* | |
| /// - aliasesByArgb: additional names that share the same ARGB (all priorities) | |
| static final _ResolvedNames _resolved = _buildResolved(); | |
| static String _hexFromArgb(int argb) => '0x${argb.toRadixString(16).padLeft(8, '0').toUpperCase()}'; | |
| /// Debug-friendly string for this color. | |
| /// | |
| /// Priority for representative name (same as original code): | |
| /// 1) `AppColors.*` | |
| /// 2) `Colors.*` constants (colorless) | |
| /// 3) `Colors.<primary swatch>(.shadeX)` | |
| /// 4) `Colors.<accent swatch>(.shadeX)` | |
| /// | |
| /// Release/Profile: always returns hex to keep it cheap. | |
| String get debug { | |
| final argb = toARGB32(); | |
| if (!kDebugMode) { | |
| return 'const Color(${_hexFromArgb(argb)})'; | |
| } | |
| final primary = _resolved.primaryByArgb[argb]; | |
| if (primary == null) { | |
| return 'const Color(${_hexFromArgb(argb)})'; | |
| } | |
| if (!_includeAliasesInDebugString) return primary; | |
| final aliases = _resolved.aliasesByArgb[argb]; | |
| if (aliases == null || aliases.isEmpty) return primary; | |
| if (aliases.length <= _maxAliasesToShow) { | |
| return '$primary /* aka: ${aliases.join(', ')} */'; | |
| } | |
| final shown = aliases.take(_maxAliasesToShow).join(', '); | |
| final remaining = aliases.length - _maxAliasesToShow; | |
| return '$primary /* aka: $shown (+$remaining more) */'; | |
| } | |
| static _ResolvedNames _buildResolved() { | |
| // Representative name and its priority tier (lower is higher priority). | |
| final primaryByArgb = <int, String>{}; | |
| final tierByArgb = <int, int>{}; | |
| // Aliases in insertion order, de-duplicated per ARGB. | |
| final aliasesByArgb = <int, List<String>>{}; | |
| final seenNamesByArgb = <int, Set<String>>{}; | |
| void register({ | |
| required int argb, | |
| required String name, | |
| required int tier, | |
| }) { | |
| final existing = primaryByArgb[argb]; | |
| if (existing == null) { | |
| primaryByArgb[argb] = name; | |
| tierByArgb[argb] = tier; | |
| (seenNamesByArgb[argb] ??= <String>{}).add(name); | |
| return; | |
| } | |
| // De-dup exact repeats. | |
| final seen = seenNamesByArgb[argb] ??= <String>{}; | |
| if (!seen.add(name)) return; | |
| final existingTier = tierByArgb[argb] ?? 999; | |
| // This should never trigger if we build in strict priority order, | |
| // but keeping it makes the priority rule explicit and future-proof. | |
| if (tier < existingTier) { | |
| // Demote old primary into aliases. | |
| (aliasesByArgb[argb] ??= <String>[]).add(existing); | |
| // Promote new primary. | |
| primaryByArgb[argb] = name; | |
| tierByArgb[argb] = tier; | |
| return; | |
| } | |
| // Otherwise, keep the original representative and add as alias. | |
| (aliasesByArgb[argb] ??= <String>[]).add(name); | |
| } | |
| // Tier 0: AppColors (highest) | |
| for (final entry in _appColors.entries) { | |
| register( | |
| argb: entry.value.toARGB32(), | |
| name: 'AppColors.${entry.key}', | |
| tier: 0, | |
| ); | |
| } | |
| // Tier 1: Colors constants | |
| for (final entry in _colorless.entries) { | |
| register( | |
| argb: entry.value.toARGB32(), | |
| name: 'Colors.${entry.key}', | |
| tier: 1, | |
| ); | |
| } | |
| // Tier 2: Material primaries + shades | |
| for (final swatchEntry in _primaries.entries) { | |
| final swatchName = swatchEntry.key; | |
| final swatch = swatchEntry.value; | |
| for (final shade in swatch.keys) { | |
| final color = swatch[shade]; | |
| if (color == null) continue; | |
| final name = shade == 500 ? 'Colors.$swatchName' : 'Colors.$swatchName.shade$shade'; | |
| register( | |
| argb: color.toARGB32(), | |
| name: name, | |
| tier: 2, | |
| ); | |
| } | |
| } | |
| // Tier 3: Material accents + shades | |
| for (final swatchEntry in _accents.entries) { | |
| final swatchName = swatchEntry.key; | |
| final swatch = swatchEntry.value; | |
| for (final shade in swatch.keys) { | |
| final color = swatch[shade]; | |
| if (color == null) continue; | |
| final name = shade == 200 ? 'Colors.$swatchName' : 'Colors.$swatchName.shade$shade'; | |
| register( | |
| argb: color.toARGB32(), | |
| name: name, | |
| tier: 3, | |
| ); | |
| } | |
| } | |
| // Optional: very light debug visibility (no throwing). | |
| assert(() { | |
| final collided = aliasesByArgb.entries.where((e) => e.value.isNotEmpty).toList() | |
| ..sort((a, b) => b.value.length.compareTo(a.value.length)); | |
| if (collided.isNotEmpty) { | |
| final top = collided.take(10); | |
| final msg = top | |
| .map((e) => '${_hexFromArgb(e.key)}: ${primaryByArgb[e.key]} + ${e.value.length} aliases') | |
| .join('\n'); | |
| debugPrint('ColorDebugExtension: ARGB collisions (expected):\n$msg'); | |
| } | |
| return true; | |
| }(), 'Just for count'); | |
| return _ResolvedNames(primaryByArgb, aliasesByArgb); | |
| } | |
| } | |
| final class _ResolvedNames { | |
| const _ResolvedNames(this.primaryByArgb, this.aliasesByArgb); | |
| final Map<int, String> primaryByArgb; | |
| final Map<int, List<String>> aliasesByArgb; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!-- assets/colors/colors.xml --> | |
| <?xml version="1.0" encoding="utf-8"?> | |
| <resources> | |
| <color name="black">#000000</color> | |
| <color name="white">#FFFFFF</color> | |
| <color name="transparent">#00000000</color> | |
| <!-- Primary --> | |
| <color name="primary">#FFB4AB</color> | |
| <color name="primary_light">#FFDAD6</color> | |
| <color name="primary_dark">#FF8B81</color> | |
| <!-- Secondary --> | |
| <color name="secondary">#A8C7FA</color> | |
| <color name="secondary_light">#D3E3FD</color> | |
| <color name="secondary_dark">#7CACF8</color> | |
| <!-- Background / Surface --> | |
| <color name="background">#FFFBFE</color> | |
| <color name="background_dark">#1C1B1F</color> | |
| <color name="surface">#FFFFFF</color> | |
| <color name="surface_dark">#2B2930</color> | |
| <!-- Text --> | |
| <color name="text_primary">#1C1B1F</color> | |
| <color name="text_secondary">#49454F</color> | |
| <color name="text_tertiary">#79747E</color> | |
| <color name="text_on_primary">#FFFFFF</color> | |
| <color name="text_dark">#E6E1E5</color> | |
| <!-- Accent --> | |
| <color name="accent">#F2B8B5</color> | |
| <color name="accent_light">#FFF5F4</color> | |
| <color name="success">#81C995</color> | |
| <color name="warning">#FABD2F</color> | |
| <color name="error">#FF6B6B</color> | |
| <color name="info">#7CB9E8</color> | |
| <!-- Functional --> | |
| <color name="divider">#E7E0EC</color> | |
| <color name="divider_dark">#49454F</color> | |
| <color name="disabled">#CAC4D0</color> | |
| <color name="disabled_dark">#49454F</color> | |
| <color name="shadow">#1F000000</color> | |
| <color name="shadow_dark">#3F000000</color> | |
| <!-- Alarm Ring Screen Gradient --> | |
| <color name="gradient_start">#FFF5F5</color> | |
| <color name="gradient_middle">#FFF0F5</color> | |
| <color name="gradient_end">#FFF8F0</color> | |
| <!-- Recording / Playing --> | |
| <color name="recording">#FF6B6B</color> | |
| <color name="playing">#81C995</color> | |
| <color name="paused">#FABD2F</color> | |
| <!-- Card --> | |
| <color name="card_light">#FFFBFE</color> | |
| <color name="card_dark">#2B2930</color> | |
| <color name="card_elevated">#FFFFFF</color> | |
| <color name="card_elevated_dark">#37343B</color> | |
| <!-- Overlay --> | |
| <color name="overlay">#66000000</color> | |
| <color name="scrim">#99000000</color> | |
| <!-- Semantic --> | |
| <color name="alarm_active">#81C995</color> | |
| <color name="alarm_inactive">#CAC4D0</color> | |
| <color name="today_highlight">#FFDAD6</color> | |
| </resources> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: flutter_gen_example | |
| description: Example app for securely managing assets used in your app using the flutter_gen tool | |
| # The following line prevents the package from being accidentally published to | |
| # pub.dev using `flutter pub publish`. This is preferred for private packages. | |
| publish_to: none # Remove this line if you wish to publish to pub.dev | |
| # The following defines the version and build number for your application. | |
| # A version number is three numbers separated by dots, like 1.2.43 | |
| # followed by an optional build number separated by a +. | |
| # Both the version and the builder number may be overridden in flutter | |
| # build by specifying --build-name and --build-number, respectively. | |
| # In Android, build-name is used as versionName while build-number used as versionCode. | |
| # Read more about Android versioning at https://developer.android.com/studio/publish/versioning | |
| # In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. | |
| # Read more about iOS versioning at | |
| # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html | |
| # In Windows, build-name is used as the major, minor, and patch parts | |
| # of the product and file versions while build-number is used as the build suffix. | |
| version: 1.0.0+1 | |
| environment: | |
| sdk: ^3.10.4 | |
| # Dependencies specify other packages that your package needs in order to work. | |
| # To automatically upgrade your package dependencies to the latest versions | |
| # consider running `flutter pub upgrade --major-versions`. Alternatively, | |
| # dependencies can be manually updated by changing the version numbers below to | |
| # the latest version available on pub.dev. To see which dependencies have newer | |
| # versions available, run `flutter pub outdated`. | |
| dependencies: | |
| # The following adds the Cupertino Icons font to your application. | |
| # Use with the CupertinoIcons class for iOS style icons. | |
| cupertino_icons: ^1.0.8 | |
| flutter: | |
| sdk: flutter | |
| dev_dependencies: | |
| build_runner: ^2.10.5 | |
| flutter_gen_runner: ^5.12.0 | |
| flutter_test: | |
| sdk: flutter | |
| # For information on the generic Dart part of this file, see the | |
| # following page: https://dart.dev/tools/pub/pubspec | |
| # The following section is specific to Flutter packages. | |
| flutter: | |
| # The following line ensures that the Material Icons font is included with your application, | |
| # so that you can use the icons in the material Icons class. | |
| uses-material-design: true | |
| generate: true | |
| assets: | |
| - assets/colors/ | |
| # https://pub.dev/packages/flutter_gen_runner | |
| flutter_gen: | |
| line_length: 120 # Optional (default: 80) | |
| colors: | |
| enabled: true | |
| outputs: | |
| class_name: AppColors | |
| inputs: | |
| - assets/colors/colors.xml |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // lib/debug/theme_extension.dart | |
| // 🐦 Flutter imports: | |
| import 'package:flutter/foundation.dart'; | |
| import 'package:flutter/material.dart'; | |
| // 🌎 Project imports: | |
| import 'package:mirae/gen/colors.gen.dart'; | |
| /// Debug helpers for theme inspection. | |
| /// | |
| /// Notes: | |
| /// - Expensive stringification is gated behind debug-only paths. | |
| /// - Color name resolution uses O(1) lookup via an ARGB->name reverse map. | |
| extension ColorSchemeDebugExtension on ColorScheme { | |
| /// Human-friendly dump of the ColorScheme. | |
| /// | |
| /// In release/profile builds, returns a lightweight identifier only. | |
| String get debug { | |
| if (!kDebugMode) { | |
| // Keep release/profile output cheap and predictable. | |
| return 'ColorScheme.${brightness.name}'; | |
| } | |
| return _debugImpl(); | |
| } | |
| String _debugImpl() { | |
| final items = <(String, Color)>[ | |
| ('error', error), | |
| ('errorContainer', errorContainer), | |
| ('onError', onError), | |
| ('onErrorContainer', onErrorContainer), | |
| ('onInverseSurface', onInverseSurface), | |
| ('outline', outline), | |
| ('outlineVariant', outlineVariant), | |
| ('primary', primary), | |
| ('inversePrimary', inversePrimary), | |
| ('primaryContainer', primaryContainer), | |
| ('primaryFixed', primaryFixed), | |
| ('primaryFixedDim', primaryFixedDim), | |
| ('onPrimary', onPrimary), | |
| ('onPrimaryContainer', onPrimaryContainer), | |
| ('onPrimaryFixed', onPrimaryFixed), | |
| ('onPrimaryFixedVariant', onPrimaryFixedVariant), | |
| ('scrim', scrim), | |
| ('secondary', secondary), | |
| ('secondaryContainer', secondaryContainer), | |
| ('secondaryFixed', secondaryFixed), | |
| ('secondaryFixedDim', secondaryFixedDim), | |
| ('onSecondary', onSecondary), | |
| ('onSecondaryContainer', onSecondaryContainer), | |
| ('onSecondaryFixed', onSecondaryFixed), | |
| ('onSecondaryFixedVariant', onSecondaryFixedVariant), | |
| ('shadow', shadow), | |
| ('surface', surface), | |
| ('inverseSurface', inverseSurface), | |
| ('surfaceBright', surfaceBright), | |
| ('surfaceContainer', surfaceContainer), | |
| ('surfaceContainerHigh', surfaceContainerHigh), | |
| ('surfaceContainerHighest', surfaceContainerHighest), | |
| ('surfaceContainerLow', surfaceContainerLow), | |
| ('surfaceContainerLowest', surfaceContainerLowest), | |
| ('surfaceDim', surfaceDim), | |
| ('surfaceTint', surfaceTint), | |
| ('onSurface', onSurface), | |
| ('onSurfaceVariant', onSurfaceVariant), | |
| ('tertiary', tertiary), | |
| ('tertiaryContainer', tertiaryContainer), | |
| ('tertiaryFixed', tertiaryFixed), | |
| ('tertiaryFixedDim', tertiaryFixedDim), | |
| ('onTertiary', onTertiary), | |
| ('onTertiaryContainer', onTertiaryContainer), | |
| ('onTertiaryFixed', onTertiaryFixed), | |
| ('onTertiaryFixedVariant', onTertiaryFixedVariant), | |
| ]; | |
| final b = StringBuffer()..writeln('ColorScheme.${brightness.name}('); | |
| for (final (name, color) in items) { | |
| b.writeln(' $name: ${color.debug},'); | |
| } | |
| b.writeln(')'); | |
| return b.toString(); | |
| } | |
| } | |
| extension ColorDebugExtension on Color { | |
| static const Map<String, Color> _appColors = { | |
| 'black': AppColors.black, | |
| 'white': AppColors.white, | |
| 'transparent': AppColors.transparent, | |
| 'primary': AppColors.primary, | |
| 'primaryLight': AppColors.primaryLight, | |
| 'primaryDark': AppColors.primaryDark, | |
| 'secondary': AppColors.secondary, | |
| 'secondaryLight': AppColors.secondaryLight, | |
| 'secondaryDark': AppColors.secondaryDark, | |
| 'background': AppColors.background, | |
| 'backgroundDark': AppColors.backgroundDark, | |
| 'surface': AppColors.surface, | |
| 'surfaceDark': AppColors.surfaceDark, | |
| 'textPrimary': AppColors.textPrimary, | |
| 'textSecondary': AppColors.textSecondary, | |
| 'textTertiary': AppColors.textTertiary, | |
| 'textOnPrimary': AppColors.textOnPrimary, | |
| 'textDark': AppColors.textDark, | |
| 'accent': AppColors.accent, | |
| 'accentLight': AppColors.accentLight, | |
| 'success': AppColors.success, | |
| 'warning': AppColors.warning, | |
| 'error': AppColors.error, | |
| 'info': AppColors.info, | |
| 'divider': AppColors.divider, | |
| 'dividerDark': AppColors.dividerDark, | |
| 'disabled': AppColors.disabled, | |
| 'disabledDark': AppColors.disabledDark, | |
| 'shadow': AppColors.shadow, | |
| 'shadowDark': AppColors.shadowDark, | |
| 'gradientStart': AppColors.gradientStart, | |
| 'gradientMiddle': AppColors.gradientMiddle, | |
| 'gradientEnd': AppColors.gradientEnd, | |
| 'recording': AppColors.recording, | |
| 'playing': AppColors.playing, | |
| 'paused': AppColors.paused, | |
| 'cardLight': AppColors.cardLight, | |
| 'cardDark': AppColors.cardDark, | |
| 'cardElevated': AppColors.cardElevated, | |
| 'cardElevatedDark': AppColors.cardElevatedDark, | |
| 'overlay': AppColors.overlay, | |
| 'scrim': AppColors.scrim, | |
| 'alarmActive': AppColors.alarmActive, | |
| 'alarmInactive': AppColors.alarmInactive, | |
| 'todayHighlight': AppColors.todayHighlight, | |
| }; | |
| static const Map<String, Color> _colorless = { | |
| 'transparent': Color(0x00000000), | |
| 'black': Color(0xFF000000), | |
| 'black87': Color(0xDD000000), | |
| 'black54': Color(0x8A000000), | |
| 'black45': Color(0x73000000), | |
| 'black38': Color(0x61000000), | |
| 'black26': Color(0x42000000), | |
| 'black12': Color(0x1F000000), | |
| 'white': Color(0xFFFFFFFF), | |
| 'white70': Color(0xB3FFFFFF), | |
| 'white60': Color(0x99FFFFFF), | |
| 'white54': Color(0x8AFFFFFF), | |
| 'white38': Color(0x62FFFFFF), | |
| 'white30': Color(0x4DFFFFFF), | |
| 'white24': Color(0x3DFFFFFF), | |
| 'white12': Color(0x1FFFFFFF), | |
| 'white10': Color(0x1AFFFFFF), | |
| }; | |
| /// The Material Design primary color swatches. | |
| static const Map<String, MaterialColor> _primaries = { | |
| 'red': Colors.red, | |
| 'pink': Colors.pink, | |
| 'purple': Colors.purple, | |
| 'deepPurple': Colors.deepPurple, | |
| 'indigo': Colors.indigo, | |
| 'blue': Colors.blue, | |
| 'lightBlue': Colors.lightBlue, | |
| 'cyan': Colors.cyan, | |
| 'teal': Colors.teal, | |
| 'green': Colors.green, | |
| 'lightGreen': Colors.lightGreen, | |
| 'lime': Colors.lime, | |
| 'yellow': Colors.yellow, | |
| 'amber': Colors.amber, | |
| 'orange': Colors.orange, | |
| 'deepOrange': Colors.deepOrange, | |
| 'brown': Colors.brown, | |
| 'grey': Colors.grey, | |
| 'blueGrey': Colors.blueGrey, | |
| }; | |
| /// The Material Design accent color swatches. | |
| static const Map<String, MaterialAccentColor> _accents = { | |
| 'redAccent': Colors.redAccent, | |
| 'pinkAccent': Colors.pinkAccent, | |
| 'purpleAccent': Colors.purpleAccent, | |
| 'deepPurpleAccent': Colors.deepPurpleAccent, | |
| 'indigoAccent': Colors.indigoAccent, | |
| 'blueAccent': Colors.blueAccent, | |
| 'lightBlueAccent': Colors.lightBlueAccent, | |
| 'cyanAccent': Colors.cyanAccent, | |
| 'tealAccent': Colors.tealAccent, | |
| 'greenAccent': Colors.greenAccent, | |
| 'lightGreenAccent': Colors.lightGreenAccent, | |
| 'limeAccent': Colors.limeAccent, | |
| 'yellowAccent': Colors.yellowAccent, | |
| 'amberAccent': Colors.amberAccent, | |
| 'orangeAccent': Colors.orangeAccent, | |
| 'deepOrangeAccent': Colors.deepOrangeAccent, | |
| }; | |
| /// O(1) reverse lookup from ARGB to the "best" debug name. | |
| /// | |
| /// Priority: AppColors > Colors constants > Colors primaries > Colors accents. | |
| static final Map<int, String> _nameByArgb = _buildNameByArgb(); | |
| static Map<int, String> _buildNameByArgb() { | |
| final out = <int, String>{}; | |
| // Track duplicates only in debug to avoid any runtime overhead in release. | |
| final duplicates = <int, Set<String>>{}; | |
| void putIfAbsent(int argb, String name) { | |
| final existing = out[argb]; | |
| if (existing == null) { | |
| out[argb] = name; | |
| } else if (existing != name) { | |
| // Keep the earlier (higher-priority) name. Record duplicate in debug. | |
| assert(() { | |
| (duplicates[argb] ??= <String>{}).add(existing); | |
| duplicates[argb]!.add(name); | |
| return true; | |
| }(), 'Keep the earlier (higher-priority) name. Record duplicate in debug.'); | |
| } | |
| } | |
| // 1) App-specific palette: highest priority. | |
| for (final entry in _appColors.entries) { | |
| putIfAbsent(entry.value.toARGB32(), 'AppColors.${entry.key}'); | |
| } | |
| // 2) Core Colors constants: next priority. | |
| for (final entry in _colorless.entries) { | |
| putIfAbsent(entry.value.toARGB32(), 'Colors.${entry.key}'); | |
| } | |
| // 3) Material primaries and shades. | |
| for (final swatchEntry in _primaries.entries) { | |
| final swatchName = swatchEntry.key; | |
| final swatch = swatchEntry.value; | |
| for (final shade in swatch.keys) { | |
| final color = swatch[shade]; | |
| if (color == null) continue; | |
| final name = shade == 500 ? 'Colors.$swatchName' : 'Colors.$swatchName.shade$shade'; | |
| putIfAbsent(color.toARGB32(), name); | |
| } | |
| } | |
| // 4) Material accents and shades. | |
| for (final swatchEntry in _accents.entries) { | |
| final swatchName = swatchEntry.key; | |
| final swatch = swatchEntry.value; | |
| for (final shade in swatch.keys) { | |
| final color = swatch[shade]; | |
| if (color == null) continue; | |
| // Accent defaults are typically 200. | |
| final name = shade == 200 ? 'Colors.$swatchName' : 'Colors.$swatchName.shade$shade'; | |
| putIfAbsent(color.toARGB32(), name); | |
| } | |
| } | |
| assert(() { | |
| if (duplicates.isNotEmpty) { | |
| // Fail fast in debug: duplicates can make logs ambiguous. | |
| final lines = duplicates.entries | |
| .map( | |
| (e) => | |
| '0x${e.key.toRadixString(16).padLeft(8, '0').toUpperCase()}: ' | |
| '${e.value.toList()..sort()}', | |
| ) | |
| .join('\n'); | |
| throw FlutterError( | |
| 'Duplicate color ARGB mappings detected in ColorDebugExtension:\n$lines', | |
| ); | |
| } | |
| return true; | |
| }(), 'Duplicate color ARGB mappings detected.'); | |
| return out; | |
| } | |
| static String _hexFromArgb(int argb) => '0x${argb.toRadixString(16).padLeft(8, '0').toUpperCase()}'; | |
| /// Debug-friendly string for this color. | |
| /// | |
| /// - In debug builds: resolves to `AppColors.*` or `Colors.*` where possible. | |
| /// - In release/profile: always returns a cheap, deterministic hex form. | |
| String get debug { | |
| final argb = toARGB32(); | |
| if (!kDebugMode) { | |
| return 'const Color(${_hexFromArgb(argb)})'; | |
| } | |
| final name = _nameByArgb[argb]; | |
| if (name != null) return name; | |
| return 'const Color(${_hexFromArgb(argb)})'; | |
| } | |
| } |
Author
Author
File: lib/presentation/screens/settings/settings_screen.dart
final l10n = AppLocalizations.of(context);
final state = ref.watch(themeModeProvider);
unawaited(
showDialog(
context: context,
builder: (dialogContext) => AlertDialog(
title: Text(l10n.selectTheme),
content: Column(
mainAxisSize: MainAxisSize.min,
children: AppThemeMode.values.map((mode) {
return RadioListTile<AppThemeMode>(
value: mode,
groupValue: state,
title: Text(mode.displayName),
secondary: Icon(mode.icon),
onChanged: (AppThemeMode? value) {
if (value != null) {
ref.read(themeModeProvider.notifier).setThemeMode(value);
if (kDebugMode) {
final scheme = Theme.of(context).colorScheme;
log('[Theme] AppThemeMode=$state, colorScheme=${scheme.debug}');
}
Navigator.pop(dialogContext);
}
},
);
}).toList(),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext),
child: Text(l10n.cancel),
),
],
),
),
);[log] [Theme] AppThemeMode=Dark, colorScheme=ColorScheme.dark(
error: AppColors.error /* aka: AppColors.recording */,
errorContainer: AppColors.error /* aka: AppColors.recording */,
onError: AppColors.white /* aka: AppColors.surface, AppColors.textOnPrimary, AppColors.cardElevated, Colors.white */,
onErrorContainer: AppColors.white /* aka: AppColors.surface, AppColors.textOnPrimary, AppColors.cardElevated, Colors.white */,
onInverseSurface: AppColors.surfaceDark /* aka: AppColors.cardDark */,
outline: AppColors.textSecondary /* aka: AppColors.dividerDark, AppColors.disabledDark */,
outlineVariant: AppColors.white /* aka: AppColors.surface, AppColors.textOnPrimary, AppColors.cardElevated, Colors.white */,
primary: AppColors.primary,
inversePrimary: AppColors.white /* aka: AppColors.surface, AppColors.textOnPrimary, AppColors.cardElevated, Colors.white */,
primaryContainer: AppColors.primaryDark,
primaryFixed: AppColors.primary,
primaryFixedDim: AppColors.primary,
onPrimary: AppColors.white /* aka: AppColors.surface, AppColors.textOnPrimary, AppColors.cardElevated, Colors.white */,
onPrimaryContainer: AppColors.white /* aka: AppColors.surface, AppColors.textOnPrimary, AppColors.cardElevated, Colors.white */,
onPrimaryFixed: AppColors.white /* aka: AppColors.surface, AppColors.textOnPrimary, AppColors.cardElevated, Colors.white */,
onPrimaryFixedVariant: AppColors.white /* aka: AppColors.surface, AppColors.textOnPrimary, AppColors.cardElevated, Colors.white */,
scrim: AppColors.black /* aka: Colors.black */,
secondary: AppColors.secondary,
secondaryContainer: AppColors.secondaryDark,
secondaryFixed: AppColors.secondary,
secondaryFixedDim: AppColors.secondary,
onSecondary: AppColors.white /* aka: AppColors.surface, AppColors.textOnPrimary, AppColors.cardElevated, Colors.white */,
onSecondaryContainer: AppColors.white /* aka: AppColors.surface, AppColors.textOnPrimary, AppColors.cardElevated, Colors.white */,
onSecondaryFixed: AppColors.white /* aka: AppColors.surface, AppColors.textOnPrimary, AppColors.cardElevated, Colors.white */,
onSecondaryFixedVariant: AppColors.white /* aka: AppColors.surface, AppColors.textOnPrimary, AppColors.cardElevated, Colors.white */,
shadow: AppColors.shadowDark,
surface: AppColors.surfaceDark /* aka: AppColors.cardDark */,
inverseSurface: AppColors.textDark,
surfaceBright: AppColors.surfaceDark /* aka: AppColors.cardDark */,
surfaceContainer: AppColors.surfaceDark /* aka: AppColors.cardDark */,
surfaceContainerHigh: AppColors.surfaceDark /* aka: AppColors.cardDark */,
surfaceContainerHighest: AppColors.surfaceDark /* aka: AppColors.cardDark */,
surfaceContainerLow: AppColors.surfaceDark /* aka: AppColors.cardDark */,
surfaceContainerLowest: AppColors.surfaceDark /* aka: AppColors.cardDark */,
surfaceDim: AppColors.surfaceDark /* aka: AppColors.cardDark */,
surfaceTint: AppColors.primary,
onSurface: AppColors.textDark,
onSurfaceVariant: AppColors.textDark,
tertiary: AppColors.secondary,
tertiaryContainer: AppColors.secondary,
tertiaryFixed: AppColors.secondary,
tertiaryFixedDim: AppColors.secondary,
onTertiary: AppColors.white /* aka: AppColors.surface, AppColors.textOnPrimary, AppColors.cardElevated, Colors.white */,
onTertiaryContainer: AppColors.white /* aka: AppColors.surface, AppColors.textOnPrimary, AppColors.cardElevated, Colors.white */,
onTertiaryFixed: AppColors.white /* aka: AppColors.surface, AppColors.textOnPrimary, AppColors.cardElevated, Colors.white */,
onTertiaryFixedVariant: AppColors.white /* aka: AppColors.surface, AppColors.textOnPrimary, AppColors.cardElevated, Colors.white */,
)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
EXAMPLE USAGE:
DEBUG CONSOLE: