Files
GroundedHelper/frontend/lib/Grounded/designs/input/InputFields.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

239 lines
7.5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../about/internal/application/TextType.dart';
import '../../utils/Colors.dart';
import '../Component.dart';
import '../text/Text.dart';
/// The standard text field.
Widget inputField(
String label,
TextEditingController controller, {
String hint = "",
String? Function(String?)? validator,
TextInputType keyboard = TextInputType.text,
bool obscure = false,
int maxLines = 1,
List<TextInputFormatter>? formatters,
IconData? icon,
ValueChanged<String>? onChanged,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
text(label.toUpperCase(), 10, TextType.Bold,
color: colorGrey2, letterSpacing: 0.8),
const SizedBox(height: 8),
TextFormField(
controller: controller,
validator: validator,
keyboardType: keyboard,
obscureText: obscure,
maxLines: obscure ? 1 : maxLines,
inputFormatters: formatters,
onChanged: onChanged,
style: TextStyle(
fontFamily: getTextType(TextType.Regular),
fontSize: 14,
color: colorPrimaryDark,
),
decoration: InputDecoration(
hintText: hint,
hintStyle: TextStyle(
fontFamily: getTextType(TextType.Regular),
fontSize: 13,
color: colorGrey,
),
prefixIcon: icon == null
? null
: Icon(icon, size: 18, color: colorGrey2),
filled: true,
fillColor: colorWhite,
contentPadding:
const EdgeInsets.symmetric(horizontal: 14, vertical: 14),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: colorBorder, width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: colorPrimaryDark, width: 1.4),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: colorNegative, width: 1),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: colorNegative, width: 1.4),
),
errorStyle: TextStyle(
fontFamily: getTextType(TextType.Regular),
fontSize: 11,
color: colorNegative,
),
),
),
],
);
}
/// The bare add-a-line field used inside a card: no label, no box of its
/// own; return or the trailing button submits.
Widget quickAddField(
TextEditingController controller,
String hint,
ValueChanged<String> onSubmitted, {
FocusNode? focusNode,
}) {
return TextField(
controller: controller,
focusNode: focusNode,
onSubmitted: onSubmitted,
textInputAction: TextInputAction.done,
style: TextStyle(
fontFamily: getTextType(TextType.Regular),
fontSize: 16,
color: colorInk,
),
decoration: InputDecoration(
hintText: hint,
hintStyle: TextStyle(
fontFamily: getTextType(TextType.Regular),
fontSize: 16,
color: colorInkSoft,
),
isDense: true,
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(vertical: 14),
),
);
}
/// The excuse field. Deliberately unadorned — no templates, no quick-picks, a
/// live character count that shows the minimum, because the friction is the
/// feature rather than an obstacle to route around.
Widget excuseField(
TextEditingController controller,
int minimumLength, {
String? Function(String?)? validator,
ValueChanged<String>? onChanged,
}) {
final int length = controller.text.trim().length;
final bool satisfied = length >= minimumLength;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
text("WHY", 10, TextType.Bold, color: colorGrey2, letterSpacing: 0.8),
text(
satisfied ? "$length characters" : "$length / $minimumLength",
10,
TextType.Bold,
color: satisfied ? colorPositive : colorGrey,
),
],
),
const SizedBox(height: 8),
TextFormField(
controller: controller,
validator: validator,
onChanged: onChanged,
maxLines: 4,
style: TextStyle(
fontFamily: getTextType(TextType.Regular),
fontSize: 14,
color: colorPrimaryDark,
height: 1.5,
),
decoration: InputDecoration(
hintText: "In your own words. No shortcuts here.",
hintStyle: TextStyle(
fontFamily: getTextType(TextType.Regular),
fontSize: 13,
color: colorGrey,
),
filled: true,
fillColor: colorWhite,
contentPadding: const EdgeInsets.all(14),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: colorBorder, width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: colorPrimaryDark, width: 1.4),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: colorNegative, width: 1),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: colorNegative, width: 1.4),
),
errorStyle: TextStyle(
fontFamily: getTextType(TextType.Regular),
fontSize: 11,
color: colorNegative,
),
),
),
],
);
}
/// A read-only field that loads its options on tap rather than pre-loading
/// them — the ViewModel fetches, then calls back to open the picker.
Widget selectField(
String label,
String value,
VoidCallback onTap, {
String hint = "Select",
IconData icon = Icons.expand_more_rounded,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
text(label.toUpperCase(), 10, TextType.Bold,
color: colorGrey2, letterSpacing: 0.8),
const SizedBox(height: 8),
GestureDetector(
onTap: onTap,
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 16),
decoration: BoxDecoration(
color: colorWhite,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: colorBorder, width: 1),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: text(
value.isEmpty ? hint : value,
14,
value.isEmpty ? TextType.Regular : TextType.Bold,
color: value.isEmpty ? colorGrey : colorPrimaryDark,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
Icon(icon, size: 20, color: colorGrey2),
],
),
),
),
],
);
}