feat: ajout du parcours complet d’inscription parent avec UI harmonisée et gestion centralisée des données
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
class AppCustomCheckbox extends StatelessWidget {
|
||||
final String label;
|
||||
final bool value;
|
||||
final ValueChanged<bool> onChanged;
|
||||
final double checkboxSize;
|
||||
final double checkmarkSizeFactor;
|
||||
|
||||
const AppCustomCheckbox({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
this.checkboxSize = 20.0,
|
||||
this.checkmarkSizeFactor = 1.4,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => onChanged(!value), // Inverse la valeur au clic
|
||||
behavior: HitTestBehavior.opaque, // Pour s'assurer que toute la zone du Row est cliquable
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: checkboxSize,
|
||||
height: checkboxSize,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/images/square.png',
|
||||
height: checkboxSize,
|
||||
width: checkboxSize,
|
||||
),
|
||||
if (value)
|
||||
Image.asset(
|
||||
'assets/images/coche.png',
|
||||
height: checkboxSize * checkmarkSizeFactor,
|
||||
width: checkboxSize * checkmarkSizeFactor,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
// Utiliser Flexible pour que le texte ne cause pas d'overflow si trop long
|
||||
Flexible(
|
||||
child: Text(
|
||||
label,
|
||||
style: GoogleFonts.merienda(fontSize: 16),
|
||||
overflow: TextOverflow.ellipsis, // Gérer le texte long
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
import 'package:flutter/material.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 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;
|
||||
|
||||
const CustomAppTextField({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.labelText,
|
||||
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,
|
||||
});
|
||||
|
||||
@override
|
||||
State<CustomAppTextField> createState() => _CustomAppTextFieldState();
|
||||
}
|
||||
|
||||
class _CustomAppTextFieldState extends State<CustomAppTextField> {
|
||||
String getBackgroundImagePath() {
|
||||
switch (widget.style) {
|
||||
case CustomAppTextFieldStyle.lavande:
|
||||
return 'assets/images/input_field_lavande.png';
|
||||
case CustomAppTextFieldStyle.jaune:
|
||||
return 'assets/images/input_field_jaune.png';
|
||||
case CustomAppTextFieldStyle.beige:
|
||||
default:
|
||||
return 'assets/images/input_field_bg.png';
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const double fontHeightMultiplier = 1.2;
|
||||
const double internalVerticalPadding = 16.0;
|
||||
final double dynamicFieldHeight = widget.fieldHeight;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
widget.labelText,
|
||||
style: GoogleFonts.merienda(
|
||||
fontSize: widget.labelFontSize,
|
||||
color: Colors.black87,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
SizedBox(
|
||||
width: widget.fieldWidth,
|
||||
height: dynamicFieldHeight,
|
||||
child: Stack(
|
||||
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: TextFormField(
|
||||
controller: widget.controller,
|
||||
obscureText: widget.obscureText,
|
||||
keyboardType: widget.keyboardType,
|
||||
enabled: widget.enabled,
|
||||
readOnly: widget.readOnly,
|
||||
onTap: widget.onTap,
|
||||
style: GoogleFonts.merienda(
|
||||
fontSize: widget.inputFontSize,
|
||||
color: widget.enabled ? Colors.black87 : Colors.grey
|
||||
),
|
||||
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: Colors.black54.withOpacity(0.7)),
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
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,
|
||||
isDense: true,
|
||||
),
|
||||
textAlignVertical: TextAlignVertical.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
class CustomDecoratedTextField extends StatelessWidget {
|
||||
final TextEditingController controller;
|
||||
final String hintText;
|
||||
final int maxLines;
|
||||
final double? fieldHeight; // Hauteur optionnelle pour le champ
|
||||
final bool expandDynamically; // Nouvelle propriété
|
||||
final bool readOnly;
|
||||
final double fontSize;
|
||||
|
||||
const CustomDecoratedTextField({
|
||||
super.key,
|
||||
required this.controller,
|
||||
this.hintText = 'Écrire votre texte ici...',
|
||||
this.maxLines = 10, // Un nombre raisonnable de lignes par défaut si non dynamique
|
||||
this.fieldHeight, // Si non fourni, la hauteur sera intrinsèque ou définie par l'image
|
||||
this.expandDynamically = false, // Par défaut, non dynamique
|
||||
this.readOnly = false,
|
||||
this.fontSize = 15.0,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: fieldHeight, // Permet de forcer une hauteur si besoin
|
||||
child: Stack(
|
||||
alignment: Alignment.topLeft,
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/images/square.png', // L'image de fond
|
||||
fit: BoxFit.fill, // Pour remplir l'espace du Stack/SizedBox
|
||||
width: double.infinity, // S'assurer qu'elle prend toute la largeur disponible
|
||||
height: fieldHeight != null ? double.infinity : null, // Et toute la hauteur si fieldHeight est spécifié
|
||||
),
|
||||
Padding(
|
||||
// Ajouter un padding interne pour que le texte ne colle pas aux bords de l'image
|
||||
padding: const EdgeInsets.only(top: 25.0, bottom: 15.0, left: 20.0, right: 20.0), // Augmentation de la marge supérieure
|
||||
child: TextFormField(
|
||||
controller: controller,
|
||||
keyboardType: TextInputType.multiline,
|
||||
maxLines: expandDynamically ? null : maxLines, // S'étend dynamiquement si expandDynamically est true
|
||||
style: GoogleFonts.merienda(fontSize: fontSize, color: Colors.black87),
|
||||
textAlignVertical: TextAlignVertical.top,
|
||||
readOnly: readOnly,
|
||||
decoration: InputDecoration(
|
||||
hintText: hintText,
|
||||
hintStyle: GoogleFonts.merienda(fontSize: fontSize, color: Colors.black54.withOpacity(0.7)),
|
||||
border: InputBorder.none, // Pas de bordure pour le TextFormField lui-même
|
||||
contentPadding: EdgeInsets.zero, // Le padding est géré par le widget Padding externe
|
||||
// Pour aligner le hintText en haut à gauche
|
||||
alignLabelWithHint: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class HoverReliefWidget extends StatefulWidget {
|
||||
final Widget child;
|
||||
final VoidCallback? onPressed;
|
||||
final BorderRadius borderRadius;
|
||||
final double initialElevation;
|
||||
final double hoverElevation;
|
||||
final double scaleFactor;
|
||||
final bool enableHoverEffect; // Pour activer/désactiver l'effet de survol
|
||||
final Color initialShadowColor; // Nouveau paramètre
|
||||
final Color hoverShadowColor; // Nouveau paramètre
|
||||
|
||||
const HoverReliefWidget({
|
||||
required this.child,
|
||||
this.onPressed,
|
||||
this.borderRadius = const BorderRadius.all(Radius.circular(15.0)),
|
||||
this.initialElevation = 4.0,
|
||||
this.hoverElevation = 8.0,
|
||||
this.scaleFactor = 1.03, // Légèrement réduit par rapport à l'exemple précédent
|
||||
this.enableHoverEffect = true, // Par défaut, l'effet est activé
|
||||
this.initialShadowColor = const Color(0x26000000), // Default: Colors.black.withOpacity(0.15)
|
||||
this.hoverShadowColor = const Color(0x4D000000), // Default: Colors.black.withOpacity(0.3)
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<HoverReliefWidget> createState() => _HoverReliefWidgetState();
|
||||
}
|
||||
|
||||
class _HoverReliefWidgetState extends State<HoverReliefWidget> {
|
||||
bool _isHovering = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bool canHover = widget.enableHoverEffect && widget.onPressed != null;
|
||||
|
||||
final hoverTransform = Matrix4.identity()..scale(widget.scaleFactor);
|
||||
final transform = _isHovering && canHover ? hoverTransform : Matrix4.identity();
|
||||
final shadowColor = _isHovering && canHover ? widget.hoverShadowColor : widget.initialShadowColor;
|
||||
final elevation = _isHovering && canHover ? widget.hoverElevation : widget.initialElevation;
|
||||
|
||||
Widget content = AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
transform: transform,
|
||||
transformAlignment: Alignment.center,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
elevation: elevation,
|
||||
shadowColor: shadowColor,
|
||||
borderRadius: widget.borderRadius,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: widget.child,
|
||||
),
|
||||
);
|
||||
|
||||
if (widget.onPressed == null) {
|
||||
// Si non cliquable, on retourne juste le contenu avec l'élévation initiale (pas de survol)
|
||||
// Ajustement: pour toujours avoir un Material de base même si non cliquable et sans hover.
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
elevation: widget.initialElevation, // Utilise l'élévation initiale
|
||||
shadowColor: widget.initialShadowColor, // Appliqué ici pour l'état non cliquable
|
||||
borderRadius: widget.borderRadius,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
|
||||
return MouseRegion(
|
||||
onEnter: (_) {
|
||||
if (widget.enableHoverEffect) setState(() => _isHovering = true);
|
||||
},
|
||||
onExit: (_) {
|
||||
if (widget.enableHoverEffect) setState(() => _isHovering = false);
|
||||
},
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: InkWell(
|
||||
onTap: widget.onPressed,
|
||||
borderRadius: widget.borderRadius,
|
||||
hoverColor: Colors.transparent,
|
||||
splashColor: Colors.grey.withOpacity(0.2),
|
||||
highlightColor: Colors.grey.withOpacity(0.1),
|
||||
child: content,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
class ImageButton extends StatelessWidget {
|
||||
final String bg;
|
||||
final double width;
|
||||
final double height;
|
||||
final String text;
|
||||
final Color textColor;
|
||||
final VoidCallback onPressed;
|
||||
final double fontSize; // Ajout pour la flexibilité
|
||||
|
||||
const ImageButton({
|
||||
super.key,
|
||||
required this.bg,
|
||||
required this.width,
|
||||
required this.height,
|
||||
required this.text,
|
||||
required this.textColor,
|
||||
required this.onPressed,
|
||||
this.fontSize = 16, // Valeur par défaut
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: onPressed,
|
||||
child: Container(
|
||||
width: width,
|
||||
height: height,
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage(bg),
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
text,
|
||||
style: GoogleFonts.merienda(
|
||||
color: textColor,
|
||||
fontSize: fontSize, // Utilisation du paramètre
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user