petitspas/frontend/lib/widgets/custom_app_text_field.dart
Julien Martin bf05b1d7d7 feat(inscription): email accusé réception, photos enfants, formulaires identité
- Backend: mail après inscription parent avec n° dossier, UPLOAD_PHOTOS_DIR, réponse API
- Frontend: imageBytes + payload photo, utils email/code postal/téléphone, champs dédiés
- Formulaires admin, login (focus), personal_info, child_card, custom_app_text_field

Made-with: Cursor
2026-03-31 00:04:38 +02:00

193 lines
6.6 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
// Définition de l'enum pour les styles de couleur/fond
enum CustomAppTextFieldStyle {
beige,
lavande,
jaune,
}
class CustomAppTextField extends StatefulWidget {
final TextEditingController controller;
final FocusNode? focusNode;
final String labelText;
final String hintText;
final double fieldWidth;
final double fieldHeight;
final bool obscureText;
final TextInputType keyboardType;
final String? Function(String?)? validator;
final CustomAppTextFieldStyle style;
final bool isRequired;
final bool enabled;
final bool readOnly;
final VoidCallback? onTap;
final IconData? suffixIcon;
final double labelFontSize;
final double inputFontSize;
final bool showLabel;
final Iterable<String>? autofillHints;
final TextInputAction? textInputAction;
final ValueChanged<String>? onFieldSubmitted;
final List<TextInputFormatter>? inputFormatters;
final bool autocorrect;
final bool enableSuggestions;
final GlobalKey<FormFieldState<String>>? formFieldKey;
const CustomAppTextField({
super.key,
required this.controller,
this.focusNode,
required this.labelText,
this.showLabel = true,
this.hintText = '',
this.fieldWidth = 300.0,
this.fieldHeight = 53.0,
this.obscureText = false,
this.keyboardType = TextInputType.text,
this.validator,
this.style = CustomAppTextFieldStyle.beige,
this.isRequired = false,
this.enabled = true,
this.readOnly = false,
this.onTap,
this.suffixIcon,
this.labelFontSize = 18.0,
this.inputFontSize = 18.0,
this.autofillHints,
this.textInputAction,
this.onFieldSubmitted,
this.inputFormatters,
this.autocorrect = true,
this.enableSuggestions = true,
this.formFieldKey,
});
@override
State<CustomAppTextField> createState() => _CustomAppTextFieldState();
}
class _CustomAppTextFieldState extends State<CustomAppTextField> {
String getBackgroundImagePath() {
switch (widget.style) {
case CustomAppTextFieldStyle.lavande:
return 'assets/images/bg_lavender.png';
case CustomAppTextFieldStyle.jaune:
return 'assets/images/bg_yellow.png';
case CustomAppTextFieldStyle.beige:
default:
return 'assets/images/bg_beige.png';
}
}
@override
Widget build(BuildContext context) {
final double dynamicFieldHeight = widget.fieldHeight;
// Indication « non éditable » : libellé + hint en gris.
final Color labelColor =
widget.enabled ? Colors.black87 : Colors.grey;
final Color hintColor = widget.enabled
? Colors.black54.withValues(alpha: 0.7)
: Colors.grey;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
if (widget.showLabel) ...[
Text(
widget.labelText,
style: GoogleFonts.merienda(
fontSize: widget.labelFontSize,
color: labelColor,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 6),
],
// Pas de hauteur fixe sur le TextFormField : le message derreur du
// validateur saffiche en dessous ; un SizedBox fixe le masquait.
SizedBox(
width: widget.fieldWidth,
child: Stack(
clipBehavior: Clip.none,
alignment: Alignment.centerLeft,
children: [
Positioned.fill(
child: Image.asset(
getBackgroundImagePath(),
fit: BoxFit.fill,
),
),
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 18.0, vertical: 8.0),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight:
(dynamicFieldHeight - 16.0).clamp(24.0, double.infinity),
),
child: TextFormField(
key: widget.formFieldKey,
controller: widget.controller,
focusNode: widget.focusNode,
obscureText: widget.obscureText,
keyboardType: widget.keyboardType,
autocorrect: widget.autocorrect,
enableSuggestions: widget.enableSuggestions,
inputFormatters: widget.inputFormatters,
autofillHints: widget.autofillHints,
textInputAction: widget.textInputAction,
onFieldSubmitted: widget.onFieldSubmitted,
enabled: widget.enabled,
readOnly: widget.readOnly,
onTap: widget.onTap,
style: GoogleFonts.merienda(
fontSize: widget.inputFontSize,
color: Colors.black87),
validator: widget.validator ??
(value) {
if (!widget.enabled || widget.readOnly) return null;
if (widget.isRequired &&
(value == null || value.isEmpty)) {
return 'Ce champ est obligatoire';
}
return null;
},
decoration: InputDecoration(
hintText: widget.hintText,
hintStyle: GoogleFonts.merienda(
fontSize: widget.inputFontSize,
color: hintColor),
border: InputBorder.none,
contentPadding: EdgeInsets.zero,
isDense: true,
errorStyle: GoogleFonts.merienda(
fontSize: widget.inputFontSize * 0.75,
color: Colors.red.shade800,
height: 1.2,
),
errorMaxLines: 3,
suffixIcon: widget.suffixIcon != null
? Padding(
padding: const EdgeInsets.only(right: 0.0),
child: Icon(widget.suffixIcon,
color: Colors.black54,
size: widget.inputFontSize * 1.1),
)
: null,
),
textAlignVertical: TextAlignVertical.center,
),
),
),
],
),
),
],
);
}
}