diff --git a/lib/core/theme/theme_definition.dart b/lib/core/theme/theme_definition.dart new file mode 100644 index 00000000..57fd507a --- /dev/null +++ b/lib/core/theme/theme_definition.dart @@ -0,0 +1,42 @@ +enum ThemeSource { + builtin, + imported, + filesystem, + legacyImported, +} + +enum ThemeFormat { + queryaCustom, + vscode, +} + +/// Lightweight theme metadata for registry lists and load results. +class ThemeDefinition { + const ThemeDefinition({ + required this.id, + required this.name, + required this.source, + required this.format, + required this.isDark, + this.path, + this.lastModified, + this.contentHash, + }); + + final String id; + final String name; + final ThemeSource source; + final ThemeFormat format; + final bool isDark; + final String? path; + final DateTime? lastModified; + final String? contentHash; + + bool get isFileBacked => + source == ThemeSource.filesystem || + source == ThemeSource.imported || + source == ThemeSource.legacyImported; + + String get stableCacheKey => + '${source.name}:$id:${contentHash ?? path ?? name}'; +} diff --git a/lib/core/theme/theme_load_result.dart b/lib/core/theme/theme_load_result.dart new file mode 100644 index 00000000..3a722d4c --- /dev/null +++ b/lib/core/theme/theme_load_result.dart @@ -0,0 +1,29 @@ +import 'querya_theme.dart'; +import 'theme_definition.dart'; + +/// Result of loading a [ThemeDefinition] into a runtime [QueryaTheme]. +sealed class ThemeLoadResult { + const ThemeLoadResult(); +} + +class ThemeLoadSuccess extends ThemeLoadResult { + const ThemeLoadSuccess({ + required this.definition, + required this.theme, + }); + + final ThemeDefinition definition; + final QueryaTheme theme; +} + +class ThemeLoadFailure extends ThemeLoadResult { + const ThemeLoadFailure({ + required this.definition, + required this.message, + this.error, + }); + + final ThemeDefinition definition; + final String message; + final Object? error; +} diff --git a/test/core/theme/theme_load_result_test.dart b/test/core/theme/theme_load_result_test.dart new file mode 100644 index 00000000..52e7e046 --- /dev/null +++ b/test/core/theme/theme_load_result_test.dart @@ -0,0 +1,57 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:querya_desktop/core/theme/querya_theme.dart'; +import 'package:querya_desktop/core/theme/theme_definition.dart'; +import 'package:querya_desktop/core/theme/theme_load_result.dart'; + +void main() { + const definition = ThemeDefinition( + id: 'fixture-custom-dark', + name: 'Fixture Custom Dark', + source: ThemeSource.filesystem, + format: ThemeFormat.queryaCustom, + isDark: true, + path: '/tmp/fixture-custom-dark.json', + contentHash: 'abc123', + ); + + group('ThemeLoadResult', () { + test('success carries definition and theme', () { + const result = ThemeLoadSuccess( + definition: definition, + theme: QueryaTheme.darkDefault, + ); + + expect(result, isA()); + expect(result.definition.id, 'fixture-custom-dark'); + expect(result.theme.brightness, QueryaTheme.darkDefault.brightness); + }); + + test('failure carries definition, message, and optional error', () { + final error = StateError('parse failed'); + final result = ThemeLoadFailure( + definition: definition, + message: 'Invalid theme file.', + error: error, + ); + + expect(result, isA()); + expect(result.definition.path, definition.path); + expect(result.message, 'Invalid theme file.'); + expect(result.error, same(error)); + }); + + test('sealed result supports pattern matching', () { + const ThemeLoadResult result = ThemeLoadSuccess( + definition: definition, + theme: QueryaTheme.lightDefault, + ); + + final matched = switch (result) { + ThemeLoadSuccess(:final theme) => theme.brightness, + ThemeLoadFailure() => null, + }; + + expect(matched, QueryaTheme.lightDefault.brightness); + }); + }); +}