You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

726 lines
34KB

  1. import 'dart:convert';
  2. import 'package:farm_tpf/custom_model/Nursery.dart';
  3. import 'package:farm_tpf/custom_model/NurseryDetail.dart';
  4. import 'package:farm_tpf/custom_model/Supply.dart';
  5. import 'package:farm_tpf/data/api/app_exception.dart';
  6. import 'package:farm_tpf/data/repository/repository.dart';
  7. import 'package:farm_tpf/presentation/custom_widgets/app_bar_widget.dart';
  8. import 'package:farm_tpf/presentation/custom_widgets/bloc/media_helper_bloc.dart';
  9. import 'package:farm_tpf/presentation/custom_widgets/button_widget.dart';
  10. import 'package:farm_tpf/presentation/custom_widgets/widget_field_time_picker.dart';
  11. import 'package:farm_tpf/presentation/custom_widgets/widget_loading.dart';
  12. import 'package:farm_tpf/presentation/custom_widgets/widget_media_picker.dart';
  13. import 'package:farm_tpf/presentation/custom_widgets/widget_text_field_description.dart';
  14. import 'package:farm_tpf/presentation/custom_widgets/widget_text_form_field.dart';
  15. import 'package:farm_tpf/presentation/custom_widgets/widget_utils.dart';
  16. import 'package:farm_tpf/presentation/screens/actions/bloc/action_detail_bloc.dart';
  17. import 'package:farm_tpf/presentation/screens/actions/nursery/bloc/expansion_list_bloc.dart';
  18. import 'package:farm_tpf/presentation/screens/actions/state_management_helper/change_file_controller.dart';
  19. import 'package:farm_tpf/presentation/screens/actions/state_management_helper/change_supply.dart';
  20. import 'package:farm_tpf/presentation/screens/resources/sc_resource_helper.dart';
  21. import 'package:farm_tpf/utils/bloc/bloc/status_add_form_bloc.dart';
  22. import 'package:farm_tpf/utils/const_common.dart';
  23. import 'package:farm_tpf/utils/const_string.dart';
  24. import 'package:farm_tpf/utils/const_style.dart';
  25. import 'package:farm_tpf/utils/pref.dart';
  26. import 'package:farm_tpf/utils/validators.dart';
  27. import 'package:flutter/material.dart';
  28. import 'package:flutter_bloc/flutter_bloc.dart';
  29. import 'package:get/get.dart';
  30. import 'package:get/state_manager.dart';
  31. import 'package:keyboard_dismisser/keyboard_dismisser.dart';
  32. import 'package:farm_tpf/utils/formatter.dart';
  33. import '../util_action.dart';
  34. class EditActionNurseryScreen extends StatefulWidget {
  35. final int cropId;
  36. final bool isEdit;
  37. final int activityId;
  38. EditActionNurseryScreen(
  39. {@required this.cropId, this.isEdit = false, this.activityId});
  40. @override
  41. _EditActionNurseryState createState() => _EditActionNurseryState();
  42. }
  43. class _EditActionNurseryState extends State<EditActionNurseryScreen> {
  44. final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
  45. final _repository = Repository();
  46. GlobalKey<FormState> _formKey = GlobalKey();
  47. GlobalKey<FormState> _formWorkerKey = GlobalKey();
  48. bool _autoValidate = false;
  49. Nursery _nursery = Nursery();
  50. var pref = LocalPref();
  51. TextEditingController _substrateController = TextEditingController();
  52. TextEditingController _seedLengthController = TextEditingController();
  53. TextEditingController _quantityController = TextEditingController();
  54. TextEditingController _seedIncubationTimeController = TextEditingController();
  55. TextEditingController _descriptionController = TextEditingController();
  56. TextEditingController _workerNameController = TextEditingController();
  57. TextEditingController _trayNumberController = TextEditingController();
  58. final _executeByController = TextEditingController();
  59. DateTime executeTime = DateTime.now();
  60. List<NurseryDetail> currentNurseryDetail = List<NurseryDetail>();
  61. int currentIndexUpdate = -1;
  62. bool isResetForm = true;
  63. final changeSupply = Get.put(ChangeSupply());
  64. int selectedSupplyId = -1;
  65. List<String> filePaths = List<String>();
  66. var changeFileController = Get.put(ChangeFileController());
  67. Future<Null> getSharedPrefs() async {
  68. var currentFullName = await pref.getString(DATA_CONST.CURRENT_FULL_NAME);
  69. _executeByController.text = currentFullName ?? "";
  70. }
  71. @override
  72. void initState() {
  73. super.initState();
  74. getSharedPrefs();
  75. changeSupply.initValue();
  76. changeFileController.initValue();
  77. _nursery.nurseryDetail = new List<NurseryDetail>();
  78. _nursery.cropId = widget.cropId;
  79. }
  80. _validateInputs() async {
  81. if (_formKey.currentState.validate()) {
  82. _formKey.currentState.save();
  83. LoadingDialog.showLoadingDialog(context);
  84. _nursery.nurseryDetail = currentNurseryDetail;
  85. filePaths = Get.find<ChangeFileController>().newFiles;
  86. _nursery.mediaDel = Get.find<ChangeFileController>().deleteFiles;
  87. var activityNursery = jsonEncode(_nursery.toJson()).toString();
  88. //ADD NEW
  89. if (_nursery.activityId == null) {
  90. _repository.createAction((value) {
  91. LoadingDialog.hideLoadingDialog(context);
  92. Get.back(result: value);
  93. Utils.showSnackBarSuccess(message: label_add_success);
  94. }, (error) {
  95. LoadingDialog.hideLoadingDialog(context);
  96. Utils.showSnackBarError(message: AppException.handleError(error));
  97. },
  98. apiAddAction: ConstCommon.apiAddNursery,
  99. paramActivity: ConstCommon.paramsActionNursery,
  100. activityAction: activityNursery,
  101. filePaths: filePaths);
  102. } else {
  103. //UPDATE
  104. _repository.updateAction((value) {
  105. LoadingDialog.hideLoadingDialog(context);
  106. Get.back(result: value);
  107. Utils.showSnackBarSuccess(message: label_update_success);
  108. }, (error) {
  109. LoadingDialog.hideLoadingDialog(context);
  110. Utils.showSnackBarError(message: AppException.handleError(error));
  111. },
  112. apiUpdateAction: ConstCommon.apiUpdateNursery,
  113. paramActivity: ConstCommon.paramsActionNursery,
  114. activityAction: activityNursery,
  115. filePaths: filePaths);
  116. }
  117. } else {
  118. _autoValidate = true;
  119. }
  120. }
  121. Widget _btnExecuteTimePicker() {
  122. return WidgetFieldDateTimePicker(
  123. initDateTime: executeTime,
  124. onUpdateDateTime: (selectedDate) {
  125. _nursery.executeDate =
  126. selectedDate.convertLocalDateTimeToStringUtcDateTime();
  127. });
  128. }
  129. Widget _btnSelectSubstrates() {
  130. return FlatButton(
  131. padding: EdgeInsets.only(top: 0.0, right: 0.0, bottom: 0.0, left: 0.0),
  132. onPressed: () {
  133. Navigator.of(context)
  134. .push(MaterialPageRoute(
  135. builder: (_) => ResourceHelperScreen(
  136. titleName: "Giá thể",
  137. type: ConstCommon.supplyTypeSubStrate,
  138. selectedId: Get.find<ChangeSupply>().selectedSupplyId,
  139. currentItems: [],
  140. currentEditId: -1),
  141. fullscreenDialog: false))
  142. .then((value) {
  143. if (value != null) {
  144. var result = value as Supply;
  145. _nursery.substratesId = result.id;
  146. changeSupply.change(result);
  147. print("Home: $value");
  148. }
  149. });
  150. },
  151. child: Container(
  152. padding:
  153. EdgeInsets.only(top: 0.0, right: 0.0, bottom: 10.5, left: 0.0),
  154. decoration: BoxDecoration(
  155. border: kBorderTextField,
  156. ),
  157. child: Row(
  158. children: [
  159. GetBuilder<ChangeSupply>(
  160. builder: (_) => Expanded(
  161. child: Text(
  162. changeSupply.selectedSupplyName == null
  163. ? "Loại giá thể"
  164. : changeSupply.selectedSupplyName.toString(),
  165. style: TextStyle(
  166. fontSize: 14.0, color: Colors.black87)))),
  167. Icon(
  168. Icons.arrow_drop_down,
  169. color: Colors.grey,
  170. ),
  171. ],
  172. )));
  173. }
  174. Widget _seedLengthField() {
  175. return WidgetTextFormFieldNumber(
  176. hintValue: "Chiều dài mầm",
  177. textController: _seedLengthController,
  178. onSaved: (newValue) {
  179. _nursery.seedLength = newValue.parseDoubleThousand();
  180. },
  181. );
  182. }
  183. Widget _quantityField() {
  184. return WidgetTextFormFieldNumber(
  185. hintValue: "Số lượng hạt gieo",
  186. textController: _quantityController,
  187. onSaved: (newValue) {
  188. _nursery.quantity = newValue.parseDoubleThousand();
  189. },
  190. );
  191. }
  192. Widget _seedIncubationTimeField() {
  193. return WidgetTextFormFieldNumber(
  194. hintValue: "Thời gian ngâm hạt",
  195. textController: _seedIncubationTimeController,
  196. onSaved: (newValue) {
  197. _nursery.seedIncubationTime = newValue.parseDoubleThousand();
  198. },
  199. );
  200. }
  201. Widget _desciptionField() {
  202. return TextFieldDescriptionWidget(
  203. controller: _descriptionController,
  204. onSaved: (newValue) {
  205. _nursery.description = newValue;
  206. });
  207. }
  208. Widget _executeByField() {
  209. return TextFormField(
  210. keyboardType: TextInputType.text,
  211. decoration: InputDecoration(labelText: "Người thực hiện"),
  212. enabled: false,
  213. controller: _executeByController,
  214. onSaved: (newValue) {},
  215. );
  216. }
  217. Widget _btnAddWorker() {
  218. //TODO :check flow error sua item -> xoa list -> bam nut them
  219. return Builder(builder: (context) {
  220. return BlocConsumer<StatusAddFormBloc, StatusAddFormState>(
  221. listener: (context, state) {
  222. if (state is Edit) {
  223. isResetForm = false;
  224. _workerNameController.text = state.nurseryDetail.workerName;
  225. _trayNumberController.text = state.nurseryDetail.trayNumber;
  226. } else if (state is Delete) {
  227. if (currentIndexUpdate == state.index) {
  228. isResetForm = true;
  229. _workerNameController.text = "";
  230. _trayNumberController.text = "";
  231. }
  232. } else {
  233. isResetForm = true;
  234. _workerNameController.text = "";
  235. _trayNumberController.text = "";
  236. }
  237. }, builder: (context, state) {
  238. return Container(
  239. child: Form(
  240. key: _formWorkerKey,
  241. child: Column(
  242. children: [
  243. Container(
  244. padding: EdgeInsets.all(8.0),
  245. margin: EdgeInsets.all(8),
  246. decoration: BoxDecoration(
  247. shape: BoxShape.rectangle,
  248. borderRadius: BorderRadius.circular(10),
  249. color: Colors.white,
  250. border: Border.all(color: Colors.grey[300])),
  251. child: Column(
  252. children: [
  253. TextFormField(
  254. keyboardType: TextInputType.text,
  255. controller: _workerNameController,
  256. decoration: InputDecoration(labelText: "Tên công nhân *"),
  257. validator: (value) {
  258. return Validators.validateNotNullOrEmpty(
  259. value, label_validate_input_empty);
  260. },
  261. onSaved: (newValue) {},
  262. ),
  263. TextFormField(
  264. keyboardType: TextInputType.text,
  265. controller: _trayNumberController,
  266. decoration: InputDecoration(labelText: "Ươm khây số"),
  267. onSaved: (newValue) {},
  268. ),
  269. ],
  270. ),
  271. ),
  272. Container(
  273. margin: EdgeInsets.all(8),
  274. child: Row(
  275. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  276. mainAxisSize: MainAxisSize.max,
  277. children: [
  278. isResetForm
  279. ? Container()
  280. : OutlineButton(
  281. shape: RoundedRectangleBorder(
  282. borderRadius: new BorderRadius.circular(8.0)),
  283. child: Text("Huỷ"),
  284. onPressed: () {
  285. context.bloc<StatusAddFormBloc>().add(Reset());
  286. }),
  287. Expanded(
  288. child: FlatButton(
  289. onPressed: () {
  290. if (_formWorkerKey.currentState.validate()) {
  291. _formWorkerKey.currentState.save();
  292. if (Validators.stringNotNullOrEmpty(
  293. _workerNameController.text)) {
  294. NurseryDetail _nurseryDetail = NurseryDetail()
  295. ..workerName = _workerNameController.text
  296. ..trayNumber = _trayNumberController.text;
  297. if (state is Edit) {
  298. context.bloc<ExpansionListBloc>().add(Update(
  299. index: state.index,
  300. item: _nurseryDetail,
  301. items: state.items));
  302. } else {
  303. currentNurseryDetail.insert(
  304. 0, _nurseryDetail);
  305. BlocProvider.of<ExpansionListBloc>(context)
  306. .add(AddNew(items: currentNurseryDetail));
  307. }
  308. context.bloc<StatusAddFormBloc>().add(Reset());
  309. } else {
  310. Utils.showSnackBarWarning(
  311. message: "Vui lòng nhập tên công nhân");
  312. }
  313. } else {
  314. //
  315. if (!Validators.stringNotNullOrEmpty(
  316. _workerNameController.text)) {
  317. Utils.showSnackBarWarning(
  318. message: "Vui lòng nhập tên công nhân");
  319. }
  320. }
  321. },
  322. child: Text(
  323. (state is Edit)
  324. ? "Sửa người thực hiện"
  325. : "+ Thêm người thực hiện",
  326. style: TextStyle(color: Colors.blue),
  327. )),
  328. )
  329. ],
  330. ),
  331. ),
  332. ],
  333. ),
  334. ));
  335. });
  336. });
  337. }
  338. Widget _buildListAddWorker() {
  339. return Builder(builder: (context) {
  340. return BlocBuilder<ExpansionListBloc, ExpansionListState>(
  341. builder: (context, state) {
  342. if (state is ExpansionListSuccess) {
  343. currentNurseryDetail = state.items;
  344. if (currentNurseryDetail.isEmpty) {
  345. return Container();
  346. }
  347. return Container(
  348. height: 70,
  349. child: ListView.builder(
  350. physics: ClampingScrollPhysics(),
  351. scrollDirection: Axis.horizontal,
  352. shrinkWrap: true,
  353. itemCount: currentNurseryDetail.length,
  354. itemBuilder: (context, index) {
  355. return GestureDetector(
  356. onTap: () {
  357. print("edit worker");
  358. currentIndexUpdate = index;
  359. context.bloc<StatusAddFormBloc>().add(Changed(
  360. status: CRUDStatus.edit,
  361. index: index,
  362. nurseryDetail: currentNurseryDetail[index],
  363. items: currentNurseryDetail));
  364. },
  365. child: Card(
  366. child: Stack(
  367. alignment: Alignment.bottomCenter,
  368. overflow: Overflow.visible,
  369. children: <Widget>[
  370. Positioned(
  371. child: ClipRRect(
  372. borderRadius: BorderRadius.circular(8),
  373. child: Container(
  374. padding: EdgeInsets.all(4),
  375. width: 120,
  376. child: Column(
  377. mainAxisAlignment: MainAxisAlignment.center,
  378. children: [
  379. SizedBox(
  380. height: 12.0,
  381. ),
  382. Flexible(
  383. child: Text(
  384. currentNurseryDetail[index]
  385. .workerName ??
  386. "",
  387. overflow: TextOverflow.ellipsis,
  388. maxLines: 1),
  389. ),
  390. Validators.stringNotNullOrEmpty(
  391. currentNurseryDetail[index]
  392. .trayNumber)
  393. ? Flexible(
  394. child: Text(
  395. currentNurseryDetail[index]
  396. .trayNumber ??
  397. ""))
  398. : SizedBox()
  399. ],
  400. ),
  401. ),
  402. )),
  403. Positioned(
  404. top: -10,
  405. right: -10,
  406. child: IconButton(
  407. icon: Icon(
  408. Icons.cancel,
  409. color: Colors.redAccent,
  410. ),
  411. onPressed: () {
  412. print("Delete worker");
  413. context.bloc<ExpansionListBloc>().add(
  414. DeleteItem(
  415. index: index,
  416. items: currentNurseryDetail));
  417. context.bloc<StatusAddFormBloc>().add(
  418. Changed(
  419. status: CRUDStatus.delete,
  420. index: index,
  421. nurseryDetail:
  422. currentNurseryDetail[index],
  423. items: currentNurseryDetail));
  424. }),
  425. )
  426. ],
  427. )));
  428. }));
  429. } else if (state is ExpansionListFailure) {
  430. return Container();
  431. } else {
  432. return Container();
  433. }
  434. });
  435. });
  436. }
  437. @override
  438. Widget build(BuildContext context) => KeyboardDismisser(
  439. gestures: [
  440. GestureType.onTap,
  441. GestureType.onPanUpdateDownDirection,
  442. ],
  443. child: Scaffold(
  444. backgroundColor: Colors.white,
  445. key: _scaffoldKey,
  446. appBar: AppBarWidget(
  447. isBack: true,
  448. action: InkWell(
  449. child: Text(
  450. 'Huỷ',
  451. style: TextStyle(
  452. color: Colors.red, fontWeight: FontWeight.normal),
  453. ),
  454. onTap: () {
  455. if (Get.isSnackbarOpen) Get.back();
  456. Get.back();
  457. },
  458. ),
  459. ),
  460. body: KeyboardDismisser(
  461. child: MultiBlocProvider(
  462. providers: [
  463. BlocProvider<ExpansionListBloc>(
  464. create: (context) => ExpansionListBloc(),
  465. ),
  466. BlocProvider<StatusAddFormBloc>(
  467. create: (context) => StatusAddFormBloc(),
  468. ),
  469. BlocProvider<ActionDetailBloc>(
  470. create: (context) =>
  471. ActionDetailBloc(repository: Repository())
  472. ..add(FetchData(
  473. isNeedFetchData: widget.isEdit,
  474. apiActivity: ConstCommon.apiDetailNursery,
  475. activityId: widget.activityId))),
  476. BlocProvider<MediaHelperBloc>(
  477. create: (context) =>
  478. MediaHelperBloc()..add(ChangeListMedia(items: [])),
  479. )
  480. ],
  481. child: Form(
  482. key: _formKey,
  483. autovalidate: _autoValidate,
  484. child: SafeArea(
  485. child: SingleChildScrollView(
  486. child: BlocConsumer<ActionDetailBloc,
  487. ActionDetailState>(
  488. listener: (context, state) async {
  489. if (state is ActionDetailFailure) {
  490. print("fail");
  491. LoadingDialog.hideLoadingDialog(context);
  492. } else if (state is ActionDetailSuccess) {
  493. LoadingDialog.hideLoadingDialog(context);
  494. print("success");
  495. print(state.item);
  496. _nursery = Nursery.fromJson(state.item);
  497. _seedLengthController.text = _nursery
  498. .seedLength
  499. .formatNumtoStringDecimal();
  500. _quantityController.text = _nursery.quantity
  501. .formatNumtoStringDecimal();
  502. _seedIncubationTimeController.text =
  503. _nursery.seedIncubationTime
  504. .formatNumtoStringDecimal();
  505. _descriptionController.text =
  506. _nursery.description;
  507. _executeByController.text =
  508. _nursery.executeBy;
  509. Get.find<ChangeDateTimePicker>().change(_nursery
  510. .executeDate
  511. .convertStringServerDateTimeToLocalDateTime());
  512. //Show media
  513. if (Validators.stringNotNullOrEmpty(
  514. _nursery.media)) {
  515. BlocProvider.of<MediaHelperBloc>(context)
  516. .add(ChangeListMedia(
  517. items: UtilAction
  518. .convertFilePathToMedia(
  519. _nursery.media)));
  520. }
  521. //Show worker
  522. if (_nursery.nurseryDetail.length > 0) {
  523. BlocProvider.of<ExpansionListBloc>(
  524. context)
  525. .add(AddNew(
  526. items: _nursery.nurseryDetail));
  527. }
  528. //change subStrates
  529. if (_nursery.substratesId != null) {
  530. Get.find<ChangeSupply>()
  531. .changeByIdAndName(
  532. _nursery.substratesId,
  533. _nursery.substrateName);
  534. }
  535. } else if (state is ActionDetailInitial) {
  536. print("init");
  537. } else if (state is ActionDetailLoading) {
  538. print("loading");
  539. LoadingDialog.showLoadingDialog(context);
  540. }
  541. },
  542. builder: (context, state) {
  543. return Column(
  544. children: [
  545. Padding(
  546. padding: EdgeInsets.all(8),
  547. child: Column(
  548. crossAxisAlignment:
  549. CrossAxisAlignment.start,
  550. children: <Widget>[
  551. Text(
  552. 'Ươm',
  553. style: TextStyle(
  554. fontWeight: FontWeight.w500,
  555. fontSize: 22),
  556. ),
  557. SizedBox(
  558. height: 8.0,
  559. ),
  560. Container(
  561. width: double.infinity,
  562. child: Text(
  563. "Ngày thực hiện *",
  564. style: TextStyle(
  565. color: Colors.black54,
  566. fontSize: 13.0),
  567. ),
  568. ),
  569. _btnExecuteTimePicker(),
  570. SizedBox(
  571. height: 8.0,
  572. ),
  573. Container(
  574. width: double.infinity,
  575. child: Text(
  576. "Loại giá thể",
  577. style: TextStyle(
  578. color: Colors.black54,
  579. fontSize: 13.0),
  580. ),
  581. ),
  582. _btnSelectSubstrates(),
  583. SizedBox(
  584. height: 8.0,
  585. ),
  586. _seedLengthField(),
  587. SizedBox(
  588. height: 8.0,
  589. ),
  590. _quantityField(),
  591. SizedBox(
  592. height: 8.0,
  593. ),
  594. _seedIncubationTimeField(),
  595. SizedBox(
  596. height: 8.0,
  597. ),
  598. _desciptionField(),
  599. SizedBox(
  600. height: 8.0,
  601. ),
  602. _executeByField(),
  603. SizedBox(
  604. height: 8.0,
  605. ),
  606. ],
  607. ),
  608. ),
  609. Container(
  610. width: double.infinity,
  611. height: 16,
  612. color: Colors.grey[200],
  613. ),
  614. SizedBox(
  615. height: 8.0,
  616. ),
  617. _buildListAddWorker(),
  618. SizedBox(
  619. height: 8.0,
  620. ),
  621. _btnAddWorker(),
  622. SizedBox(
  623. height: 8.0,
  624. ),
  625. Container(
  626. width: double.infinity,
  627. height: 16,
  628. color: Colors.grey[200],
  629. ),
  630. SizedBox(
  631. height: 8.0,
  632. ),
  633. BlocBuilder<MediaHelperBloc,
  634. MediaHelperState>(
  635. builder: (context, state) {
  636. if (state is MediaHelperSuccess) {
  637. print("length: " +
  638. state.items.length.toString());
  639. return WidgetMediaPicker(
  640. currentItems: state.items,
  641. onChangeFiles: (newPathFiles,
  642. deletePathFiles) async {
  643. Get.find<ChangeFileController>()
  644. .change(newPathFiles,
  645. deletePathFiles);
  646. });
  647. } else {
  648. return Center(
  649. child:
  650. CircularProgressIndicator());
  651. }
  652. }),
  653. SizedBox(
  654. height: 16,
  655. ),
  656. Padding(
  657. padding: const EdgeInsets.all(8.0),
  658. child: ButtonWidget(
  659. title: 'CẬP NHẬT',
  660. onPressed: () {
  661. FocusScopeNode currentFocus =
  662. FocusScope.of(context);
  663. if (!currentFocus
  664. .hasPrimaryFocus) {
  665. currentFocus.unfocus();
  666. }
  667. if (!Validators
  668. .stringNotNullOrEmpty(
  669. _workerNameController
  670. .text) &&
  671. !Validators
  672. .stringNotNullOrEmpty(
  673. _trayNumberController
  674. .text)) {
  675. _validateInputs();
  676. } else {
  677. Utils.showDialog(
  678. title:
  679. "Tên công nhân hoặc khây trồng đang cập nhật",
  680. message:
  681. "Bạn có muốn cập nhật?",
  682. textConfirm: "Tiếp tục",
  683. textCancel: "Xem lại",
  684. onConfirm: () {
  685. Get.back();
  686. _validateInputs();
  687. });
  688. }
  689. }),
  690. ),
  691. ],
  692. );
  693. },
  694. ),
  695. ),
  696. ))))));
  697. @override
  698. void dispose() {
  699. _substrateController.dispose();
  700. _seedLengthController.dispose();
  701. _quantityController.dispose();
  702. _seedIncubationTimeController.dispose();
  703. _descriptionController.dispose();
  704. _executeByController.dispose();
  705. super.dispose();
  706. }
  707. }