Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions lib/core/theme/parser/querya_theme_color_scheme.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import 'package:flutter/foundation.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';

import '../querya_theme.dart';
import 'color_parser.dart';

const _knownShadcnColorKeys = {
'background',
'foreground',
'card',
'cardForeground',
'popover',
'popoverForeground',
'primary',
'primaryForeground',
'secondary',
'secondaryForeground',
'muted',
'mutedForeground',
'accent',
'accentForeground',
'destructive',
'destructiveForeground',
'border',
'input',
'ring',
'chart1',
'chart2',
'chart3',
'chart4',
'chart5',
};

/// Builds a shadcn [ColorScheme] from Querya custom `shadcn_colors`.
///
/// Missing keys and invalid optional colors fall back to [fallback.colorScheme].
/// [ColorScheme.brightness] always comes from [fallback], not from [colors].
ColorScheme colorSchemeFromQueryaThemeColors({
required Map<String, String> colors,
required QueryaTheme fallback,
}) {
final base = fallback.colorScheme;

if (kDebugMode) {
for (final key in colors.keys) {
if (!_knownShadcnColorKeys.contains(key)) {
debugPrint('Querya theme: ignored shadcn_colors key "$key"');
}
}
}

Color pick(String key, Color defaultValue) {
final raw = colors[key];
if (raw == null) return defaultValue;
try {
return parseQueryaThemeColor(raw);
} on FormatException {
if (kDebugMode) {
debugPrint('Querya theme: invalid shadcn_colors."$key": $raw');
}
return defaultValue;
}
}

final destructive = pick('destructive', base.destructive);
// querya.theme.v1 still maps this key; shadcn marks the ColorScheme field legacy.
// ignore: deprecated_member_use
final destructiveForeground = pick('destructiveForeground', base.destructiveForeground);

return ColorScheme(
brightness: base.brightness,
background: pick('background', base.background),
foreground: pick('foreground', base.foreground),
card: pick('card', base.card),
cardForeground: pick('cardForeground', base.cardForeground),
popover: pick('popover', base.popover),
popoverForeground: pick('popoverForeground', base.popoverForeground),
primary: pick('primary', base.primary),
primaryForeground: pick('primaryForeground', base.primaryForeground),
secondary: pick('secondary', base.secondary),
secondaryForeground: pick('secondaryForeground', base.secondaryForeground),
muted: pick('muted', base.muted),
mutedForeground: pick('mutedForeground', base.mutedForeground),
accent: pick('accent', base.accent),
accentForeground: pick('accentForeground', base.accentForeground),
destructive: destructive,
// ignore: deprecated_member_use
destructiveForeground: destructiveForeground,
border: pick('border', base.border),
input: pick('input', base.input),
ring: pick('ring', base.ring),
chart1: pick('chart1', base.chart1),
chart2: pick('chart2', base.chart2),
chart3: pick('chart3', base.chart3),
chart4: pick('chart4', base.chart4),
chart5: pick('chart5', base.chart5),
);
}
86 changes: 86 additions & 0 deletions test/core/theme/parser/querya_theme_color_scheme_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import 'dart:io';

import 'package:flutter_test/flutter_test.dart';
import 'package:querya_desktop/core/theme/parser/color_parser.dart';
import 'package:querya_desktop/core/theme/parser/querya_theme_color_scheme.dart';
import 'package:querya_desktop/core/theme/parser/querya_theme_manifest.dart';
import 'package:querya_desktop/core/theme/querya_theme.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';

void main() {
group('colorSchemeFromQueryaThemeColors', () {
test('full fixture maps custom shadcn values', () {
final raw =
File('test/fixtures/themes/querya_custom_dark.json').readAsStringSync();
final manifest = QueryaThemeManifest.fromJsonString(raw);
final scheme = colorSchemeFromQueryaThemeColors(
colors: manifest.shadcnColors,
fallback: QueryaTheme.darkDefault,
);

expect(scheme.brightness, Brightness.dark);
expect(scheme.background, parseQueryaThemeColor('#101014'));
expect(scheme.foreground, parseQueryaThemeColor('#E2E8F0'));
expect(scheme.primary, parseQueryaThemeColor('#38BDF8'));
expect(scheme.accent, parseQueryaThemeColor('#6366F1'));
expect(scheme.chart3, parseQueryaThemeColor('#F472B6'));
expect(scheme.chart5, parseQueryaThemeColor('#34D399'));
});

test('missing keys preserve fallback colorScheme values', () {
final scheme = colorSchemeFromQueryaThemeColors(
colors: const {'primary': '#FF00AA'},
fallback: QueryaTheme.darkDefault,
);
final fallback = QueryaTheme.darkDefault.colorScheme;

expect(scheme.primary, parseQueryaThemeColor('#FF00AA'));
expect(scheme.background, fallback.background);
expect(scheme.foreground, fallback.foreground);
expect(scheme.border, fallback.border);
expect(scheme.chart1, fallback.chart1);
});

test('invalid optional color uses fallback value', () {
final fallback = QueryaTheme.darkDefault.colorScheme;
final scheme = colorSchemeFromQueryaThemeColors(
colors: const {
'primary': 'not-a-color',
'background': '#101014',
},
fallback: QueryaTheme.darkDefault,
);

expect(scheme.primary, fallback.primary);
expect(scheme.background, parseQueryaThemeColor('#101014'));
});

test('chart colors fall back when omitted', () {
final fallback = QueryaTheme.lightDefault.colorScheme;
final scheme = colorSchemeFromQueryaThemeColors(
colors: const {'background': '#FFFFFF'},
fallback: QueryaTheme.lightDefault,
);

expect(scheme.background, parseQueryaThemeColor('#FFFFFF'));
expect(scheme.chart1, fallback.chart1);
expect(scheme.chart2, fallback.chart2);
expect(scheme.chart3, fallback.chart3);
expect(scheme.chart4, fallback.chart4);
expect(scheme.chart5, fallback.chart5);
});

test('brightness comes from fallback theme, not colors map', () {
final scheme = colorSchemeFromQueryaThemeColors(
colors: const {
'background': '#101014',
'foreground': '#E2E8F0',
},
fallback: QueryaTheme.lightDefault,
);

expect(scheme.brightness, Brightness.light);
expect(scheme.background, parseQueryaThemeColor('#101014'));
});
});
}
Loading