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:
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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,
|
||||||
),
|
)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user