feat: extend SeasonTheme with visual identity fields (ARGB ints)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,18 +1,62 @@
|
||||
import 'stage.dart';
|
||||
|
||||
/// Visual identity of a season. Colors are int ARGB so this file stays
|
||||
/// pure Dart (architecture guard forbids Flutter imports here).
|
||||
class SeasonTheme {
|
||||
const SeasonTheme({required this.tileSet, required this.background});
|
||||
const SeasonTheme({
|
||||
this.tileSet = 'spring',
|
||||
this.background = '',
|
||||
this.backgroundGradient = defaultGradient,
|
||||
this.accentColor = 0xFFFF7EB3,
|
||||
this.particleType = 'petals',
|
||||
this.tilePalette,
|
||||
this.boardTint,
|
||||
});
|
||||
|
||||
factory SeasonTheme.fromJson(Map<String, dynamic> json) => SeasonTheme(
|
||||
tileSet: json['tileSet'] as String,
|
||||
background: json['background'] as String,
|
||||
tileSet: (json['tileSet'] as String?) ?? 'spring',
|
||||
background: (json['background'] as String?) ?? '',
|
||||
backgroundGradient: json['backgroundGradient'] != null
|
||||
? [for (final c in json['backgroundGradient'] as List) c as int]
|
||||
: defaultGradient,
|
||||
accentColor: (json['accentColor'] as int?) ?? 0xFFFF7EB3,
|
||||
particleType: (json['particleType'] as String?) ?? 'petals',
|
||||
tilePalette: json['tilePalette'] != null
|
||||
? [for (final c in json['tilePalette'] as List) c as int]
|
||||
: null,
|
||||
boardTint: json['boardTint'] as int?,
|
||||
);
|
||||
|
||||
/// Season 1 "First Bloom": deep navy dusk.
|
||||
static const defaultGradient = [0xFF0E1430, 0xFF16204A, 0xFF2A2E5E];
|
||||
|
||||
static const fallback = SeasonTheme();
|
||||
|
||||
final String tileSet;
|
||||
final String background;
|
||||
|
||||
Map<String, dynamic> toJson() =>
|
||||
{'tileSet': tileSet, 'background': background};
|
||||
/// Top-to-bottom screen gradient, int ARGB.
|
||||
final List<int> backgroundGradient;
|
||||
final int accentColor;
|
||||
|
||||
/// petals | snow | leaves | none
|
||||
final String particleType;
|
||||
|
||||
/// Optional 8-color tile override; null = built-in candy palette.
|
||||
final List<int>? tilePalette;
|
||||
|
||||
/// Optional board background override.
|
||||
final int? boardTint;
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'tileSet': tileSet,
|
||||
'background': background,
|
||||
'backgroundGradient': backgroundGradient,
|
||||
'accentColor': accentColor,
|
||||
'particleType': particleType,
|
||||
if (tilePalette != null) 'tilePalette': tilePalette,
|
||||
if (boardTint != null) 'boardTint': boardTint,
|
||||
};
|
||||
}
|
||||
|
||||
/// A season's full content: metadata, theme, and its stages. The unit of
|
||||
|
||||
@@ -41,7 +41,24 @@ void main() {
|
||||
|
||||
test('round-trips to JSON', () {
|
||||
final pack = SeasonPack.fromJson(packJson);
|
||||
expect(pack.toJson(), packJson);
|
||||
// toJson() always emits all SeasonTheme fields (new fields added in
|
||||
// Task 1). Compare everything except theme separately so that adding
|
||||
// more theme fields in the future only requires updating theme tests.
|
||||
final json = pack.toJson();
|
||||
expect(json['schemaVersion'], packJson['schemaVersion']);
|
||||
expect(json['seasonId'], packJson['seasonId']);
|
||||
expect(json['version'], packJson['version']);
|
||||
expect(json['title'], packJson['title']);
|
||||
expect(json['stages'], packJson['stages']);
|
||||
// Theme: legacy fields preserved, new fields present with defaults.
|
||||
final theme = json['theme'] as Map<String, dynamic>;
|
||||
expect(theme['tileSet'], 'spring');
|
||||
expect(theme['background'], 'background.webp');
|
||||
expect(theme['backgroundGradient'], SeasonTheme.defaultGradient);
|
||||
expect(theme['accentColor'], 0xFFFF7EB3);
|
||||
expect(theme['particleType'], 'petals');
|
||||
expect(theme.containsKey('tilePalette'), isFalse);
|
||||
expect(theme.containsKey('boardTint'), isFalse);
|
||||
});
|
||||
|
||||
test('localized title falls back to English', () {
|
||||
@@ -58,4 +75,42 @@ void main() {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('SeasonTheme visuals', () {
|
||||
test('legacy theme json (tileSet/background only) gets defaults', () {
|
||||
final theme = SeasonTheme.fromJson({
|
||||
'tileSet': 'spring',
|
||||
'background': 'background.webp',
|
||||
});
|
||||
expect(theme.backgroundGradient, SeasonTheme.defaultGradient);
|
||||
expect(theme.accentColor, 0xFFFF7EB3);
|
||||
expect(theme.particleType, 'petals');
|
||||
expect(theme.tilePalette, isNull);
|
||||
expect(theme.boardTint, isNull);
|
||||
});
|
||||
|
||||
test('full theme json round-trips', () {
|
||||
final theme = SeasonTheme(
|
||||
tileSet: 'summer',
|
||||
background: 'bg.webp',
|
||||
backgroundGradient: const [0xFF0A2430, 0xFF10394A, 0xFF1E5A66],
|
||||
accentColor: 0xFF6FCDF5,
|
||||
particleType: 'snow',
|
||||
tilePalette: const [0xFF111111, 0xFF222222],
|
||||
boardTint: 0xFF041016,
|
||||
);
|
||||
final decoded = SeasonTheme.fromJson(theme.toJson());
|
||||
expect(decoded.backgroundGradient, theme.backgroundGradient);
|
||||
expect(decoded.accentColor, theme.accentColor);
|
||||
expect(decoded.particleType, theme.particleType);
|
||||
expect(decoded.tilePalette, theme.tilePalette);
|
||||
expect(decoded.boardTint, theme.boardTint);
|
||||
});
|
||||
|
||||
test('fallback constant matches season 1 defaults', () {
|
||||
expect(SeasonTheme.fallback.backgroundGradient,
|
||||
const [0xFF0E1430, 0xFF16204A, 0xFF2A2E5E]);
|
||||
expect(SeasonTheme.fallback.particleType, 'petals');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user