From 9f5400919e7fb1ae0d94b97b3d09d4a220017c7c Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Fri, 14 Aug 2026 23:35:08 +0200 Subject: [PATCH] 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) Claude-Session: https://claude.ai/code/session_01XS1m7b1piBtAL22USLW6jL --- billsplit/lib/main.dart | 8 +++++--- billsplit/lib/ui/assignment_sheet.dart | 7 +++++-- billsplit/lib/ui/home_screen.dart | 13 ++++++++++--- billsplit/lib/ui/item_edit.dart | 13 ++++++++++--- billsplit/lib/ui/people_screen.dart | 10 +++++++--- billsplit/lib/ui/receipt_screen.dart | 16 +++++++++++---- billsplit/pubspec.lock | 27 +++++++++++++++++--------- billsplit/pubspec.yaml | 7 +++++++ 8 files changed, 74 insertions(+), 27 deletions(-) diff --git a/billsplit/lib/main.dart b/billsplit/lib/main.dart index cd84c2c..e8ad84e 100644 --- a/billsplit/lib/main.dart +++ b/billsplit/lib/main.dart @@ -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(), ), ); diff --git a/billsplit/lib/ui/assignment_sheet.dart b/billsplit/lib/ui/assignment_sheet.dart index 6ef5d8d..6dd7c40 100644 --- a/billsplit/lib/ui/assignment_sheet.dart +++ b/billsplit/lib/ui/assignment_sheet.dart @@ -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 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, ), ), ], diff --git a/billsplit/lib/ui/home_screen.dart b/billsplit/lib/ui/home_screen.dart index 04c4e81..55a26b3 100644 --- a/billsplit/lib/ui/home_screen.dart +++ b/billsplit/lib/ui/home_screen.dart @@ -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), diff --git a/billsplit/lib/ui/item_edit.dart b/billsplit/lib/ui/item_edit.dart index 227bee1..40e2ec9 100644 --- a/billsplit/lib/ui/item_edit.dart +++ b/billsplit/lib/ui/item_edit.dart @@ -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 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 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), diff --git a/billsplit/lib/ui/people_screen.dart b/billsplit/lib/ui/people_screen.dart index 70648b4..e8ad102 100644 --- a/billsplit/lib/ui/people_screen.dart +++ b/billsplit/lib/ui/people_screen.dart @@ -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), diff --git a/billsplit/lib/ui/receipt_screen.dart b/billsplit/lib/ui/receipt_screen.dart index c307d21..d7e06bf 100644 --- a/billsplit/lib/ui/receipt_screen.dart +++ b/billsplit/lib/ui/receipt_screen.dart @@ -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), diff --git a/billsplit/pubspec.lock b/billsplit/pubspec.lock index 9593591..c8ff25d 100644 --- a/billsplit/pubspec.lock +++ b/billsplit/pubspec.lock @@ -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" diff --git a/billsplit/pubspec.yaml b/billsplit/pubspec.yaml index 869f870..ed46da0 100644 --- a/billsplit/pubspec.yaml +++ b/billsplit/pubspec.yaml @@ -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: