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';
|
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 {
|
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(
|
factory SeasonTheme.fromJson(Map<String, dynamic> json) => SeasonTheme(
|
||||||
tileSet: json['tileSet'] as String,
|
tileSet: (json['tileSet'] as String?) ?? 'spring',
|
||||||
background: json['background'] as String,
|
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 tileSet;
|
||||||
final String background;
|
final String background;
|
||||||
|
|
||||||
Map<String, dynamic> toJson() =>
|
/// Top-to-bottom screen gradient, int ARGB.
|
||||||
{'tileSet': tileSet, 'background': background};
|
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
|
/// A season's full content: metadata, theme, and its stages. The unit of
|
||||||
|
|||||||
@@ -41,7 +41,24 @@ void main() {
|
|||||||
|
|
||||||
test('round-trips to JSON', () {
|
test('round-trips to JSON', () {
|
||||||
final pack = SeasonPack.fromJson(packJson);
|
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', () {
|
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