mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 20:27:02 +01:00
billsplit: adopt the shared palette, retire the teal seed
Last of the four drifted apps. The theme was ColorScheme.fromSeed(teal) plus fifteen ad hoc colour literals that mostly bypassed it -- not one widget outside main.dart read colorScheme at all, so the seed barely affected what you saw. It now uses design_system-v0.1.0's buildLightTheme(), and the literals that have a real theme role read it off the scheme. Light, not dark, deliberately: the receipt screens carry black-on-white helper text, so inverting brightness would make it unreadable. The Colors.black54 sites are exactly those, now onSurfaceVariant. The error family maps cleanly -- Colors.red to error, red.shade50 to errorContainer with onErrorContainer text on it, and the swipe-to-delete background+icon to error/onError so the fill and the glyph drawn on it cannot drift apart. The four category dots (deepOrange/amber/blueGrey/teal) stay literals, with a comment saying why: they encode which category an item is, not a theme role, and a single-accent system has no four distinct hues to lend them. Mapping them onto primary/secondary/tertiary would make the categories stop being distinguishable, which is their only job. They are tracked in nielsen-audit.md as needing a categorical ramp. Note this decouples the default dot from the old seed -- both were Colors.teal, so they used to match by coincidence rather than by design. Verified: 60/60 tests, 100% line coverage across 11 lib files, analyze clean apart from four pre-existing unnecessary_unawaited infos. A temporary assertion pumped the real app and read primary=ffb8862e and surface=fff6f4f3 off a live Scaffold, confirming the palette changed rather than merely compiling. Colour literals: 15 -> 4. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XS1m7b1piBtAL22USLW6jL
This commit is contained in:
parent
01778d4f05
commit
40d2c96a77
@ -2,6 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:billsplit/state/app_state.dart';
|
||||
import 'package:billsplit/ui/home_screen.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
@ -27,9 +28,10 @@ class BillSplitApp extends StatelessWidget {
|
||||
child: MaterialApp(
|
||||
title: 'BillSplit',
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
|
||||
),
|
||||
// The shared light theme, replacing a teal-seeded scheme. Light, not
|
||||
// dark: the receipt screens carry black-on-white helper text, so
|
||||
// switching brightness here would make it unreadable.
|
||||
theme: buildLightTheme(),
|
||||
home: const HomeScreen(),
|
||||
),
|
||||
);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import 'package:billsplit/domain/models.dart';
|
||||
import 'package:billsplit/domain/split_engine.dart';
|
||||
import 'package:billsplit/state/app_state.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Bottom sheet to assign an item: quick modes (everyone / drinkers /
|
||||
@ -126,8 +127,10 @@ Future<void> showAssignmentSheet(
|
||||
'totals until someone is assigned.'
|
||||
: _eachLabel(resolved.length, item.totalGr),
|
||||
style: TextStyle(
|
||||
color: resolved.isEmpty ? Colors.red : Colors.black54,
|
||||
fontSize: 12,
|
||||
color: resolved.isEmpty
|
||||
? Theme.of(c).colorScheme.error
|
||||
: Theme.of(c).colorScheme.onSurfaceVariant,
|
||||
fontSize: AppTextSize.caption,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@ -6,6 +6,7 @@ import 'package:billsplit/domain/models.dart';
|
||||
import 'package:billsplit/state/app_state.dart';
|
||||
import 'package:billsplit/ui/people_screen.dart';
|
||||
import 'package:billsplit/ui/receipt_screen.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:file_selector/file_selector.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@ -45,11 +46,17 @@ class HomeScreen extends StatelessWidget {
|
||||
return Dismissible(
|
||||
key: ValueKey(r.id),
|
||||
direction: DismissDirection.endToStart,
|
||||
// The delete affordance revealed by swiping. The fill
|
||||
// and the icon on top of it must move together, hence
|
||||
// error/onError rather than two independent colours.
|
||||
background: Container(
|
||||
color: Colors.red.shade300,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
alignment: Alignment.centerRight,
|
||||
padding: const EdgeInsets.only(right: 16),
|
||||
child: const Icon(Icons.delete, color: Colors.white),
|
||||
padding: const EdgeInsets.only(right: AppSpacing.md),
|
||||
child: Icon(
|
||||
Icons.delete,
|
||||
color: Theme.of(context).colorScheme.onError,
|
||||
),
|
||||
),
|
||||
confirmDismiss: (_) => _confirmDelete(context, r),
|
||||
onDismissed: (_) => state.removeReceipt(r),
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import 'package:billsplit/domain/models.dart';
|
||||
import 'package:billsplit/state/app_state.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Add ([item] == null) or edit an item. The user types amounts in złoty
|
||||
@ -65,10 +66,13 @@ Future<void> showItemEditDialog(
|
||||
setState(() => category = v ?? Categories.general),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const Text(
|
||||
Text(
|
||||
'Alcohol items default to drinkers only; everything else to '
|
||||
'everyone. Fine-tune by tapping the item afterwards.',
|
||||
style: TextStyle(fontSize: 11, color: Colors.black54),
|
||||
style: TextStyle(
|
||||
fontSize: AppTextSize.caption,
|
||||
color: Theme.of(c).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -80,7 +84,10 @@ Future<void> showItemEditDialog(
|
||||
state.mutate(() => receipt.items.remove(item));
|
||||
Navigator.pop(c);
|
||||
},
|
||||
child: const Text('Delete', style: TextStyle(color: Colors.red)),
|
||||
child: Text(
|
||||
'Delete',
|
||||
style: TextStyle(color: Theme.of(c).colorScheme.error),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(c),
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import 'package:billsplit/domain/models.dart';
|
||||
import 'package:billsplit/state/app_state.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
@ -25,12 +26,15 @@ class PeopleScreen extends StatelessWidget {
|
||||
for (final p in state.people) _PersonTile(person: p),
|
||||
const Divider(height: 32),
|
||||
const _SectionHeader('Groups'),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text(
|
||||
'Drinkers / non-drinkers are built in (the toggle on each '
|
||||
'person). Groups below are extra tags, e.g. "meat eaters".',
|
||||
style: TextStyle(fontSize: 12, color: Colors.black54),
|
||||
style: TextStyle(
|
||||
fontSize: AppTextSize.caption,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
for (final g in state.groups) _GroupTile(group: g),
|
||||
|
||||
@ -234,7 +234,7 @@ class _ItemsList extends StatelessWidget {
|
||||
subtitle: Text(
|
||||
_assignmentLabel(item, people.length, state),
|
||||
style: TextStyle(
|
||||
color: unassigned ? Colors.red : null,
|
||||
color: unassigned ? Theme.of(context).colorScheme.error : null,
|
||||
fontWeight: unassigned ? FontWeight.bold : null,
|
||||
),
|
||||
),
|
||||
@ -272,6 +272,12 @@ class _CategoryDot extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final color = switch (category) {
|
||||
// Categorical encoding, deliberately NOT mapped onto ColorScheme slots.
|
||||
// These four say "which category", not "which theme role", and a
|
||||
// single-accent design system has no four distinct hues to lend. Folding
|
||||
// them into primary/secondary/tertiary would make the categories stop
|
||||
// being distinguishable, which is the only job they have. Tracked in
|
||||
// unified-design-system/nielsen-audit.md as needing a categorical ramp.
|
||||
Categories.alcohol => Colors.deepOrange,
|
||||
Categories.mixer => Colors.amber,
|
||||
Categories.deposit => Colors.blueGrey,
|
||||
@ -300,14 +306,16 @@ class _TotalsView extends StatelessWidget {
|
||||
children: [
|
||||
if (split.unassigned.isNotEmpty)
|
||||
Card(
|
||||
color: Colors.red.shade50,
|
||||
color: Theme.of(context).colorScheme.errorContainer,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Text(
|
||||
'${split.unassigned.length} item(s) have nobody assigned and '
|
||||
'are excluded from totals:\n'
|
||||
'${split.unassigned.map((i) => '· ${i.name}').join('\n')}',
|
||||
style: const TextStyle(color: Colors.red),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onErrorContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -347,7 +355,7 @@ class _TotalsView extends StatelessWidget {
|
||||
}) {
|
||||
final style = TextStyle(
|
||||
fontWeight: bold || warn ? FontWeight.bold : null,
|
||||
color: warn ? Colors.red : null,
|
||||
color: warn ? Theme.of(context).colorScheme.error : null,
|
||||
);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 2),
|
||||
|
||||
@ -97,6 +97,15 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.7"
|
||||
design_system:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: design_system
|
||||
ref: "design_system-v0.1.0"
|
||||
resolved-ref: e6c9af81e9ac3000d87d9168d3ef87ba2979258b
|
||||
url: "https://github.com/kuhyx/utils"
|
||||
source: git
|
||||
version: "0.1.0"
|
||||
equatable:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -332,10 +341,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||
sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.19"
|
||||
version: "0.12.20"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -348,10 +357,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
||||
sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
version: "1.19.0"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -529,10 +538,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
||||
sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.11"
|
||||
version: "0.7.12"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -545,10 +554,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||
sha256: f36f9f3be64c6198714492bb455c11056e33e2f85d9a0b676a48301e44fdcf47
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
version: "2.4.2"
|
||||
very_good_analysis:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@ -598,5 +607,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.12.0 <4.0.0"
|
||||
dart: ">=3.12.2 <4.0.0"
|
||||
flutter: ">=3.44.0"
|
||||
|
||||
@ -7,6 +7,13 @@ environment:
|
||||
sdk: ^3.5.0
|
||||
|
||||
dependencies:
|
||||
# The shared palette and scales.
|
||||
design_system:
|
||||
git:
|
||||
url: https://github.com/kuhyx/utils
|
||||
ref: design_system-v0.1.0
|
||||
path: design_system
|
||||
|
||||
excel: ^4.0.6
|
||||
file_selector: ^1.1.0
|
||||
flutter:
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
{
|
||||
"title": "billsplit adopts the shared design_system palette",
|
||||
"objective": "billsplit is the last of four Flutter apps that had drifted off the frozen palette. It renders from a teal-seeded ColorScheme plus fifteen ad hoc colour literals that largely bypass the theme entirely. Success is billsplit rendering from design_system-v0.1.0's buildLightTheme(), with every literal that carries a theme role reading it off the ColorScheme instead, and with no behavioural, layout or coverage regression.",
|
||||
"acceptance_criteria": [
|
||||
"lib/main.dart builds its theme from design_system's buildLightTheme() rather than ColorScheme.fromSeed(seedColor: Colors.teal).",
|
||||
"The rendered app's colorScheme.primary is ffb8862e and surface is fff6f4f3, proven by pumping the real BillSplitApp and reading the theme off a live element -- not by inspecting source.",
|
||||
"Error-family literals map to error / onError / errorContainer / onErrorContainer; de-emphasised text maps to onSurfaceVariant.",
|
||||
"The swipe-to-delete background and the icon drawn on it use error and onError respectively, so they cannot be changed independently.",
|
||||
"flutter test reports 60/60 passing.",
|
||||
"python3 -m python_pkg.billsplit_coverage reports 100% line coverage across all lib files.",
|
||||
"flutter analyze --fatal-infos introduces no new findings beyond the pre-existing unnecessary_unawaited infos."
|
||||
],
|
||||
"out_of_scope": [
|
||||
"Switching billsplit to a dark theme. It is light-only by design; its helper text is black-on-white and would become unreadable.",
|
||||
"Mapping the four category-dot colours (deepOrange/amber/blueGrey/teal) onto ColorScheme slots. They encode category identity rather than a theme role, and a single-accent system has no four distinct hues for them. Recorded in unified-design-system/nielsen-audit.md as needing a categorical ramp instead.",
|
||||
"Fixing the pre-existing unnecessary_unawaited analyzer infos, which come from SDK drift and are present across several repos.",
|
||||
"Any change to split logic, e-paragon parsing, xlsx export, or app state."
|
||||
],
|
||||
"verifier": "flutter analyze --fatal-infos && flutter test (60/60) && python3 -m python_pkg.billsplit_coverage (100%, 11 files), plus a temporary widget test asserting primary=ffb8862e and surface=fff6f4f3 on the pumped app, deleted after it passed. CI re-runs the first three via .github/workflows/billsplit-ci.yml."
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
{
|
||||
"intent": "Put billsplit on the one shared palette every other kuhy Flutter app now uses, instead of a teal-seeded scheme plus fifteen ad hoc colour literals.",
|
||||
"scope": [
|
||||
"billsplit/lib/main.dart (theme construction)",
|
||||
"billsplit/lib/ui/{home_screen,receipt_screen,item_edit,people_screen,assignment_sheet}.dart (literal call sites)",
|
||||
"billsplit/pubspec.yaml (new design_system git dep)",
|
||||
"Non-goal: no behaviour, layout or business-logic change; no switch to a dark theme"
|
||||
],
|
||||
"changes": [
|
||||
"theme: ColorScheme.fromSeed(seedColor: Colors.teal) -> buildLightTheme() from design_system-v0.1.0. Light, not dark: the receipt screens carry black-on-white helper text that would be unreadable inverted.",
|
||||
"Mapped the error-family literals onto ColorScheme slots: Colors.red -> error (4 sites), Colors.red.shade50 -> errorContainer with onErrorContainer text, and the swipe-to-delete Colors.red.shade300 + Colors.white -> error + onError, so the fill and the icon on top of it cannot drift apart.",
|
||||
"Mapped the de-emphasised helper text Colors.black54 -> onSurfaceVariant (3 sites). These were the literals that would have broken outright on any dark surface.",
|
||||
"Left the four-colour category ramp (deepOrange/amber/blueGrey/teal for alcohol/mixer/deposit/other) as literals, with a comment saying why: they encode category identity, not a theme role, and a single-accent design system has no four distinct hues to lend. Folding them into ColorScheme slots would destroy the only job they do. Tracked in unified-design-system/nielsen-audit.md as needing a categorical ramp."
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"command": "flutter analyze --fatal-infos (billsplit)",
|
||||
"result": "pass",
|
||||
"evidence": "4 issues, all pre-existing unnecessary_unawaited infos from SDK drift (the same class of info also present in epopeja_karta and kuhylog before this work). One real error introduced mid-edit -- const_eval_method_invocation, from putting Theme.of(context) inside a const Padding -- was found by the analyzer and fixed before commit."
|
||||
},
|
||||
{
|
||||
"command": "flutter test (billsplit)",
|
||||
"result": "pass",
|
||||
"evidence": "60/60 tests passed"
|
||||
},
|
||||
{
|
||||
"command": "python3 -m python_pkg.billsplit_coverage",
|
||||
"result": "pass",
|
||||
"evidence": "100% line coverage across 11 lib files"
|
||||
},
|
||||
{
|
||||
"command": "flutter test (ad-hoc palette assertion, then deleted)",
|
||||
"result": "pass",
|
||||
"evidence": "Pumped the real BillSplitApp and read the theme off a Scaffold element: primary=ffb8862e, surface=fff6f4f3 (paper), brightness=light. Confirms the palette actually changed rather than merely compiling."
|
||||
},
|
||||
{
|
||||
"command": "grep -rc 'Colors\\.' billsplit/lib",
|
||||
"result": "pass",
|
||||
"evidence": "15 -> 4, and the 4 remaining are exactly the category-dot ramp that was deliberately not mapped"
|
||||
}
|
||||
],
|
||||
"risks": [
|
||||
"The app moves from a teal-derived Material scheme to the hand-picked gold palette, so every screen changes colour. That is the intent, and it is the last of four apps being brought onto one palette.",
|
||||
"Colors.teal was both the theme seed and the default category dot. The seed is gone but the dot is deliberately unchanged, so those two are now decoupled -- previously they matched by coincidence, not by design. Noted here so the next reader does not read it as an oversight.",
|
||||
"billsplit declares sdk ^3.5.0 while design_system declares ^3.12.2. Resolution succeeds on the installed SDK, but a machine on an older Dart could fail pub get; bumping billsplit's floor is the fix if that ever bites."
|
||||
],
|
||||
"rollback": [
|
||||
"git revert the commit and run flutter pub get; there is no build artifact or device state to unwind, since billsplit ships no APK from this change.",
|
||||
"After rollback, confirm flutter test still reports 60/60 and the coverage gate still reports 100% across 11 files."
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user