diff --git a/lib/core/app_theme.dart b/lib/core/app_theme.dart index 84f8d91..17abb1a 100644 --- a/lib/core/app_theme.dart +++ b/lib/core/app_theme.dart @@ -49,8 +49,10 @@ class AppColors extends ThemeExtension { final Color messageSent; final Color messageReceived; final Color systemMessage; + /// Dark-gold color used for the notification count badge. final Color badgeGold; + /// Amber used for time-sensitive warnings (running timers, backup nags). final Color warningAmber; @@ -146,9 +148,16 @@ class AppColors extends ThemeExtension { // ── Order Book redesign palette ─────────────────────────────────────────────── /// Palette of the "Mostro UX Redesign" mock (Claude Design, screen -/// #3 · Order book). Applied only to the redesigned Order Book screen while -/// the rest of the app migrates screen by screen; dark values follow the mock -/// except where the mock fails WCAG AA (4.5:1) on its real rendered surface — +/// #3 · Order book), on the v1 tonal recipe: app bar, tabs and card share one +/// surface tone ([bg] == [bgCard]) while the list area behind the cards sits +/// on the slightly lighter [bgWell] — v1's inverted contrast: dark cards on a +/// lighter well, deepened by [cardShadow] and the [border] hairline, with +/// inner panels one more step up on [bgElevated]. The green glow of the +/// "Card Contrast Options" mock (option 07) stays, reserved for the one card +/// per screen that is selected / needs action. +/// Applied only to the redesigned Order Book screen while +/// the rest of the app migrates screen by screen; values follow the mock +/// except where it fails WCAG AA (4.5:1) on its real rendered surface — /// [textTertiary] and [red] are lightened just enough to pass. The mock is /// dark-only, so [light] is a legibility mapping onto the existing light /// surfaces, darkened where needed to pass AA. Every text-role/surface pair is @@ -157,6 +166,7 @@ class AppColors extends ThemeExtension { class OrderBookPalette { const OrderBookPalette({ required this.bg, + required this.bgWell, required this.bgCard, required this.bgElevated, required this.border, @@ -172,9 +182,17 @@ class OrderBookPalette { required this.blueFill, required this.amber, required this.red, + required this.glowBorder, + required this.glowRing, + required this.cardShadow, }); final Color bg; + + /// Background of the list area behind the offer cards, one step lighter + /// than [bg] (v1's `dark1` well): the cards — which share [bg]'s tone — + /// read as darker panels floating on it. + final Color bgWell; final Color bgCard; final Color bgElevated; final Color border; @@ -195,15 +213,34 @@ class OrderBookPalette { final Color amber; final Color red; + /// Border of the one selected / action-required card per screen + /// (mock: `rgba(143,224,74,0.55)`). Pair with [glowRing]; every other + /// card keeps the plain [border] hairline — if all cards glow, none does. + final Color glowBorder; + + /// Soft green halo behind the selected card + /// (mock: `0 0 0 1px rgba(143,224,74,0.25), 0 0 22px rgba(143,224,74,0.18)`). + final List glowRing; + + /// Depth shadow every offer card carries. With [bgCard] equal to [bg] + /// (the v1 recipe) this shadow — not fill contrast — is what makes the + /// card read as a card: a strong drop below plus a subtle sheen along the + /// top edge (v1's `AppTheme.cardShadow`). + final List cardShadow; + static const dark = OrderBookPalette( - bg: Color(0xFF0F151C), - bgCard: Color(0xFF1A2029), - bgElevated: Color(0xFF222A35), - border: Color(0x0FFFFFFF), // rgba(255,255,255,0.06) + // v1's exact surface family (mobile `AppTheme`): chrome and card share + // `backgroundDark`, the list well is `dark1`, inner panels sit on + // `backgroundCard`. + bg: Color(0xFF171A23), + bgWell: Color(0xFF1D212C), + bgCard: Color(0xFF171A23), + bgElevated: Color(0xFF1E2230), + border: Color(0x0DFFFFFF), // rgba(255,255,255,0.05), v1's hairline textPrimary: Color(0xFFF2F4F7), textSecondary: Color(0xFFA8B0BC), - // Mock #6B7280 is 3.4:1 on the card — lightened to pass AA (4.8:1). - textTertiary: Color(0xFF848C9A), + // Mock #6B7280 is 3.5:1 on the card — lightened to pass AA (4.6:1). + textTertiary: Color(0xFF8B93A1), tabInactive: Color(0xFF4A5060), green: Color(0xFF8FE04A), greenDim: Color(0xFF2A4015), @@ -212,13 +249,40 @@ class OrderBookPalette { blue: Color(0xFF7BB4F0), blueFill: Color(0xFF1E2B42), amber: Color(0xFFE89C3C), - // Mock #E5484D is 3.7:1 on its 13% pill fill — lightened to pass AA. - red: Color(0xFFF27D81), + // Mock #E5484D is 3.8:1 on its 13% pill fill — lightened to pass AA. + red: Color(0xFFF48489), + glowBorder: Color(0x8C8FE04A), // rgba(143,224,74,0.55) + glowRing: [ + BoxShadow( + color: Color(0x408FE04A), // rgba(143,224,74,0.25) + spreadRadius: 1, + ), + BoxShadow( + color: Color(0x2E8FE04A), // rgba(143,224,74,0.18) + blurRadius: 22, + ), + ], + cardShadow: [ + BoxShadow( + color: Color(0xB3000000), // black 70% + blurRadius: 15, + offset: Offset(0, 5), + spreadRadius: -3, + ), + BoxShadow( + color: Color(0x12FFFFFF), // white 7% — top-edge sheen + blurRadius: 1, + offset: Offset(0, -1), + ), + ], ); static const light = OrderBookPalette( + // Same structural recipe as dark: white cards on a faintly darker list + // well, depth from [cardShadow]; inner panels one more step down. bg: Color(0xFFFFFFFF), - bgCard: Color(0xFFF5F5F5), + bgWell: Color(0xFFF4F4F6), + bgCard: Color(0xFFFFFFFF), bgElevated: Color(0xFFEEEEEE), border: Color(0x14000000), textPrimary: Color(0xFF1A1A1A), @@ -233,6 +297,20 @@ class OrderBookPalette { blueFill: Color(0x2635638F), amber: Color(0xFF845010), red: Color(0xFFAE3333), + // Same alphas as dark over the light theme's AA-safe green. + glowBorder: Color(0x8C426800), + glowRing: [ + BoxShadow(color: Color(0x40426800), spreadRadius: 1), + BoxShadow(color: Color(0x2E426800), blurRadius: 22), + ], + cardShadow: [ + BoxShadow( + color: Color(0x1F000000), // black 12% — soft daylight drop + blurRadius: 15, + offset: Offset(0, 5), + spreadRadius: -3, + ), + ], ); static OrderBookPalette of(BuildContext context) => @@ -325,16 +403,16 @@ const _light = AppColors( // ── ThemeData factories ──────────────────────────────────────────────────────── ThemeData buildDarkTheme() => _buildTheme( - brightness: Brightness.dark, - colors: _dark, - scaffold: const Color(0xFF1B1E28), - ); + brightness: Brightness.dark, + colors: _dark, + scaffold: const Color(0xFF1B1E28), +); ThemeData buildLightTheme() => _buildTheme( - brightness: Brightness.light, - colors: _light, - scaffold: const Color(0xFFFFFFFF), - ); + brightness: Brightness.light, + colors: _light, + scaffold: const Color(0xFFFFFFFF), +); ThemeData _buildTheme({ required Brightness brightness, @@ -467,6 +545,5 @@ class _NoTransitionBuilder extends PageTransitionsBuilder { Animation animation, Animation secondaryAnimation, Widget child, - ) => - child; + ) => child; } diff --git a/lib/features/home/screens/home_screen.dart b/lib/features/home/screens/home_screen.dart index ccfe53e..9022bf2 100644 --- a/lib/features/home/screens/home_screen.dart +++ b/lib/features/home/screens/home_screen.dart @@ -66,9 +66,10 @@ class _HomeScreenState extends ConsumerState final isDesktop = screenWidth >= AppBreakpoints.desktop; // ── Order list: responsive column count ────────────────────────────────── - final columns = screenWidth >= AppBreakpoints.desktop - ? 3 - : screenWidth >= AppBreakpoints.tablet + final columns = + screenWidth >= AppBreakpoints.desktop + ? 3 + : screenWidth >= AppBreakpoints.tablet ? 2 : 1; @@ -155,10 +156,7 @@ class _HomeScreenState extends ConsumerState fontWeight: FontWeight.w700, letterSpacing: 1, ), - tabs: [ - Tab(text: l10n.tabBuyBtc), - Tab(text: l10n.tabSellBtc), - ], + tabs: [Tab(text: l10n.tabBuyBtc), Tab(text: l10n.tabSellBtc)], ), ), @@ -171,7 +169,10 @@ class _HomeScreenState extends ConsumerState // 320px-wide screens without a RenderFlex overflow. Flexible( child: Material( - color: pal.bgCard, + // bgElevated, not bgCard: with the v1 recipe bgCard equals + // the page tone, which would make the pill invisible (v1's + // filter uses its lighter input tone for the same reason). + color: pal.bgElevated, shape: StadiumBorder(side: BorderSide(color: pal.border)), child: InkWell( customBorder: const StadiumBorder(), @@ -229,28 +230,38 @@ class _HomeScreenState extends ConsumerState ), // Order list — shimmer while loading, error state, or live data. + // The well is one step lighter than the chrome (v1's `dark1` + // container): the cards share the chrome's tone, so this inverted + // contrast is what makes them read as panels. Expanded( - child: ref.watch(orderBookProvider).when( - loading: () => const OrderListSkeleton(), - error: (e, _) => Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - l10n.errorLoadingOrders, - style: TextStyle(color: pal.textSecondary), - textAlign: TextAlign.center, - ), - const SizedBox(height: AppSpacing.md), - TextButton( - onPressed: () => ref.invalidate(orderBookProvider), - child: Text(l10n.retry), + child: ColoredBox( + color: pal.bgWell, + child: ref + .watch(orderBookProvider) + .when( + loading: () => const OrderListSkeleton(), + error: + (e, _) => Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + l10n.errorLoadingOrders, + style: TextStyle(color: pal.textSecondary), + textAlign: TextAlign.center, + ), + const SizedBox(height: AppSpacing.md), + TextButton( + onPressed: + () => ref.invalidate(orderBookProvider), + child: Text(l10n.retry), + ), + ], + ), ), - ], - ), + data: (_) => orderContent(onOrderTap), ), - data: (_) => orderContent(onOrderTap), - ), + ), ), ], ); @@ -258,23 +269,24 @@ class _HomeScreenState extends ConsumerState // ── Scaffold layout ─────────────────────────────────────────────────────── // Desktop: persistent sidebar + main content in a Row (no overlay drawer). // Mobile/tablet: Stack with optional overlay drawer. - final body = isDesktop - ? Row( - children: [ - const DrawerMenu(persistent: true), - const VerticalDivider(width: 1), - Expanded(child: mainContent), - ], - ) - : Stack( - children: [ - mainContent, - if (_drawerOpen) - DrawerMenu( - onClose: () => setState(() => _drawerOpen = false), - ), - ], - ); + final body = + isDesktop + ? Row( + children: [ + const DrawerMenu(persistent: true), + const VerticalDivider(width: 1), + Expanded(child: mainContent), + ], + ) + : Stack( + children: [ + mainContent, + if (_drawerOpen) + DrawerMenu( + onClose: () => setState(() => _drawerOpen = false), + ), + ], + ); // The scaffold background is overridden at the theme level so shared // chrome that reads scaffoldBackgroundColor (bottom nav) matches the @@ -294,10 +306,7 @@ class _HomeScreenState extends ConsumerState /// Custom app bar per the mock: hamburger left, notification bell right, /// empty center, 52px tall over a 1px hairline. class _MostroAppBar extends StatelessWidget { - const _MostroAppBar({ - required this.palette, - required this.onMenuTap, - }); + const _MostroAppBar({required this.palette, required this.onMenuTap}); final OrderBookPalette palette; diff --git a/lib/features/home/widgets/order_list_item.dart b/lib/features/home/widgets/order_list_item.dart index 2af25c2..e0d4116 100644 --- a/lib/features/home/widgets/order_list_item.dart +++ b/lib/features/home/widgets/order_list_item.dart @@ -13,6 +13,9 @@ import 'package:mostro/l10n/app_localizations.dart'; /// Layout, colors, and proportions mirror the mock's offer card: /// reason pill + timestamp, 26px amount row with premium pill, "Market price" /// caption, elevated numeric-reputation strip, and payment-method line. +/// Every card carries the palette's 9% hairline border; a [highlighted] card +/// swaps it for the green glow ring (at most one per screen — the selected / +/// action-required card). /// Each card may carry one [OrderReason] pill (computed once per visible list /// and passed in — never computed here). class OrderListItem extends StatelessWidget { @@ -22,6 +25,7 @@ class OrderListItem extends StatelessWidget { this.onTap, this.currencyFlags = const {}, this.reason, + this.highlighted = false, }); final OrderItem order; @@ -32,6 +36,12 @@ class OrderListItem extends StatelessWidget { /// visible list (see [orderReasonsProvider]) and passed in by the screen. final OrderReason? reason; + /// Marks this card as the screen's selected / action-required one: + /// green [OrderBookPalette.glowBorder] + [OrderBookPalette.glowRing] + /// instead of the plain hairline. Callers must set it on at most one + /// card per screen — if all cards glow, none stands out. + final bool highlighted; + @override Widget build(BuildContext context) { final pal = OrderBookPalette.of(context); @@ -40,9 +50,10 @@ class OrderListItem extends StatelessWidget { final flag = currencyFlags[order.fiatCode] ?? ''; // Premium pill: green < 2 (incl. negative), amber 2–5, red > 5. - final premiumColor = order.premium < 2 - ? pal.green - : order.premium > 5 + final premiumColor = + order.premium < 2 + ? pal.green + : order.premium > 5 ? pal.red : pal.amber; final premiumText = @@ -50,178 +61,203 @@ class OrderListItem extends StatelessWidget { final (reasonLabel, reasonColor, reasonBg) = switch (reason) { OrderReason.bestPremium => ( - l10n.reasonBestPremium, - pal.green, - pal.greenDim, - ), + l10n.reasonBestPremium, + pal.green, + pal.greenDim, + ), OrderReason.mostReputable => ( - l10n.reasonMostReputable, - pal.gold, - pal.goldDim, - ), + l10n.reasonMostReputable, + pal.gold, + pal.goldDim, + ), OrderReason.justPublished => ( - l10n.reasonJustPublished, - pal.blue, - pal.blueFill, - ), + l10n.reasonJustPublished, + pal.blue, + pal.blueFill, + ), null => (null, null, null), }; // The mock's cards carry no buy/sell pill (the tabs already scope the // side); the only functional signal kept is "yours" on own orders. - final mineLabel = order.isMine - ? (order.kind == 'sell' - ? l10n.orderPillYouAreSelling - : l10n.orderPillYouAreBuying) - : null; + final mineLabel = + order.isMine + ? (order.kind == 'sell' + ? l10n.orderPillYouAreSelling + : l10n.orderPillYouAreBuying) + : null; // Material + InkWell (not GestureDetector) so each offer card is - // focusable, keyboard-activatable, and announced as a button. - return Material( - color: pal.bgCard, - borderRadius: BorderRadius.circular(16), - child: InkWell( - onTap: onTap, - borderRadius: BorderRadius.circular(16), - child: Padding( - padding: const EdgeInsets.fromLTRB(14, 12, 14, 14), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Row 1: reason pill (+ "yours" pill) · relative timestamp - Row( + // focusable, keyboard-activatable, and announced as a button. The depth + // shadow (and the glow ring) live on an outer DecoratedBox because + // Material shapes clip shadows; the border rides the Material shape so + // InkWell clips to it. With bgCard == bg the shadow is what separates + // the card from the page. + return DecoratedBox( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + boxShadow: highlighted + ? [...pal.glowRing, ...pal.cardShadow] + : pal.cardShadow, + ), + child: Material( + color: pal.bgCard, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + side: BorderSide(color: highlighted ? pal.glowBorder : pal.border), + ), + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(20), + child: Padding( + padding: const EdgeInsets.fromLTRB(14, 12, 14, 14), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (reasonLabel != null) ...[ - Flexible( - child: _Pill( - label: reasonLabel, - color: reasonColor!, - background: reasonBg!, - ), - ), - const SizedBox(width: 6), - ], - if (mineLabel != null) ...[ - Flexible( - child: _Pill( - label: mineLabel, - color: pal.textSecondary, - background: pal.bgElevated, + // Row 1: reason pill (+ "yours" pill) · relative timestamp + Row( + children: [ + if (reasonLabel != null) ...[ + Flexible( + child: _Pill( + label: reasonLabel, + color: reasonColor!, + background: reasonBg!, + ), + ), + const SizedBox(width: 6), + ], + if (mineLabel != null) ...[ + Flexible( + child: _Pill( + label: mineLabel, + color: pal.textSecondary, + background: pal.bgElevated, + ), + ), + const SizedBox(width: 6), + ], + const Spacer(), + Text( + _relativeTime(order.createdAt, l10n), + style: TextStyle(fontSize: 11, color: pal.textTertiary), ), - ), - const SizedBox(width: 6), - ], - const Spacer(), - Text( - _relativeTime(order.createdAt, l10n), - style: TextStyle(fontSize: 11, color: pal.textTertiary), + ], ), - ], - ), - const SizedBox(height: 10), + const SizedBox(height: 10), - // Row 2: amount + currency + flag · premium pill - Row( - crossAxisAlignment: CrossAxisAlignment.baseline, - textBaseline: TextBaseline.alphabetic, - children: [ - Flexible( - child: Text( - order.displayAmount, - style: TextStyle( - fontSize: 26, - fontWeight: FontWeight.w700, - color: pal.textPrimary, - height: 1.2, + // Row 2: amount + currency + flag · premium pill + Row( + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + children: [ + Flexible( + child: Text( + order.displayAmount, + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.w700, + color: pal.textPrimary, + height: 1.2, + ), + overflow: TextOverflow.ellipsis, + ), ), - overflow: TextOverflow.ellipsis, - ), + const SizedBox(width: 8), + Text( + order.fiatCode, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: pal.textSecondary, + ), + ), + if (flag.isNotEmpty) ...[ + const SizedBox(width: 8), + Text(flag, style: const TextStyle(fontSize: 18)), + ], + const Spacer(), + _Pill( + label: premiumText, + color: premiumColor, + background: premiumColor.withValues(alpha: 0.13), + ), + ], ), - const SizedBox(width: 8), + const SizedBox(height: 4), + + // Row 3: "Market price" caption Text( - order.fiatCode, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: pal.textSecondary, - ), - ), - if (flag.isNotEmpty) ...[ - const SizedBox(width: 8), - Text(flag, style: const TextStyle(fontSize: 18)), - ], - const Spacer(), - _Pill( - label: premiumText, - color: premiumColor, - background: premiumColor.withValues(alpha: 0.13), + l10n.marketPriceCaption, + style: TextStyle(fontSize: 11, color: pal.textTertiary), ), - ], - ), - const SizedBox(height: 4), - - // Row 3: "Market price" caption - Text( - l10n.marketPriceCaption, - style: TextStyle(fontSize: 11, color: pal.textTertiary), - ), - const SizedBox(height: 10), + const SizedBox(height: 10), - // Row 4: numeric reputation — ★ 4.9 · 47 trades · 312 days - Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), - decoration: BoxDecoration( - color: pal.bgElevated, - borderRadius: BorderRadius.circular(10), - ), - child: Row( - children: [ - Icon(Icons.star, size: 16, color: pal.gold), - const SizedBox(width: 4), - Text( - _formatRating(order.rating, locale), - style: TextStyle( - color: pal.textPrimary, - fontSize: 13, - fontWeight: FontWeight.w700, - ), + // Row 4: numeric reputation — ★ 4.9 · 47 trades · 312 days + Container( + width: double.infinity, + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 10, ), - const SizedBox(width: 14), - Text('·', - style: TextStyle(fontSize: 12, color: pal.textTertiary)), - const SizedBox(width: 14), - _StatText( - value: - NumberFormat.decimalPattern(locale).format(order.tradeCount), - label: l10n.reputationTradesLabel(order.tradeCount), - palette: pal, + decoration: BoxDecoration( + color: pal.bgElevated, + borderRadius: BorderRadius.circular(12), ), - const SizedBox(width: 14), - Text('·', - style: TextStyle(fontSize: 12, color: pal.textTertiary)), - const SizedBox(width: 14), - Flexible( - child: _StatText( - value: NumberFormat.decimalPattern(locale) - .format(order.daysActive), - label: l10n.reputationDaysLabel(order.daysActive), - palette: pal, - ), + child: Row( + children: [ + Icon(Icons.star, size: 16, color: pal.gold), + const SizedBox(width: 4), + Text( + _formatRating(order.rating, locale), + style: TextStyle( + color: pal.textPrimary, + fontSize: 13, + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(width: 14), + Text( + '·', + style: TextStyle(fontSize: 12, color: pal.textTertiary), + ), + const SizedBox(width: 14), + _StatText( + value: NumberFormat.decimalPattern( + locale, + ).format(order.tradeCount), + label: l10n.reputationTradesLabel(order.tradeCount), + palette: pal, + ), + const SizedBox(width: 14), + Text( + '·', + style: TextStyle(fontSize: 12, color: pal.textTertiary), + ), + const SizedBox(width: 14), + Flexible( + child: _StatText( + value: NumberFormat.decimalPattern( + locale, + ).format(order.daysActive), + label: l10n.reputationDaysLabel(order.daysActive), + palette: pal, + ), + ), + ], ), - ], - ), - ), - const SizedBox(height: 8), + ), + const SizedBox(height: 8), - // Row 5: payment methods - Text( - order.paymentMethod, - style: TextStyle(fontSize: 12, color: pal.textSecondary), - overflow: TextOverflow.ellipsis, + // Row 5: payment methods + Text( + order.paymentMethod, + style: TextStyle(fontSize: 12, color: pal.textSecondary), + overflow: TextOverflow.ellipsis, + ), + ], ), - ], - ), + ), ), ), ); @@ -321,7 +357,9 @@ class OrderListItemSkeleton extends StatelessWidget { padding: const EdgeInsets.fromLTRB(14, 12, 14, 14), decoration: BoxDecoration( color: pal.bgCard, - borderRadius: BorderRadius.circular(16), + border: Border.all(color: pal.border), + borderRadius: BorderRadius.circular(20), + boxShadow: pal.cardShadow, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/shared/widgets/order_list_skeleton.dart b/lib/shared/widgets/order_list_skeleton.dart index 52a8a9b..a7bf954 100644 --- a/lib/shared/widgets/order_list_skeleton.dart +++ b/lib/shared/widgets/order_list_skeleton.dart @@ -17,6 +17,8 @@ class OrderListSkeleton extends StatelessWidget { return Semantics( label: AppLocalizations.of(context).loadingOrders, child: Shimmer.fromColors( + // Card tone over the lighter list well, pulsing toward the inner + // panel tone — matches how a loaded card sits on [bgWell]. baseColor: pal.bgCard, highlightColor: pal.bgElevated, child: ListView.builder( @@ -42,7 +44,7 @@ class _SkeletonCard extends StatelessWidget { margin: const EdgeInsets.only(bottom: 12), decoration: BoxDecoration( color: Colors.white, - borderRadius: BorderRadius.circular(16), + borderRadius: BorderRadius.circular(20), ), ); } diff --git a/test/core/order_book_palette_contrast_test.dart b/test/core/order_book_palette_contrast_test.dart index aa81282..8acb4b7 100644 --- a/test/core/order_book_palette_contrast_test.dart +++ b/test/core/order_book_palette_contrast_test.dart @@ -65,9 +65,12 @@ void main() { _expectAA( '$mode textSecondary/bgElevated', pal.textSecondary, pal.bgElevated); _expectAA('$mode textSecondary/bg', pal.textSecondary, pal.bg); + // Empty/error state copy renders on the list well behind the cards. + _expectAA('$mode textSecondary/bgWell', pal.textSecondary, pal.bgWell); // Timestamps, "Market price", the sort caption, and separators. _expectAA('$mode textTertiary/bgCard', pal.textTertiary, pal.bgCard); _expectAA('$mode textTertiary/bg', pal.textTertiary, pal.bg); + _expectAA('$mode textTertiary/bgWell', pal.textTertiary, pal.bgWell); }); test('reason pills on their fills', () { diff --git a/test/features/home/goldens/order_list_item_dark.png b/test/features/home/goldens/order_list_item_dark.png index f791845..b29d9df 100644 Binary files a/test/features/home/goldens/order_list_item_dark.png and b/test/features/home/goldens/order_list_item_dark.png differ diff --git a/test/features/home/goldens/order_list_item_light.png b/test/features/home/goldens/order_list_item_light.png index 4e0a5c3..6f7a828 100644 Binary files a/test/features/home/goldens/order_list_item_light.png and b/test/features/home/goldens/order_list_item_light.png differ diff --git a/test/features/home/order_list_item_golden_test.dart b/test/features/home/order_list_item_golden_test.dart index f792c80..c5aaf1d 100644 --- a/test/features/home/order_list_item_golden_test.dart +++ b/test/features/home/order_list_item_golden_test.dart @@ -8,6 +8,8 @@ import '../../support/fake_orders.dart'; import '../../support/golden_harness.dart'; /// Fixed-amount and range order cards stacked, keyed for a tight golden. +/// The first card is [OrderListItem.highlighted] so one golden pair covers +/// both the glow ring and the plain hairline. Widget _gallery() { return Padding( key: const ValueKey('order-gallery'), @@ -27,6 +29,7 @@ Widget _gallery() { minutesAgo: 12, ), reason: OrderReason.bestPremium, + highlighted: true, ), const SizedBox(height: 8), OrderListItem(