| @@ -189,6 +189,14 @@ class Activities { | |||
| this.activityTypeName, | |||
| this.activityTypeDescription}); | |||
| Activities.clone(Activities activities) { | |||
| this.id = activities.id; | |||
| this.cropId = activities.cropId; | |||
| this.activityTypeName = activities.activityTypeName; | |||
| this.activityTypeDescription = activities.activityTypeDescription; | |||
| this.executeDate = activities.executeDate; | |||
| } | |||
| Activities.fromJson(Map<String, dynamic> json) { | |||
| id = json['id']; | |||
| ageDay = json['ageDay']; | |||
| @@ -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); | |||
| } | |||
| } | |||
| @@ -71,19 +71,22 @@ class Repository { | |||
| } | |||
| //Action | |||
| Future<void> createNursery(Function(dynamic) onSuccess, | |||
| Function(String) onError, String activityNursery, | |||
| {List<String> filePaths}) async { | |||
| Future<void> createAction( | |||
| Function(dynamic) onSuccess, Function(String) onError, | |||
| {String apiAddAction, | |||
| String paramActivity, | |||
| String activityAction, | |||
| List<String> filePaths}) async { | |||
| var formData = FormData(); | |||
| filePaths.forEach((f) { | |||
| formData.files.add(MapEntry("images", MultipartFile.fromFileSync(f))); | |||
| }); | |||
| formData.fields.add(MapEntry("activityNursery", activityNursery)); | |||
| formData.fields.add(MapEntry(paramActivity, activityAction)); | |||
| try { | |||
| await dio | |||
| .post("${ConstCommon.baseUrl}/api/createNursery", data: formData) | |||
| .post("${ConstCommon.baseUrl}/$apiAddAction", data: formData) | |||
| .then((value) { | |||
| onSuccess(value); | |||
| onSuccess(value.data); | |||
| }).catchError((onError) { | |||
| onError(AppException.handleError(onError)); | |||
| }); | |||
| @@ -92,19 +95,22 @@ class Repository { | |||
| } | |||
| } | |||
| Future<void> updateNursery(Function(dynamic) onSuccess, | |||
| Function(String) onError, String activityNursery, | |||
| {List<String> filePaths}) async { | |||
| Future<void> updateAction( | |||
| Function(dynamic) onSuccess, Function(String) onError, | |||
| {String apiUpdateAction, | |||
| String paramActivity, | |||
| String activityAction, | |||
| List<String> filePaths}) async { | |||
| var formData = FormData(); | |||
| filePaths.forEach((f) { | |||
| formData.files.add(MapEntry("images", MultipartFile.fromFileSync(f))); | |||
| }); | |||
| formData.fields.add(MapEntry("activityNursery", activityNursery)); | |||
| formData.fields.add(MapEntry(paramActivity, activityAction)); | |||
| try { | |||
| await dio | |||
| .post("${ConstCommon.baseUrl}/api/updateNursery", data: formData) | |||
| .post("${ConstCommon.baseUrl}/$apiUpdateAction", data: formData) | |||
| .then((value) { | |||
| onSuccess(value); | |||
| onSuccess(value.data); | |||
| }).catchError((onError) { | |||
| onError(AppException.handleError(onError)); | |||
| }); | |||
| @@ -31,7 +31,7 @@ class ActionDetailBloc extends Bloc<ActionDetailEvent, ActionDetailState> { | |||
| try { | |||
| yield ActionDetailLoading(); | |||
| Response<String> response = await dio.get( | |||
| "${ConstCommon.baseUrl}/api/activity-nursery/${event.activityId}"); | |||
| "${ConstCommon.baseUrl}/${event.apiActivity}/${event.activityId}"); | |||
| final jsonData = json.decode(response.data); | |||
| yield ActionDetailSuccess(item: jsonData); | |||
| } catch (error) { | |||
| @@ -9,6 +9,10 @@ abstract class ActionDetailEvent { | |||
| class FetchData extends ActionDetailEvent { | |||
| final bool isNeedFetchData; | |||
| final String apiActivity; | |||
| final int activityId; | |||
| FetchData({@required this.isNeedFetchData, @required this.activityId}); | |||
| FetchData( | |||
| {@required this.isNeedFetchData, | |||
| @required this.apiActivity, | |||
| @required this.activityId}); | |||
| } | |||
| @@ -98,9 +98,9 @@ class _EditActionNurseryState extends State<EditActionNurseryScreen> { | |||
| var activityNursery = jsonEncode(_nursery.toJson()).toString(); | |||
| //ADD NEW | |||
| if (_nursery.activityId == null) { | |||
| _repository.createNursery((value) { | |||
| _repository.createAction((value) { | |||
| LoadingDialog.hideLoadingDialog(context); | |||
| Get.back(); | |||
| Get.back(result: value); | |||
| Get.snackbar(label_add_success, "Hoạt động ươm", | |||
| snackPosition: SnackPosition.BOTTOM); | |||
| }, (error) { | |||
| @@ -116,12 +116,16 @@ class _EditActionNurseryState extends State<EditActionNurseryScreen> { | |||
| backgroundColor: Colors.red, | |||
| duration: Duration(seconds: 3), | |||
| )); | |||
| }, activityNursery, filePaths: filePaths); | |||
| }, | |||
| apiAddAction: ConstCommon.apiAddNursery, | |||
| paramActivity: ConstCommon.paramsActionNursery, | |||
| activityAction: activityNursery, | |||
| filePaths: filePaths); | |||
| } else { | |||
| //UPDATE | |||
| _repository.updateNursery((value) { | |||
| _repository.updateAction((value) { | |||
| LoadingDialog.hideLoadingDialog(context); | |||
| Get.back(); | |||
| Get.back(result: value); | |||
| Get.snackbar(label_update_success, "Hoạt động ươm", | |||
| snackPosition: SnackPosition.BOTTOM); | |||
| }, (error) { | |||
| @@ -137,7 +141,11 @@ class _EditActionNurseryState extends State<EditActionNurseryScreen> { | |||
| backgroundColor: Colors.red, | |||
| duration: Duration(seconds: 3), | |||
| )); | |||
| }, activityNursery, filePaths: filePaths); | |||
| }, | |||
| apiUpdateAction: ConstCommon.apiUpdateNursery, | |||
| paramActivity: ConstCommon.paramsActionNursery, | |||
| activityAction: activityNursery, | |||
| filePaths: filePaths); | |||
| } | |||
| } else { | |||
| _autoValidate = true; | |||
| @@ -524,6 +532,7 @@ class _EditActionNurseryState extends State<EditActionNurseryScreen> { | |||
| ActionDetailBloc(repository: Repository()) | |||
| ..add(FetchData( | |||
| isNeedFetchData: widget.isEdit, | |||
| apiActivity: ConstCommon.apiDetailNursery, | |||
| activityId: widget.activityId))), | |||
| BlocProvider<MediaHelperBloc>( | |||
| create: (context) => | |||
| @@ -94,6 +94,16 @@ class PlotDetailBloc extends Bloc<PlotDetailEvent, PlotDetailState> { | |||
| } catch (e) { | |||
| yield PlotDetailFailure(errorString: AppException.handleError(e)); | |||
| } | |||
| } else if (event is OnUpdate) { | |||
| yield PlotDetailLoading(); | |||
| try { | |||
| yield PlotDetailSuccess( | |||
| items: event.currentItems, | |||
| page: event.currentPage, | |||
| hasReachedMax: event.hasReachedMax); | |||
| } catch (e) { | |||
| yield PlotDetailFailure(errorString: AppException.handleError(e)); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -1,6 +1,6 @@ | |||
| part of 'plot_detail_bloc.dart'; | |||
| abstract class PlotDetailEvent extends Equatable { | |||
| abstract class PlotDetailEvent { | |||
| const PlotDetailEvent(); | |||
| @override | |||
| @@ -18,3 +18,13 @@ class OnRefresh extends PlotDetailEvent { | |||
| final String cropCode; | |||
| OnRefresh({this.cropId, this.cropCode}); | |||
| } | |||
| class OnUpdate<T> extends PlotDetailEvent { | |||
| final List<T> currentItems; | |||
| final int currentPage; | |||
| final bool hasReachedMax; | |||
| OnUpdate( | |||
| {@required this.currentItems, | |||
| @required this.currentPage, | |||
| @required this.hasReachedMax}); | |||
| } | |||
| @@ -1,6 +1,6 @@ | |||
| part of 'plot_detail_bloc.dart'; | |||
| abstract class PlotDetailState extends Equatable { | |||
| abstract class PlotDetailState { | |||
| const PlotDetailState(); | |||
| @override | |||
| @@ -33,10 +33,12 @@ class PlotActionScreen extends StatefulWidget { | |||
| class _PlotActionScreenState extends State<PlotActionScreen> { | |||
| List<ActionType> actions = new List<ActionType>(); | |||
| ScrollController _scrollController; | |||
| var changeToRefresh = Get.put(ChangeToRefreshLists()); | |||
| @override | |||
| void initState() { | |||
| super.initState(); | |||
| changeToRefresh.initValue(); | |||
| _scrollController = ScrollController()..addListener(() => setState(() {})); | |||
| _initActionButtons(); | |||
| } | |||
| @@ -66,63 +68,76 @@ class _PlotActionScreenState extends State<PlotActionScreen> { | |||
| actions.add(ActionType(plot_action_finish, null, EditActionEndScreen())); | |||
| } | |||
| Widget _createActionButtons(ActionType actionType) { | |||
| return GestureDetector( | |||
| onTap: () { | |||
| Navigator.of(context).push( | |||
| MaterialPageRoute(builder: (context) => actionType.listScreen)); | |||
| }, | |||
| child: Container( | |||
| height: 75, | |||
| margin: EdgeInsets.all(4.0), | |||
| padding: EdgeInsets.all(0.0), | |||
| decoration: BoxDecoration( | |||
| color: COLOR_CONST.WHITE, | |||
| borderRadius: BorderRadius.only( | |||
| topLeft: Radius.circular(8.0), | |||
| bottomLeft: Radius.circular(8.0), | |||
| bottomRight: Radius.circular(8.0), | |||
| topRight: Radius.circular(8.0)), | |||
| boxShadow: <BoxShadow>[ | |||
| BoxShadow( | |||
| color: COLOR_CONST.GRAY1.withOpacity(0.2), | |||
| offset: Offset(1.1, 1.1), | |||
| blurRadius: 10.0), | |||
| ], | |||
| ), | |||
| child: Stack( | |||
| children: <Widget>[ | |||
| Positioned( | |||
| top: -10, | |||
| right: -3, | |||
| child: (actionType.addScreen == null) | |||
| ? Container() | |||
| : IconButton( | |||
| icon: Icon( | |||
| Icons.add_circle, | |||
| size: 40, | |||
| color: Colors.green, | |||
| ), | |||
| onPressed: () { | |||
| Navigator.of(context).push(MaterialPageRoute( | |||
| builder: (context) => actionType.addScreen)); | |||
| })), | |||
| Positioned.fill( | |||
| child: Align( | |||
| alignment: Alignment.center, | |||
| child: Text( | |||
| actionType.actionName, | |||
| textAlign: TextAlign.center, | |||
| style: TextStyle( | |||
| fontWeight: FontWeight.w400, | |||
| fontSize: 13, | |||
| color: COLOR_CONST.BLACK2, | |||
| ), | |||
| )), | |||
| ), | |||
| ], | |||
| ), | |||
| )); | |||
| Widget _createActionButtons(ActionType actionType, BuildContext _context) { | |||
| return BlocProvider<PlotDetailBloc>( | |||
| create: (context) => PlotDetailBloc(repository: Repository()), | |||
| child: GestureDetector( | |||
| onTap: () { | |||
| Navigator.of(context) | |||
| .push(MaterialPageRoute( | |||
| builder: (context) => actionType.listScreen)) | |||
| .then((value) { | |||
| if (1 == 1) { | |||
| try { | |||
| //TODO: refresh list | |||
| } catch (e) { | |||
| print(e.toString()); | |||
| } | |||
| } | |||
| }); | |||
| }, | |||
| child: Container( | |||
| height: 75, | |||
| margin: EdgeInsets.all(4.0), | |||
| padding: EdgeInsets.all(0.0), | |||
| decoration: BoxDecoration( | |||
| color: COLOR_CONST.WHITE, | |||
| borderRadius: BorderRadius.only( | |||
| topLeft: Radius.circular(8.0), | |||
| bottomLeft: Radius.circular(8.0), | |||
| bottomRight: Radius.circular(8.0), | |||
| topRight: Radius.circular(8.0)), | |||
| boxShadow: <BoxShadow>[ | |||
| BoxShadow( | |||
| color: COLOR_CONST.GRAY1.withOpacity(0.2), | |||
| offset: Offset(1.1, 1.1), | |||
| blurRadius: 10.0), | |||
| ], | |||
| ), | |||
| child: Stack( | |||
| children: <Widget>[ | |||
| Positioned( | |||
| top: -10, | |||
| right: -3, | |||
| child: (actionType.addScreen == null) | |||
| ? Container() | |||
| : IconButton( | |||
| icon: Icon( | |||
| Icons.add_circle, | |||
| size: 40, | |||
| color: Colors.green, | |||
| ), | |||
| onPressed: () { | |||
| Navigator.of(context).push(MaterialPageRoute( | |||
| builder: (context) => actionType.addScreen)); | |||
| })), | |||
| Positioned.fill( | |||
| child: Align( | |||
| alignment: Alignment.center, | |||
| child: Text( | |||
| actionType.actionName, | |||
| textAlign: TextAlign.center, | |||
| style: TextStyle( | |||
| fontWeight: FontWeight.w400, | |||
| fontSize: 13, | |||
| color: COLOR_CONST.BLACK2, | |||
| ), | |||
| )), | |||
| ), | |||
| ], | |||
| ), | |||
| )), | |||
| ); | |||
| } | |||
| bool _showTitle(BuildContext context) { | |||
| @@ -147,17 +162,23 @@ class _PlotActionScreenState extends State<PlotActionScreen> { | |||
| expandedHeight: MediaQuery.of(context).size.width * 1.125 + 32, | |||
| flexibleSpace: _showTitle(context) | |||
| ? null | |||
| : FlexibleSpaceBar( | |||
| centerTitle: true, | |||
| title: GridView.count( | |||
| shrinkWrap: true, | |||
| crossAxisCount: 2, | |||
| childAspectRatio: 16 / 6, | |||
| children: actions.map( | |||
| (item) { | |||
| return _createActionButtons(item); | |||
| }, | |||
| ).toList()), | |||
| : BlocProvider<PlotDetailBloc>( | |||
| create: (context) => PlotDetailBloc(repository: Repository()), | |||
| child: BlocBuilder<PlotDetailBloc, PlotDetailState>( | |||
| builder: (contextB, state) { | |||
| return FlexibleSpaceBar( | |||
| centerTitle: true, | |||
| title: GridView.count( | |||
| shrinkWrap: true, | |||
| crossAxisCount: 2, | |||
| childAspectRatio: 16 / 6, | |||
| children: actions.map( | |||
| (item) { | |||
| return _createActionButtons(item, contextB); | |||
| }, | |||
| ).toList()), | |||
| ); | |||
| }), | |||
| ), | |||
| ), | |||
| ], | |||
| @@ -196,6 +217,7 @@ class _InfinityViewState extends State<InfinityView> { | |||
| final _scrollController = ScrollController(); | |||
| final _scrollThreshold = 250.0; | |||
| PlotDetailBloc _plotDetailBloc; | |||
| var a = Get.put(ChangeToRefreshLists()); | |||
| @override | |||
| void initState() { | |||
| @@ -222,6 +244,9 @@ class _InfinityViewState extends State<InfinityView> { | |||
| if (state.items.isEmpty) { | |||
| return Center(child: Text(label_list_empty)); | |||
| } | |||
| List<Activities> currentItems = List<Activities>.from(state.items); | |||
| Get.find<ChangeToRefreshLists>() | |||
| .updateValue(currentItems, state.page, state.hasReachedMax); | |||
| return RefreshIndicator( | |||
| child: Column( | |||
| children: [ | |||
| @@ -247,7 +272,11 @@ class _InfinityViewState extends State<InfinityView> { | |||
| itemBuilder: (BuildContext context, int index) { | |||
| return index >= state.items.length | |||
| ? BottomLoader() | |||
| : ItemInfinityWidget(item: state.items[index]); | |||
| : ItemInfinityWidget( | |||
| currentItems: currentItems, | |||
| item: state.items[index], | |||
| currentPage: state.page, | |||
| currentReachedMax: state.hasReachedMax); | |||
| }, | |||
| itemCount: state.hasReachedMax | |||
| ? state.items.length | |||
| @@ -276,9 +305,18 @@ class _InfinityViewState extends State<InfinityView> { | |||
| } | |||
| class ItemInfinityWidget extends StatelessWidget { | |||
| final List<Activities> currentItems; | |||
| final Activities item; | |||
| final int currentPage; | |||
| final bool currentReachedMax; | |||
| const ItemInfinityWidget({Key key, @required this.item}) : super(key: key); | |||
| const ItemInfinityWidget( | |||
| {Key key, | |||
| @required this.currentItems, | |||
| @required this.item, | |||
| @required this.currentPage, | |||
| @required this.currentReachedMax}) | |||
| : super(key: key); | |||
| @override | |||
| Widget build(BuildContext context) { | |||
| @@ -296,7 +334,34 @@ class ItemInfinityWidget extends StatelessWidget { | |||
| cropId: item.cropId, | |||
| activityId: item.id, | |||
| isEdit: true, | |||
| )); | |||
| )).then((value) { | |||
| if (value != null) { | |||
| try { | |||
| //TODO: refresh when edit activity | |||
| // BlocProvider.of<PlotDetailBloc>(context) | |||
| // .add(OnRefresh(cropId: item.cropId)); | |||
| // var updatedItem = Activities.fromJson(value); | |||
| // List<Activities> updatedItems = new List<Activities>(); | |||
| // currentItems.forEach((e) { | |||
| // if (e.id == updatedItem.id) { | |||
| // e.executeDate = updatedItem.executeDate; | |||
| // } else { | |||
| // // | |||
| // } | |||
| // updatedItems.add(Activities.clone(e)); | |||
| // }); | |||
| // BlocProvider.of<PlotDetailBloc>(context).add( | |||
| // OnUpdate<Activities>( | |||
| // currentItems: updatedItems, | |||
| // currentPage: currentPage, | |||
| // hasReachedMax: currentReachedMax)); | |||
| } catch (e) { | |||
| print(e.toString()); | |||
| } | |||
| } | |||
| }); | |||
| } | |||
| }); | |||
| } | |||
| @@ -312,3 +377,23 @@ class ActionType { | |||
| this.listScreen = listScreen; | |||
| } | |||
| } | |||
| class ChangeToRefreshLists extends GetxController { | |||
| List<Activities> currentItems; | |||
| int currentPage; | |||
| bool currentReachedMax; | |||
| void initValue() { | |||
| currentItems = List<Activities>(); | |||
| currentPage = 0; | |||
| currentReachedMax = true; | |||
| update(); | |||
| } | |||
| void updateValue(List<Activities> updateItems, int page, bool reachedMax) { | |||
| currentItems = updateItems; | |||
| currentPage = page; | |||
| currentReachedMax = reachedMax; | |||
| update(); | |||
| } | |||
| } | |||
| @@ -4,6 +4,12 @@ class ConstCommon { | |||
| static const String baseUrl = "http://tpf.aztrace.vn"; | |||
| static const String baseImageUrl = "http://s3.tpf.aztrace.vn/upload/"; | |||
| static const String apiDetailNursery = "api/activity-nursery"; | |||
| static const String paramsActionNursery = "activityNursery"; | |||
| static const String apiUpdateNursery = "api/updateNursery"; | |||
| static const String apiAddNursery = "api/createNursery"; | |||
| static const String supplyTypeSeed = "GIONG"; | |||
| static const String supplyTypeDung = "PHANBON"; | |||
| static const String supplyTypeSubStrate = "GIATHE"; | |||
| @@ -16,11 +16,12 @@ extension ddMM_HHmm on String { | |||
| } | |||
| } | |||
| //TODO: Check timezone with api update true/false | |||
| String format_DDMMYY_HHmm() { | |||
| try { | |||
| final str = this.toString(); | |||
| var dateFromString = | |||
| DateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").parse(str, true).toLocal(); | |||
| DateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").parse(str, false).toLocal(); | |||
| return DateFormat("dd/MM/yyyy HH:mm").format(dateFromString); | |||
| } catch (_) { | |||
| return ""; | |||