| @@ -60,5 +60,15 @@ | |||
| <meta-data | |||
| android:name="flutterEmbedding" | |||
| android:value="2" /> | |||
| <provider | |||
| android:name="androidx.core.content.FileProvider" | |||
| android:authorities="au.com.homecarenet.caregiver.fileprovider" | |||
| android:exported="false" | |||
| android:grantUriPermissions="true"> | |||
| <meta-data | |||
| android:name="android.support.FILE_PROVIDER_PATHS" | |||
| android:resource="@xml/provider_paths" | |||
| tools:replace="android:resource" /> | |||
| </provider> | |||
| </application> | |||
| </manifest> | |||
| @@ -0,0 +1,6 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <paths xmlns:android="http://schemas.android.com/apk/res/android"> | |||
| <external-path | |||
| name="external_files" | |||
| path="." /> | |||
| </paths> | |||
| @@ -25,6 +25,14 @@ subprojects { | |||
| } | |||
| subprojects { | |||
| project.evaluationDependsOn(':app') | |||
| project.configurations.all { | |||
| resolutionStrategy.eachDependency { details -> | |||
| if (details.requested.group == 'com.android.support' | |||
| && !details.requested.name.contains('multidex') ) { | |||
| details.useVersion "27.1.1" | |||
| } | |||
| } | |||
| } | |||
| } | |||
| task clean(type: Delete) { | |||
| @@ -120,6 +120,8 @@ PODS: | |||
| - nanopb/encode (= 2.30909.0) | |||
| - nanopb/decode (2.30909.0) | |||
| - nanopb/encode (2.30909.0) | |||
| - open_filex (0.0.2): | |||
| - Flutter | |||
| - package_info_plus (0.4.5): | |||
| - Flutter | |||
| - path_provider_foundation (0.0.1): | |||
| @@ -151,6 +153,7 @@ DEPENDENCIES: | |||
| - Flutter (from `Flutter`) | |||
| - flutter_image_compress_common (from `.symlinks/plugins/flutter_image_compress_common/ios`) | |||
| - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) | |||
| - open_filex (from `.symlinks/plugins/open_filex/ios`) | |||
| - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) | |||
| - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`) | |||
| - qr_code_scanner (from `.symlinks/plugins/qr_code_scanner/ios`) | |||
| @@ -193,6 +196,8 @@ EXTERNAL SOURCES: | |||
| :path: ".symlinks/plugins/flutter_image_compress_common/ios" | |||
| image_picker_ios: | |||
| :path: ".symlinks/plugins/image_picker_ios/ios" | |||
| open_filex: | |||
| :path: ".symlinks/plugins/open_filex/ios" | |||
| package_info_plus: | |||
| :path: ".symlinks/plugins/package_info_plus/ios" | |||
| path_provider_foundation: | |||
| @@ -226,6 +231,7 @@ SPEC CHECKSUMS: | |||
| Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d | |||
| MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb | |||
| nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431 | |||
| open_filex: 6e26e659846ec990262224a12ef1c528bb4edbe4 | |||
| package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 | |||
| path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 | |||
| PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4 | |||
| @@ -238,4 +244,4 @@ SPEC CHECKSUMS: | |||
| PODFILE CHECKSUM: f08360d062df7ffa8ee251115991830e526d3068 | |||
| COCOAPODS: 1.12.1 | |||
| COCOAPODS: 1.15.2 | |||
| @@ -369,7 +369,7 @@ | |||
| CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; | |||
| CODE_SIGN_IDENTITY = "Apple Development"; | |||
| CODE_SIGN_STYLE = Automatic; | |||
| CURRENT_PROJECT_VERSION = 20; | |||
| CURRENT_PROJECT_VERSION = 21; | |||
| DEVELOPMENT_TEAM = C3DTD2JH94; | |||
| ENABLE_BITCODE = NO; | |||
| FRAMEWORK_SEARCH_PATHS = ( | |||
| @@ -511,7 +511,7 @@ | |||
| CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; | |||
| CODE_SIGN_IDENTITY = "Apple Development"; | |||
| CODE_SIGN_STYLE = Automatic; | |||
| CURRENT_PROJECT_VERSION = 20; | |||
| CURRENT_PROJECT_VERSION = 21; | |||
| DEVELOPMENT_TEAM = C3DTD2JH94; | |||
| ENABLE_BITCODE = NO; | |||
| FRAMEWORK_SEARCH_PATHS = ( | |||
| @@ -547,7 +547,7 @@ | |||
| CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; | |||
| CODE_SIGN_IDENTITY = "Apple Development"; | |||
| CODE_SIGN_STYLE = Automatic; | |||
| CURRENT_PROJECT_VERSION = 20; | |||
| CURRENT_PROJECT_VERSION = 21; | |||
| DEVELOPMENT_TEAM = C3DTD2JH94; | |||
| ENABLE_BITCODE = NO; | |||
| FRAMEWORK_SEARCH_PATHS = ( | |||
| @@ -78,9 +78,9 @@ class HttpLogInterceptor extends InterceptorsWrapper { | |||
| @override | |||
| void onResponse(Response<dynamic> response, ResponseInterceptorHandler handler) { | |||
| log('🍏🍏🍏🍏🍏🍏 onResponse: ${response.requestOptions.uri}\n' | |||
| 'statusCode=${response.statusCode}\n' | |||
| 'data=${response.data is Map ? json.encode(response.data) : response.data}'); | |||
| // log('🍏🍏🍏🍏🍏🍏 onResponse: ${response.requestOptions.uri}\n' | |||
| // 'statusCode=${response.statusCode}\n' | |||
| // 'data=${response.data is Map ? json.encode(response.data) : response.data}'); | |||
| return handler.next(response); | |||
| } | |||
| @@ -22,6 +22,7 @@ import 'package:farm_tpf/models/PagedResult.dart'; | |||
| import 'package:farm_tpf/presentation/screens/codes/models/activity_request.dart'; | |||
| import 'package:farm_tpf/presentation/screens/codes/models/activity_type.dart'; | |||
| import 'package:farm_tpf/presentation/screens/codes/models/stamp.dart'; | |||
| import 'package:farm_tpf/presentation/screens/codes/models/stamp_filter_request.dart'; | |||
| import 'package:farm_tpf/presentation/screens/codes/models/stamp_request.dart'; | |||
| import 'package:farm_tpf/presentation/screens/codes/models/stamp_timeline.dart'; | |||
| import 'package:farm_tpf/utils/const_common.dart'; | |||
| @@ -293,10 +294,17 @@ class Repository { | |||
| } | |||
| // Stamp | |||
| Future<List<Stamp>> stamps({int page = 0, int size = 20}) async { | |||
| Future<List<Stamp>> stamps({ | |||
| int page = 0, | |||
| int size = 20, | |||
| required StampFilterRequest filter, | |||
| }) async { | |||
| try { | |||
| var url = '${ConstCommon.baseUrl}/api/tb-codes/list?page=$page&size=$size&sort=id,desc'; | |||
| var res = await dio.post(url, data: {}); | |||
| var url = '${ConstCommon.baseUrl}/api/tb-codes/list?page=$page&size=$size&sort=createdDate,${filter.sort ?? 'asc'}'; | |||
| var res = await dio.post(url, data: { | |||
| 'status': filter.status, | |||
| 'description': filter.description, | |||
| }); | |||
| return (res.data as List).map((e) => Stamp.fromJson(e)).toList(); | |||
| } catch (e) { | |||
| rethrow; | |||
| @@ -354,8 +362,13 @@ class Repository { | |||
| }) async { | |||
| try { | |||
| var url = '${ConstCommon.baseUrl}/api/tb-codes/create/activity'; | |||
| await dio.post(url, data: item).then( | |||
| var formData = FormData(); | |||
| formData.fields.add(MapEntry('code', item.code ?? '')); | |||
| formData.fields.add(MapEntry('activityTypeId', item.activityTypeId?.toString() ?? '')); | |||
| formData.fields.add(MapEntry('executeDate', item.executeDate ?? '')); | |||
| formData.fields.add(MapEntry('description', item.description ?? '')); | |||
| formData.fields.add(MapEntry('location', item.location ?? '')); | |||
| await dio.post(url, data: formData).then( | |||
| (value) { | |||
| onSuccess(value); | |||
| }, | |||
| @@ -377,6 +390,16 @@ class Repository { | |||
| } | |||
| } | |||
| Future<Stamp> getStampDetailByCode({required String code}) async { | |||
| try { | |||
| var url = '${ConstCommon.baseUrl}/api/tb-codes-scan-qrCode/$code'; | |||
| var res = await dio.get(url); | |||
| return Stamp.fromJson(res.data); | |||
| } catch (e) { | |||
| rethrow; | |||
| } | |||
| } | |||
| Future<StampTimeline> getStampTimeline({required int stampId}) async { | |||
| try { | |||
| var url = '${ConstCommon.baseUrl}/api/tb-codes-timeline/$stampId'; | |||
| @@ -407,4 +430,16 @@ class Repository { | |||
| onError(AppException.handleError(e)); | |||
| } | |||
| } | |||
| Future<List<TbCropDTO>> getPlotsWithoutPaging() async { | |||
| try { | |||
| var url = '${ConstCommon.baseUrl}/api/_search/tb-crops'; | |||
| var res = await dio.get( | |||
| url, | |||
| ); | |||
| return (res.data as List).map((e) => TbCropDTO.fromJson(e)).toList(); | |||
| } catch (e) { | |||
| rethrow; | |||
| } | |||
| } | |||
| } | |||
| @@ -47,7 +47,7 @@ class Button2Icon extends StatelessWidget { | |||
| ), | |||
| Text( | |||
| title, | |||
| style: StylesText.body5, | |||
| style: StylesText.caption2, | |||
| ), | |||
| const SizedBox( | |||
| width: 4, | |||
| @@ -0,0 +1,262 @@ | |||
| import 'package:farm_tpf/presentation/custom_widgets/button/second_button.dart'; | |||
| import 'package:flutter/cupertino.dart'; | |||
| import 'package:flutter/material.dart'; | |||
| import 'package:get/get.dart'; | |||
| import 'package:multi_select_flutter/multi_select_flutter.dart'; | |||
| import '../../../models/item_dropdown.dart'; | |||
| import '../../../themes/app_colors.dart'; | |||
| import '../../../themes/styles_text.dart'; | |||
| import '../button/button_2_icon.dart'; | |||
| class MultipleSelectBottomSheet extends StatefulWidget { | |||
| final List<ItemDropDown>? initValue; | |||
| final List<ItemDropDown> dataSources; | |||
| final Function(List<ItemDropDown>) onSelected; | |||
| final Color? borderColor; | |||
| final String hint; | |||
| final double? height; | |||
| final String? errorText; | |||
| final bool isDisable; | |||
| final EdgeInsetsGeometry? contentPadding; | |||
| final Color? disabledColor; | |||
| const MultipleSelectBottomSheet({ | |||
| Key? key, | |||
| required this.dataSources, | |||
| required this.onSelected, | |||
| required this.hint, | |||
| this.initValue, | |||
| this.borderColor, | |||
| this.height, | |||
| this.errorText, | |||
| this.isDisable = false, | |||
| this.contentPadding, | |||
| this.disabledColor, | |||
| }) : super(key: key); | |||
| @override | |||
| _MultipleSelectBottomSheetState createState() => _MultipleSelectBottomSheetState(); | |||
| } | |||
| class _MultipleSelectBottomSheetState extends State<MultipleSelectBottomSheet> { | |||
| var dataSources = ValueNotifier(<ItemDropDown>[]); | |||
| var selectValue = ValueNotifier(<ItemDropDown>[]); | |||
| FixedExtentScrollController? scrollController; | |||
| var items = <MultiSelectItem<ItemDropDown>>[]; | |||
| final _searchCtl = TextEditingController(); | |||
| @override | |||
| void initState() { | |||
| super.initState(); | |||
| } | |||
| @override | |||
| void dispose() { | |||
| super.dispose(); | |||
| } | |||
| @override | |||
| Widget build(BuildContext context) { | |||
| items = widget.dataSources.map((e) => MultiSelectItem(e, e.value ?? '')).toList(); | |||
| selectValue.value = widget.initValue ?? <ItemDropDown>[]; | |||
| return ValueListenableBuilder<List<ItemDropDown>>( | |||
| valueListenable: selectValue, | |||
| builder: (context, selecteds, _) { | |||
| return Button2Icon( | |||
| leftIcon: CupertinoIcons.slider_horizontal_3, | |||
| title: widget.hint, | |||
| rightWidget: Container( | |||
| width: 16, | |||
| height: 16, | |||
| decoration: BoxDecoration( | |||
| borderRadius: BorderRadius.circular(12), | |||
| color: Colors.blue, | |||
| ), | |||
| child: Center( | |||
| child: Text( | |||
| '${selecteds.length}', | |||
| style: StylesText.caption6.copyWith(color: Colors.white), | |||
| ), | |||
| ), | |||
| ), | |||
| onPressed: () { | |||
| FocusScope.of(context).requestFocus(FocusNode()); | |||
| _showMultiSelect(context); | |||
| }, | |||
| ); | |||
| }, | |||
| ); | |||
| } | |||
| void _showMultiSelect(BuildContext context) async { | |||
| var selectedIds = selectValue.value.map((e) => e.key).toList(); | |||
| var selecteds = ValueNotifier( | |||
| items | |||
| .where((element) => selectedIds.contains(element.value.key)) | |||
| .map( | |||
| (e) => e.value, | |||
| ) | |||
| .toList(), | |||
| ); | |||
| // var selectValue = ValueNotifier(<ItemDropDown>[]); | |||
| _searchCtl.clear(); | |||
| dataSources.value = List.of(widget.dataSources); | |||
| return showDialog( | |||
| context: context, | |||
| builder: (context) { | |||
| return AlertDialog( | |||
| contentPadding: const EdgeInsets.all(8), | |||
| content: Container( | |||
| width: double.maxFinite, | |||
| child: Column( | |||
| mainAxisSize: MainAxisSize.min, | |||
| children: <Widget>[ | |||
| _buildSelectAll(selecteds), | |||
| Expanded( | |||
| child: ValueListenableBuilder<List<ItemDropDown>>( | |||
| valueListenable: dataSources, | |||
| builder: (context, sources, _) { | |||
| return ValueListenableBuilder<List<ItemDropDown>>( | |||
| valueListenable: selecteds, | |||
| builder: (context, valueSelected, _) { | |||
| return ListView.builder( | |||
| shrinkWrap: true, | |||
| itemBuilder: ((context, index) { | |||
| var item = sources[index]; | |||
| var isSelected = valueSelected.map((e) => e.key ?? '').contains(item.key); | |||
| return InkWell( | |||
| onTap: () { | |||
| if (!isSelected) { | |||
| selecteds.value.add(item); | |||
| selecteds.notifyListeners(); | |||
| } else { | |||
| selecteds.value.removeWhere((element) => element.key == item.key); | |||
| selecteds.notifyListeners(); | |||
| } | |||
| }, | |||
| child: Row( | |||
| children: [ | |||
| Checkbox( | |||
| value: isSelected, | |||
| onChanged: (val) { | |||
| if (val == true) { | |||
| selecteds.value.add(item); | |||
| selecteds.notifyListeners(); | |||
| } else { | |||
| selecteds.value.removeWhere((element) => element.key == item.key); | |||
| selecteds.notifyListeners(); | |||
| } | |||
| }, | |||
| ), | |||
| Expanded( | |||
| child: Text( | |||
| item.value ?? '', | |||
| style: StylesText.body6, | |||
| ), | |||
| ), | |||
| ], | |||
| ), | |||
| ); | |||
| }), | |||
| itemCount: sources.length, | |||
| ); | |||
| }); | |||
| }, | |||
| ), | |||
| ), | |||
| Row( | |||
| children: [ | |||
| Expanded( | |||
| child: SecondButton( | |||
| title: 'Huỷ', | |||
| color: AppColors.neutral3, | |||
| onPressed: () { | |||
| Get.back(); | |||
| }, | |||
| ), | |||
| ), | |||
| const SizedBox( | |||
| width: 16, | |||
| ), | |||
| Expanded( | |||
| child: SecondButton( | |||
| title: 'Chọn', | |||
| color: AppColors.primary1, | |||
| onPressed: () { | |||
| Get.back(); | |||
| selectValue.value = selecteds.value; | |||
| widget.onSelected(selectValue.value); | |||
| }, | |||
| ), | |||
| ), | |||
| ], | |||
| ) | |||
| ], | |||
| ), | |||
| ), | |||
| ); | |||
| }, | |||
| ); | |||
| } | |||
| Widget _buildSelectAll(ValueNotifier<List<ItemDropDown>> selecteds) { | |||
| return ValueListenableBuilder<List<ItemDropDown>>( | |||
| valueListenable: dataSources, | |||
| builder: (context, sources, _) { | |||
| return ValueListenableBuilder<List<ItemDropDown>>( | |||
| valueListenable: selecteds, | |||
| builder: (context, valueSelected, _) { | |||
| final isSelectAll = valueSelected.where((e) => sources.any((t) => t.key == e.key)).length == sources.length && sources.isNotEmpty; | |||
| final tristate = valueSelected.where((e) => sources.firstWhereOrNull((t) => t.key == e.key) != null).isNotEmpty; | |||
| return Visibility( | |||
| visible: sources.isNotEmpty, | |||
| child: Container( | |||
| decoration: BoxDecoration( | |||
| border: Border( | |||
| bottom: BorderSide( | |||
| color: AppColors.neutral1, | |||
| width: 0.5, | |||
| ), | |||
| ), | |||
| ), | |||
| child: InkWell( | |||
| onTap: () { | |||
| if (isSelectAll) { | |||
| selecteds.value = List.of(valueSelected)..removeWhere((e) => sources.firstWhereOrNull((t) => t.key == e.key) != null); | |||
| } else { | |||
| final itemsAdded = List.of(sources)..removeWhere((e) => valueSelected.firstWhereOrNull((t) => t.key == e.key) != null); | |||
| selecteds.value = [...valueSelected, ...itemsAdded]; | |||
| } | |||
| }, | |||
| child: Row( | |||
| children: [ | |||
| Checkbox( | |||
| value: isSelectAll ? isSelectAll : (tristate ? null : tristate), | |||
| tristate: true, | |||
| onChanged: (val) { | |||
| if (isSelectAll) { | |||
| selecteds.value = List.of(valueSelected)..removeWhere((e) => sources.firstWhereOrNull((t) => t.key == e.key) != null); | |||
| } else { | |||
| final itemsAdded = List.of(sources)..removeWhere((e) => valueSelected.firstWhereOrNull((t) => t.key == e.key) != null); | |||
| selecteds.value = [...valueSelected, ...itemsAdded]; | |||
| } | |||
| }, | |||
| ), | |||
| Text( | |||
| 'Tất cả', | |||
| style: StylesText.body6, | |||
| ), | |||
| ], | |||
| ), | |||
| ), | |||
| ), | |||
| ); | |||
| }, | |||
| ); | |||
| }, | |||
| ); | |||
| } | |||
| } | |||
| @@ -1,8 +1,15 @@ | |||
| import 'package:bloc/bloc.dart'; | |||
| import 'package:equatable/equatable.dart'; | |||
| import 'package:farm_tpf/utils/helpers.dart'; | |||
| import 'package:flutter/foundation.dart'; | |||
| import 'package:flutter/material.dart'; | |||
| import '../../../../data/api/app_exception.dart'; | |||
| import '../../../../data/repository/repository.dart'; | |||
| import '../../../../models/item_dropdown.dart'; | |||
| import '../../../../utils/const_common.dart'; | |||
| import '../models/stamp.dart'; | |||
| import '../models/stamp_filter_request.dart'; | |||
| part 'stamp_event.dart'; | |||
| part 'stamp_state.dart'; | |||
| @@ -11,6 +18,23 @@ class StampBloc extends Bloc<StampEvent, StampState> { | |||
| final Repository repository; | |||
| int pageSize = 20; | |||
| StampBloc(this.repository) : super(StampInitial()); | |||
| final searchCtl = TextEditingController(); | |||
| var status = ValueNotifier( | |||
| StampStatus.values | |||
| .map( | |||
| (e) => ItemDropDown(key: describeEnum(e), value: Helpers.getStampStatus(describeEnum(e))), | |||
| ) | |||
| .toList(), | |||
| ); | |||
| var selectedStatus = ValueNotifier( | |||
| StampStatus.values | |||
| .map( | |||
| (e) => ItemDropDown(key: describeEnum(e), value: Helpers.getStampStatus(describeEnum(e))), | |||
| ) | |||
| .toList(), | |||
| ); | |||
| var sort = ValueNotifier(describeEnum(SortType.asc)); | |||
| @override | |||
| Stream<StampState> mapEventToState( | |||
| @@ -20,7 +44,7 @@ class StampBloc extends Bloc<StampEvent, StampState> { | |||
| try { | |||
| if (state is StampInitial) { | |||
| yield (StampLoading()); | |||
| final response = await repository.stamps(page: 0); | |||
| final response = await getListStamp(0); | |||
| yield StampSuccess( | |||
| items: response, | |||
| page: 0, | |||
| @@ -29,8 +53,11 @@ class StampBloc extends Bloc<StampEvent, StampState> { | |||
| } | |||
| if (state is StampSuccess) { | |||
| final currentState = state as StampSuccess; | |||
| if (currentState.hasReachedMax ?? false) { | |||
| return; | |||
| } | |||
| int page = (currentState.page ?? 0) + 1; | |||
| final response = await repository.stamps(page: page); | |||
| final response = await getListStamp(page); | |||
| yield response.isEmpty | |||
| ? currentState.copyWith(hasReachedMax: true) | |||
| : StampSuccess( | |||
| @@ -47,7 +74,7 @@ class StampBloc extends Bloc<StampEvent, StampState> { | |||
| if (event is OnRefresh) { | |||
| try { | |||
| yield (StampLoading()); | |||
| final response = await repository.stamps(page: 0); | |||
| final response = await getListStamp(0); | |||
| yield StampSuccess( | |||
| items: response, | |||
| page: 0, | |||
| @@ -59,11 +86,19 @@ class StampBloc extends Bloc<StampEvent, StampState> { | |||
| } else if (event is OnSearch) { | |||
| try { | |||
| yield (StampLoading()); | |||
| final response = await repository.stamps(page: 0); | |||
| final response = await getListStamp(0); | |||
| yield StampSuccess(items: response, page: 0, hasReachedMax: response.length < pageSize ? true : false); | |||
| } catch (e) { | |||
| yield (StampFailure(errorString: AppException.handleError(e))); | |||
| } | |||
| } | |||
| } | |||
| Future<List<Stamp>> getListStamp(int page) async { | |||
| var filter = StampFilterRequest() | |||
| ..sort = sort.value | |||
| ..description = searchCtl.text | |||
| ..status = selectedStatus.value.map((e) => e.key ?? '').toList(); | |||
| return await repository.stamps(page: 0, filter: filter); | |||
| } | |||
| } | |||
| @@ -1,9 +1,11 @@ | |||
| import 'package:farm_tpf/presentation/custom_widgets/app_bar_widget.dart'; | |||
| import 'package:farm_tpf/presentation/custom_widgets/button/second_button.dart'; | |||
| import 'package:farm_tpf/presentation/screens/codes/bloc/stamp_bloc.dart'; | |||
| import 'package:farm_tpf/presentation/screens/codes/cubit/detail_stamp_cubit.dart'; | |||
| import 'package:farm_tpf/presentation/screens/codes/models/stamp_timeline.dart'; | |||
| import 'package:farm_tpf/presentation/screens/codes/update_activity_page.dart'; | |||
| import 'package:farm_tpf/presentation/screens/codes/widgets/item_code_timeline.dart'; | |||
| import 'package:farm_tpf/utils/helpers.dart'; | |||
| import 'package:flutter/material.dart'; | |||
| import 'package:flutter_bloc/flutter_bloc.dart'; | |||
| import 'package:get/get.dart'; | |||
| @@ -36,21 +38,7 @@ class _CodeDetailPageState extends State<CodeDetailPage> { | |||
| @override | |||
| Widget build(BuildContext context) { | |||
| return Scaffold( | |||
| appBar: AppBarWidget( | |||
| action: IconButton( | |||
| onPressed: () { | |||
| // Get.to( | |||
| // () => UpdateActivityPage( | |||
| // stampCode: widget.stampCode, | |||
| // ), | |||
| // ); | |||
| }, | |||
| icon: Icon( | |||
| Icons.edit, | |||
| color: Colors.blue, | |||
| ), | |||
| ), | |||
| ), | |||
| appBar: AppBarWidget(), | |||
| body: Padding( | |||
| padding: const EdgeInsets.all(8.0), | |||
| child: Column( | |||
| @@ -72,7 +60,7 @@ class _CodeDetailPageState extends State<CodeDetailPage> { | |||
| children: [ | |||
| _itemCodeDetail( | |||
| title: 'Tên sản phẩm', | |||
| detail: 'Cà rốt', | |||
| detail: stamp.productName ?? '', | |||
| titleStyle: StylesText.body4, | |||
| detailStyle: StylesText.body4.copyWith( | |||
| color: Colors.blue, | |||
| @@ -80,7 +68,7 @@ class _CodeDetailPageState extends State<CodeDetailPage> { | |||
| ), | |||
| _itemCodeDetail(title: 'Mô tả', detail: stamp.description ?? ''), | |||
| _itemCodeDetail(title: 'Số lượng tem', detail: stamp.quantity?.formatNumtoStringDecimal().toString() ?? ''), | |||
| _itemCodeDetail(title: 'Trạng thái', detail: stamp.status ?? ''), | |||
| _itemCodeDetail(title: 'Trạng thái', detail: Helpers.getStampStatus(stamp.status ?? '')), | |||
| _itemCodeDetail(title: 'Hạn sử dụng', detail: stamp.expiredDate?.format_DDMMYY().toString() ?? ''), | |||
| _itemCodeDetail(title: 'Mẫu tem', detail: stamp.stampType?.exampleStampName ?? ''), | |||
| const SizedBox( | |||
| @@ -192,7 +180,9 @@ class _CodeDetailPageState extends State<CodeDetailPage> { | |||
| ), | |||
| Expanded( | |||
| child: SecondButton( | |||
| onPressed: () {}, | |||
| onPressed: () { | |||
| bloc.downloadFile(widget.stampCode); | |||
| }, | |||
| title: 'In / Xuất file', | |||
| borderColor: Colors.cyan, | |||
| textColor: Colors.white, | |||
| @@ -6,14 +6,19 @@ import 'package:farm_tpf/presentation/screens/codes/create_stamp_page.dart'; | |||
| import 'package:farm_tpf/presentation/screens/codes/models/stamp.dart'; | |||
| import 'package:farm_tpf/presentation/screens/codes/widgets/item_code.dart'; | |||
| import 'package:farm_tpf/themes/app_colors.dart'; | |||
| import 'package:farm_tpf/utils/const_common.dart'; | |||
| import 'package:flutter/cupertino.dart'; | |||
| import 'package:flutter/foundation.dart'; | |||
| import 'package:flutter/material.dart'; | |||
| import 'package:flutter_bloc/flutter_bloc.dart'; | |||
| import 'package:get/get.dart'; | |||
| import '../../../models/item_dropdown.dart'; | |||
| import '../../../themes/styles_text.dart'; | |||
| import '../../../utils/const_string.dart'; | |||
| import '../../../utils/helpers.dart'; | |||
| import '../../custom_widgets/bottom_loader.dart'; | |||
| import '../../custom_widgets/dropdown/multiple_select_bottom_sheet.dart'; | |||
| import '../../custom_widgets/loading_list_page.dart'; | |||
| import '../plot/widget_search.dart'; | |||
| import 'bloc/stamp_bloc.dart'; | |||
| @@ -71,33 +76,52 @@ class _CodePageState extends State<CodePage> { | |||
| style: TextStyle(fontWeight: FontWeight.w500, fontSize: 22), | |||
| ), | |||
| ), | |||
| WidgetSearch(), | |||
| WidgetSearch( | |||
| searchController: bloc.searchCtl, | |||
| onPressed: (value) { | |||
| Helpers.hideKeyboard(context); | |||
| bloc.add(OnRefresh()); | |||
| }, | |||
| ), | |||
| Row( | |||
| children: [ | |||
| Button2Icon( | |||
| leftIcon: CupertinoIcons.arrow_up_arrow_down, | |||
| rightIcon: CupertinoIcons.chevron_down, | |||
| title: 'Sort', | |||
| onPressed: () {}, | |||
| ValueListenableBuilder<String>( | |||
| valueListenable: bloc.sort, | |||
| builder: (context, sort, _) { | |||
| return Button2Icon( | |||
| leftIcon: (sort == describeEnum(SortType.asc)) ? CupertinoIcons.arrow_up : CupertinoIcons.arrow_down, | |||
| title: 'Ngày tạo', | |||
| onPressed: () { | |||
| if (sort == describeEnum(SortType.asc)) { | |||
| bloc.sort.value = describeEnum(SortType.desc); | |||
| } else { | |||
| bloc.sort.value = describeEnum(SortType.asc); | |||
| } | |||
| bloc.sort.notifyListeners(); | |||
| bloc.add(OnRefresh()); | |||
| }, | |||
| ); | |||
| }, | |||
| ), | |||
| Button2Icon( | |||
| leftIcon: CupertinoIcons.slider_horizontal_3, | |||
| title: 'Filter', | |||
| rightWidget: Container( | |||
| width: 16, | |||
| height: 16, | |||
| decoration: BoxDecoration( | |||
| borderRadius: BorderRadius.circular(12), | |||
| color: Colors.blue, | |||
| ), | |||
| child: Center( | |||
| child: Text( | |||
| '1', | |||
| style: StylesText.caption6.copyWith(color: Colors.white), | |||
| ), | |||
| ), | |||
| ), | |||
| onPressed: () {}, | |||
| ValueListenableBuilder<List<ItemDropDown>>( | |||
| valueListenable: bloc.selectedStatus, | |||
| builder: (context, selecteds, _) { | |||
| return ValueListenableBuilder<List<ItemDropDown>>( | |||
| valueListenable: bloc.status, | |||
| builder: (context, status, _) { | |||
| return MultipleSelectBottomSheet( | |||
| dataSources: status, | |||
| initValue: selecteds, | |||
| onSelected: (val) { | |||
| bloc.selectedStatus.value = val; | |||
| Helpers.hideKeyboard(context); | |||
| bloc.add(OnRefresh()); | |||
| }, | |||
| hint: 'Trạng thái', | |||
| ); | |||
| }, | |||
| ); | |||
| }, | |||
| ), | |||
| const Spacer(), | |||
| SecondButton( | |||
| @@ -99,6 +99,41 @@ class _CreateStampPageState extends State<CreateStampPage> { | |||
| child: Column( | |||
| crossAxisAlignment: CrossAxisAlignment.start, | |||
| children: [ | |||
| ItemColumnWidget( | |||
| title: 'Lô', | |||
| child: ValueListenableBuilder<String>( | |||
| valueListenable: bloc.selectedPlot, | |||
| builder: (context, selected, _) { | |||
| return ValueListenableBuilder<List<ItemDropDown>>( | |||
| valueListenable: bloc.plots, | |||
| builder: (context, plots, _) { | |||
| return DropdownBottomSheet( | |||
| dataSources: plots, | |||
| initValue: selected, | |||
| onSelected: (val) { | |||
| bloc.selectedPlot.value = val.key ?? ''; | |||
| }, | |||
| hint: 'Lô', | |||
| ); | |||
| }, | |||
| ); | |||
| }, | |||
| ), | |||
| ), | |||
| const SizedBox( | |||
| height: 8, | |||
| ), | |||
| ItemColumnWidget( | |||
| title: 'Tên sản phẩm', | |||
| child: TextFieldNormal( | |||
| controller: bloc.productNameCtl, | |||
| maxLines: 1, | |||
| hint: 'Tên sản phẩm', | |||
| ), | |||
| ), | |||
| const SizedBox( | |||
| height: 8, | |||
| ), | |||
| ItemColumnWidget( | |||
| title: 'Mô tả', | |||
| child: TextFieldNormal( | |||
| @@ -114,6 +149,10 @@ class _CreateStampPageState extends State<CreateStampPage> { | |||
| title: 'Số lượng tem', | |||
| child: TextFieldNormal( | |||
| controller: bloc.quantityCtl, | |||
| keyboardType: TextInputType.number, | |||
| inputFormatters: [ | |||
| FilteringTextInputFormatter.digitsOnly, | |||
| ], | |||
| maxLines: 1, | |||
| hint: 'Số lượng tem', | |||
| ), | |||
| @@ -7,6 +7,7 @@ import 'package:farm_tpf/utils/formatter.dart'; | |||
| import 'package:flutter/material.dart'; | |||
| import 'package:get/get.dart'; | |||
| import '../../../../custom_model/TbCropDTO.dart'; | |||
| import '../../../../data/api/app_exception.dart'; | |||
| import '../../../../data/repository/repository.dart'; | |||
| import '../../../../models/item_dropdown.dart'; | |||
| @@ -21,15 +22,21 @@ class CreateStampCubit extends Cubit<CreateStampState> { | |||
| final descriptionCtl = TextEditingController(); | |||
| final quantityCtl = TextEditingController(); | |||
| final expiredDateCtl = TextEditingController(); | |||
| final productNameCtl = TextEditingController(); | |||
| var stampTypeRaws = <StampType>[]; | |||
| var stampTypes = ValueNotifier(<ItemDropDown>[]); | |||
| var selectedStampType = ValueNotifier(''); | |||
| var plotRaws = <TbCropDTO>[]; | |||
| var plots = ValueNotifier(<ItemDropDown>[]); | |||
| var selectedPlot = ValueNotifier(''); | |||
| // var existedCreateStamp = UpdateCreateStamp(); | |||
| void dispose() { | |||
| descriptionCtl.dispose(); | |||
| quantityCtl.dispose(); | |||
| expiredDateCtl.dispose(); | |||
| productNameCtl.dispose(); | |||
| } | |||
| Future<void> preparedData() async { | |||
| @@ -42,6 +49,13 @@ class CreateStampCubit extends Cubit<CreateStampState> { | |||
| (e) => ItemDropDown(key: e.id?.toString(), value: e.exampleStampName), | |||
| ) | |||
| .toList(); | |||
| plotRaws = await repository.getPlotsWithoutPaging(); | |||
| plots.value = plotRaws | |||
| .map( | |||
| (e) => ItemDropDown(key: e.id?.toString(), value: e.code), | |||
| ) | |||
| .toList(); | |||
| emit(CreateStampPrepareDataSuccessful()); | |||
| } catch (e) { | |||
| emit(CreateStampFailure(AppException.handleError(e))); | |||
| @@ -51,16 +65,33 @@ class CreateStampCubit extends Cubit<CreateStampState> { | |||
| Future<void> onSubmit() async { | |||
| if (formKey.currentState!.validate()) { | |||
| var requestStamp = RequestStamp(); | |||
| var stamp = stampTypeRaws.firstWhere( | |||
| (e) => selectedStampType.value == e.id?.toString(), | |||
| orElse: () => StampType(), | |||
| ); | |||
| var expiredDate = DateTime.now().add(Duration(days: int.tryParse(expiredDateCtl.text) ?? 0)); | |||
| requestStamp | |||
| ..tBCropId = int.tryParse(selectedPlot.value) | |||
| ..productName = productNameCtl.text | |||
| ..description = descriptionCtl.text | |||
| ..quantity = int.tryParse(quantityCtl.text) ?? 0 | |||
| ..expiredDate = expiredDate.convertLocalDateTimeToStringUtcDateTime() | |||
| ..tbExampleStampId = int.tryParse(selectedStampType.value); | |||
| if (selectedPlot.value.isEmpty) { | |||
| Utils.showSnackBarWarning(message: 'Vui lòng chọn lô'); | |||
| return; | |||
| } else if (descriptionCtl.text.trim().isEmpty) { | |||
| Utils.showSnackBarWarning(message: 'Vui lòng nhập mô tả'); | |||
| return; | |||
| } else if (productNameCtl.text.trim().isEmpty) { | |||
| Utils.showSnackBarWarning(message: 'Vui lòng nhập tên sản phẩm'); | |||
| return; | |||
| } else if ((int.tryParse(quantityCtl.text) ?? 0) == 0) { | |||
| Utils.showSnackBarWarning(message: 'Vui lòng nhập số lượng tem'); | |||
| return; | |||
| } else if ((int.tryParse(expiredDateCtl.text) ?? 0) == 0) { | |||
| Utils.showSnackBarWarning(message: 'Vui lòng nhập hạn sử dụng'); | |||
| return; | |||
| } else if (selectedStampType.value.isEmpty) { | |||
| Utils.showSnackBarWarning(message: 'Vui lòng nhập mẫu tem'); | |||
| return; | |||
| } | |||
| print(requestStamp.toJson()); | |||
| UtilWidget.showLoading(); | |||
| await repository.createStamp( | |||
| @@ -1,9 +1,17 @@ | |||
| import 'package:bloc/bloc.dart'; | |||
| import 'package:dio/dio.dart'; | |||
| import 'package:equatable/equatable.dart'; | |||
| import 'package:farm_tpf/presentation/screens/codes/bloc/stamp_bloc.dart'; | |||
| import 'package:farm_tpf/presentation/screens/codes/models/stamp_timeline.dart'; | |||
| import 'package:flutter/widgets.dart'; | |||
| import 'package:flutter_bloc/flutter_bloc.dart'; | |||
| import 'package:open_filex/open_filex.dart'; | |||
| import 'package:path_provider/path_provider.dart'; | |||
| import '../../../../data/api/app_exception.dart'; | |||
| import '../../../../data/api/dio_provider.dart'; | |||
| import '../../../../data/repository/repository.dart'; | |||
| import '../../../../utils/const_common.dart'; | |||
| import '../../../../utils/utils.dart'; | |||
| import '../../../custom_widgets/widget_utils.dart'; | |||
| import '../models/stamp.dart'; | |||
| @@ -13,6 +21,8 @@ part 'detail_stamp_state.dart'; | |||
| class DetailStampCubit extends Cubit<DetailStampState> { | |||
| final repository = Repository(); | |||
| DetailStampCubit() : super(DetailStampInitial()); | |||
| static String pathDownload = 'download'; | |||
| String currentLocalPath = ''; | |||
| Future<void> preparedData(int stampId) async { | |||
| try { | |||
| @@ -20,12 +30,6 @@ class DetailStampCubit extends Cubit<DetailStampState> { | |||
| emit(DetailStampLoading()); | |||
| var stamp = await repository.getStampDetail(id: stampId); | |||
| var timeline = await repository.getStampTimeline(stampId: stampId); | |||
| // stampTypeRaws = await repository.stampTypes(); | |||
| // stampTypes.value = stampTypeRaws | |||
| // .map( | |||
| // (e) => ItemDropDown(key: e.id?.toString(), value: e.exampleStampName), | |||
| // ) | |||
| // .toList(); | |||
| emit(DetailStampSuccessful(stamp, timeline)); | |||
| } catch (e) { | |||
| emit(DetailStampFailure(AppException.handleError(e))); | |||
| @@ -51,4 +55,42 @@ class DetailStampCubit extends Cubit<DetailStampState> { | |||
| status: status, | |||
| ); | |||
| } | |||
| Future downloadFile(String stampCode) async { | |||
| try { | |||
| var dio = DioProvider(); | |||
| print(DateTime.now().millisecondsSinceEpoch); | |||
| var fileName = '${DateTime.now().millisecondsSinceEpoch.toString()}.xlsx'; | |||
| currentLocalPath = await getFilePath(fileName); | |||
| UtilWidget.showLoading(); | |||
| await dio.download( | |||
| '${ConstCommon.baseUrl}/api/tb-codes/export?tbCodeValue=$stampCode', | |||
| currentLocalPath, | |||
| onReceiveProgress: (rec, total) {}, | |||
| ); | |||
| UtilWidget.hideDialog(); | |||
| await onOpenFile(); | |||
| } catch (e) { | |||
| UtilWidget.hideDialog(); | |||
| Utils.showSnackBarError(message: 'Không thể mở tập tin'); | |||
| print(e.toString()); | |||
| } | |||
| } | |||
| Future<void> onOpenFile() async { | |||
| var result = await OpenFilex.open(currentLocalPath); | |||
| if (result.type == ResultType.done) { | |||
| //can open | |||
| } else { | |||
| Utils.showSnackBarError(message: 'Không thể mở tập tin'); | |||
| } | |||
| } | |||
| Future<String> getFilePath(uniqueFileName) async { | |||
| var dir = await getApplicationDocumentsDirectory(); | |||
| var path = '${dir.path}/$pathDownload/$uniqueFileName'; | |||
| print(path); | |||
| return path; | |||
| } | |||
| } | |||
| @@ -7,6 +7,7 @@ class Stamp { | |||
| TbCropDTO? tbCropDTO; | |||
| String? code; | |||
| num? quantity; | |||
| String? productName; | |||
| String? description; | |||
| String? pathImage; | |||
| String? status; | |||
| @@ -19,6 +20,7 @@ class Stamp { | |||
| this.tbCropDTO, | |||
| this.code, | |||
| this.quantity, | |||
| this.productName, | |||
| this.description, | |||
| this.pathImage, | |||
| this.status, | |||
| @@ -29,9 +31,10 @@ class Stamp { | |||
| Stamp.fromJson(Map<String, dynamic> json) { | |||
| id = json['id']; | |||
| tbCropDTO = json['TbCropDTO'] != null ? new TbCropDTO.fromJson(json['TbCropDTO']) : null; | |||
| tbCropDTO = json['tbCrop'] != null ? new TbCropDTO.fromJson(json['tbCrop']) : null; | |||
| code = json['code']; | |||
| quantity = json['quantity']; | |||
| productName = json['productName']; | |||
| description = json['description']; | |||
| pathImage = json['pathImage']; | |||
| status = json['status']; | |||
| @@ -44,10 +47,11 @@ class Stamp { | |||
| final Map<String, dynamic> data = new Map<String, dynamic>(); | |||
| data['id'] = this.id; | |||
| if (this.tbCropDTO != null) { | |||
| data['TbCropDTO'] = this.tbCropDTO?.toJson(); | |||
| data['tbCrop'] = this.tbCropDTO?.toJson(); | |||
| } | |||
| data['code'] = this.code; | |||
| data['quantity'] = this.quantity; | |||
| data['productName'] = this.productName; | |||
| data['description'] = this.description; | |||
| data['pathImage'] = this.pathImage; | |||
| data['status'] = this.status; | |||
| @@ -0,0 +1,21 @@ | |||
| class StampFilterRequest { | |||
| List<String>? status; | |||
| String? description; | |||
| String? sort; | |||
| StampFilterRequest({this.status, this.description, this.sort}); | |||
| StampFilterRequest.fromJson(Map<String, dynamic> json) { | |||
| status = json['status'].cast<String>(); | |||
| description = json['description']; | |||
| sort = json['sort']; | |||
| } | |||
| Map<String, dynamic> toJson() { | |||
| final Map<String, dynamic> data = new Map<String, dynamic>(); | |||
| data['status'] = this.status; | |||
| data['description'] = this.description; | |||
| data['sort'] = this.sort; | |||
| return data; | |||
| } | |||
| } | |||
| @@ -4,6 +4,7 @@ class RequestStamp { | |||
| int? quantity; | |||
| String? description; | |||
| String? expiredDate; | |||
| String? productName; | |||
| RequestStamp({ | |||
| this.tBCropId, | |||
| @@ -11,6 +12,7 @@ class RequestStamp { | |||
| this.quantity, | |||
| this.description, | |||
| this.expiredDate, | |||
| this.productName, | |||
| }); | |||
| RequestStamp.fromJson(Map<String, dynamic> json) { | |||
| @@ -19,6 +21,7 @@ class RequestStamp { | |||
| quantity = json['quantity']; | |||
| description = json['description']; | |||
| expiredDate = json['expiredDate']; | |||
| productName = json['productName']; | |||
| } | |||
| Map<String, dynamic> toJson() { | |||
| @@ -28,6 +31,8 @@ class RequestStamp { | |||
| data['quantity'] = this.quantity; | |||
| data['description'] = this.description; | |||
| data['expiredDate'] = this.expiredDate; | |||
| data['productName'] = this.productName; | |||
| return data; | |||
| } | |||
| } | |||
| @@ -27,6 +27,7 @@ class ContentTimeline { | |||
| int? cropId; | |||
| String? executeDate; | |||
| String? description; | |||
| String? location; | |||
| String? createdDate; | |||
| int? createdById; | |||
| String? createdByName; | |||
| @@ -57,6 +58,7 @@ class ContentTimeline { | |||
| cropId = json['cropId']; | |||
| executeDate = json['executeDate']; | |||
| description = json['description']; | |||
| location = json['location']; | |||
| createdDate = json['createdDate']; | |||
| createdById = json['createdById']; | |||
| createdByName = json['createdByName']; | |||
| @@ -74,6 +76,7 @@ class ContentTimeline { | |||
| data['cropId'] = this.cropId; | |||
| data['executeDate'] = this.executeDate; | |||
| data['description'] = this.description; | |||
| data['location'] = this.location; | |||
| data['createdDate'] = this.createdDate; | |||
| data['createdById'] = this.createdById; | |||
| data['createdByName'] = this.createdByName; | |||
| @@ -1,4 +1,5 @@ | |||
| // ignore_for_file: public_member_api_docs, sort_constructors_first | |||
| import 'package:farm_tpf/utils/helpers.dart'; | |||
| import 'package:flutter/material.dart'; | |||
| import 'package:farm_tpf/presentation/screens/codes/models/stamp.dart'; | |||
| @@ -20,13 +21,7 @@ class ItemCode extends StatelessWidget { | |||
| Widget build(BuildContext context) { | |||
| var expiredDate = item.expiredDate?.format_DDMMYY().toString() ?? ''; | |||
| var quantity = item.quantity; | |||
| var status = 'mới'; | |||
| switch (item.status) { | |||
| case 'NEW': | |||
| status = 'mới'; | |||
| break; | |||
| default: | |||
| } | |||
| var status = Helpers.getStampStatus(item.status ?? ''); | |||
| return GestureDetector( | |||
| onTap: () { | |||
| onPressed(); | |||
| @@ -46,8 +41,8 @@ class ItemCode extends StatelessWidget { | |||
| crossAxisAlignment: CrossAxisAlignment.start, | |||
| children: [ | |||
| Text( | |||
| item.description ?? '', | |||
| style: StylesText.body1, | |||
| '${item.description ?? ''} - ${item.tbCropDTO?.code ?? ''}', | |||
| style: StylesText.body4, | |||
| ), | |||
| const SizedBox( | |||
| height: 8, | |||
| @@ -92,7 +92,7 @@ class ItemCodeTimeline extends StatelessWidget { | |||
| _widgetItemInfoCompleted( | |||
| title: 'Mô tả', | |||
| actionDate: item.description ?? '', | |||
| location: 'dsfsdfsd f sdf dsf sd fds ', | |||
| location: item.location ?? '', | |||
| ), | |||
| ], | |||
| ), | |||
| @@ -1,18 +1,15 @@ | |||
| import 'package:farm_tpf/custom_model/TbCropDTO.dart'; | |||
| import 'package:farm_tpf/data/repository/repository.dart'; | |||
| import 'package:farm_tpf/presentation/custom_widgets/bottom_loader.dart'; | |||
| import 'package:farm_tpf/presentation/custom_widgets/loading_list_page.dart'; | |||
| import 'package:farm_tpf/presentation/screens/plot/widget_search.dart'; | |||
| import 'package:farm_tpf/presentation/screens/plot_detail/sc_plot_detail.dart'; | |||
| import 'package:farm_tpf/utils/const_assets.dart'; | |||
| import 'package:farm_tpf/utils/const_color.dart'; | |||
| import 'package:farm_tpf/utils/pref.dart'; | |||
| import 'package:flutter/material.dart'; | |||
| import 'package:flutter_bloc/flutter_bloc.dart'; | |||
| import 'package:farm_tpf/utils/const_string.dart'; | |||
| import 'package:farm_tpf/utils/formatter.dart'; | |||
| import 'bloc/plot_bloc.dart'; | |||
| import 'widgets/item_plot.dart'; | |||
| class PlotListScreen extends StatefulWidget { | |||
| @override | |||
| @@ -87,7 +84,13 @@ class _InfinityViewState extends State<InfinityView> { | |||
| style: TextStyle(fontWeight: FontWeight.w500, fontSize: 22), | |||
| ), | |||
| ), | |||
| WidgetSearch(), | |||
| WidgetSearch( | |||
| searchController: TextEditingController(), | |||
| onPressed: (value) { | |||
| FocusScope.of(context).requestFocus(FocusNode()); | |||
| BlocProvider.of<PlotBloc>(context).add(OnSearch(searchString: value)); | |||
| }, | |||
| ), | |||
| Expanded(child: BlocBuilder<PlotBloc, PlotState>( | |||
| builder: (context, state) { | |||
| if (state is PlotFailure) { | |||
| @@ -98,17 +101,34 @@ class _InfinityViewState extends State<InfinityView> { | |||
| return Center(child: Text(label_list_empty)); | |||
| } | |||
| return RefreshIndicator( | |||
| child: ListView.builder( | |||
| physics: AlwaysScrollableScrollPhysics(), | |||
| itemBuilder: (BuildContext context, int index) { | |||
| return index >= (state.items ?? []).length ? BottomLoader() : ItemInfinityWidget(item: state.items?[index]); | |||
| }, | |||
| itemCount: (state.hasReachedMax ?? false) ? (state.items ?? []).length : (state.items ?? []).length + 1, | |||
| controller: _scrollController, | |||
| ), | |||
| onRefresh: () async { | |||
| _plotBloc?.add(OnRefresh()); | |||
| }); | |||
| child: ListView.builder( | |||
| physics: AlwaysScrollableScrollPhysics(), | |||
| itemBuilder: (BuildContext context, int index) { | |||
| return index >= (state.items ?? []).length | |||
| ? BottomLoader() | |||
| : ItemPlot( | |||
| item: state.items?[index], | |||
| onPressed: () { | |||
| Navigator.push( | |||
| context, | |||
| MaterialPageRoute( | |||
| builder: (BuildContext context) => PlotDetailScreen( | |||
| cropId: state.items?[index].id ?? -1, | |||
| initialIndex: 0, | |||
| cropType: state.items?[index].tbCropTypeId ?? -1, | |||
| ), | |||
| ), | |||
| ); | |||
| }, | |||
| ); | |||
| }, | |||
| itemCount: (state.hasReachedMax ?? false) ? (state.items ?? []).length : (state.items ?? []).length + 1, | |||
| controller: _scrollController, | |||
| ), | |||
| onRefresh: () async { | |||
| _plotBloc?.add(OnRefresh()); | |||
| }, | |||
| ); | |||
| } | |||
| return Center( | |||
| child: LoadingListPage(), | |||
| @@ -125,109 +145,3 @@ class _InfinityViewState extends State<InfinityView> { | |||
| super.dispose(); | |||
| } | |||
| } | |||
| class ItemInfinityWidget extends StatelessWidget { | |||
| final TbCropDTO item; | |||
| const ItemInfinityWidget({Key? key, required this.item}) : super(key: key); | |||
| @override | |||
| Widget build(BuildContext context) { | |||
| var backgroundColor; | |||
| var textColor; | |||
| switch (item.status) { | |||
| case "STATUS_ARE_ACTIVE": | |||
| backgroundColor = Colors.white; | |||
| textColor = AppColors.DEFAULT; | |||
| break; | |||
| case "STATUS_FINISHED": | |||
| backgroundColor = AppColors.DEFAULT; | |||
| textColor = Colors.white; | |||
| break; | |||
| default: | |||
| backgroundColor = Colors.white; | |||
| textColor = Colors.black; | |||
| } | |||
| return GestureDetector( | |||
| child: Container( | |||
| margin: EdgeInsets.all(8), | |||
| decoration: BoxDecoration( | |||
| color: backgroundColor, | |||
| border: Border.all(color: Colors.grey, width: 0.35), | |||
| borderRadius: BorderRadius.circular(8), | |||
| boxShadow: [BoxShadow(color: Colors.grey, blurRadius: 3, offset: Offset(0, 3))]), | |||
| padding: EdgeInsets.all(8), | |||
| child: Row( | |||
| children: [ | |||
| Container( | |||
| child: Stack( | |||
| children: [ | |||
| Image.asset( | |||
| AppAssets.tempImage, | |||
| width: 65, | |||
| height: 65, | |||
| ), | |||
| Positioned( | |||
| child: Container( | |||
| color: Colors.grey.withOpacity(0.5), | |||
| width: 65, | |||
| height: 20, | |||
| child: Text(item.areaM2?.formatNumtoStringDecimal().toString() ?? '' + " m\u00B2", | |||
| textAlign: TextAlign.center, style: TextStyle(color: Colors.white)), | |||
| ), | |||
| bottom: 0, | |||
| ) | |||
| ], | |||
| ), | |||
| ), | |||
| SizedBox( | |||
| width: 12, | |||
| ), | |||
| Expanded( | |||
| child: Container( | |||
| height: 75, | |||
| child: Column( | |||
| crossAxisAlignment: CrossAxisAlignment.start, | |||
| mainAxisSize: MainAxisSize.min, | |||
| children: [ | |||
| Text("${item.code ?? ''} - ${item.suppliesName ?? ''}", style: TextStyle(color: textColor, fontWeight: FontWeight.bold)), | |||
| Expanded( | |||
| child: SizedBox(), | |||
| ), | |||
| Row( | |||
| children: [ | |||
| Icon( | |||
| Icons.access_time, | |||
| size: 16, | |||
| color: textColor, | |||
| ), | |||
| SizedBox( | |||
| width: 4, | |||
| ), | |||
| Expanded( | |||
| child: Text(item.startDate?.format_DDMMYY_HHmm().toString() ?? '', style: TextStyle(color: textColor)), | |||
| ), | |||
| ], | |||
| ) | |||
| ], | |||
| ), | |||
| ), | |||
| ) | |||
| ], | |||
| ), | |||
| ), | |||
| onTap: () { | |||
| Navigator.push( | |||
| context, | |||
| MaterialPageRoute( | |||
| builder: (BuildContext context) => PlotDetailScreen( | |||
| cropId: item.id ?? -1, | |||
| initialIndex: 0, | |||
| cropType: item.tbCropTypeId ?? -1, | |||
| ), | |||
| ), | |||
| ); | |||
| }); | |||
| } | |||
| } | |||
| @@ -5,19 +5,24 @@ import 'package:flutter_bloc/flutter_bloc.dart'; | |||
| import 'bloc/plot_bloc.dart'; | |||
| class WidgetSearch extends StatefulWidget { | |||
| final TextEditingController searchController; | |||
| final Function(String) onPressed; | |||
| const WidgetSearch({ | |||
| super.key, | |||
| required this.searchController, | |||
| required this.onPressed, | |||
| }); | |||
| @override | |||
| _WidgetSearchState createState() => _WidgetSearchState(); | |||
| } | |||
| class _WidgetSearchState extends State<WidgetSearch> { | |||
| BuildContext? _blocContext; | |||
| TextEditingController _searchController = TextEditingController(); | |||
| @override | |||
| void initState() { | |||
| super.initState(); | |||
| _searchController.addListener(() { | |||
| final keyword = _searchController.text; | |||
| widget.searchController.addListener(() { | |||
| final keyword = widget.searchController.text; | |||
| if (keyword.isNotEmpty) { | |||
| //search when text change | |||
| } | |||
| @@ -25,7 +30,7 @@ class _WidgetSearchState extends State<WidgetSearch> { | |||
| } | |||
| Widget getSearchBarUI() { | |||
| _searchController.text = ""; | |||
| // widget.searchController.text = ""; | |||
| return Padding( | |||
| padding: const EdgeInsets.only(left: 8, right: 8, top: 0, bottom: 4), | |||
| child: Row( | |||
| @@ -38,24 +43,23 @@ class _WidgetSearchState extends State<WidgetSearch> { | |||
| padding: const EdgeInsets.only(left: 16, right: 16, top: 4, bottom: 4), | |||
| child: TextField( | |||
| textInputAction: TextInputAction.done, | |||
| controller: _searchController, | |||
| controller: widget.searchController, | |||
| onChanged: (String txt) {}, | |||
| cursorColor: AppColors.GRAY1, | |||
| decoration: InputDecoration( | |||
| suffixIcon: IconButton( | |||
| icon: Icon( | |||
| Icons.search, | |||
| size: 30, | |||
| ), | |||
| onPressed: () { | |||
| FocusScope.of(context).requestFocus(FocusNode()); | |||
| BlocProvider.of<PlotBloc>(_blocContext!).add(OnSearch(searchString: _searchController.text)); | |||
| }), | |||
| icon: Icon( | |||
| Icons.search, | |||
| size: 30, | |||
| ), | |||
| onPressed: () { | |||
| widget.onPressed(widget.searchController.text); | |||
| }, | |||
| ), | |||
| hintText: 'Tìm theo mã, tên lô', | |||
| hintStyle: TextStyle(color: Colors.grey[500])), | |||
| onSubmitted: (value) { | |||
| FocusScope.of(context).requestFocus(FocusNode()); | |||
| BlocProvider.of<PlotBloc>(_blocContext!).add(OnSearch(searchString: value)); | |||
| widget.onPressed(widget.searchController.text); | |||
| }, | |||
| ), | |||
| ), | |||
| @@ -69,13 +73,12 @@ class _WidgetSearchState extends State<WidgetSearch> { | |||
| @override | |||
| Widget build(BuildContext context) { | |||
| _blocContext = context; | |||
| return Container(child: getSearchBarUI()); | |||
| } | |||
| @override | |||
| void dispose() { | |||
| _searchController.dispose(); | |||
| widget.searchController.dispose(); | |||
| super.dispose(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,70 @@ | |||
| // ignore_for_file: public_member_api_docs, sort_constructors_first | |||
| import 'package:farm_tpf/utils/helpers.dart'; | |||
| import 'package:flutter/material.dart'; | |||
| import 'package:farm_tpf/presentation/screens/codes/models/stamp.dart'; | |||
| import '../../../../custom_model/TbCropDTO.dart'; | |||
| import '../../../../themes/app_colors.dart'; | |||
| import '../../../../themes/styles_text.dart'; | |||
| import 'package:farm_tpf/utils/formatter.dart'; | |||
| class ItemPlot extends StatelessWidget { | |||
| final TbCropDTO item; | |||
| final Function onPressed; | |||
| ItemPlot({ | |||
| Key? key, | |||
| required this.item, | |||
| required this.onPressed, | |||
| }) : super(key: key); | |||
| @override | |||
| Widget build(BuildContext context) { | |||
| var startDate = item.startDate?.format_DDMMYY().toString() ?? ''; | |||
| var area = item.areaM2?.formatNumtoStringDecimal().toString() ?? ''; | |||
| var backgroundColor; | |||
| switch (item.status) { | |||
| case "STATUS_ARE_ACTIVE": | |||
| backgroundColor = Colors.white; | |||
| break; | |||
| case "STATUS_FINISHED": | |||
| backgroundColor = AppColors.primary1; | |||
| break; | |||
| default: | |||
| backgroundColor = Colors.white; | |||
| } | |||
| return GestureDetector( | |||
| onTap: () { | |||
| onPressed(); | |||
| }, | |||
| child: Container( | |||
| padding: EdgeInsets.symmetric(vertical: 12, horizontal: 8), | |||
| margin: EdgeInsets.symmetric(vertical: 4, horizontal: 8), | |||
| decoration: BoxDecoration( | |||
| borderRadius: BorderRadius.circular(10), | |||
| border: Border.all( | |||
| color: Colors.grey.shade200, | |||
| width: 1, | |||
| ), | |||
| color: backgroundColor, | |||
| ), | |||
| child: Column( | |||
| crossAxisAlignment: CrossAxisAlignment.start, | |||
| children: [ | |||
| Text( | |||
| "${item.code ?? ''} - ${item.suppliesName ?? ''}", | |||
| style: StylesText.body4, | |||
| ), | |||
| const SizedBox( | |||
| height: 8, | |||
| ), | |||
| Text( | |||
| '$startDate - $area m\u00B2', | |||
| style: StylesText.body6, | |||
| ), | |||
| ], | |||
| ), | |||
| ), | |||
| ); | |||
| } | |||
| } | |||
| @@ -30,54 +30,11 @@ class _PlotDetailScreenState extends State<PlotDetailScreen> { | |||
| String? code, | |||
| num? areaM2, | |||
| }) { | |||
| return Column( | |||
| children: [ | |||
| Container( | |||
| padding: EdgeInsets.all(8), | |||
| width: double.infinity, | |||
| color: Colors.white, | |||
| child: Row( | |||
| children: [ | |||
| SizedBox( | |||
| width: 12, | |||
| ), | |||
| Expanded( | |||
| child: Container( | |||
| height: 75, | |||
| child: Column( | |||
| crossAxisAlignment: CrossAxisAlignment.start, | |||
| mainAxisSize: MainAxisSize.min, | |||
| children: [ | |||
| Text('${suppliesName ?? ''}', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)), | |||
| Expanded( | |||
| child: SizedBox(), | |||
| ), | |||
| Text( | |||
| 'Mã lô: ${code ?? ''}', | |||
| style: TextStyle(color: Colors.grey), | |||
| overflow: TextOverflow.clip, | |||
| ), | |||
| Text('Diện tích: ${areaM2?.formatNumtoStringDecimal() ?? '0'} m\u00B2', style: TextStyle(color: Colors.grey)) | |||
| ], | |||
| ), | |||
| ), | |||
| ), | |||
| Image.asset( | |||
| AppAssets.tempImage, | |||
| width: 75, | |||
| height: 75, | |||
| ) | |||
| ], | |||
| ), | |||
| ), | |||
| Expanded( | |||
| child: HomeTabbarWidget( | |||
| cropType: widget.cropType, | |||
| cropId: widget.cropId, | |||
| cropCode: widget.cropCode, | |||
| initialIndex: widget.initialIndex, | |||
| )) | |||
| ], | |||
| return HomeTabbarWidget( | |||
| cropType: widget.cropType, | |||
| cropId: widget.cropId, | |||
| cropCode: widget.cropCode, | |||
| initialIndex: widget.initialIndex, | |||
| ); | |||
| } | |||
| @@ -4,6 +4,7 @@ import 'package:farm_tpf/data/repository/repository.dart'; | |||
| import 'package:farm_tpf/data/repository/user_repository.dart'; | |||
| import 'package:farm_tpf/presentation/custom_widgets/widget_utils.dart'; | |||
| import 'package:farm_tpf/presentation/screens/account/sc_account.dart'; | |||
| import 'package:farm_tpf/presentation/screens/codes/code_detail_page.dart'; | |||
| import 'package:farm_tpf/presentation/screens/codes/code_page.dart'; | |||
| import 'package:farm_tpf/presentation/screens/control_device/sc_control_device.dart'; | |||
| import 'package:farm_tpf/presentation/screens/notification/sc_notification.dart'; | |||
| @@ -303,35 +304,47 @@ class _TabbarScreenState extends State<TabbarScreen> { | |||
| ))); | |||
| } | |||
| _showAlertCheckCropCode(String cropCode) async { | |||
| _showAlertCheckCropCode(String code) async { | |||
| var repository = Repository(); | |||
| Get.defaultDialog(title: "Kiểm tra thông tin lô ....", middleText: "", content: CircularProgressIndicator()); | |||
| try { | |||
| await repository.getPlotDetailByCode(cropCode).then((value) { | |||
| print("ok"); | |||
| if (Get.isDialogOpen ?? false) Get.back(); | |||
| Get.to(PlotDetailScreen(cropId: value.tbCropDTO?.id ?? -1, cropType: value.tbCropDTO?.tbCropTypeId ?? -1, initialIndex: 0)); | |||
| }).catchError((onError) { | |||
| Utils.showDialog( | |||
| title: "Không tìm thấy lô", | |||
| message: "Thử lại với mã tem khác?", | |||
| textConfirm: "Thử lại", | |||
| textCancel: "Huỷ", | |||
| onConfirm: () { | |||
| Get.back(); | |||
| // scan(context); | |||
| }); | |||
| }); | |||
| if (code.startsWith('LO')) { | |||
| await repository.getPlotDetailByCode(code).then((value) { | |||
| print("ok"); | |||
| if (Get.isDialogOpen ?? false) Get.back(); | |||
| Get.to( | |||
| PlotDetailScreen( | |||
| cropId: value.tbCropDTO?.id ?? -1, | |||
| cropType: value.tbCropDTO?.tbCropTypeId ?? -1, | |||
| initialIndex: 0, | |||
| ), | |||
| ); | |||
| }).catchError((onError) { | |||
| Utils.showDialog( | |||
| title: "Không tìm thấy lô", | |||
| message: "Thử lại với mã tem khác?", | |||
| textConfirm: "Thử lại", | |||
| textCancel: "Huỷ", | |||
| onConfirm: () { | |||
| Get.back(); | |||
| // scan(context); | |||
| }); | |||
| }); | |||
| } else if (code.startsWith('AC')) { | |||
| var stamp = await repository.getStampDetailByCode(code: code); | |||
| Get.to(() => CodeDetailPage(stampId: stamp.id ?? 0, stampCode: stamp.code ?? '')); | |||
| } | |||
| } catch (e) { | |||
| Utils.showDialog( | |||
| title: "Không tìm thấy lô", | |||
| message: "Thử lại với mã tem khác?", | |||
| textConfirm: "Thử lại", | |||
| textCancel: "Huỷ", | |||
| onConfirm: () { | |||
| Get.back(); | |||
| // scan(context); | |||
| }); | |||
| title: "Không tìm thấy lô", | |||
| message: "Thử lại với mã tem khác?", | |||
| textConfirm: "Thử lại", | |||
| textCancel: "Huỷ", | |||
| onConfirm: () { | |||
| Get.back(); | |||
| // scan(context); | |||
| }, | |||
| ); | |||
| } | |||
| } | |||
| } | |||
| @@ -20,4 +20,17 @@ class ConstCommon { | |||
| } | |||
| enum CRUDStatus { unknown, add, edit, delete } | |||
| enum LocationType { country, province, district, ward } | |||
| enum StampStatus { | |||
| NEW, | |||
| ACTIVE, | |||
| CANCELED, | |||
| EXPIRED, | |||
| } | |||
| enum SortType { | |||
| asc, | |||
| desc, | |||
| } | |||
| @@ -1,3 +1,5 @@ | |||
| import 'package:farm_tpf/utils/const_common.dart'; | |||
| import 'package:flutter/foundation.dart'; | |||
| import 'package:flutter/material.dart'; | |||
| class Helpers { | |||
| @@ -29,4 +31,17 @@ class Helpers { | |||
| return ''; | |||
| } | |||
| } | |||
| static String getStampStatus(String status) { | |||
| if (status.toUpperCase() == describeEnum(StampStatus.NEW)) { | |||
| return 'Mới'; | |||
| } else if (status.toUpperCase() == describeEnum(StampStatus.ACTIVE)) { | |||
| return 'Đã kích hoạt'; | |||
| } else if (status.toUpperCase() == describeEnum(StampStatus.CANCELED)) { | |||
| return 'Đã huỷ'; | |||
| } else if (status.toUpperCase() == describeEnum(StampStatus.EXPIRED)) { | |||
| return 'Hết hạn'; | |||
| } | |||
| return ''; | |||
| } | |||
| } | |||
| @@ -744,6 +744,14 @@ packages: | |||
| url: "https://pub.dev" | |||
| source: hosted | |||
| version: "3.0.0-pre" | |||
| multi_select_flutter: | |||
| dependency: "direct main" | |||
| description: | |||
| name: multi_select_flutter | |||
| sha256: "503857b415d390d29159df8a9d92d83c6aac17aaf1c307fb7bcfc77d097d20ed" | |||
| url: "https://pub.dev" | |||
| source: hosted | |||
| version: "4.1.3" | |||
| nested: | |||
| dependency: transitive | |||
| description: | |||
| @@ -760,6 +768,14 @@ packages: | |||
| url: "https://pub.dev" | |||
| source: hosted | |||
| version: "1.0.2" | |||
| open_filex: | |||
| dependency: "direct main" | |||
| description: | |||
| name: open_filex | |||
| sha256: "74e2280754cf8161e860746c3181db2c996d6c1909c7057b738ede4a469816b8" | |||
| url: "https://pub.dev" | |||
| source: hosted | |||
| version: "4.4.0" | |||
| package_config: | |||
| dependency: transitive | |||
| description: | |||
| @@ -2,7 +2,7 @@ name: farm_tpf | |||
| description: A new Flutter project. | |||
| publish_to: 'none' | |||
| version: 1.1.5+20 | |||
| version: 1.1.6+21 | |||
| environment: | |||
| sdk: ">=2.17.0 <=3.0.0" | |||
| @@ -65,6 +65,8 @@ dependencies: | |||
| flutter_switch: ^0.3.2 | |||
| image_picker: ^0.8.3+2 | |||
| modal_bottom_sheet: ^3.0.0-pre | |||
| open_filex: ^4.4.0 | |||
| multi_select_flutter: ^4.1.3 | |||
| dev_dependencies: | |||
| flutter_test: | |||