import "package:flutter/widgets.dart"; // our own theme now - we only need it for the icon size theme (iconTheme.small etc). import "package:garage_ui/theme/garage_theme.dart"; // Hand rolled replacements for the shadcn Widget/Icon extension methods. // These get used 350+ times across the app so behaviour has to match the // original exactly — same SizedBox/Padding/Center/Expanded wrappers, same // icon sizes pulled from the theme. /// Layout helpers that shadcn hangs off every Widget. extension WidgetExtension on Widget { /// Wraps this widget in a [SizedBox]. If it's already a SizedBox we merge /// the dims instead of double wrapping (matches shadcn). Widget sized({double? width, double? height, double? size}) { if (this is SizedBox) { return SizedBox( width: width ?? size ?? (this as SizedBox).width, height: height ?? size ?? (this as SizedBox).height, child: (this as SizedBox).child, ); } return SizedBox(width: width ?? size, height: height ?? size, child: this); } /// Wraps this widget in [Padding]. You can pass individual edges, the /// combined horizontal/vertical, a uniform `all`, or a raw EdgeInsets via /// `padding` (which wins over everything else). Widget withPadding({ double? top, double? bottom, double? left, double? right, double? horizontal, double? vertical, double? all, EdgeInsetsGeometry? padding, }) { assert(() { if (all != null) { if (top != null || bottom != null || left != null || right != null || horizontal != null || vertical != null) { throw FlutterError( "All padding properties cannot be used with other padding properties.", ); } } else if (horizontal != null) { if (left != null || right != null) { throw FlutterError( "Horizontal padding cannot be used with left or right padding.", ); } } else if (vertical != null) { if (top != null || bottom != null) { throw FlutterError( "Vertical padding cannot be used with top or bottom padding.", ); } } return true; }()); var edgeInsets = EdgeInsets.only( top: top ?? vertical ?? all ?? 0, bottom: bottom ?? vertical ?? all ?? 0, left: left ?? horizontal ?? all ?? 0, right: right ?? horizontal ?? all ?? 0, ); return Padding(padding: padding ?? edgeInsets, child: this); } /// Centers the widget in its parent. Widget center({Key? key}) { return Center(key: key, child: this); } /// Makes this widget [Expanded] inside a Row/Column. Widget expanded({int flex = 1}) { return Expanded(flex: flex, child: this); } } /// Icon size helpers. shadcn returns a WrappedIcon that reads the size off the /// theme (theme.iconTheme.small etc) so it respects AdaptiveScaling — we do the /// exact same thing rather than hardcoding, otherwise scaled builds drift. extension IconExtension on Widget { /// small icon — 16px at scale 1. Widget get iconSmall { return _GarageWrappedIcon(pick: (t) => t.iconTheme.small, child: this); } /// medium icon — 20px at scale 1. Widget get iconMedium { return _GarageWrappedIcon(pick: (t) => t.iconTheme.medium, child: this); } /// large icon — 24px at scale 1. Widget get iconLarge { return _GarageWrappedIcon(pick: (t) => t.iconTheme.large, child: this); } } typedef _IconThemePicker = IconThemeData Function(ThemeData theme); // mirrors shadcn's WrappedIcon — but merges the SIZE ONLY. // // The tier's IconThemeData carries the global scheme foreground alongside its // size, and merging that whole thing overrode whatever IconTheme already // enclosed the icon. Inside a Button that meant the button's own variant // colour lost to the global one: a primary button hands its leading icon // primaryForeground, and `.iconSmall` put scheme.foreground back over the top. // On a dark scheme both of those are near-white while the primary FILL is too, // so the icon painted white on white and simply vanished. // // Size is the only thing these helpers are for. Colour is the enclosing // theme's business - a button's, or the app's for an icon standing on its own, // which is the same value the tier was supplying anyway. class _GarageWrappedIcon extends StatelessWidget { final _IconThemePicker pick; final Widget child; const _GarageWrappedIcon({required this.pick, required this.child}); @override Widget build(BuildContext context) { double? size; try { size = pick(GarageTheme.of(context)).size; } catch (e, st) { // shouldnt happen (there's always a Theme above us) but if it does we // still want a visible icon rather than a crash. log it so it's debuggable. debugPrint("ana iconTheme lookup failed: $e"); debugPrintStack(stackTrace: st); size = 16; } return IconTheme.merge( data: IconThemeData(size: size), child: child, ); } }