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.

722 lines
33KB

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