| @@ -5,7 +5,7 @@ class CropPlot { | |||
| List<Activities> activities; | |||
| String sowingDate; | |||
| int soakSeedsTime; | |||
| int seedIncubationTime; | |||
| num seedIncubationTime; | |||
| int numberPlants; | |||
| int numberCurrentPlants; | |||
| String endOfFarmingDate; | |||
| @@ -0,0 +1,84 @@ | |||
| import 'package:farm_tpf/utils/validators.dart'; | |||
| import 'package:flutter/material.dart'; | |||
| class DropdownSupplyWidget extends StatelessWidget { | |||
| final String condition; | |||
| final String hint; | |||
| final String value; | |||
| final Function onPressed; | |||
| final String invalidMessage; | |||
| DropdownSupplyWidget( | |||
| {@required this.hint, | |||
| this.value, | |||
| @required this.condition, | |||
| @required this.onPressed, | |||
| this.invalidMessage}); | |||
| @override | |||
| Widget build(BuildContext context) { | |||
| return SizedBox( | |||
| width: double.infinity, | |||
| height: Validators.stringNotNullOrEmpty(invalidMessage) ? 85 : 65, | |||
| child: Column( | |||
| crossAxisAlignment: CrossAxisAlignment.start, | |||
| children: [ | |||
| Text( | |||
| hint ?? '', | |||
| style: TextStyle( | |||
| color: Validators.stringNotNullOrEmpty(invalidMessage) | |||
| ? Colors.red | |||
| : Colors.black54, | |||
| fontSize: 13.0), | |||
| ), | |||
| SizedBox( | |||
| width: double.infinity, | |||
| height: 44, | |||
| child: FlatButton( | |||
| padding: EdgeInsets.only( | |||
| top: 0.0, right: 0.0, bottom: 0.0, left: 0.0), | |||
| onPressed: onPressed, | |||
| child: Column( | |||
| crossAxisAlignment: CrossAxisAlignment.start, | |||
| children: [ | |||
| Container( | |||
| padding: EdgeInsets.only( | |||
| top: 0.0, right: 0.0, bottom: 10.5, left: 0.0), | |||
| decoration: BoxDecoration( | |||
| border: Border( | |||
| bottom: BorderSide( | |||
| width: 0.5, | |||
| color: Validators.stringNotNullOrEmpty( | |||
| invalidMessage) | |||
| ? Colors.red | |||
| : Colors.black54)), | |||
| ), | |||
| child: Row( | |||
| children: [ | |||
| Expanded( | |||
| child: Text(value ?? hint, | |||
| style: TextStyle( | |||
| fontSize: 16.0, | |||
| color: Colors.black45))), | |||
| Icon( | |||
| Icons.arrow_drop_down, | |||
| color: Colors.grey, | |||
| ), | |||
| ], | |||
| )) | |||
| ], | |||
| )), | |||
| ), | |||
| Validators.stringNotNullOrEmpty(invalidMessage) | |||
| ? Text( | |||
| invalidMessage ?? '', | |||
| style: TextStyle( | |||
| fontSize: 12.0, | |||
| color: Colors.red, | |||
| fontWeight: FontWeight.normal), | |||
| textAlign: TextAlign.left, | |||
| ) | |||
| : SizedBox(), | |||
| ], | |||
| ), | |||
| ); | |||
| } | |||
| } | |||
| @@ -0,0 +1,82 @@ | |||
| import 'package:farm_tpf/utils/validators.dart'; | |||
| import 'package:flutter/material.dart'; | |||
| class FieldDateWidget extends StatelessWidget { | |||
| final String hint; | |||
| final String value; | |||
| final Function onPressed; | |||
| final String invalidMessage; | |||
| FieldDateWidget( | |||
| {@required this.hint, | |||
| this.value, | |||
| @required this.onPressed, | |||
| this.invalidMessage}); | |||
| @override | |||
| Widget build(BuildContext context) { | |||
| return SizedBox( | |||
| width: double.infinity, | |||
| height: Validators.stringNotNullOrEmpty(invalidMessage) ? 85 : 65, | |||
| child: Column( | |||
| crossAxisAlignment: CrossAxisAlignment.start, | |||
| children: [ | |||
| Text( | |||
| hint ?? '', | |||
| style: TextStyle( | |||
| color: Validators.stringNotNullOrEmpty(invalidMessage) | |||
| ? Colors.red | |||
| : Colors.black54, | |||
| fontSize: 13.0), | |||
| ), | |||
| SizedBox( | |||
| width: double.infinity, | |||
| height: 44, | |||
| child: FlatButton( | |||
| padding: EdgeInsets.only( | |||
| top: 0.0, right: 0.0, bottom: 0.0, left: 0.0), | |||
| onPressed: onPressed, | |||
| child: Column( | |||
| crossAxisAlignment: CrossAxisAlignment.start, | |||
| children: [ | |||
| Container( | |||
| padding: EdgeInsets.only( | |||
| top: 0.0, right: 0.0, bottom: 10.5, left: 0.0), | |||
| decoration: BoxDecoration( | |||
| border: Border( | |||
| bottom: BorderSide( | |||
| width: 0.5, | |||
| color: Validators.stringNotNullOrEmpty( | |||
| invalidMessage) | |||
| ? Colors.red | |||
| : Colors.black54)), | |||
| ), | |||
| child: Row( | |||
| children: [ | |||
| Expanded( | |||
| child: Text(value ?? hint, | |||
| style: TextStyle( | |||
| fontSize: 16.0, | |||
| color: Colors.black45))), | |||
| Icon( | |||
| Icons.date_range, | |||
| color: Colors.grey, | |||
| ), | |||
| ], | |||
| )) | |||
| ], | |||
| )), | |||
| ), | |||
| Validators.stringNotNullOrEmpty(invalidMessage) | |||
| ? Text( | |||
| invalidMessage ?? '', | |||
| style: TextStyle( | |||
| fontSize: 12.0, | |||
| color: Colors.red, | |||
| fontWeight: FontWeight.normal), | |||
| textAlign: TextAlign.left, | |||
| ) | |||
| : SizedBox(), | |||
| ], | |||
| ), | |||
| ); | |||
| } | |||
| } | |||
| @@ -0,0 +1,26 @@ | |||
| import 'package:farm_tpf/utils/const_color.dart'; | |||
| import 'package:flutter/material.dart'; | |||
| class TextFieldAreaWidget extends StatelessWidget { | |||
| final String hint; | |||
| final TextEditingController controller; | |||
| final Function onSaved; | |||
| TextFieldAreaWidget( | |||
| {@required this.hint, @required this.onSaved, @required this.controller}); | |||
| @override | |||
| Widget build(BuildContext context) { | |||
| return Container( | |||
| padding: EdgeInsets.all(8), | |||
| decoration: BoxDecoration( | |||
| color: AppColors.YELLOW.withOpacity(0.1), | |||
| borderRadius: BorderRadius.circular(8)), | |||
| child: TextFormField( | |||
| keyboardType: TextInputType.text, | |||
| controller: controller, | |||
| decoration: InputDecoration( | |||
| labelText: hint, hintText: hint, border: InputBorder.none), | |||
| onSaved: onSaved, | |||
| ), | |||
| ); | |||
| } | |||
| } | |||
| @@ -1,11 +1,16 @@ | |||
| import 'package:farm_tpf/custom_model/action_form/ActionUIField.dart'; | |||
| import 'package:farm_tpf/custom_model/action_form/ActionUIForm.dart'; | |||
| import 'package:farm_tpf/data/repository/repository.dart'; | |||
| import 'package:farm_tpf/presentation/custom_widgets/app_bar_widget.dart'; | |||
| import 'package:farm_tpf/presentation/custom_widgets/bloc/media_helper_bloc.dart'; | |||
| import 'package:farm_tpf/presentation/custom_widgets/button_widget.dart'; | |||
| import 'package:farm_tpf/presentation/custom_widgets/loading_list_page.dart'; | |||
| import 'package:farm_tpf/presentation/custom_widgets/dropdown_supply_widget.dart'; | |||
| import 'package:farm_tpf/presentation/custom_widgets/widget_action_field_date.dart'; | |||
| import 'package:farm_tpf/presentation/custom_widgets/widget_field_time_picker.dart'; | |||
| import 'package:farm_tpf/presentation/custom_widgets/widget_loading.dart'; | |||
| import 'package:farm_tpf/presentation/custom_widgets/widget_media_picker.dart'; | |||
| import 'package:farm_tpf/presentation/custom_widgets/widget_text_field_area.dart'; | |||
| import 'package:farm_tpf/presentation/custom_widgets/widget_text_form_field.dart'; | |||
| import 'package:farm_tpf/presentation/screens/actions/cubit/action_ui_cubit.dart'; | |||
| import 'package:farm_tpf/utils/pref.dart'; | |||
| import 'package:farm_tpf/utils/validators.dart'; | |||
| @@ -33,6 +38,7 @@ class _ActionScreenState extends State<ActionScreen> { | |||
| DateTime executeTime = DateTime.now(); | |||
| List<String> filePaths = List<String>(); | |||
| var changeFileController = Get.put(ChangeFileController()); | |||
| Map<String, TextEditingController> textFieldControllers = {}; | |||
| Future<Null> getSharedPrefs() async { | |||
| var currentFullName = await pref.getString(DATA_CONST.CURRENT_FULL_NAME); | |||
| @@ -52,8 +58,12 @@ class _ActionScreenState extends State<ActionScreen> { | |||
| filePaths = Get.find<ChangeFileController>().newFiles; | |||
| //Create request general model | |||
| try { | |||
| LoadingDialog.hideLoadingDialog(context); | |||
| //ADD NEW | |||
| //Update | |||
| textFieldControllers.forEach((key, value) { | |||
| print(textFieldControllers[key].text); | |||
| }); | |||
| } catch (e) { | |||
| LoadingDialog.hideLoadingDialog(context); | |||
| print(e.toString()); | |||
| @@ -81,6 +91,76 @@ class _ActionScreenState extends State<ActionScreen> { | |||
| ); | |||
| } | |||
| // | |||
| // GENERATE DYNAMIC FORM | |||
| // | |||
| Widget generateTextField(List<ActionUIField> fields) { | |||
| return Container( | |||
| height: fields.length * 70.0, | |||
| child: ListView.separated( | |||
| itemCount: fields.length, | |||
| separatorBuilder: (context, index) { | |||
| return SizedBox( | |||
| height: 8, | |||
| ); | |||
| }, | |||
| itemBuilder: (context, index) { | |||
| var field = fields[index]; | |||
| if (field.tbControlTypeName == 'text') { | |||
| return TextFormField( | |||
| keyboardType: TextInputType.text, | |||
| decoration: InputDecoration(labelText: field.description), | |||
| controller: textFieldControllers[field.id.toString()], | |||
| onSaved: (newValue) {}, | |||
| validator: field.isMandatory == 0 | |||
| ? (String value) { | |||
| return Validators.validateNotNullOrEmpty( | |||
| value, 'Vui lòng nhập ${field.description}'); | |||
| } | |||
| : null, | |||
| ); | |||
| } else if (field.tbControlTypeName == 'number') { | |||
| return WidgetTextFormFieldNumber( | |||
| hintValue: field.description, | |||
| textController: textFieldControllers[field.id.toString()], | |||
| onSaved: (newValue) {}, | |||
| validator: field.isMandatory == 1 | |||
| ? (String value) { | |||
| return Validators.validNumberOrEmpty( | |||
| value, 'Vui lòng nhập ${field.description}'); | |||
| } | |||
| : null, | |||
| ); | |||
| } else if (field.tbControlTypeName == 'textarea') { | |||
| return TextFieldAreaWidget( | |||
| hint: field.description, | |||
| controller: textFieldControllers[field.id.toString()], | |||
| onSaved: (newValue) {}); | |||
| } else if (field.tbControlTypeName == 'dropdown') { | |||
| return DropdownSupplyWidget( | |||
| value: field.description, | |||
| hint: | |||
| '${field.description} ${field.isMandatory == 1 ? '*' : ''}', | |||
| condition: field.tbActivityExtendTypeCondition, | |||
| invalidMessage: '', | |||
| onPressed: () {}); | |||
| } else if (field.tbControlTypeName == 'date') { | |||
| return FieldDateWidget( | |||
| value: field.description, | |||
| hint: | |||
| '${field.description} ${field.isMandatory == 1 ? '*' : ''}', | |||
| invalidMessage: '', | |||
| onPressed: () {}); | |||
| } else if (field.tbControlTypeName == 'radiobutton') { | |||
| return Text(field.tbControlTypeName); | |||
| } else { | |||
| return Text(field.tbControlTypeName); | |||
| } | |||
| }), | |||
| ); | |||
| } | |||
| @override | |||
| Widget build(BuildContext context) => KeyboardDismisser( | |||
| gestures: [ | |||
| @@ -98,7 +178,13 @@ class _ActionScreenState extends State<ActionScreen> { | |||
| style: TextStyle( | |||
| color: Colors.red, fontWeight: FontWeight.normal), | |||
| ), | |||
| onTap: () {}, | |||
| onTap: () { | |||
| FocusScopeNode currentFocus = FocusScope.of(context); | |||
| if (!currentFocus.hasPrimaryFocus) { | |||
| currentFocus.unfocus(); | |||
| } | |||
| _validateInputs(); | |||
| }, | |||
| ), | |||
| ), | |||
| body: KeyboardDismisser( | |||
| @@ -123,7 +209,15 @@ class _ActionScreenState extends State<ActionScreen> { | |||
| print('loading...'); | |||
| return Center(child: CircularProgressIndicator()); | |||
| } else if (state is ActionUiSuccess) { | |||
| print('success'); | |||
| var actionUiForm = state.item as ActionUIForm; | |||
| actionUiForm.objectParameterDTOList | |||
| .forEach((element) { | |||
| var textEditingController = | |||
| new TextEditingController(); | |||
| textFieldControllers.putIfAbsent( | |||
| element.id.toString(), | |||
| () => textEditingController); | |||
| }); | |||
| return Column( | |||
| children: [ | |||
| Padding( | |||
| @@ -143,6 +237,8 @@ class _ActionScreenState extends State<ActionScreen> { | |||
| SizedBox( | |||
| height: 8.0, | |||
| ), | |||
| generateTextField(actionUiForm | |||
| .objectParameterDTOList), | |||
| _executeByField(), | |||
| SizedBox( | |||
| height: 8.0, | |||
| @@ -39,10 +39,11 @@ class PlotParameterBloc extends Bloc<PlotParameterEvent, PlotParameterState> { | |||
| } | |||
| if (state is PlotParameterSuccess) { | |||
| if (_periodicSubscription == null) { | |||
| _periodicSubscription ??= | |||
| Stream.periodic(const Duration(seconds: 5), (x) => x).listen( | |||
| (_) => add(OnRefresh(cropId: event.cropId)), | |||
| onError: (error) => print("Do something with $error")); | |||
| //TODO: open when release | |||
| // _periodicSubscription ??= | |||
| // Stream.periodic(const Duration(seconds: 5), (x) => x).listen( | |||
| // (_) => add(OnRefresh(cropId: event.cropId)), | |||
| // onError: (error) => print("Do something with $error")); | |||
| } else { | |||
| _periodicSubscription.resume(); | |||
| } | |||
| @@ -74,6 +75,7 @@ class PlotParameterBloc extends Bloc<PlotParameterEvent, PlotParameterState> { | |||
| Future<void> close() async { | |||
| await _periodicSubscription?.cancel(); | |||
| _periodicSubscription = null; | |||
| print('closed subscription'); | |||
| return super.close(); | |||
| } | |||
| } | |||
| @@ -10,7 +10,7 @@ class GetPlotInfoBloc { | |||
| void getPlotInfo( | |||
| int cropId, Function(dynamic) onSuccess, Function(String) onError) async { | |||
| _repository.getPlotDetail(cropId).then((value) { | |||
| _repository.getPlotDetail(cropId, page: 0, size: 20).then((value) { | |||
| onSuccess(value); | |||
| _getPlotInfoFetcher.sink.add(value); | |||
| }).catchError((onError) { | |||
| @@ -274,7 +274,7 @@ class _PlotInformationScreenState extends State<PlotInformationScreen> | |||
| @override | |||
| void dispose() { | |||
| _descriptionController.dispose(); | |||
| _descriptionController?.dispose(); | |||
| super.dispose(); | |||
| } | |||
| @@ -29,8 +29,8 @@ class Validators { | |||
| } | |||
| static String validNumberOrEmpty(String value, String errorMessage) { | |||
| if (value.isEmpty) return null; | |||
| try { | |||
| if (value.isEmpty) return errorMessage; | |||
| var doubleValue = value.parseDoubleThousand(); | |||
| if (doubleValue > 0) { | |||
| return null; | |||