| CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; | CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; | ||||
| CODE_SIGN_IDENTITY = "Apple Development"; | CODE_SIGN_IDENTITY = "Apple Development"; | ||||
| CODE_SIGN_STYLE = Automatic; | CODE_SIGN_STYLE = Automatic; | ||||
| CURRENT_PROJECT_VERSION = 22; | |||||
| CURRENT_PROJECT_VERSION = 23; | |||||
| DEVELOPMENT_TEAM = C3DTD2JH94; | DEVELOPMENT_TEAM = C3DTD2JH94; | ||||
| ENABLE_BITCODE = NO; | ENABLE_BITCODE = NO; | ||||
| FRAMEWORK_SEARCH_PATHS = ( | FRAMEWORK_SEARCH_PATHS = ( | ||||
| "$(inherited)", | "$(inherited)", | ||||
| "$(PROJECT_DIR)/Flutter", | "$(PROJECT_DIR)/Flutter", | ||||
| ); | ); | ||||
| MARKETING_VERSION = 1.1.6; | |||||
| MARKETING_VERSION = 1.1.7; | |||||
| PRODUCT_BUNDLE_IDENTIFIER = vn.azteam.farmdemo; | PRODUCT_BUNDLE_IDENTIFIER = vn.azteam.farmdemo; | ||||
| PRODUCT_NAME = "$(TARGET_NAME)"; | PRODUCT_NAME = "$(TARGET_NAME)"; | ||||
| PROVISIONING_PROFILE_SPECIFIER = ""; | PROVISIONING_PROFILE_SPECIFIER = ""; | ||||
| CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; | CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; | ||||
| CODE_SIGN_IDENTITY = "Apple Development"; | CODE_SIGN_IDENTITY = "Apple Development"; | ||||
| CODE_SIGN_STYLE = Automatic; | CODE_SIGN_STYLE = Automatic; | ||||
| CURRENT_PROJECT_VERSION = 22; | |||||
| CURRENT_PROJECT_VERSION = 23; | |||||
| DEVELOPMENT_TEAM = C3DTD2JH94; | DEVELOPMENT_TEAM = C3DTD2JH94; | ||||
| ENABLE_BITCODE = NO; | ENABLE_BITCODE = NO; | ||||
| FRAMEWORK_SEARCH_PATHS = ( | FRAMEWORK_SEARCH_PATHS = ( | ||||
| "$(inherited)", | "$(inherited)", | ||||
| "$(PROJECT_DIR)/Flutter", | "$(PROJECT_DIR)/Flutter", | ||||
| ); | ); | ||||
| MARKETING_VERSION = 1.1.6; | |||||
| MARKETING_VERSION = 1.1.7; | |||||
| PRODUCT_BUNDLE_IDENTIFIER = vn.azteam.farmdemo; | PRODUCT_BUNDLE_IDENTIFIER = vn.azteam.farmdemo; | ||||
| PRODUCT_NAME = "$(TARGET_NAME)"; | PRODUCT_NAME = "$(TARGET_NAME)"; | ||||
| PROVISIONING_PROFILE_SPECIFIER = ""; | PROVISIONING_PROFILE_SPECIFIER = ""; | ||||
| CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; | CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; | ||||
| CODE_SIGN_IDENTITY = "Apple Development"; | CODE_SIGN_IDENTITY = "Apple Development"; | ||||
| CODE_SIGN_STYLE = Automatic; | CODE_SIGN_STYLE = Automatic; | ||||
| CURRENT_PROJECT_VERSION = 22; | |||||
| CURRENT_PROJECT_VERSION = 23; | |||||
| DEVELOPMENT_TEAM = C3DTD2JH94; | DEVELOPMENT_TEAM = C3DTD2JH94; | ||||
| ENABLE_BITCODE = NO; | ENABLE_BITCODE = NO; | ||||
| FRAMEWORK_SEARCH_PATHS = ( | FRAMEWORK_SEARCH_PATHS = ( | ||||
| "$(inherited)", | "$(inherited)", | ||||
| "$(PROJECT_DIR)/Flutter", | "$(PROJECT_DIR)/Flutter", | ||||
| ); | ); | ||||
| MARKETING_VERSION = 1.1.6; | |||||
| MARKETING_VERSION = 1.1.7; | |||||
| PRODUCT_BUNDLE_IDENTIFIER = vn.azteam.farmdemo; | PRODUCT_BUNDLE_IDENTIFIER = vn.azteam.farmdemo; | ||||
| PRODUCT_NAME = "$(TARGET_NAME)"; | PRODUCT_NAME = "$(TARGET_NAME)"; | ||||
| PROVISIONING_PROFILE_SPECIFIER = ""; | PROVISIONING_PROFILE_SPECIFIER = ""; |
| import 'package:farm_tpf/presentation/screens/codes/models/stamp_filter_request.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_request.dart'; | ||||
| import 'package:farm_tpf/presentation/screens/codes/models/stamp_timeline.dart'; | import 'package:farm_tpf/presentation/screens/codes/models/stamp_timeline.dart'; | ||||
| import 'package:farm_tpf/presentation/screens/plot/models/crop_filter_request.dart'; | |||||
| import 'package:farm_tpf/presentation/screens/task/models/employee.dart'; | import 'package:farm_tpf/presentation/screens/task/models/employee.dart'; | ||||
| import 'package:farm_tpf/presentation/screens/task/models/supply_filter.dart'; | |||||
| import 'package:farm_tpf/presentation/screens/task/models/task_request.dart'; | import 'package:farm_tpf/presentation/screens/task/models/task_request.dart'; | ||||
| import 'package:farm_tpf/utils/const_common.dart'; | import 'package:farm_tpf/utils/const_common.dart'; | ||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||
| var url = '${ConstCommon.baseUrl}/api/tb-todo-lists/list?page=$page&size=$size'; | var url = '${ConstCommon.baseUrl}/api/tb-todo-lists/list?page=$page&size=$size'; | ||||
| var res = await dio.post(url, data: { | var res = await dio.post(url, data: { | ||||
| // 'status': filter.status, | // 'status': filter.status, | ||||
| "crop_id": filter.cropId, | |||||
| }); | }); | ||||
| return (res.data as List).map((e) => Task.fromJson(e)).toList(); | return (res.data as List).map((e) => Task.fromJson(e)).toList(); | ||||
| rethrow; | rethrow; | ||||
| } | } | ||||
| } | } | ||||
| Future<List<SupplyFilter>> getSuppliesFilter() async { | |||||
| try { | |||||
| var url = '${ConstCommon.baseUrl}/api/tb-supplies/by-login-info/'; | |||||
| var res = await dio.get( | |||||
| url, | |||||
| ); | |||||
| return (res.data as List).map((e) => SupplyFilter.fromJson(e)).toList(); | |||||
| } catch (e) { | |||||
| rethrow; | |||||
| } | |||||
| } | |||||
| // Crop | |||||
| Future<List<TbCropDTO>> crops({ | |||||
| int page = 0, | |||||
| int size = 20, | |||||
| required CropFilterRequest filter, | |||||
| }) async { | |||||
| try { | |||||
| // var url = '${ConstCommon.baseUrl}/api/tb-todo-lists/list?page=$page&size=$size&sort=createdDate,${filter.sort ?? 'asc'}'; | |||||
| var url = '${ConstCommon.baseUrl}/api/tb-crops/list?page=$page&size=$size'; | |||||
| var res = await dio.post(url, data: { | |||||
| 'tbSuppliesIds': filter.supplyIds, | |||||
| }); | |||||
| return (res.data as List).map((e) => TbCropDTO.fromJson(e)).toList(); | |||||
| } catch (e) { | |||||
| rethrow; | |||||
| } | |||||
| } | |||||
| Future<Task> getTaskDetail({required int id}) async { | |||||
| try { | |||||
| var url = '${ConstCommon.baseUrl}/api/tb-todo-lists/$id'; | |||||
| var res = await dio.get(url); | |||||
| return Task.fromJson(res.data); | |||||
| } catch (e) { | |||||
| rethrow; | |||||
| } | |||||
| } | |||||
| } | } |
| import 'package:farm_tpf/data/repository/repository.dart'; | import 'package:farm_tpf/data/repository/repository.dart'; | ||||
| import 'package:farm_tpf/presentation/screens/codes/bloc/stamp_bloc.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/cubit/detail_stamp_cubit.dart'; | ||||
| import 'package:farm_tpf/presentation/screens/plot/bloc/plot_bloc.dart'; | |||||
| import 'package:firebase_core/firebase_core.dart'; | import 'package:firebase_core/firebase_core.dart'; | ||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/services.dart'; | import 'package:flutter/services.dart'; | ||||
| Repository(), | Repository(), | ||||
| ), | ), | ||||
| ), | ), | ||||
| BlocProvider( | |||||
| create: (_) => PlotBloc( | |||||
| repository: Repository(), | |||||
| ), | |||||
| ), | |||||
| BlocProvider( | BlocProvider( | ||||
| create: (_) => DetailStampCubit(), | create: (_) => DetailStampCubit(), | ||||
| ), | ), |
| import 'package:bloc/bloc.dart'; | import 'package:bloc/bloc.dart'; | ||||
| import 'package:equatable/equatable.dart'; | import 'package:equatable/equatable.dart'; | ||||
| import 'package:farm_tpf/custom_model/TbCropDTO.dart'; | |||||
| import 'package:farm_tpf/data/api/app_exception.dart'; | import 'package:farm_tpf/data/api/app_exception.dart'; | ||||
| import 'package:farm_tpf/data/repository/repository.dart'; | import 'package:farm_tpf/data/repository/repository.dart'; | ||||
| import 'package:farm_tpf/presentation/screens/plot/models/crop_filter_request.dart'; | |||||
| import 'package:farm_tpf/presentation/screens/task/models/supply_filter.dart'; | |||||
| import 'package:flutter/material.dart'; | |||||
| import 'package:meta/meta.dart'; | import 'package:meta/meta.dart'; | ||||
| import '../../../../models/item_dropdown.dart'; | |||||
| part 'plot_event.dart'; | part 'plot_event.dart'; | ||||
| part 'plot_state.dart'; | part 'plot_state.dart'; | ||||
| PlotBloc({required this.repository}) : super(PlotInitial()); | PlotBloc({required this.repository}) : super(PlotInitial()); | ||||
| static int pageSize = 20; | static int pageSize = 20; | ||||
| var supplyRaws = <SupplyFilter>[]; | |||||
| var supplies = ValueNotifier(<ItemDropDown>[]); | |||||
| var selectedSupply = ValueNotifier(<ItemDropDown>[]); | |||||
| @override | @override | ||||
| Stream<PlotState> mapEventToState( | Stream<PlotState> mapEventToState( | ||||
| PlotEvent event, | PlotEvent event, | ||||
| try { | try { | ||||
| if (state is PlotInitial) { | if (state is PlotInitial) { | ||||
| yield PlotLoading(); | yield PlotLoading(); | ||||
| final response = await repository.getPlots(page: 0, size: pageSize, searchString: ""); | |||||
| final response = await getPlots(0); | |||||
| yield PlotSuccess(items: response, page: 0, hasReachedMax: response.length < pageSize ? true : false); | yield PlotSuccess(items: response, page: 0, hasReachedMax: response.length < pageSize ? true : false); | ||||
| } | } | ||||
| if (state is PlotSuccess) { | if (state is PlotSuccess) { | ||||
| final currentState = state as PlotSuccess; | final currentState = state as PlotSuccess; | ||||
| if (currentState.hasReachedMax ?? false) { | |||||
| return; | |||||
| } | |||||
| int page = (currentState.page ?? 0) + 1; | int page = (currentState.page ?? 0) + 1; | ||||
| final response = await repository.getPlots(page: page, size: pageSize, searchString: ""); | |||||
| final response = await getPlots(page); | |||||
| yield response.isEmpty | yield response.isEmpty | ||||
| ? currentState.copyWith(hasReachedMax: true) | ? currentState.copyWith(hasReachedMax: true) | ||||
| : PlotSuccess( | : PlotSuccess( | ||||
| if (event is OnRefresh) { | if (event is OnRefresh) { | ||||
| try { | try { | ||||
| yield PlotLoading(); | yield PlotLoading(); | ||||
| final response = await repository.getPlots(page: 0, size: pageSize, searchString: ""); | |||||
| final response = await getPlots(0); | |||||
| yield PlotSuccess(items: response, page: 0, hasReachedMax: response.length < pageSize ? true : false); | yield PlotSuccess(items: response, page: 0, hasReachedMax: response.length < pageSize ? true : false); | ||||
| } catch (e) { | } catch (e) { | ||||
| yield PlotFailure(errorString: AppException.handleError(e)); | yield PlotFailure(errorString: AppException.handleError(e)); | ||||
| } else if (event is OnSearch) { | } else if (event is OnSearch) { | ||||
| try { | try { | ||||
| yield PlotLoading(); | yield PlotLoading(); | ||||
| final response = await repository.getPlots(page: 0, size: pageSize, searchString: event.searchString); | |||||
| final response = await getPlots(0); | |||||
| yield PlotSuccess(items: response, page: 0, hasReachedMax: response.length < pageSize ? true : false); | yield PlotSuccess(items: response, page: 0, hasReachedMax: response.length < pageSize ? true : false); | ||||
| } catch (e) { | } catch (e) { | ||||
| yield PlotFailure(errorString: AppException.handleError(e)); | yield PlotFailure(errorString: AppException.handleError(e)); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| Future<List<TbCropDTO>> getPlots(int page) async { | |||||
| var supplyIds = selectedSupply.value.length > 0 | |||||
| ? selectedSupply.value | |||||
| .map( | |||||
| (e) => int.tryParse(e.key ?? '') ?? -1, | |||||
| ) | |||||
| .toList() | |||||
| : <int>[]; | |||||
| var filter = CropFilterRequest()..supplyIds = supplyIds; | |||||
| return await repository.crops(page: 0, filter: filter); | |||||
| } | |||||
| Future<void> preparedData() async { | |||||
| try { | |||||
| await Future.delayed(const Duration(seconds: 0)); | |||||
| // emit(CreateStampLoading()); | |||||
| supplyRaws = await repository.getSuppliesFilter(); | |||||
| supplies.value = supplyRaws | |||||
| .map( | |||||
| (e) => ItemDropDown(key: e.id?.toString(), value: e.name), | |||||
| ) | |||||
| .toList(); | |||||
| // emit(CreateStampPrepareDataSuccessful()); | |||||
| } catch (e) { | |||||
| print(e); | |||||
| // emit(CreateStampFailure(AppException.handleError(e))); | |||||
| } | |||||
| } | |||||
| } | } |
| class CropFilterRequest { | |||||
| List<int>? supplyIds; | |||||
| String? sort; | |||||
| CropFilterRequest({this.supplyIds, this.sort}); | |||||
| CropFilterRequest.fromJson(Map<String, dynamic> json) { | |||||
| supplyIds = json['tbSuppliesIds'].cast<String>(); | |||||
| sort = json['sort']; | |||||
| } | |||||
| Map<String, dynamic> toJson() { | |||||
| final Map<String, dynamic> data = new Map<String, dynamic>(); | |||||
| data['tbSuppliesIds'] = this.supplyIds; | |||||
| data['sort'] = this.sort; | |||||
| return data; | |||||
| } | |||||
| } |
| import 'package:flutter_bloc/flutter_bloc.dart'; | import 'package:flutter_bloc/flutter_bloc.dart'; | ||||
| import 'package:farm_tpf/utils/const_string.dart'; | import 'package:farm_tpf/utils/const_string.dart'; | ||||
| import '../../../models/item_dropdown.dart'; | |||||
| import '../../../utils/helpers.dart'; | |||||
| import '../../custom_widgets/dropdown/multiple_select_bottom_sheet.dart'; | |||||
| import 'bloc/plot_bloc.dart'; | import 'bloc/plot_bloc.dart'; | ||||
| import 'widgets/item_plot.dart'; | import 'widgets/item_plot.dart'; | ||||
| } | } | ||||
| class _PlotListScreenState extends State<PlotListScreen> { | class _PlotListScreenState extends State<PlotListScreen> { | ||||
| final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>(); | |||||
| var pref = LocalPref(); | var pref = LocalPref(); | ||||
| var token; | var token; | ||||
| var client; | var client; | ||||
| String pushkey = ""; | String pushkey = ""; | ||||
| String currentFullName = ""; | String currentFullName = ""; | ||||
| @override | |||||
| void initState() { | |||||
| super.initState(); | |||||
| } | |||||
| @override | |||||
| Widget build(BuildContext context) { | |||||
| return BlocProvider( | |||||
| create: (context) => PlotBloc(repository: Repository())..add(DataFetched()), | |||||
| child: HoldInfinityWidget(), | |||||
| ); | |||||
| } | |||||
| } | |||||
| class HoldInfinityWidget extends StatelessWidget { | |||||
| final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>(); | |||||
| @override | |||||
| Widget build(BuildContext context) { | |||||
| return Scaffold(backgroundColor: Colors.white, key: _scaffoldKey, body: SafeArea(child: InfinityView())); | |||||
| } | |||||
| } | |||||
| class InfinityView extends StatefulWidget { | |||||
| @override | |||||
| _InfinityViewState createState() => _InfinityViewState(); | |||||
| } | |||||
| class _InfinityViewState extends State<InfinityView> { | |||||
| PlotBloc bloc = PlotBloc(repository: Repository()); | |||||
| final _scrollController = ScrollController(); | final _scrollController = ScrollController(); | ||||
| final _scrollThreshold = 250.0; | final _scrollThreshold = 250.0; | ||||
| PlotBloc? _plotBloc; | |||||
| @override | @override | ||||
| void initState() { | void initState() { | ||||
| super.initState(); | |||||
| bloc.preparedData(); | |||||
| bloc.add(DataFetched()); | |||||
| _scrollController.addListener(() { | _scrollController.addListener(() { | ||||
| final maxScroll = _scrollController.position.maxScrollExtent; | final maxScroll = _scrollController.position.maxScrollExtent; | ||||
| final currentScroll = _scrollController.position.pixels; | final currentScroll = _scrollController.position.pixels; | ||||
| if (maxScroll - currentScroll < _scrollThreshold) { | if (maxScroll - currentScroll < _scrollThreshold) { | ||||
| _plotBloc?.add(DataFetched()); | |||||
| bloc.add(DataFetched()); | |||||
| } | } | ||||
| }); | }); | ||||
| _plotBloc = BlocProvider.of<PlotBloc>(context); | |||||
| super.initState(); | |||||
| } | |||||
| @override | |||||
| Widget build(BuildContext context) { | |||||
| return Column( | |||||
| crossAxisAlignment: CrossAxisAlignment.start, | |||||
| children: <Widget>[ | |||||
| SizedBox( | |||||
| height: 8, | |||||
| ), | |||||
| Container( | |||||
| padding: EdgeInsets.all(8), | |||||
| color: Colors.white, | |||||
| child: Text( | |||||
| 'Danh sách lô trồng', | |||||
| style: TextStyle(fontWeight: FontWeight.w500, fontSize: 22), | |||||
| ), | |||||
| ), | |||||
| 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) { | |||||
| return Center(child: Text(state.errorString)); | |||||
| } | |||||
| if (state is PlotSuccess) { | |||||
| if ((state.items ?? []).isEmpty) { | |||||
| 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() | |||||
| : 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(), | |||||
| ); | |||||
| }, | |||||
| )) | |||||
| ], | |||||
| ); | |||||
| } | } | ||||
| @override | @override | ||||
| _scrollController.dispose(); | _scrollController.dispose(); | ||||
| super.dispose(); | super.dispose(); | ||||
| } | } | ||||
| @override | |||||
| Widget build(BuildContext context) { | |||||
| return Scaffold( | |||||
| backgroundColor: Colors.white, | |||||
| key: _scaffoldKey, | |||||
| body: SafeArea( | |||||
| child: Column( | |||||
| crossAxisAlignment: CrossAxisAlignment.start, | |||||
| children: <Widget>[ | |||||
| SizedBox( | |||||
| height: 8, | |||||
| ), | |||||
| Container( | |||||
| padding: EdgeInsets.all(8), | |||||
| color: Colors.white, | |||||
| child: Text( | |||||
| 'Danh sách lô trồng', | |||||
| style: TextStyle(fontWeight: FontWeight.w500, fontSize: 22), | |||||
| ), | |||||
| ), | |||||
| WidgetSearch( | |||||
| searchController: TextEditingController(), | |||||
| onPressed: (value) { | |||||
| FocusScope.of(context).requestFocus(FocusNode()); | |||||
| BlocProvider.of<PlotBloc>(context).add(OnSearch(searchString: value)); | |||||
| }, | |||||
| ), | |||||
| ValueListenableBuilder<List<ItemDropDown>>( | |||||
| valueListenable: bloc.selectedSupply, | |||||
| builder: (context, selecteds, _) { | |||||
| return ValueListenableBuilder<List<ItemDropDown>>( | |||||
| valueListenable: bloc.supplies, | |||||
| builder: (context, status, _) { | |||||
| return MultipleSelectBottomSheet( | |||||
| dataSources: status, | |||||
| initValue: selecteds, | |||||
| onSelected: (val) { | |||||
| bloc.selectedSupply.value = val; | |||||
| Helpers.hideKeyboard(context); | |||||
| bloc.add(OnRefresh()); | |||||
| }, | |||||
| hint: 'Giống', | |||||
| ); | |||||
| }, | |||||
| ); | |||||
| }, | |||||
| ), | |||||
| Expanded( | |||||
| child: BlocBuilder<PlotBloc, PlotState>( | |||||
| bloc: bloc, | |||||
| builder: (context, state) { | |||||
| if (state is PlotFailure) { | |||||
| return Center(child: Text(state.errorString)); | |||||
| } | |||||
| if (state is PlotSuccess) { | |||||
| if ((state.items ?? []).isEmpty) { | |||||
| 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() | |||||
| : 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 { | |||||
| bloc.add(OnRefresh()); | |||||
| }, | |||||
| ); | |||||
| } | |||||
| return Center( | |||||
| child: LoadingListPage(), | |||||
| ); | |||||
| }, | |||||
| )) | |||||
| ], | |||||
| ), | |||||
| ), | |||||
| ); | |||||
| } | |||||
| } | } |
| .toList(), | .toList(), | ||||
| ); | ); | ||||
| var sort = ValueNotifier(describeEnum(SortType.asc)); | var sort = ValueNotifier(describeEnum(SortType.asc)); | ||||
| var cropId = -1; | |||||
| @override | @override | ||||
| Stream<TaskState> mapEventToState( | Stream<TaskState> mapEventToState( | ||||
| Future<List<Task>> getListTask(int page) async { | Future<List<Task>> getListTask(int page) async { | ||||
| var filter = TaskFilterRequest() | var filter = TaskFilterRequest() | ||||
| ..cropId = cropId | |||||
| ..sort = sort.value | ..sort = sort.value | ||||
| ..status = selectedStatus.value.map((e) => e.key ?? '').toList(); | ..status = selectedStatus.value.map((e) => e.key ?? '').toList(); | ||||
| return await repository.tasks(page: 0, filter: filter); | return await repository.tasks(page: 0, filter: filter); | ||||
| await repository.updateTask( | await repository.updateTask( | ||||
| (success) { | (success) { | ||||
| UtilWidget.hideDialog(); | UtilWidget.hideDialog(); | ||||
| Utils.showSnackBarSuccess(); | |||||
| // Utils.showSnackBarSuccess(); | |||||
| onSuccess(); | onSuccess(); | ||||
| }, | }, | ||||
| (errorMessage) { | (errorMessage) { |
| import 'package:bloc/bloc.dart'; | |||||
| import 'package:equatable/equatable.dart'; | |||||
| import 'package:get/get.dart'; | |||||
| import '../../../../data/api/app_exception.dart'; | |||||
| import '../../../../data/repository/repository.dart'; | |||||
| import '../../../../utils/utils.dart'; | |||||
| import '../../../custom_widgets/widget_utils.dart'; | |||||
| import '../models/task.dart'; | |||||
| import '../models/task_update_request.dart'; | |||||
| part 'task_detail_state.dart'; | |||||
| class TaskDetailCubit extends Cubit<TaskDetailState> { | |||||
| final repository = Repository(); | |||||
| TaskDetailCubit() : super(TaskDetailInitial()); | |||||
| Future<void> preparedData(int taskId) async { | |||||
| try { | |||||
| await Future.delayed(const Duration(seconds: 0)); | |||||
| emit(TaskDetailLoading()); | |||||
| var task = await repository.getTaskDetail(id: taskId); | |||||
| emit(TaskDetailSuccessful(task)); | |||||
| } catch (e) { | |||||
| emit(TaskDetailFailure(AppException.handleError(e))); | |||||
| } | |||||
| } | |||||
| Future<void> updateStatusTask( | |||||
| RequestTaskUpdate task, | |||||
| ) async { | |||||
| print(task.toJson()); | |||||
| UtilWidget.showLoading(); | |||||
| await repository.updateTask( | |||||
| (success) { | |||||
| UtilWidget.hideDialog(); | |||||
| Get.back(result: 'ok'); | |||||
| // Utils.showSnackBarSuccess(); | |||||
| }, | |||||
| (errorMessage) { | |||||
| UtilWidget.hideDialog(); | |||||
| Utils.showSnackBarError(); | |||||
| }, | |||||
| item: task, | |||||
| ); | |||||
| } | |||||
| } |
| part of 'task_detail_cubit.dart'; | |||||
| abstract class TaskDetailState extends Equatable { | |||||
| const TaskDetailState(); | |||||
| @override | |||||
| List<Object> get props => []; | |||||
| } | |||||
| class TaskDetailInitial extends TaskDetailState {} | |||||
| class TaskDetailLoading extends TaskDetailState {} | |||||
| class TaskDetailFailure extends TaskDetailState { | |||||
| final String errorMessage; | |||||
| TaskDetailFailure(this.errorMessage); | |||||
| } | |||||
| class TaskDetailSuccessful extends TaskDetailState { | |||||
| final Task task; | |||||
| TaskDetailSuccessful(this.task); | |||||
| } |
| class SupplyFilter { | |||||
| int? id; | |||||
| String? name; | |||||
| SupplyFilter({ | |||||
| this.id, | |||||
| this.name, | |||||
| }); | |||||
| SupplyFilter.fromJson(Map<String, dynamic> json) { | |||||
| id = json['id']; | |||||
| name = json['name']; | |||||
| } | |||||
| Map<String, dynamic> toJson() { | |||||
| final Map<String, dynamic> data = new Map<String, dynamic>(); | |||||
| data['id'] = this.id; | |||||
| data['name'] = this.name; | |||||
| return data; | |||||
| } | |||||
| } |
| id = json['id']; | id = json['id']; | ||||
| description = json['detail']; | description = json['detail']; | ||||
| dueDate = json['deadline']; | dueDate = json['deadline']; | ||||
| executeDate = json['executeDate']; | |||||
| executeDate = json['completedAt']; | |||||
| assigned = json['assigned'] != null ? new Assigned.fromJson(json['assigned']) : null; | assigned = json['assigned'] != null ? new Assigned.fromJson(json['assigned']) : null; | ||||
| isCompleted = json['completed']; | isCompleted = json['completed']; | ||||
| } | } | ||||
| data['title'] = this.title; | data['title'] = this.title; | ||||
| data['detail'] = this.description; | data['detail'] = this.description; | ||||
| data['deadline'] = this.dueDate; | data['deadline'] = this.dueDate; | ||||
| data['executeDate'] = this.executeDate; | |||||
| data['completedAt'] = this.executeDate; | |||||
| if (this.assigned != null) { | if (this.assigned != null) { | ||||
| data['assigned'] = this.assigned?.toJson(); | data['assigned'] = this.assigned?.toJson(); | ||||
| } | } |
| class TaskFilterRequest { | class TaskFilterRequest { | ||||
| List<String>? status; | List<String>? status; | ||||
| String? sort; | String? sort; | ||||
| int? cropId; | |||||
| TaskFilterRequest({this.status, this.sort}); | TaskFilterRequest({this.status, this.sort}); | ||||
| TaskFilterRequest.fromJson(Map<String, dynamic> json) { | TaskFilterRequest.fromJson(Map<String, dynamic> json) { | ||||
| status = json['status'].cast<String>(); | status = json['status'].cast<String>(); | ||||
| sort = json['sort']; | sort = json['sort']; | ||||
| cropId = json['cropId']; | |||||
| } | } | ||||
| Map<String, dynamic> toJson() { | Map<String, dynamic> toJson() { | ||||
| final Map<String, dynamic> data = new Map<String, dynamic>(); | final Map<String, dynamic> data = new Map<String, dynamic>(); | ||||
| data['status'] = this.status; | data['status'] = this.status; | ||||
| data['sort'] = this.sort; | data['sort'] = this.sort; | ||||
| data['cropId'] = this.cropId; | |||||
| return data; | return data; | ||||
| } | } | ||||
| } | } |
| import 'package:farm_tpf/presentation/custom_widgets/checkbox/checkbox_widget.dart'; | |||||
| import 'package:farm_tpf/presentation/screens/task/models/task_update_request.dart'; | |||||
| import 'package:flutter/material.dart'; | |||||
| import 'package:flutter/src/widgets/framework.dart'; | |||||
| import 'package:flutter/src/widgets/placeholder.dart'; | |||||
| import 'package:flutter_bloc/flutter_bloc.dart'; | |||||
| import '../../../themes/styles_text.dart'; | |||||
| import '../../custom_widgets/app_bar_widget.dart'; | |||||
| import '../../custom_widgets/loading_list_page.dart'; | |||||
| import 'cubit/task_detail_cubit.dart'; | |||||
| import 'package:farm_tpf/utils/formatter.dart'; | |||||
| class TaskDetailPage extends StatefulWidget { | |||||
| final int taskId; | |||||
| const TaskDetailPage({super.key, required this.taskId}); | |||||
| @override | |||||
| State<TaskDetailPage> createState() => _TaskDetailPageState(); | |||||
| } | |||||
| class _TaskDetailPageState extends State<TaskDetailPage> { | |||||
| var bloc = TaskDetailCubit(); | |||||
| @override | |||||
| void initState() { | |||||
| super.initState(); | |||||
| bloc.preparedData(widget.taskId); | |||||
| } | |||||
| @override | |||||
| Widget build(BuildContext context) { | |||||
| return Scaffold( | |||||
| appBar: AppBarWidget(), | |||||
| body: Padding( | |||||
| padding: const EdgeInsets.all(8.0), | |||||
| child: Column( | |||||
| children: [ | |||||
| Expanded( | |||||
| child: BlocBuilder<TaskDetailCubit, TaskDetailState>( | |||||
| bloc: bloc, | |||||
| builder: (context, state) { | |||||
| if (state is TaskDetailLoading) { | |||||
| return Center( | |||||
| child: LoadingListPage(), | |||||
| ); | |||||
| } else if (state is TaskDetailFailure) { | |||||
| return Center(child: Text(state.errorMessage)); | |||||
| } else if (state is TaskDetailSuccessful) { | |||||
| return SingleChildScrollView( | |||||
| child: Column( | |||||
| crossAxisAlignment: CrossAxisAlignment.start, | |||||
| children: [ | |||||
| _itemTaskDetail( | |||||
| title: '', | |||||
| detail: state.task.title ?? '', | |||||
| titleStyle: StylesText.body1, | |||||
| detailStyle: StylesText.body1.copyWith( | |||||
| color: Colors.blue, | |||||
| ), | |||||
| ), | |||||
| _itemTaskDetail(title: 'Hạn chót : ', detail: state.task.dueDate?.format_DDMMYY().toString() ?? ''), | |||||
| (state.task.isCompleted ?? false) | |||||
| ? _itemTaskDetail(title: 'Thời gian hoàn thành : ', detail: state.task.executeDate?.format_DDMMYY().toString() ?? '') | |||||
| : const SizedBox.shrink(), | |||||
| Padding( | |||||
| padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), | |||||
| child: Text( | |||||
| 'Mô tả công việc :', | |||||
| style: StylesText.body2, | |||||
| ), | |||||
| ), | |||||
| Padding( | |||||
| padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), | |||||
| child: Text( | |||||
| state.task.description ?? '', | |||||
| style: StylesText.body3, | |||||
| ), | |||||
| ), | |||||
| Padding( | |||||
| padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), | |||||
| child: CheckboxWidget( | |||||
| title: 'Hoàn thành', | |||||
| style: StylesText.body2, | |||||
| isChecked: state.task.isCompleted ?? false, | |||||
| onChange: (status) { | |||||
| bloc.updateStatusTask( | |||||
| RequestTaskUpdate( | |||||
| id: widget.taskId, | |||||
| completed: status, | |||||
| ), | |||||
| ); | |||||
| }, | |||||
| ), | |||||
| ), | |||||
| const SizedBox( | |||||
| height: 8, | |||||
| ), | |||||
| ], | |||||
| ), | |||||
| ); | |||||
| } | |||||
| return const SizedBox.shrink(); | |||||
| }, | |||||
| )), | |||||
| const SizedBox( | |||||
| height: 8, | |||||
| ), | |||||
| ], | |||||
| ), | |||||
| ), | |||||
| ); | |||||
| } | |||||
| Widget _itemTaskDetail({ | |||||
| required String title, | |||||
| required String detail, | |||||
| TextStyle? titleStyle, | |||||
| TextStyle? detailStyle, | |||||
| }) { | |||||
| return Padding( | |||||
| padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), | |||||
| child: Row( | |||||
| crossAxisAlignment: CrossAxisAlignment.start, | |||||
| children: [ | |||||
| Text( | |||||
| title, | |||||
| style: titleStyle ?? StylesText.body2, | |||||
| ), | |||||
| Expanded( | |||||
| child: Text( | |||||
| '$detail', | |||||
| style: detailStyle ?? StylesText.body3, | |||||
| ), | |||||
| ) | |||||
| ], | |||||
| ), | |||||
| ); | |||||
| } | |||||
| } |
| import '../plot/widget_search.dart'; | import '../plot/widget_search.dart'; | ||||
| import 'bloc/task_bloc.dart'; | import 'bloc/task_bloc.dart'; | ||||
| import 'create_task_page.dart'; | import 'create_task_page.dart'; | ||||
| import 'task_detail_page.dart'; | |||||
| import 'widgets/task_item.dart'; | import 'widgets/task_item.dart'; | ||||
| class TaskPage extends StatefulWidget { | class TaskPage extends StatefulWidget { | ||||
| @override | @override | ||||
| void initState() { | void initState() { | ||||
| bloc.cropId = widget.cropId; | |||||
| bloc.add(DataFetched()); | bloc.add(DataFetched()); | ||||
| _scrollController.addListener(() { | _scrollController.addListener(() { | ||||
| : ItemTask( | : ItemTask( | ||||
| item: state.items?[index], | item: state.items?[index], | ||||
| onPressed: () { | onPressed: () { | ||||
| // Get.to( | |||||
| // () => TaskDetailPage( | |||||
| // stampId: state.items?[index].id, | |||||
| // stampTask: state.items?[index].code, | |||||
| // ), | |||||
| // ); | |||||
| Get.to( | |||||
| () => TaskDetailPage( | |||||
| taskId: state.items?[index].id, | |||||
| ), | |||||
| )?.then((value) { | |||||
| if (value != null) { | |||||
| bloc.add(OnRefresh()); | |||||
| } | |||||
| }); | |||||
| }, | }, | ||||
| onChangedStatus: (status) { | onChangedStatus: (status) { | ||||
| bloc.updateStatusTask( | bloc.updateStatusTask( |
| description: A new Flutter project. | description: A new Flutter project. | ||||
| publish_to: 'none' | publish_to: 'none' | ||||
| version: 1.1.7+23 | |||||
| version: 1.1.8+24 | |||||
| environment: | environment: | ||||
| sdk: ">=2.17.0 <=3.0.0" | sdk: ">=2.17.0 <=3.0.0" |