feat(brand): app icon painter + generated 1024px icon PNGs
Introduces AppIconMark (navy gradient field + 2×2 glossy blocks reusing paintGlossyTile) and a testWidgets generator that writes icon.png, icon_background.png, and icon_foreground.png to assets/icon/ for the upcoming flutter_launcher_icons task. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 422 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 326 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 115 KiB |
@@ -0,0 +1,45 @@
|
||||
// lib/ui/branding/app_icon_painter.dart
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../widgets/tile_painter.dart';
|
||||
|
||||
/// Draws the Block Seasons brand mark: deep-navy field + a 2×2 grid of glossy
|
||||
/// brand-color blocks. Shared by the launcher-icon and feature-graphic
|
||||
/// generators so the brand stays identical everywhere.
|
||||
class AppIconMark {
|
||||
static const navy = [Color(0xFF101736), Color(0xFF192555), Color(0xFF2C3168)];
|
||||
static const pink = Color(0xFFFF7EB3);
|
||||
static const yellow = Color(0xFFFFD166);
|
||||
static const cyan = Color(0xFF6FCDF5);
|
||||
static const green = Color(0xFF7EDB9C);
|
||||
|
||||
/// Fills [rect] with the navy gradient.
|
||||
static void paintBackground(Canvas canvas, Rect rect) {
|
||||
final paint = Paint()
|
||||
..shader = const LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: navy,
|
||||
).createShader(rect);
|
||||
canvas.drawRect(rect, paint);
|
||||
}
|
||||
|
||||
/// Paints the 2×2 glossy blocks centered in a square of side [size], the
|
||||
/// block group occupying [groupFraction] of the side.
|
||||
static void paintBlocks(Canvas canvas, double size,
|
||||
{double groupFraction = 0.6}) {
|
||||
final group = size * groupFraction;
|
||||
final gap = size * 0.05;
|
||||
final block = (group - gap) / 2;
|
||||
final m = (size - group) / 2;
|
||||
final far = m + block + gap;
|
||||
void tile(double x, double y, Color c) => paintGlossyTile(
|
||||
canvas, Rect.fromLTWH(x, y, block, block), c, radiusFactor: 0.24);
|
||||
tile(m, m, pink);
|
||||
tile(far, m, yellow);
|
||||
tile(m, far, cyan);
|
||||
tile(far, far, green);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// test/tool/generate_brand_assets_test.dart
|
||||
import 'dart:io';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:block_seasons/ui/branding/app_icon_painter.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
Future<void> _writePng(String path, int size, void Function(Canvas) draw) async {
|
||||
final recorder = PictureRecorder();
|
||||
final canvas = Canvas(recorder);
|
||||
draw(canvas);
|
||||
final picture = recorder.endRecording();
|
||||
final image = await picture.toImage(size, size);
|
||||
final bytes = await image.toByteData(format: ImageByteFormat.png);
|
||||
File(path).parent.createSync(recursive: true);
|
||||
File(path).writeAsBytesSync(bytes!.buffer.asUint8List());
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('generate launcher icon PNGs', (tester) async {
|
||||
const s = 1024;
|
||||
final full = Rect.fromLTWH(0, 0, s.toDouble(), s.toDouble());
|
||||
|
||||
await tester.runAsync(() async {
|
||||
// Master (iOS + fallback): opaque navy + blocks at 60%.
|
||||
await _writePng('assets/icon/icon.png', s, (c) {
|
||||
AppIconMark.paintBackground(c, full);
|
||||
AppIconMark.paintBlocks(c, s.toDouble(), groupFraction: 0.6);
|
||||
});
|
||||
// Adaptive background: navy only.
|
||||
await _writePng('assets/icon/icon_background.png', s, (c) {
|
||||
AppIconMark.paintBackground(c, full);
|
||||
});
|
||||
// Adaptive foreground: blocks only (transparent), 52% for the safe zone.
|
||||
await _writePng('assets/icon/icon_foreground.png', s, (c) {
|
||||
AppIconMark.paintBlocks(c, s.toDouble(), groupFraction: 0.52);
|
||||
});
|
||||
});
|
||||
|
||||
for (final f in ['icon.png', 'icon_background.png', 'icon_foreground.png']) {
|
||||
expect(File('assets/icon/$f').existsSync(), isTrue, reason: f);
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user