Design system - MilerSurface ladder (canvas → working → raised → floating) with MilerPanel as layer 1; canvas moved to #DEE3EA so white separates at 1.290:1. - Visible vocabulary applied across Home, Deliveries, Activity, Account and the sheets: hero heads (tabular numeral + small caption, clamped at 1.3x), canvas wells for anything that opens, small filled tags for shelf labels, demoted placeholders. Recorded in DESIGN_SYSTEM.md §6. - One icon family: 222 Material glyphs migrated to Lucide; none left outside lib/xpress. - Colour semantics corrected: amber only for what is genuinely owed, brand red reserved for the live stop, disabled primaries go neutral rather than pale. Data and lifecycle - lib/data/lifecycle.dart reads mutations for what they prove; route_order.dart makes admin sequence the single ordering authority; service_day.dart, and stop_area.dart rewritten against live Coimbatore addresses (digit-token stripping, city stoplist, street suffixes, stammer collapse). - countLabel states the load once, in bags. Testing - 1440 tests passing; golden shot harnesses for Home, Deliveries, Activity, sheets and verify, with test/failures/ now gitignored (diff debris). - New pins: home_gutter_test, stop_area_test, plus updated structural bounds. Note: this commit also carries pre-existing working-tree deletions that were present before this work (API_SPEC.md, README.md, demo test fixtures). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
85 lines
2.9 KiB
Dart
85 lines
2.9 KiB
Dart
import 'dart:ui' as ui;
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:miler/views/helpers/constants/Colorconstants.dart';
|
|
import 'package:miler/views/helpers/constants/Font_constant.dart';
|
|
|
|
/// Mirrors `_calloutBitmap` in map.dart so the drawing maths can be checked
|
|
/// headless — the real one is private to a State inside a `part` file.
|
|
Future<({int w, int h})> callout(String text, double dpr) async {
|
|
const double padH = 12, padV = 9, radius = 12, tail = 9, tailW = 14;
|
|
const double maxTextWidth = 190;
|
|
final painter = TextPainter(
|
|
text: TextSpan(
|
|
text: text,
|
|
style: TextStyle(
|
|
fontSize: 12 * dpr,
|
|
height: 1.25,
|
|
fontWeight: FontWeight.w600,
|
|
color: ColorConstants.slateText,
|
|
fontFamily: FontConstants.fontFamily,
|
|
),
|
|
),
|
|
maxLines: 2,
|
|
ellipsis: '…',
|
|
textDirection: TextDirection.ltr,
|
|
)..layout(maxWidth: maxTextWidth * dpr);
|
|
|
|
final w = painter.width + padH * 2 * dpr;
|
|
final h = painter.height + padV * 2 * dpr;
|
|
final totalH = h + tail * dpr;
|
|
|
|
final rec = ui.PictureRecorder();
|
|
final canvas = Canvas(rec);
|
|
final body = RRect.fromRectAndRadius(
|
|
Rect.fromLTWH(0, 0, w, h),
|
|
Radius.circular(radius * dpr),
|
|
);
|
|
canvas.drawRRect(
|
|
body.shift(Offset(0, 1.5 * dpr)),
|
|
Paint()
|
|
..color = Colors.black.withValues(alpha: 0.18)
|
|
..maskFilter = MaskFilter.blur(BlurStyle.normal, 3 * dpr),
|
|
);
|
|
canvas.drawRRect(body, Paint()..color = Colors.white);
|
|
final tip = Path()
|
|
..moveTo(w / 2 - tailW / 2 * dpr, h - 1)
|
|
..lineTo(w / 2, totalH)
|
|
..lineTo(w / 2 + tailW / 2 * dpr, h - 1)
|
|
..close();
|
|
canvas.drawPath(tip, Paint()..color = Colors.white);
|
|
canvas.drawRRect(
|
|
RRect.fromRectAndCorners(
|
|
Rect.fromLTWH(0, 0, 4 * dpr, h),
|
|
topLeft: Radius.circular(radius * dpr),
|
|
bottomLeft: Radius.circular(radius * dpr),
|
|
),
|
|
Paint()..color = ColorConstants.primary,
|
|
);
|
|
painter.paint(canvas, Offset(padH * dpr + 2 * dpr, padV * dpr));
|
|
|
|
final img = await rec.endRecording().toImage(w.ceil(), totalH.ceil());
|
|
final bytes = await img.toByteData(format: ui.ImageByteFormat.png);
|
|
if (bytes == null) throw StateError('no png');
|
|
return (w: img.width, h: img.height);
|
|
}
|
|
|
|
void main() {
|
|
testWidgets('callout rasterises at every dpr', (tester) async {
|
|
for (final dpr in [1.0, 2.0, 3.0]) {
|
|
final r = await callout('18, Chinnavedampatti Road, Coimbatore', dpr);
|
|
print('dpr=$dpr ${r.w}x${r.h}px');
|
|
expect(r.w, greaterThan(0));
|
|
expect(r.h, greaterThan(0));
|
|
}
|
|
// A very long address must clamp, not grow without bound.
|
|
final long = await callout(
|
|
'Plot 42B, Second Cross Street, Behind Sri Ramakrishna Mills, '
|
|
'Ganapathy Post, Coimbatore, Tamil Nadu 641006',
|
|
3.0,
|
|
);
|
|
print('long address at dpr=3: ${long.w}x${long.h}px');
|
|
expect(long.w, lessThanOrEqualTo((190 + 24) * 3 + 2));
|
|
});
|
|
}
|