fix: première arme invisible (race init DB) + recentrage des champs sous le clavier dans la modale d'édition d'arme

This commit is contained in:
qguillaume
2026-06-19 13:41:01 +02:00
parent f7fecf0ef2
commit ba9975f047
2 changed files with 53 additions and 17 deletions

View File

@@ -10,6 +10,12 @@ import '../../core/constants/app_constants.dart';
class DatabaseHelper { class DatabaseHelper {
static DatabaseHelper? _instance; static DatabaseHelper? _instance;
static Database? _database; static Database? _database;
// On met en cache le Future d'initialisation (et non la Database résolue)
// pour éviter qu'un démarrage concurrent (les 4 onglets de l'IndexedStack
// interrogent la base en même temps) ne lance plusieurs _initDatabase() en
// parallèle. Sur une base fraîche, cela dédoublait onCreate et rendait la
// toute première écriture peu fiable.
static Future<Database>? _initFuture;
DatabaseHelper._internal(); DatabaseHelper._internal();
@@ -19,7 +25,9 @@ class DatabaseHelper {
} }
Future<Database> get database async { Future<Database> get database async {
_database ??= await _initDatabase(); if (_database != null) return _database!;
_initFuture ??= _initDatabase();
_database = await _initFuture!;
return _database!; return _database!;
} }
@@ -552,5 +560,6 @@ class DatabaseHelper {
final db = await database; final db = await database;
await db.close(); await db.close();
_database = null; _database = null;
_initFuture = null;
} }
} }

View File

@@ -235,6 +235,33 @@ class _WeaponDetailScreenState extends State<WeaponDetailScreen> {
} }
} }
// Recentre le champ qui prend le focus au milieu de la vue restante une fois
// le clavier ouvert. Sans cela, l'AlertDialog se rétrécit et le champ ciblé
// (ex: Modérateur) se retrouve caché sous le clavier.
Widget _autoScrollOnFocus(Widget child) {
return Builder(
builder: (context) => Focus(
canRequestFocus: false,
skipTraversal: true,
onFocusChange: (hasFocus) {
if (!hasFocus) return;
// On attend que le clavier ait fini de redimensionner la vue.
Future.delayed(const Duration(milliseconds: 300), () {
if (context.mounted) {
Scrollable.ensureVisible(
context,
alignment: 0.5,
duration: const Duration(milliseconds: 250),
curve: Curves.easeInOut,
);
}
});
},
child: child,
),
);
}
void _showEditWeaponDialog(BuildContext context) async { void _showEditWeaponDialog(BuildContext context) async {
final nameController = TextEditingController(text: _weapon.name); final nameController = TextEditingController(text: _weapon.name);
final caliberController = TextEditingController(text: _weapon.caliber); final caliberController = TextEditingController(text: _weapon.caliber);
@@ -256,41 +283,41 @@ class _WeaponDetailScreenState extends State<WeaponDetailScreen> {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
TextField( _autoScrollOnFocus(TextField(
controller: nameController, controller: nameController,
decoration: const InputDecoration(labelText: 'Modèle', hintText: 'ex: Glock 17'), decoration: const InputDecoration(labelText: 'Modèle', hintText: 'ex: Glock 17'),
), )),
TextField( _autoScrollOnFocus(TextField(
controller: customNameController, controller: customNameController,
decoration: const InputDecoration(labelText: 'Surnom / Custom Name', hintText: 'ex: Mon Glock de Compète'), decoration: const InputDecoration(labelText: 'Surnom / Custom Name', hintText: 'ex: Mon Glock de Compète'),
), )),
DropdownButtonFormField<WeaponType>( DropdownButtonFormField<WeaponType>(
value: selectedType, value: selectedType,
decoration: const InputDecoration(labelText: 'Type'), decoration: const InputDecoration(labelText: 'Type'),
items: WeaponType.values.map((t) => DropdownMenuItem(value: t, child: Text(t.displayName))).toList(), items: WeaponType.values.map((t) => DropdownMenuItem(value: t, child: Text(t.displayName))).toList(),
onChanged: (v) => setState(() => selectedType = v!), onChanged: (v) => setState(() => selectedType = v!),
), ),
TextField( _autoScrollOnFocus(TextField(
controller: caliberController, controller: caliberController,
decoration: const InputDecoration(labelText: 'Calibre'), decoration: const InputDecoration(labelText: 'Calibre'),
), )),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text('Équipement & Options', style: TextStyle(fontWeight: FontWeight.bold)), const Text('Équipement & Options', style: TextStyle(fontWeight: FontWeight.bold)),
TextField( _autoScrollOnFocus(TextField(
controller: opticController, controller: opticController,
decoration: const InputDecoration(labelText: 'Optique / Lunette', hintText: 'ex: Holosun 507C'), decoration: const InputDecoration(labelText: 'Optique / Lunette', hintText: 'ex: Holosun 507C'),
), )),
TextField( _autoScrollOnFocus(TextField(
controller: silencerController, controller: silencerController,
decoration: const InputDecoration(labelText: 'Modérateur / Silencieux'), decoration: const InputDecoration(labelText: 'Modérateur / Silencieux'),
), )),
TextField( _autoScrollOnFocus(TextField(
controller: triggerController, controller: triggerController,
decoration: const InputDecoration(labelText: 'Détente / Trigger'), decoration: const InputDecoration(labelText: 'Détente / Trigger'),
), )),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text('Logistique', style: TextStyle(fontWeight: FontWeight.bold)), const Text('Logistique', style: TextStyle(fontWeight: FontWeight.bold)),
Row( _autoScrollOnFocus(Row(
children: [ children: [
Expanded( Expanded(
child: TextField( child: TextField(
@@ -308,12 +335,12 @@ class _WeaponDetailScreenState extends State<WeaponDetailScreen> {
), ),
), ),
], ],
), )),
TextField( _autoScrollOnFocus(TextField(
controller: notesController, controller: notesController,
decoration: const InputDecoration(labelText: 'Notes'), decoration: const InputDecoration(labelText: 'Notes'),
maxLines: 2, maxLines: 2,
), )),
], ],
), ),
), ),