Files
sure/mobile/lib/widgets/sure_card.dart
ghost 56000d7834 feat(mobile): add SureCard primitive and migrate account cards (#2370)
* feat(mobile): add SureCard primitive and migrate account cards

SureCard mirrors the web card chrome — `bg-container` + a hairline border +
rounded corners + the subtle DS shadow (`shadowXs`, from the scale shipped in
#2349) — resolved from the active SureColors palette so it stays in lockstep with
`sure.tokens.json` in light and dark. An optional `onTap` gives a flat ink
response clipped to the card radius.

Migrates AccountCard off the Material `Card` to `SureCard`.

Refs #2235.

* fix(coinstats): deterministic wallet batch order in bulk fetches

The bulk balance/transaction fetches built the "blockchain:address" param in
linked-account query order, which isn't stable across runs — so the batched
param (and its mocked expectations) varied seed to seed, intermittently failing
CoinstatsItem::ImporterTest and red-flagging unrelated PRs. Sort the wallets
before joining so the batch order is deterministic, and align the test
expectations to the sorted order.

* fix(mobile): tokenize swipe-reveal radius + cover SureCard dark theme

Address PR review:
- The account-card swipe-reveal background still used a hardcoded radius (12)
  that mismatched SureCard's radiusLg (10) once the card was migrated — tokenize
  it so the reveal corners line up during the swipe.
- Parameterize the SureCard chrome test over light + dark, since the card is
  brightness-aware (catches palette regressions in either mode).

* test(mobile): assert SureCard onTap ink is clipped to the card radius

Address review nit: the onTap test claimed the ink is clipped to the card but
only checked the InkWell exists. Now it asserts the InkWell's borderRadius equals
SureTokens.radiusLg (tester.widget already enforces a single InkWell).
2026-06-20 21:43:20 +02:00

67 lines
2.1 KiB
Dart

import 'package:flutter/material.dart';
import '../theme/sure_colors.dart';
import '../theme/sure_tokens.dart';
/// Sure design-system card — a tokenized content surface mirroring the web card
/// chrome (`bg-container` + a hairline border + rounded corners + the subtle DS
/// shadow). Use it instead of a Material [Card] so the chrome stays in lockstep
/// with `sure.tokens.json` and reads correctly in light and dark.
///
/// Colors resolve from the active [SureColors] palette (brightness-aware). When
/// [onTap] is provided the whole card is tappable, with a flat ink response
/// clipped to the card's radius.
class SureCard extends StatelessWidget {
const SureCard({
super.key,
required this.child,
this.padding = const EdgeInsets.all(16),
this.margin,
this.onTap,
this.elevated = true,
});
final Widget child;
/// Inner padding around [child].
final EdgeInsetsGeometry padding;
/// Outer spacing around the card (e.g. separation in a list).
final EdgeInsetsGeometry? margin;
/// When non-null, the card is tappable.
final VoidCallback? onTap;
/// Apply the subtle DS card shadow. Set false for cards sitting on an inset
/// surface, where the border alone provides enough separation.
final bool elevated;
@override
Widget build(BuildContext context) {
final palette = SureColors.of(context).palette;
final radius = BorderRadius.circular(SureTokens.radiusLg);
Widget content = Padding(padding: padding, child: child);
if (onTap != null) {
// Material(transparency) gives InkWell a surface to paint on without
// covering the card's tokenized background or border.
content = Material(
type: MaterialType.transparency,
child: InkWell(onTap: onTap, borderRadius: radius, child: content),
);
}
return Container(
margin: margin,
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
color: palette.container,
borderRadius: radius,
border: Border.all(color: palette.borderSecondary),
boxShadow: elevated ? palette.shadowXs : null,
),
child: content,
);
}
}