| @@ -85,6 +85,9 @@ PODS: | |||
| - Protobuf (>= 3.9.2, ~> 3.9) | |||
| - FLAnimatedImage (1.0.12) | |||
| - Flutter (1.0.0) | |||
| - FMDB (2.7.5): | |||
| - FMDB/standard (= 2.7.5) | |||
| - FMDB/standard (2.7.5) | |||
| - GoogleDataTransport (7.3.0): | |||
| - nanopb (~> 1.30906.0) | |||
| - GoogleUtilities/AppDelegateSwizzler (6.7.2): | |||
| @@ -126,6 +129,9 @@ PODS: | |||
| - SDWebImage/Core (~> 5.6) | |||
| - shared_preferences (0.0.1): | |||
| - Flutter | |||
| - sqflite (0.0.1): | |||
| - Flutter | |||
| - FMDB (~> 2.7.2) | |||
| - SwiftProtobuf (1.12.0) | |||
| - thumbnails (0.0.1): | |||
| - Flutter | |||
| @@ -143,6 +149,7 @@ DEPENDENCIES: | |||
| - package_info (from `.symlinks/plugins/package_info/ios`) | |||
| - path_provider (from `.symlinks/plugins/path_provider/ios`) | |||
| - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) | |||
| - sqflite (from `.symlinks/plugins/sqflite/ios`) | |||
| - thumbnails (from `.symlinks/plugins/thumbnails/ios`) | |||
| - video_player (from `.symlinks/plugins/video_player/ios`) | |||
| @@ -159,6 +166,7 @@ SPEC REPOS: | |||
| - FirebaseInstanceID | |||
| - FirebaseMessaging | |||
| - FLAnimatedImage | |||
| - FMDB | |||
| - GoogleDataTransport | |||
| - GoogleUtilities | |||
| - MTBBarcodeScanner | |||
| @@ -190,6 +198,8 @@ EXTERNAL SOURCES: | |||
| :path: ".symlinks/plugins/path_provider/ios" | |||
| shared_preferences: | |||
| :path: ".symlinks/plugins/shared_preferences/ios" | |||
| sqflite: | |||
| :path: ".symlinks/plugins/sqflite/ios" | |||
| thumbnails: | |||
| :path: ".symlinks/plugins/thumbnails/ios" | |||
| video_player: | |||
| @@ -213,6 +223,7 @@ SPEC CHECKSUMS: | |||
| FirebaseMessaging: 29543feb343b09546ab3aa04d008ee8595b43c44 | |||
| FLAnimatedImage: 4a0b56255d9b05f18b6dd7ee06871be5d3b89e31 | |||
| Flutter: 0e3d915762c693b495b44d77113d4970485de6ec | |||
| FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a | |||
| GoogleDataTransport: e85fb700c9b027079ce182c3d08e12e0f9618bb4 | |||
| GoogleUtilities: 7f2f5a07f888cdb145101d6042bc4422f57e70b3 | |||
| image_picker: 9c3312491f862b28d21ecd8fdf0ee14e601b3f09 | |||
| @@ -225,6 +236,7 @@ SPEC CHECKSUMS: | |||
| SDWebImage: a990c053fff71e388a10f3357edb0be17929c9c5 | |||
| SDWebImageFLPlugin: 6c2295fb1242d44467c6c87dc5db6b0a13228fd8 | |||
| shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d | |||
| sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0 | |||
| SwiftProtobuf: 4ef85479c18ca85b5482b343df9c319c62bda699 | |||
| thumbnails: bb4f4e9bb4b51c8ae4e6ad9a2fa81373f9b634ad | |||
| video_player: 9cc823b1d9da7e8427ee591e8438bfbcde500e6e | |||
| @@ -21,24 +21,24 @@ class HttpLogInterceptor extends InterceptorsWrapper { | |||
| var token = await pref.getString(DATA_CONST.TOKEN_KEY); | |||
| options.headers["Authorization"] = "Bearer $token"; | |||
| options.receiveTimeout = 20000; | |||
| // log("onRequest: ${options.uri}\n" | |||
| // "data=${options.data}\n" | |||
| // "method=${options.method}\n" | |||
| // "headers=${options.headers}\n" | |||
| // "queryParameters=${options.queryParameters}"); | |||
| log("onRequest: ${options.uri}\n" | |||
| "data=${options.data}\n" | |||
| "method=${options.method}\n" | |||
| "headers=${options.headers}\n" | |||
| "queryParameters=${options.queryParameters}"); | |||
| return options; | |||
| } | |||
| @override | |||
| Future onResponse(Response response) { | |||
| // log("onResponse: $response"); | |||
| log("onResponse: $response"); | |||
| return super.onResponse(response); | |||
| } | |||
| @override | |||
| Future onError(DioError err) { | |||
| // log("onError: $err\n" | |||
| // "Response: ${err.response}"); | |||
| log("onError: $err\n" | |||
| "Response: ${err.response}"); | |||
| return super.onError(err); | |||
| } | |||
| } | |||
| @@ -92,6 +92,27 @@ class Repository { | |||
| } | |||
| } | |||
| Future<void> updateNursery(Function(dynamic) onSuccess, | |||
| Function(String) onError, String activityNursery, | |||
| {List<String> filePaths}) async { | |||
| var formData = FormData(); | |||
| filePaths.forEach((f) { | |||
| formData.files.add(MapEntry("images", MultipartFile.fromFileSync(f))); | |||
| }); | |||
| formData.fields.add(MapEntry("activityNursery", activityNursery)); | |||
| try { | |||
| await dio | |||
| .post("${ConstCommon.baseUrl}/api/updateNursery", data: formData) | |||
| .then((value) { | |||
| onSuccess(value); | |||
| }).catchError((onError) { | |||
| onError(AppException.handleError(onError)); | |||
| }); | |||
| } catch (e) { | |||
| onError(AppException.handleError(e)); | |||
| } | |||
| } | |||
| //Device | |||
| Future<List<Device>> getDevices() { | |||
| final client = RestClient(dio); | |||
| @@ -16,8 +16,9 @@ import 'bloc/media_helper_bloc.dart'; | |||
| import 'hoz_list_view.dart'; | |||
| class WidgetMediaPicker extends StatefulWidget { | |||
| final List<Media> currentItems; | |||
| final Function(List<String> filePaths) onChangeFiles; | |||
| WidgetMediaPicker({@required this.onChangeFiles}); | |||
| WidgetMediaPicker({this.currentItems, @required this.onChangeFiles}); | |||
| @override | |||
| _WidgetMediaPickerState createState() => _WidgetMediaPickerState(); | |||
| } | |||
| @@ -41,6 +42,7 @@ class _WidgetMediaPickerState extends State<WidgetMediaPicker> { | |||
| if (state is MediaHelperFailure) { | |||
| return Container(); | |||
| } else if (state is MediaHelperSuccess) { | |||
| currentItems = widget.currentItems ?? []; | |||
| return Container( | |||
| padding: EdgeInsets.all(8), | |||
| child: Column( | |||
| @@ -203,9 +205,15 @@ class _WidgetMediaPickerState extends State<WidgetMediaPicker> { | |||
| return BlocBuilder<MediaHelperBloc, MediaHelperState>( | |||
| builder: (context, state) { | |||
| if (state is MediaHelperSuccess) { | |||
| print("build list items length: " + state.items.length.toString()); | |||
| print("" + currentItems.length.toString()); | |||
| files = []; | |||
| currentItems.forEach((element) { | |||
| files.add(element.pathFile); | |||
| }); | |||
| return WrapContentHozListView( | |||
| itemBuilder: (context, index) { | |||
| var item = state.items[index]; | |||
| var item = currentItems[index]; | |||
| return _WidgetItemMedia( | |||
| item: item, | |||
| deleteImage: (item) { | |||
| @@ -219,7 +227,7 @@ class _WidgetMediaPickerState extends State<WidgetMediaPicker> { | |||
| separatorBuilder: (context, index) { | |||
| return SizedBox(width: 4); | |||
| }, | |||
| list: state.items, | |||
| list: currentItems, | |||
| ); | |||
| } | |||
| return Container(); | |||
| @@ -1,9 +1,12 @@ | |||
| import 'dart:convert'; | |||
| import 'package:farm_tpf/custom_model/Media.dart'; | |||
| import 'package:farm_tpf/custom_model/Nursery.dart'; | |||
| import 'package:farm_tpf/custom_model/NurseryDetail.dart'; | |||
| import 'package:farm_tpf/data/api/app_exception.dart'; | |||
| import 'package:farm_tpf/data/repository/repository.dart'; | |||
| import 'package:farm_tpf/models/index.dart'; | |||
| import 'package:farm_tpf/presentation/custom_widgets/bloc/media_helper_bloc.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/screens/actions/bloc/action_detail_bloc.dart'; | |||
| @@ -18,16 +21,23 @@ import 'package:farm_tpf/utils/pref.dart'; | |||
| import 'package:farm_tpf/utils/validators.dart'; | |||
| import 'package:flutter/material.dart'; | |||
| import 'package:flutter_bloc/flutter_bloc.dart'; | |||
| import 'package:flutter_cache_manager/flutter_cache_manager.dart'; | |||
| import 'package:flutter_datetime_picker/flutter_datetime_picker.dart'; | |||
| import 'package:fluttertoast/fluttertoast.dart'; | |||
| import 'package:get/get.dart'; | |||
| import 'package:get/state_manager.dart'; | |||
| import 'package:intl/intl.dart'; | |||
| import 'package:keyboard_dismisser/keyboard_dismisser.dart'; | |||
| import 'package:mime/mime.dart'; | |||
| import 'package:pattern_formatter/pattern_formatter.dart'; | |||
| import 'package:farm_tpf/utils/formatter.dart'; | |||
| class EditActionNurseryScreen extends StatefulWidget { | |||
| final int cropId; | |||
| final bool isEdit; | |||
| final int activityId; | |||
| EditActionNurseryScreen( | |||
| {@required this.cropId, this.isEdit = false, this.activityId}); | |||
| @override | |||
| _EditActionNurseryState createState() => _EditActionNurseryState(); | |||
| } | |||
| @@ -66,7 +76,7 @@ class _EditActionNurseryState extends State<EditActionNurseryScreen> { | |||
| _nursery.nurseryDetail = new List<NurseryDetail>(); | |||
| flutterToast = FlutterToast(context); | |||
| //UPDATE | |||
| if (_nursery.cropId != null) { | |||
| if (_nursery.id != null) { | |||
| try { | |||
| executeTime = | |||
| DateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").parse(_nursery.executeDate); | |||
| @@ -76,24 +86,61 @@ class _EditActionNurseryState extends State<EditActionNurseryScreen> { | |||
| var parsedExecuteDate = | |||
| DateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(executeTime); | |||
| _nursery.executeDate = "$parsedExecuteDate"; | |||
| //TODO: update cropid | |||
| _nursery.cropId = 1; | |||
| } | |||
| executeTimeView = DateFormat("dd/MM/yyyy HH:mm").format(executeTime); | |||
| _nursery.cropId = widget.cropId; | |||
| } | |||
| _validateInputs() async { | |||
| if (_formKey.currentState.validate()) { | |||
| _formKey.currentState.save(); | |||
| LoadingDialog.showLoadingDialog(context); | |||
| _nursery.nurseryDetail = currentNurseryDetail; | |||
| filePaths = Get.find<ChangeFileController>().files; | |||
| var activityNursery = jsonEncode(_nursery.toJson()).toString(); | |||
| _repository.createNursery((value) { | |||
| print("post ok"); | |||
| }, (error) { | |||
| print("--------------------------------"); | |||
| print(error); | |||
| }, activityNursery, filePaths: filePaths); | |||
| //ADD NEW | |||
| if (_nursery.activityId == null) { | |||
| _repository.createNursery((value) { | |||
| LoadingDialog.hideLoadingDialog(context); | |||
| Get.back(); | |||
| Get.snackbar(label_add_success, "Hoạt động ươm", | |||
| snackPosition: SnackPosition.BOTTOM); | |||
| }, (error) { | |||
| LoadingDialog.hideLoadingDialog(context); | |||
| _scaffoldKey.currentState.showSnackBar(SnackBar( | |||
| content: Row( | |||
| mainAxisAlignment: MainAxisAlignment.spaceBetween, | |||
| children: <Widget>[ | |||
| Flexible(child: Text(AppException.handleError(error))), | |||
| Icon(Icons.error), | |||
| ], | |||
| ), | |||
| backgroundColor: Colors.red, | |||
| duration: Duration(seconds: 3), | |||
| )); | |||
| }, activityNursery, filePaths: filePaths); | |||
| } else { | |||
| //UPDATE | |||
| _repository.updateNursery((value) { | |||
| LoadingDialog.hideLoadingDialog(context); | |||
| Get.back(); | |||
| Get.snackbar(label_add_success, "Hoạt động ươm", | |||
| snackPosition: SnackPosition.BOTTOM); | |||
| }, (error) { | |||
| LoadingDialog.hideLoadingDialog(context); | |||
| _scaffoldKey.currentState.showSnackBar(SnackBar( | |||
| content: Row( | |||
| mainAxisAlignment: MainAxisAlignment.spaceBetween, | |||
| children: <Widget>[ | |||
| Flexible(child: Text(AppException.handleError(error))), | |||
| Icon(Icons.error), | |||
| ], | |||
| ), | |||
| backgroundColor: Colors.red, | |||
| duration: Duration(seconds: 3), | |||
| )); | |||
| }, activityNursery, filePaths: filePaths); | |||
| } | |||
| } else { | |||
| _autoValidate = true; | |||
| } | |||
| @@ -278,17 +325,13 @@ class _EditActionNurseryState extends State<EditActionNurseryScreen> { | |||
| keyboardType: TextInputType.text, | |||
| controller: _workerNameController, | |||
| decoration: InputDecoration(labelText: "Tên công nhân"), | |||
| onSaved: (newValue) { | |||
| _nursery.description = newValue; | |||
| }, | |||
| onSaved: (newValue) {}, | |||
| ), | |||
| TextFormField( | |||
| keyboardType: TextInputType.text, | |||
| controller: _trayNumberController, | |||
| decoration: InputDecoration(labelText: "Ươm khây số"), | |||
| onSaved: (newValue) { | |||
| _nursery.description = newValue; | |||
| }, | |||
| onSaved: (newValue) {}, | |||
| ), | |||
| Align( | |||
| alignment: Alignment.centerRight, | |||
| @@ -479,10 +522,15 @@ class _EditActionNurseryState extends State<EditActionNurseryScreen> { | |||
| create: (context) => StatusAddFormBloc(), | |||
| ), | |||
| BlocProvider<ActionDetailBloc>( | |||
| create: (context) => ActionDetailBloc( | |||
| repository: Repository()) | |||
| ..add( | |||
| FetchData(isNeedFetchData: true, activityId: 1))) | |||
| create: (context) => | |||
| ActionDetailBloc(repository: Repository()) | |||
| ..add(FetchData( | |||
| isNeedFetchData: widget.isEdit, | |||
| activityId: widget.activityId))), | |||
| BlocProvider<MediaHelperBloc>( | |||
| create: (context) => | |||
| MediaHelperBloc()..add(ChangeListMedia(items: [])), | |||
| ) | |||
| ], | |||
| child: Form( | |||
| key: _formKey, | |||
| @@ -491,16 +539,34 @@ class _EditActionNurseryState extends State<EditActionNurseryScreen> { | |||
| padding: EdgeInsets.all(8.0), | |||
| child: BlocConsumer<ActionDetailBloc, | |||
| ActionDetailState>( | |||
| listener: (context, state) { | |||
| listener: (context, state) async { | |||
| if (state is ActionDetailFailure) { | |||
| print("fail"); | |||
| LoadingDialog.hideLoadingDialog(context); | |||
| } else if (state is ActionDetailSuccess) { | |||
| LoadingDialog.hideLoadingDialog(context); | |||
| print("success"); | |||
| print(state.item); | |||
| _nursery = Nursery.fromJson(state.item); | |||
| _quantityController.text = | |||
| "sfdf ${_nursery.id}"; | |||
| _seedLengthController.text = | |||
| _nursery.seedLength.toString(); | |||
| //Show media | |||
| if (_nursery.media.isNotEmpty) { | |||
| await cacheFiles(_nursery.media) | |||
| .then((value) { | |||
| print("then: ${value.length}"); | |||
| BlocProvider.of<MediaHelperBloc>(context) | |||
| .add(ChangeListMedia(items: value)); | |||
| }).whenComplete(() { | |||
| print("completed"); | |||
| }); | |||
| } | |||
| //Show worker | |||
| if (_nursery.nurseryDetail.length > 0) { | |||
| BlocProvider.of<ExpansionListBloc>(context) | |||
| .add(AddNew( | |||
| items: _nursery.nurseryDetail)); | |||
| } | |||
| } else if (state is ActionDetailInitial) { | |||
| print("init"); | |||
| } else if (state is ActionDetailLoading) { | |||
| @@ -561,14 +627,23 @@ class _EditActionNurseryState extends State<EditActionNurseryScreen> { | |||
| SizedBox( | |||
| height: 8.0, | |||
| ), | |||
| WidgetMediaPicker( | |||
| onChangeFiles: (filePaths) { | |||
| Get.find<ChangeFileController>() | |||
| .addAllFile(filePaths); | |||
| // setState(() { | |||
| // filePaths = filePaths; | |||
| // }); | |||
| }) | |||
| BlocBuilder<MediaHelperBloc, | |||
| MediaHelperState>( | |||
| builder: (context, state) { | |||
| if (state is MediaHelperSuccess) { | |||
| print("length: " + | |||
| state.items.length.toString()); | |||
| return WidgetMediaPicker( | |||
| currentItems: state.items, | |||
| onChangeFiles: (filePaths) async { | |||
| Get.find<ChangeFileController>() | |||
| .addAllFile(filePaths); | |||
| }); | |||
| } else { | |||
| return Center( | |||
| child: CircularProgressIndicator()); | |||
| } | |||
| }), | |||
| ], | |||
| ); | |||
| }, | |||
| @@ -583,6 +658,26 @@ class _EditActionNurseryState extends State<EditActionNurseryScreen> { | |||
| _descriptionController.dispose(); | |||
| super.dispose(); | |||
| } | |||
| Future<List<Media>> cacheFiles(String existedMedias) async { | |||
| var medias = List<Media>(); | |||
| var mediaPathsLocal = List<String>(); | |||
| var mediaPaths = existedMedias.split(";"); | |||
| for (int i = 0; i < mediaPaths.length; i++) { | |||
| var tempFile = await DefaultCacheManager() | |||
| .getSingleFile(ConstCommon.baseImageUrl + mediaPaths[i]); | |||
| print(tempFile.path); | |||
| var isVideo = lookupMimeType(tempFile.path) == "video/mp4"; | |||
| print("file type: " + lookupMimeType(tempFile.path)); | |||
| Media media = Media() | |||
| ..pathFile = tempFile.path | |||
| ..isVideo = isVideo; | |||
| medias.add(media); | |||
| mediaPathsLocal.add(tempFile.path); | |||
| } | |||
| Get.find<ChangeFileController>().addAllFile(filePaths); | |||
| return medias; | |||
| } | |||
| } | |||
| class ChangeSupply extends GetxController { | |||
| @@ -20,6 +20,7 @@ import 'package:farm_tpf/utils/const_string.dart'; | |||
| import 'package:flutter/material.dart'; | |||
| import 'package:flutter_bloc/flutter_bloc.dart'; | |||
| import 'package:farm_tpf/utils/formatter.dart'; | |||
| import 'package:get/get.dart'; | |||
| class PlotActionScreen extends StatefulWidget { | |||
| int cropId; | |||
| @@ -41,8 +42,12 @@ class _PlotActionScreenState extends State<PlotActionScreen> { | |||
| } | |||
| _initActionButtons() { | |||
| actions | |||
| .add(ActionType(plot_action_nursery, null, EditActionNurseryScreen())); | |||
| actions.add(ActionType( | |||
| plot_action_nursery, | |||
| null, | |||
| EditActionNurseryScreen( | |||
| cropId: widget.cropId, | |||
| ))); | |||
| actions.add(ActionType(plot_action_plant, null, EditActionPlantScreen())); | |||
| actions.add(ActionType( | |||
| plot_action_crop_status, null, EditActionCropStatusScreen())); | |||
| @@ -282,9 +287,18 @@ class ItemInfinityWidget extends StatelessWidget { | |||
| child: ListTile( | |||
| title: Text(item.activityTypeDescription ?? ''), | |||
| subtitle: Text(item.executeDate.format_DDMMYY_HHmm()), | |||
| trailing: Text(item.id.toString()), | |||
| ), | |||
| ), | |||
| onTap: () {}); | |||
| onTap: () { | |||
| if (item.activityTypeName == "ACTIVE_TYPE_NURSERY") { | |||
| Get.to(EditActionNurseryScreen( | |||
| cropId: item.cropId, | |||
| activityId: item.id, | |||
| isEdit: true, | |||
| )); | |||
| } | |||
| }); | |||
| } | |||
| } | |||
| @@ -2,6 +2,7 @@ class ConstCommon { | |||
| static int kExpiredTime = 12 * 60 * 60 * 1000; //24h | |||
| static int kFileSize = 1000000; //1M = 1000.000 bytes | |||
| static const String baseUrl = "http://tpf.aztrace.vn"; | |||
| static const String baseImageUrl = "http://s3.tpf.aztrace.vn/upload/"; | |||
| static const String supplyTypeSeed = "GIONG"; | |||
| static const String supplyTypeDung = "PHANBON"; | |||
| @@ -293,6 +293,13 @@ packages: | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "6.0.5" | |||
| flutter_cache_manager: | |||
| dependency: "direct main" | |||
| description: | |||
| name: flutter_cache_manager | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "1.4.2" | |||
| flutter_datetime_picker: | |||
| dependency: "direct main" | |||
| description: | |||
| @@ -467,7 +474,7 @@ packages: | |||
| source: hosted | |||
| version: "1.1.8" | |||
| mime: | |||
| dependency: transitive | |||
| dependency: "direct main" | |||
| description: | |||
| name: mime | |||
| url: "https://pub.dartlang.org" | |||
| @@ -723,6 +730,20 @@ packages: | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "1.7.0" | |||
| sqflite: | |||
| dependency: transitive | |||
| description: | |||
| name: sqflite | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "1.3.1+1" | |||
| sqflite_common: | |||
| dependency: transitive | |||
| description: | |||
| name: sqflite_common | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "1.0.2+1" | |||
| stack_trace: | |||
| dependency: transitive | |||
| description: | |||
| @@ -751,6 +772,13 @@ packages: | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "1.0.5" | |||
| synchronized: | |||
| dependency: transitive | |||
| description: | |||
| name: synchronized | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "2.2.0+2" | |||
| term_glyph: | |||
| dependency: transitive | |||
| description: | |||
| @@ -793,6 +821,13 @@ packages: | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "1.2.0" | |||
| uuid: | |||
| dependency: transitive | |||
| description: | |||
| name: uuid | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "2.2.2" | |||
| vector_math: | |||
| dependency: transitive | |||
| description: | |||
| @@ -51,6 +51,9 @@ dependencies: | |||
| path_provider: ^1.6.14 | |||
| file_picker: ^2.0.0 | |||
| thumbnails: ^1.0.1 | |||
| flutter_cache_manager: ^1.4.2 | |||
| mime: ^0.9.7 | |||
| dev_dependencies: | |||
| flutter_test: | |||