Files
GroundedHelper/frontend/lib/Grounded/designs/Component.dart
alvocool d3431ca5de Switch to Inter, round the corners softly and make the nav switch tabs
Inter replaces General Sans (Inter Display for titles and big numbers).
Corners come back from before the squaring commit, scaled to a soft set
(16 cards, 14 buttons, 12 rows, 8 chips) and applied to the screens built
since. The nav is now a cluster of round frosted buttons that switches
Today, Routines, Goals, Groups and Progress in place inside Home: no
pushed pages, no back arrows, and Android back returns to Today.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KhQh614DMeYoQEH9SoMRUw
2026-11-09 07:08:46 +03:00

203 lines
5.3 KiB
Dart

import 'package:flutter/material.dart';
import '../about/internal/application/TextType.dart';
import '../utils/Colors.dart';
import 'text/Text.dart';
/// The font families, wrapped so no screen ever names a family directly.
/// Inter (SIL OFL) — see fonts/LICENSE-Inter.txt.
String getTextType(TextType type) {
switch (type) {
case TextType.Bold:
return "GroundedBold";
case TextType.Light:
return "GroundedLight";
case TextType.Regular:
return "GroundedRegular";
case TextType.Medium:
return "GroundedMedium";
}
}
/// A labelled pill — the standing chip, the class chip, the proof chip. One
/// implementation so they stay visually identical everywhere.
Widget pill(
String label,
Color foreground,
Color background, {
IconData? icon,
double textSize = 10,
}) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: background,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: foreground.withValues(alpha: 0.20), width: 1),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (icon != null) ...[
Icon(icon, size: textSize + 2, color: foreground),
const SizedBox(width: 5),
],
text(
label.toUpperCase(),
textSize,
TextType.Bold,
color: foreground,
letterSpacing: 0.7,
),
],
),
);
}
/// The standard card surface.
Widget card({
required Widget child,
EdgeInsets padding = const EdgeInsets.all(20),
EdgeInsets margin = EdgeInsets.zero,
Color? background,
Color? borderColor,
double radius = 16,
VoidCallback? onTap,
}) {
final Widget body = Container(
width: double.infinity,
padding: padding,
margin: margin,
decoration: BoxDecoration(
color: background ?? colorCard,
borderRadius: BorderRadius.circular(radius),
border: Border.all(color: borderColor ?? colorGlassBorder, width: 1),
boxShadow: const [
BoxShadow(
color: Color(0x0F2A3A30),
blurRadius: 24,
offset: Offset(0, 8),
),
],
),
child: child,
);
if (onTap == null) {
return body;
}
return GestureDetector(onTap: onTap, child: body);
}
/// Section heading — small all-caps label over a large light title, the
/// house style used on every screen header.
Widget sectionHeader(String label, String title, {Color? titleColor}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
text(label.toUpperCase(), 11, TextType.Bold,
color: colorGrey2, letterSpacing: 1.2),
const SizedBox(height: 8),
text(title, 32, TextType.Light, color: titleColor ?? colorPrimaryDark),
],
);
}
/// A labelled statistic, used across the report card and the debt header.
Widget statTile(
String label,
String value, {
Color? valueColor,
String? caption,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
text(label.toUpperCase(), 10, TextType.Bold,
color: colorGrey2, letterSpacing: 0.8),
const SizedBox(height: 6),
text(value, 26, TextType.Bold, color: valueColor ?? colorPrimaryDark),
if (caption != null) ...[
const SizedBox(height: 2),
text(caption, 11, TextType.Regular, color: colorGrey2),
],
],
);
}
/// A hairline divider at the house opacity.
Widget hairline({EdgeInsets margin = EdgeInsets.zero}) {
return Container(
height: 1,
margin: margin,
color: colorDivider,
);
}
/// Empty state. Deliberately plain — an empty queue is good news and should
/// not be celebrated with confetti.
Widget emptyState(
IconData icon,
String title,
String description, {
Color? accent,
}) {
final Color tone = accent ?? colorGrey2;
return Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 48),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: 76,
height: 76,
decoration: BoxDecoration(
color: tone.withValues(alpha: 0.08),
shape: BoxShape.circle,
),
child: Icon(icon, size: 32, color: tone),
),
const SizedBox(height: 20),
text(title, 18, TextType.Bold,
color: colorPrimaryDark, align: TextAlign.center),
const SizedBox(height: 8),
text(description, 13, TextType.Regular,
color: colorGrey2, align: TextAlign.center, height: 1.5),
],
),
),
);
}
/// A progress bar with the house geometry.
Widget meter(
double fraction, {
Color? fill,
Color? track,
double height = 8,
}) {
final double clamped = fraction.isNaN
? 0
: fraction < 0
? 0
: fraction > 1
? 1
: fraction;
return ClipRRect(
borderRadius: BorderRadius.circular(14),
child: LinearProgressIndicator(
value: clamped,
minHeight: height,
backgroundColor: track ?? colorMuted,
valueColor: AlwaysStoppedAnimation<Color>(fill ?? colorPrimary),
),
);
}