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
2 changes: 1 addition & 1 deletion docs/theme.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ flowchart TB
## Built-in presets

- **Querya Dark** — default (`QueryaThemePreset.queryaDark`)
- **Querya Light** — light UI (`QueryaThemePreset.queryaLight`)
- **Querya Light** — built-in light UI (`QueryaThemePreset.queryaLight`); slate canvas, cyan accent, WCAG AA body text
- **Imported** — after a VS Code file is imported (`QueryaThemePreset.imported`)

Access tokens in widgets:
Expand Down
2 changes: 1 addition & 1 deletion lib/core/theme/querya_workbench_theme.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class QueryaWorkbenchTheme {
gitUntracked: Color(0xFF2EB88A),
);

/// Placeholder light preset (#51 will refine).
/// Built-in light preset (slate-like canvas, cyan brand accent).
static const QueryaWorkbenchTheme lightDefault = QueryaWorkbenchTheme(
canvas: Color(0xFFFAFAFA),
surface: Color(0xFFFFFFFF),
Expand Down
57 changes: 27 additions & 30 deletions lib/features/main_screen/main_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import 'package:flutter/material.dart' as material
Widget,
RepaintBoundary;
import 'package:querya_desktop/core/storage/local_db.dart';
import 'package:querya_desktop/core/theme/app_theme.dart';
import 'package:querya_desktop/core/theme/querya_theme_scope.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:querya_desktop/features/connections/connection_creation_flow.dart';
import 'package:querya_desktop/features/connections/connections_panel.dart';
import 'package:querya_desktop/shared/widgets/widgets.dart';
Expand Down Expand Up @@ -127,37 +127,34 @@ class _MainScreenState extends State<MainScreen> {

@override
material.Widget build(material.BuildContext context) {
final theme = AppTheme.dark.colorScheme;
final scheme = Theme.of(context).colorScheme;
return material.Scaffold(
backgroundColor: theme.background,
body: Theme(
data: AppTheme.dark,
child: WindowBorder(
color: theme.border.withValues(alpha: 0.35),
width: 1,
child: Column(
children: [
_CustomTitleBar(
theme: theme,
onNewDatabaseConnection: _onNewDatabaseConnectionFromMenu,
),
Divider(height: 1, color: theme.border.withValues(alpha: 0.22)),
Expanded(
child: _MainContentSplit(
connectionsPanelKey: _connectionsPanelKey,
workspace: _workspace,
onConnectionSelected: _onConnectionSelected,
onPostgresObjectSelected: _onPostgresObjectSelected,
onMysqlObjectSelected: _onMysqlObjectSelected,
onRedisDatabaseSelected: _onRedisDatabaseSelected,
onMongoDBDatabaseSelected: _onMongoDBDatabaseSelected,
onPostgresOpenSqlWorkspace: _onPostgresOpenSqlWorkspace,
onMysqlOpenSqlWorkspace: _onMysqlOpenSqlWorkspace,
onRequestNewConnection: _openNewConnectionFromHero,
),
backgroundColor: scheme.background,
body: WindowBorder(
color: scheme.border.withValues(alpha: 0.35),
width: 1,
child: Column(
children: [
_CustomTitleBar(
theme: scheme,
onNewDatabaseConnection: _onNewDatabaseConnectionFromMenu,
),
Divider(height: 1, color: scheme.border.withValues(alpha: 0.22)),
Expanded(
child: _MainContentSplit(
connectionsPanelKey: _connectionsPanelKey,
workspace: _workspace,
onConnectionSelected: _onConnectionSelected,
onPostgresObjectSelected: _onPostgresObjectSelected,
onMysqlObjectSelected: _onMysqlObjectSelected,
onRedisDatabaseSelected: _onRedisDatabaseSelected,
onMongoDBDatabaseSelected: _onMongoDBDatabaseSelected,
onPostgresOpenSqlWorkspace: _onPostgresOpenSqlWorkspace,
onMysqlOpenSqlWorkspace: _onMysqlOpenSqlWorkspace,
onRequestNewConnection: _openNewConnectionFromHero,
),
],
),
),
],
),
),
);
Expand Down
64 changes: 64 additions & 0 deletions test/core/theme/querya_light_theme_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import 'dart:math' as math;

import 'package:flutter_test/flutter_test.dart';
import 'package:querya_desktop/core/theme/querya_color_scheme.dart';
import 'package:querya_desktop/core/theme/querya_theme.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';

void main() {
group('QueryaTheme.lightDefault', () {
test('colorScheme differs from dark preset', () {
final light = QueryaTheme.lightDefault.colorScheme;
final dark = QueryaTheme.darkDefault.colorScheme;
expect(light.background, isNot(dark.background));
expect(light.foreground, isNot(dark.foreground));
expect(light.brightness, Brightness.light);
});

test('QueryaColorScheme.light matches lightDefault', () {
expect(
QueryaColorScheme.light.background,
QueryaTheme.lightDefault.colorScheme.background,
);
});

test('primary text on canvas meets WCAG AA contrast (4.5:1)', () {
final w = QueryaTheme.lightDefault.workbench;
final ratio = contrastRatio(w.canvas, const Color(0xFF0F172A));
expect(ratio, greaterThanOrEqualTo(4.5));
});

test('editor foreground on editor background meets WCAG AA', () {
final e = QueryaTheme.lightDefault.editor;
final ratio = contrastRatio(e.background, e.foreground);
expect(ratio, greaterThanOrEqualTo(4.5));
});

test('toShadcnThemeData uses light brightness', () {
final td = QueryaTheme.lightDefault.toShadcnThemeData();
expect(td.brightness, Brightness.light);
expect(td.colorScheme.primary, QueryaTheme.lightDefault.workbench.accent);
});
});
}

/// Relative luminance contrast per WCAG 2.1.
double contrastRatio(Color a, Color b) {
final l1 = _relativeLuminance(a);
final l2 = _relativeLuminance(b);
final lighter = math.max(l1, l2);
final darker = math.min(l1, l2);
return (lighter + 0.05) / (darker + 0.05);
}

double _relativeLuminance(Color c) {
double channel(double v) {
final normalized = v <= 0.03928 ? v / 12.92 : math.pow((v + 0.055) / 1.055, 2.4).toDouble();
return normalized;
}

final r = channel(c.r);
final g = channel(c.g);
final b = channel(c.b);
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
Loading