The Garage SDKs, in the open
garage_auth, garage_entitlements, garage_iap and garage_ui, moved out of Garage-Services and Metro-Map-Maker into one public repo. MIT, one readme, docs under docs/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013F4NWNvYcdeSgqbWMT1VQ7
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user