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.

701 lines
27KB

  1. import 'dart:convert';
  2. import 'package:farm_tpf/custom_model/SuppliesUsing.dart';
  3. import 'package:farm_tpf/custom_model/action_form/ActionUIField.dart';
  4. import 'package:farm_tpf/custom_model/action_form/ActionUIForm.dart';
  5. import 'package:farm_tpf/custom_model/action_form/CommonData.dart';
  6. import 'package:farm_tpf/custom_model/action_form/RequestActivity.dart';
  7. import 'package:farm_tpf/data/api/app_exception.dart';
  8. import 'package:farm_tpf/data/repository/repository.dart';
  9. import 'package:farm_tpf/presentation/custom_widgets/app_bar_widget.dart';
  10. import 'package:farm_tpf/presentation/custom_widgets/bloc/media_helper_bloc.dart';
  11. import 'package:farm_tpf/presentation/custom_widgets/button_widget.dart';
  12. import 'package:farm_tpf/presentation/custom_widgets/dropdown_supply_widget.dart';
  13. import 'package:farm_tpf/presentation/custom_widgets/loading_list_page.dart';
  14. import 'package:farm_tpf/presentation/custom_widgets/widget_action_field_date.dart';
  15. import 'package:farm_tpf/presentation/custom_widgets/widget_field_time_picker.dart';
  16. import 'package:farm_tpf/presentation/custom_widgets/widget_loading.dart';
  17. import 'package:farm_tpf/presentation/custom_widgets/widget_media_picker.dart';
  18. import 'package:farm_tpf/presentation/custom_widgets/widget_text_field_area.dart';
  19. import 'package:farm_tpf/presentation/custom_widgets/widget_text_form_field.dart';
  20. import 'package:farm_tpf/presentation/custom_widgets/widget_utils.dart';
  21. import 'package:farm_tpf/presentation/screens/actions/cubit/action_ui_cubit.dart';
  22. import 'package:farm_tpf/utils/const_string.dart';
  23. import 'package:farm_tpf/utils/pref.dart';
  24. import 'package:farm_tpf/utils/validators.dart';
  25. import 'package:flutter/material.dart';
  26. import 'package:flutter/scheduler.dart';
  27. import 'package:flutter_bloc/flutter_bloc.dart';
  28. import 'package:get/get.dart';
  29. import 'package:keyboard_dismisser/keyboard_dismisser.dart';
  30. import 'package:farm_tpf/utils/formatter.dart';
  31. import 'controller/ChangeFieldInForm.dart';
  32. import 'controller/ChangeSupplyUsing.dart';
  33. import 'controller/ChangeWorker.dart';
  34. import 'dung/widget_dung_supply.dart';
  35. import 'nursery/widget_worker.dart';
  36. import 'plant/widget_plant_supply.dart';
  37. import 'spraying/widget_spraying_supply.dart';
  38. import 'state_management_helper/change_dropdown_controller.dart';
  39. import 'state_management_helper/change_file_controller.dart';
  40. import 'util_action.dart';
  41. class ActionScreen extends StatefulWidget {
  42. final bool isEdit;
  43. final int cropId;
  44. final int idAction;
  45. final String activityType;
  46. final String title;
  47. final int activityId;
  48. ActionScreen(
  49. {@required this.isEdit,
  50. @required this.cropId,
  51. @required this.idAction,
  52. @required this.title,
  53. @required this.activityType,
  54. @required this.activityId});
  55. @override
  56. _ActionScreenState createState() => _ActionScreenState();
  57. }
  58. class _ActionScreenState extends State<ActionScreen> {
  59. var _formKey = GlobalKey<FormState>();
  60. var pref = LocalPref();
  61. final _executeByController = TextEditingController();
  62. DateTime executeTime = DateTime.now();
  63. List<String> filePaths = <String>[];
  64. var changeFileController = Get.put(ChangeFileController());
  65. Map<String, TextEditingController> textFieldControllers = {};
  66. Map<String, String> valueObjects = {};
  67. var _requestActivity = RequestActivity();
  68. final _repository = Repository();
  69. var _actionUIForm = ActionUIForm();
  70. var _nurseryDetails = <TbNurseryDetailsDTO>[];
  71. var _supplyUsings = <SuppliesUsing>[];
  72. var _previousGroupFieldName = '';
  73. Future<Null> getSharedPrefs() async {
  74. var currentFullName = await pref.getString(DATA_CONST.CURRENT_FULL_NAME);
  75. _executeByController.text = currentFullName ?? "";
  76. }
  77. @override
  78. void initState() {
  79. super.initState();
  80. getSharedPrefs();
  81. changeFileController.initValue();
  82. }
  83. _submitForm() async {
  84. switch (widget.activityType) {
  85. case 'ACTIVE_TYPE_NURSERY':
  86. if (Get.find<ChangeFieldFormSupply>().isChanged) {
  87. Utils.showDialog(
  88. title: 'Thông tin người thực hiện chưa cập nhật',
  89. message: 'Bạn có muốn cập nhật',
  90. textConfirm: 'Tiếp tục',
  91. textCancel: 'Xem lại',
  92. onConfirm: () {
  93. Get.back();
  94. _validateInputs();
  95. });
  96. } else {
  97. _validateInputs();
  98. }
  99. break;
  100. case 'ACTIVE_TYPE_PLANTING':
  101. case 'ACTIVE_TYPE_FERTILIZE':
  102. case 'ACTIVE_TYPE_SPRAYING_PESTICIDES':
  103. if (Get.find<ChangeFieldFormSupply>().isChanged) {
  104. Utils.showDialogConfirmSupply(onConfirm: () {
  105. Get.back();
  106. _validateInputs();
  107. });
  108. } else {
  109. _validateInputs();
  110. }
  111. break;
  112. default:
  113. _validateInputs();
  114. break;
  115. }
  116. }
  117. _validateInputs() async {
  118. if (_formKey.currentState.validate()) {
  119. _formKey.currentState.save();
  120. try {
  121. LoadingDialog.showLoadingDialog(context);
  122. filePaths = Get.find<ChangeFileController>().newFiles;
  123. //Create request general model
  124. _requestActivity
  125. ..tbActivityTypeId = widget.idAction
  126. ..tbCropId = widget.cropId;
  127. if (_actionUIForm.activityExtendTypeDTOList.isNotEmpty) {
  128. _requestActivity
  129. ..externalTable = _actionUIForm
  130. ?.activityExtendTypeDTOList?.first?.externalTable ??
  131. '';
  132. }
  133. filePaths = Get.find<ChangeFileController>().newFiles;
  134. textFieldControllers.forEach((key, value) {
  135. print(textFieldControllers[key].text);
  136. valueObjects[key] = textFieldControllers[key].text;
  137. });
  138. //tbObjectUpdateDTOList
  139. var _objectPrameters = <TbObjectUpdateDTO>[];
  140. if (widget.isEdit) {
  141. // Edit
  142. if (_requestActivity.tbObjectUpdateDTOList != null) {
  143. _requestActivity.tbObjectUpdateDTOList.forEach((element) {
  144. print(valueObjects[element.tbObjectParameterId.toString()]);
  145. var updateValue = '';
  146. if (Validators.stringNotNullOrEmpty(
  147. valueObjects[element.tbObjectParameterId.toString()])) {
  148. updateValue =
  149. valueObjects[element.tbObjectParameterId.toString()];
  150. } else {
  151. updateValue = element.index;
  152. }
  153. var objectUpdate = TbObjectUpdateDTO()
  154. ..id = element.id
  155. ..tbObjectParameterId = element.tbObjectParameterId
  156. ..index = updateValue;
  157. _objectPrameters.add(objectUpdate);
  158. });
  159. _requestActivity.tbObjectUpdateDTOList = _objectPrameters;
  160. }
  161. } else {
  162. //Add new
  163. valueObjects.forEach((key, value) {
  164. var objectUpdate = TbObjectUpdateDTO()
  165. ..tbObjectParameterId = int.tryParse(key)
  166. ..index = value;
  167. _objectPrameters.add(objectUpdate);
  168. });
  169. _requestActivity.tbObjectUpdateDTOList = _objectPrameters;
  170. }
  171. //CHECK NURSERY
  172. if (widget.activityType == 'ACTIVE_TYPE_NURSERY') {
  173. _requestActivity.tbNurseryDetailsDTOList = _nurseryDetails;
  174. } else if (widget.activityType == 'ACTIVE_TYPE_PLANTING' ||
  175. widget.activityType == 'ACTIVE_TYPE_FERTILIZE' ||
  176. widget.activityType == 'ACTIVE_TYPE_SPRAYING_PESTICIDES') {
  177. _requestActivity.tbSuppliesUsingDetailsDTOs = _supplyUsings;
  178. }
  179. //delete images
  180. _requestActivity.deletedImages =
  181. Get.find<ChangeFileController>().deleteFiles;
  182. //convert data to json
  183. var activityCommonData =
  184. jsonEncode(_requestActivity.toJson()).toString();
  185. print(activityCommonData);
  186. if (widget.activityId < 0) {
  187. //ADD New
  188. _repository.createActionCommon((data) {
  189. LoadingDialog.hideLoadingDialog(context);
  190. Get.back(result: 'ok');
  191. Utils.showSnackBarSuccess(message: label_add_success);
  192. }, (error) {
  193. LoadingDialog.hideLoadingDialog(context);
  194. Utils.showSnackBarError(message: AppException.handleError(error));
  195. },
  196. activityType: widget.activityType,
  197. activityData: activityCommonData,
  198. filePaths: filePaths);
  199. } else {
  200. //UPDATE
  201. _repository.updateActionCommon((data) {
  202. LoadingDialog.hideLoadingDialog(context);
  203. Get.back(result: 'ok');
  204. Utils.showSnackBarSuccess(message: label_update_success);
  205. }, (error) {
  206. LoadingDialog.hideLoadingDialog(context);
  207. Utils.showSnackBarError(message: AppException.handleError(error));
  208. },
  209. activityType: widget.activityType,
  210. activityData: activityCommonData,
  211. filePaths: filePaths);
  212. }
  213. //ADD NEW
  214. //Update
  215. } catch (e) {
  216. LoadingDialog.hideLoadingDialog(context);
  217. print(e.toString());
  218. }
  219. } else {
  220. //
  221. }
  222. }
  223. Widget _btnExecuteTimePicker() {
  224. return WidgetFieldDateTimePicker(
  225. initDateTime: executeTime,
  226. onUpdateDateTime: (selectedDate) {
  227. _requestActivity.executeDate =
  228. selectedDate.convertLocalDateTimeToStringUtcDateTime();
  229. });
  230. }
  231. Widget _executeByField() {
  232. return TextFormField(
  233. keyboardType: TextInputType.text,
  234. decoration: InputDecoration(labelText: "Người thực hiện"),
  235. enabled: false,
  236. controller: _executeByController,
  237. onSaved: (newValue) {},
  238. );
  239. }
  240. //
  241. // GENERATE DYNAMIC FORM
  242. //
  243. Widget groupName(String name) {
  244. if (_previousGroupFieldName == name ||
  245. !Validators.stringNotNullOrEmpty(name)) {
  246. return SizedBox();
  247. } else {
  248. _previousGroupFieldName = name ?? '';
  249. return Container(
  250. child: Row(
  251. children: [
  252. Container(
  253. width: 24,
  254. height: 0.5,
  255. color: Colors.green,
  256. ),
  257. Text(' ${name ?? ''} '),
  258. Expanded(
  259. child: Container(
  260. width: 5,
  261. height: 0.5,
  262. color: Colors.green,
  263. ),
  264. ),
  265. ],
  266. ),
  267. );
  268. }
  269. }
  270. Widget generateField(List<ActionUIField> fields) {
  271. return Wrap(
  272. children: [
  273. ListView.separated(
  274. shrinkWrap: true,
  275. physics: NeverScrollableScrollPhysics(),
  276. itemCount: fields.length,
  277. separatorBuilder: (context, index) {
  278. return SizedBox(
  279. height: 8,
  280. );
  281. },
  282. itemBuilder: (context, index) {
  283. var field = fields[index];
  284. if (field.tbControlTypeName == 'text') {
  285. return Column(
  286. children: [
  287. groupName(field.groupName),
  288. TextFormField(
  289. keyboardType: TextInputType.text,
  290. decoration: InputDecoration(labelText: field.description),
  291. controller: textFieldControllers[field.id.toString()],
  292. onSaved: (newValue) {},
  293. validator: field.isMandatory
  294. ? (String value) {
  295. return Validators.validateNotNullOrEmpty(
  296. value, 'Vui lòng nhập ${field.description}');
  297. }
  298. : null,
  299. ),
  300. ],
  301. );
  302. } else if (field.tbControlTypeName == 'number') {
  303. return Column(
  304. children: [
  305. groupName(field.groupName),
  306. WidgetTextFormFieldNumber(
  307. hintValue: field.description,
  308. textController: textFieldControllers[field.id.toString()],
  309. onSaved: (newValue) {},
  310. validator: field.isMandatory
  311. ? (String value) {
  312. return Validators.validNumberOrEmpty(
  313. value, 'Vui lòng nhập ${field.description}');
  314. }
  315. : null,
  316. ),
  317. ],
  318. );
  319. } else if (field.tbControlTypeName == 'textarea') {
  320. return Column(
  321. children: [
  322. groupName(field.groupName),
  323. TextFieldAreaWidget(
  324. hint: field.description,
  325. controller: textFieldControllers[field.id.toString()],
  326. onSaved: (newValue) {}),
  327. ],
  328. );
  329. } else if (field.tbControlTypeName == 'dropdown' ||
  330. field.tbControlTypeName == 'radiobutton') {
  331. return Column(
  332. children: [
  333. groupName(field.groupName),
  334. DropdownSupplyWidget(
  335. titleName: field.description ?? '',
  336. tbSupply: field.tbActivityExtendTypeExternalTable ?? '',
  337. tag: field.name,
  338. value: field.description,
  339. hint:
  340. '${field.description} ${field.isMandatory ? '*' : ''}',
  341. condition: field.tbActivityExtendTypeCondition,
  342. invalidMessage: '',
  343. onPressed: (commonData) {
  344. valueObjects[field.id.toString()] =
  345. commonData.id.toString();
  346. }),
  347. ],
  348. );
  349. } else if (field.tbControlTypeName == 'date') {
  350. return Column(
  351. children: [
  352. groupName(field.groupName),
  353. FieldDateWidget(
  354. tag: field.name,
  355. value: field.description,
  356. hint:
  357. '${field.description} ${field.isMandatory ? '*' : ''}',
  358. invalidMessage: '',
  359. onPressed: (selectedDate) {
  360. valueObjects[field.id.toString()] = selectedDate
  361. .convertLocalDateTimeToStringUtcDateTime();
  362. }),
  363. ],
  364. );
  365. } else {
  366. return Container();
  367. }
  368. })
  369. ],
  370. );
  371. }
  372. //
  373. // GENERATE SUPPLY
  374. //
  375. Widget generateSupply(String activityType) {
  376. switch (activityType) {
  377. case 'ACTIVE_TYPE_NURSERY':
  378. return WidgetWorker(onChangeWorkers: (nurseryDetails) {
  379. _nurseryDetails = nurseryDetails;
  380. });
  381. break;
  382. case 'ACTIVE_TYPE_PLANTING':
  383. return Column(
  384. children: [
  385. Container(
  386. width: double.infinity,
  387. height: 16,
  388. color: Colors.grey[200],
  389. ),
  390. WidgetPlantSupply(
  391. currentItems: [],
  392. onChangeSupplies: (value) {
  393. _supplyUsings = value;
  394. }),
  395. ],
  396. );
  397. break;
  398. case 'ACTIVE_TYPE_FERTILIZE':
  399. return Column(
  400. children: [
  401. Container(
  402. width: double.infinity,
  403. height: 16,
  404. color: Colors.grey[200],
  405. ),
  406. WidgetDungSupply(
  407. currentItems: [],
  408. onChangeSupplies: (value) {
  409. _supplyUsings = value;
  410. }),
  411. ],
  412. );
  413. break;
  414. case 'ACTIVE_TYPE_SPRAYING_PESTICIDES':
  415. return Column(
  416. children: [
  417. Container(
  418. width: double.infinity,
  419. height: 16,
  420. color: Colors.grey[200],
  421. ),
  422. WidgetSprayingSupply(
  423. currentItems: [],
  424. onChangeSupplies: (value) {
  425. _supplyUsings = value;
  426. }),
  427. ],
  428. );
  429. break;
  430. default:
  431. return Container();
  432. break;
  433. }
  434. }
  435. void showDataWhenEdit(BuildContext context) {
  436. //Show media
  437. try {
  438. if (Validators.stringNotNullOrEmpty(_requestActivity.media)) {
  439. BlocProvider.of<MediaHelperBloc>(context).add(ChangeListMedia(
  440. items: UtilAction.convertFilePathToMedia(_requestActivity.media)));
  441. }
  442. } catch (e) {
  443. print(e);
  444. }
  445. SchedulerBinding.instance.addPostFrameCallback((_) {
  446. if (widget.activityType == 'ACTIVE_TYPE_PLANTING' ||
  447. widget.activityType == 'ACTIVE_TYPE_FERTILIZE' ||
  448. widget.activityType == 'ACTIVE_TYPE_SPRAYING_PESTICIDES') {
  449. //list supply
  450. Get.find<ChangeSupplyUsing>()
  451. .changeInitList(_requestActivity.tbSuppliesUsingDetailsDTOs);
  452. } else if (widget.activityType == 'ACTIVE_TYPE_NURSERY') {
  453. //list nursery
  454. Get.find<ChangeWorker>()
  455. .changeInitList(_requestActivity.tbNurseryDetailsDTOList);
  456. }
  457. });
  458. //Show value textfield
  459. if (_requestActivity.tbObjectUpdateDTOList != null) {
  460. print(textFieldControllers.keys.toList());
  461. _requestActivity.tbObjectUpdateDTOList.forEach((element) {
  462. if (element.tbObjectParameterDTO?.tbControlTypeName == 'text' ||
  463. element.tbObjectParameterDTO?.tbControlTypeName == 'textarea') {
  464. SchedulerBinding.instance.addPostFrameCallback((_) {
  465. textFieldControllers[element.tbObjectParameterId.toString()].text =
  466. element.index;
  467. });
  468. } else if (element.tbObjectParameterDTO?.tbControlTypeName ==
  469. 'number') {
  470. SchedulerBinding.instance.addPostFrameCallback((_) {
  471. textFieldControllers[element.tbObjectParameterId.toString()].text =
  472. element.index.formatStringToStringDecimal();
  473. });
  474. } else {
  475. SchedulerBinding.instance.addPostFrameCallback((_) {
  476. if (element.tbObjectParameterDTO?.tbControlTypeName == 'dropdown' ||
  477. element.tbObjectParameterDTO?.tbControlTypeName == 'radio') {
  478. var dropdownValueName = '';
  479. if (element.tbObjectParameterDTO
  480. ?.tbActivityExtendTypeDropDownDTOList?.isNotEmpty ||
  481. element.tbObjectParameterDTO
  482. ?.tbActivityExtendTypeDropDownDTOList !=
  483. null) {
  484. element
  485. .tbObjectParameterDTO?.tbActivityExtendTypeDropDownDTOList
  486. .forEach((dropdownData) {
  487. if (dropdownData.id == int.tryParse(element.index)) {
  488. dropdownValueName = dropdownData.name;
  489. }
  490. });
  491. }
  492. var commonData = CommonData()
  493. ..id = int.tryParse(element.index)
  494. ..name = dropdownValueName;
  495. Get.find<ChangeDropdownController>(
  496. tag: element.tbObjectParameterDTO?.name)
  497. .change(commonData);
  498. } else if (element.tbObjectParameterDTO?.tbControlTypeName ==
  499. 'date') {
  500. Get.find<ChangeDateTimePicker>(
  501. tag: element.tbObjectParameterDTO?.name)
  502. .change(element.index
  503. .convertStringServerDateTimeToLocalDateTime());
  504. }
  505. });
  506. }
  507. });
  508. } else {
  509. //
  510. }
  511. }
  512. @override
  513. Widget build(BuildContext context) => KeyboardDismisser(
  514. gestures: [
  515. GestureType.onTap,
  516. GestureType.onPanUpdateDownDirection,
  517. ],
  518. child: Scaffold(
  519. backgroundColor: Colors.white,
  520. appBar: AppBarWidget(
  521. isBack: true,
  522. action: InkWell(
  523. child: Text(
  524. 'Lưu',
  525. style: TextStyle(
  526. color: Colors.red, fontWeight: FontWeight.normal),
  527. ),
  528. onTap: () {
  529. FocusScopeNode currentFocus = FocusScope.of(context);
  530. if (!currentFocus.hasPrimaryFocus) {
  531. currentFocus.unfocus();
  532. }
  533. _submitForm();
  534. },
  535. ),
  536. ),
  537. body: MultiBlocProvider(
  538. providers: [
  539. BlocProvider<ActionUiCubit>(
  540. create: (context) =>
  541. ActionUiCubit(repository: Repository())
  542. ..getActionUIForm(
  543. actionId: widget.idAction,
  544. actionType: widget.activityType,
  545. isEdit: widget.isEdit,
  546. activityId: widget.activityId)),
  547. BlocProvider<MediaHelperBloc>(
  548. create: (context) =>
  549. MediaHelperBloc()..add(ChangeListMedia(items: [])),
  550. )
  551. ],
  552. child: Form(
  553. key: _formKey,
  554. child: SafeArea(
  555. child: BlocBuilder<ActionUiCubit, ActionUiState>(
  556. builder: (context, state) {
  557. if (state is ActionUiLoading) {
  558. print('loading...');
  559. return Center(
  560. child: LoadingListPage(),
  561. );
  562. } else if (state is ActionUiSuccess) {
  563. _actionUIForm = state.actionUIForm;
  564. _requestActivity = state.activityDetail;
  565. //CREATE UI
  566. _actionUIForm.objectParameterDTOList
  567. .forEach((element) {
  568. //generate controller
  569. if (element.tbControlTypeName == 'text' ||
  570. element.tbControlTypeName == 'number' ||
  571. element.tbControlTypeName == 'textarea') {
  572. var textEditingController =
  573. new TextEditingController();
  574. textFieldControllers.putIfAbsent(
  575. element.id.toString(),
  576. () => textEditingController);
  577. }
  578. // generate value each parameter
  579. valueObjects.putIfAbsent(
  580. element.id.toString(), () => '');
  581. });
  582. //SHOW EDIT DATA
  583. if (widget.isEdit) {
  584. showDataWhenEdit(context);
  585. }
  586. return SingleChildScrollView(
  587. child: Column(
  588. children: [
  589. Padding(
  590. padding: const EdgeInsets.all(8.0),
  591. child: Column(
  592. children: <Widget>[
  593. Container(
  594. width: double.infinity,
  595. child: Text(
  596. "Ngày thực hiện *",
  597. style: TextStyle(
  598. color: Colors.black54,
  599. fontSize: 13.0),
  600. ),
  601. ),
  602. _btnExecuteTimePicker(),
  603. SizedBox(
  604. height: 8.0,
  605. ),
  606. generateField(_actionUIForm
  607. .objectParameterDTOList),
  608. _executeByField(),
  609. SizedBox(
  610. height: 8.0,
  611. ),
  612. ],
  613. ),
  614. ),
  615. generateSupply(widget.activityType),
  616. Container(
  617. width: double.infinity,
  618. height: 16,
  619. color: Colors.grey[200],
  620. ),
  621. BlocBuilder<MediaHelperBloc,
  622. MediaHelperState>(
  623. builder: (context, state) {
  624. if (state is MediaHelperSuccess) {
  625. return WidgetMediaPicker(
  626. currentItems: state.items,
  627. onChangeFiles: (newPathFiles,
  628. deletePathFiles) async {
  629. Get.find<ChangeFileController>()
  630. .change(newPathFiles,
  631. deletePathFiles);
  632. });
  633. } else {
  634. return Center(
  635. child: CircularProgressIndicator());
  636. }
  637. }),
  638. Padding(
  639. padding: const EdgeInsets.all(8.0),
  640. child: ButtonWidget(
  641. title: 'CẬP NHẬT',
  642. onPressed: () {
  643. FocusScopeNode currentFocus =
  644. FocusScope.of(context);
  645. if (!currentFocus.hasPrimaryFocus) {
  646. currentFocus.unfocus();
  647. }
  648. _submitForm();
  649. }),
  650. ),
  651. ],
  652. ),
  653. );
  654. } else if (state is ActionUiFailure) {
  655. return Center(child: Text(state.errorString));
  656. }
  657. return Container();
  658. },
  659. ),
  660. )),
  661. )));
  662. @override
  663. void dispose() {
  664. _executeByController.dispose();
  665. textFieldControllers.forEach((key, value) {
  666. textFieldControllers[key].dispose();
  667. });
  668. super.dispose();
  669. }
  670. }