import 'package:fintracker_api/fintracker_api.dart'; import 'package:flutter/material.dart'; import 'labels.dart'; /// Edit form for the fields of an instrument a person may correct. Returns a patch holding /// only what changed, or null when cancelled or nothing changed. class InstrumentEditDialog extends StatefulWidget { const InstrumentEditDialog({required this.instrument, super.key}); final InstrumentOut instrument; @override State createState() => _InstrumentEditDialogState(); } class _InstrumentEditDialogState extends State { final _formKey = GlobalKey(); late final _name = TextEditingController(text: widget.instrument.name); late final _board = TextEditingController( text: widget.instrument.board ?? '', ); late final _lot = TextEditingController(text: '${widget.instrument.lot}'); late final _sector = TextEditingController( text: widget.instrument.sector ?? '', ); late String _assetClass = widget.instrument.assetClass; @override void dispose() { _name.dispose(); _board.dispose(); _lot.dispose(); _sector.dispose(); super.dispose(); } InstrumentPatch? _patch() { final i = widget.instrument; final name = _name.text.trim(); final board = _board.text.trim(); final sector = _sector.text.trim(); final lot = int.parse(_lot.text.trim()); final patch = InstrumentPatch( name: name != i.name ? name : null, assetClass: _assetClass != i.assetClass ? _assetClass : null, // an empty field means "leave as is": the wire format cannot express "clear" board: board.isNotEmpty && board != (i.board ?? '') ? board : null, lot: lot != i.lot ? lot : null, sector: sector.isNotEmpty && sector != (i.sector ?? '') ? sector : null, ); final changed = patch.name != null || patch.assetClass != null || patch.board != null || patch.lot != null || patch.sector != null; return changed ? patch : null; } @override Widget build(BuildContext context) { return AlertDialog( title: Text('Инструмент ${widget.instrument.ticker ?? ''}'.trim()), content: SizedBox( width: 420, child: Form( key: _formKey, child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ TextFormField( controller: _name, decoration: const InputDecoration(labelText: 'Название'), validator: (v) => (v ?? '').trim().isEmpty ? 'Обязательное поле' : null, ), const SizedBox(height: 12), DropdownButtonFormField( initialValue: assetClassLabels.containsKey(_assetClass) ? _assetClass : null, isExpanded: true, decoration: const InputDecoration(labelText: 'Класс актива'), items: [ for (final e in assetClassLabels.entries) DropdownMenuItem( value: e.key, child: Text('${e.value} (${e.key})'), ), ], onChanged: (v) => setState(() => _assetClass = v ?? _assetClass), ), const SizedBox(height: 12), TextFormField( controller: _board, textCapitalization: TextCapitalization.characters, decoration: const InputDecoration( labelText: 'Доска (TQBR, TQTF…)', helperText: 'По ней синк Мосбиржи находит котировки', ), ), const SizedBox(height: 12), TextFormField( controller: _lot, keyboardType: TextInputType.number, decoration: const InputDecoration( labelText: 'Лот', helperText: 'Ребалансировка округляет сделки до целого числа лотов', helperMaxLines: 2, ), validator: (v) { final n = int.tryParse((v ?? '').trim()); return n == null || n < 1 ? 'Целое число не меньше 1' : null; }, ), const SizedBox(height: 12), TextFormField( controller: _sector, decoration: const InputDecoration(labelText: 'Сектор'), ), ], ), ), ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Отмена'), ), FilledButton( onPressed: () { if (!(_formKey.currentState?.validate() ?? false)) return; Navigator.of(context).pop(_patch()); }, child: const Text('Сохранить'), ), ], ); } }