manager-app/lib/Components/rounded_input_field.dart
Thomas Fransolet 0df5dc03fa Web mobile : le clavier se refermait aussitot ouvert sur le login
Le meta viewport manquant n'expliquait pas tout : le clavier s'ouvrait puis se
refermait apres une demi-seconde. C'est le focus qui etait perdu, pas la saisie
qui n'arrivait pas.

Sur Flutter web, un AutofillGroup se materialise par un <form> DOM qui porte les
inputs caches par lesquels passe la saisie. L'ouverture du clavier redimensionne
la fenetre, l'ecran se reconstruit, le groupe d'autofill est renegocie et le
<form> recree : l'input focus disparait avec lui. Sur desktop rien ne
redimensionne au clic, d'ou un bug invisible hors mobile.

L'AutofillGroup est retire — les autofillHints portes par chaque champ suffisent
au navigateur pour proposer le remplissage, le groupe ne servait qu'a
finishAutofillContext. Trois protections completent la correction :

- les deux champs passent par un TextEditingController detenu par le State, donc
  la saisie survit a une reconstruction, la ou initialValue repartait de zero ;
- ils portent une ValueKey stable, pour etre reapparies plutot que recrees si la
  structure de leurs freres bouge ;
- le Scaffold ne se redimensionne plus a l'ouverture du clavier ; le contenu est
  deja dans un SingleChildScrollView, rien ne reste masque.

Au passage, le pre-remplissage de developpement (localhost) etait ecrit dans
build() a chaque reconstruction : il passe dans initState, ou il a du sens.

Le routeur, lui, etait fabrique dans le builder d'un FutureBuilder et l'appel a
getInstanceInfo partait de l'arbre passe a runApp. Chaque passage du builder
rendait un GoRouter neuf, donc un arbre neuf et un historique perdu. Les deux
sont desormais resolus une fois, avant runApp — 45 lignes de moins.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 22:07:17 +02:00

75 lines
2.5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:manager_app/Components/text_field_container.dart';
import 'package:manager_app/constants.dart';
class RoundedInputField extends StatelessWidget {
final String? hintText;
final IconData? icon;
final ValueChanged<String> onChanged;
final String? initialValue;
final Color color, textColor, iconColor;
final int? maxLength;
final bool isEmail;
final double fontSize;
final String? autofill;
final bool isInt;
/// A preferer a [initialValue] pour un champ vivant dans un ecran qui se
/// reconstruit : le texte survit alors a la reconstruction du TextFormField.
final TextEditingController? controller;
const RoundedInputField({
Key? key,
this.hintText,
this.initialValue,
this.icon,
this.color = kSurface2,
this.textColor = kInk,
this.iconColor = kPrimaryColor,
required this.onChanged,
this.maxLength, // 50
this.isEmail = false,
this.fontSize = 13,
this.autofill,
this.isInt = false,
this.controller,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return TextFieldContainer(
color: color,
child: TextFormField (
onChanged: onChanged,
controller: controller,
initialValue: controller == null ? initialValue : null,
cursorColor: textColor,
maxLength: maxLength,
autofillHints: autofill != null ? [autofill!] : const [],
keyboardType: isEmail ? TextInputType.emailAddress : isInt ? TextInputType.number : TextInputType.text,
inputFormatters: !isInt ? [] : <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
],
style: TextStyle(fontSize: fontSize, color: textColor),
// Le compteur reste, mais discret : c'est une indication, pas une ligne de texte.
buildCounter: maxLength == null
? null
: (context, {required currentLength, required isFocused, maxLength}) =>
Text('$currentLength/$maxLength',
style: const TextStyle(fontSize: 10.5, color: kInk3)),
decoration: InputDecoration(
isDense: true,
contentPadding: const EdgeInsets.symmetric(vertical: kSpace3),
icon: icon != null ? Icon(
icon,
color: iconColor,
): null,
hintText: hintText,
hintStyle: TextStyle(fontSize: fontSize, color: kInk3, fontWeight: FontWeight.w400),
border: InputBorder.none,
)
),
);
}
}