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 {
|
||||
static DatabaseHelper? _instance;
|
||||
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();
|
||||
|
||||
@@ -19,7 +25,9 @@ class DatabaseHelper {
|
||||
}
|
||||
|
||||
Future<Database> get database async {
|
||||
_database ??= await _initDatabase();
|
||||
if (_database != null) return _database!;
|
||||
_initFuture ??= _initDatabase();
|
||||
_database = await _initFuture!;
|
||||
return _database!;
|
||||
}
|
||||
|
||||
@@ -552,5 +560,6 @@ class DatabaseHelper {
|
||||
final db = await database;
|
||||
await db.close();
|
||||
_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 {
|
||||
final nameController = TextEditingController(text: _weapon.name);
|
||||
final caliberController = TextEditingController(text: _weapon.caliber);
|
||||
@@ -256,41 +283,41 @@ class _WeaponDetailScreenState extends State<WeaponDetailScreen> {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
_autoScrollOnFocus(TextField(
|
||||
controller: nameController,
|
||||
decoration: const InputDecoration(labelText: 'Modèle', hintText: 'ex: Glock 17'),
|
||||
),
|
||||
TextField(
|
||||
)),
|
||||
_autoScrollOnFocus(TextField(
|
||||
controller: customNameController,
|
||||
decoration: const InputDecoration(labelText: 'Surnom / Custom Name', hintText: 'ex: Mon Glock de Compète'),
|
||||
),
|
||||
)),
|
||||
DropdownButtonFormField<WeaponType>(
|
||||
value: selectedType,
|
||||
decoration: const InputDecoration(labelText: 'Type'),
|
||||
items: WeaponType.values.map((t) => DropdownMenuItem(value: t, child: Text(t.displayName))).toList(),
|
||||
onChanged: (v) => setState(() => selectedType = v!),
|
||||
),
|
||||
TextField(
|
||||
_autoScrollOnFocus(TextField(
|
||||
controller: caliberController,
|
||||
decoration: const InputDecoration(labelText: 'Calibre'),
|
||||
),
|
||||
)),
|
||||
const SizedBox(height: 16),
|
||||
const Text('Équipement & Options', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
TextField(
|
||||
_autoScrollOnFocus(TextField(
|
||||
controller: opticController,
|
||||
decoration: const InputDecoration(labelText: 'Optique / Lunette', hintText: 'ex: Holosun 507C'),
|
||||
),
|
||||
TextField(
|
||||
)),
|
||||
_autoScrollOnFocus(TextField(
|
||||
controller: silencerController,
|
||||
decoration: const InputDecoration(labelText: 'Modérateur / Silencieux'),
|
||||
),
|
||||
TextField(
|
||||
)),
|
||||
_autoScrollOnFocus(TextField(
|
||||
controller: triggerController,
|
||||
decoration: const InputDecoration(labelText: 'Détente / Trigger'),
|
||||
),
|
||||
)),
|
||||
const SizedBox(height: 16),
|
||||
const Text('Logistique', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
Row(
|
||||
_autoScrollOnFocus(Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
@@ -308,12 +335,12 @@ class _WeaponDetailScreenState extends State<WeaponDetailScreen> {
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
TextField(
|
||||
)),
|
||||
_autoScrollOnFocus(TextField(
|
||||
controller: notesController,
|
||||
decoration: const InputDecoration(labelText: 'Notes'),
|
||||
maxLines: 2,
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user