| @@ -369,7 +369,7 @@ | |||
| CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; | |||
| CODE_SIGN_IDENTITY = "Apple Development"; | |||
| CODE_SIGN_STYLE = Automatic; | |||
| CURRENT_PROJECT_VERSION = 22; | |||
| CURRENT_PROJECT_VERSION = 23; | |||
| DEVELOPMENT_TEAM = C3DTD2JH94; | |||
| ENABLE_BITCODE = NO; | |||
| FRAMEWORK_SEARCH_PATHS = ( | |||
| @@ -385,7 +385,7 @@ | |||
| "$(inherited)", | |||
| "$(PROJECT_DIR)/Flutter", | |||
| ); | |||
| MARKETING_VERSION = 1.1.6; | |||
| MARKETING_VERSION = 1.1.7; | |||
| PRODUCT_BUNDLE_IDENTIFIER = vn.azteam.farmdemo; | |||
| PRODUCT_NAME = "$(TARGET_NAME)"; | |||
| PROVISIONING_PROFILE_SPECIFIER = ""; | |||
| @@ -511,7 +511,7 @@ | |||
| CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; | |||
| CODE_SIGN_IDENTITY = "Apple Development"; | |||
| CODE_SIGN_STYLE = Automatic; | |||
| CURRENT_PROJECT_VERSION = 22; | |||
| CURRENT_PROJECT_VERSION = 23; | |||
| DEVELOPMENT_TEAM = C3DTD2JH94; | |||
| ENABLE_BITCODE = NO; | |||
| FRAMEWORK_SEARCH_PATHS = ( | |||
| @@ -527,7 +527,7 @@ | |||
| "$(inherited)", | |||
| "$(PROJECT_DIR)/Flutter", | |||
| ); | |||
| MARKETING_VERSION = 1.1.6; | |||
| MARKETING_VERSION = 1.1.7; | |||
| PRODUCT_BUNDLE_IDENTIFIER = vn.azteam.farmdemo; | |||
| PRODUCT_NAME = "$(TARGET_NAME)"; | |||
| PROVISIONING_PROFILE_SPECIFIER = ""; | |||
| @@ -547,7 +547,7 @@ | |||
| CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; | |||
| CODE_SIGN_IDENTITY = "Apple Development"; | |||
| CODE_SIGN_STYLE = Automatic; | |||
| CURRENT_PROJECT_VERSION = 22; | |||
| CURRENT_PROJECT_VERSION = 23; | |||
| DEVELOPMENT_TEAM = C3DTD2JH94; | |||
| ENABLE_BITCODE = NO; | |||
| FRAMEWORK_SEARCH_PATHS = ( | |||
| @@ -563,7 +563,7 @@ | |||
| "$(inherited)", | |||
| "$(PROJECT_DIR)/Flutter", | |||
| ); | |||
| MARKETING_VERSION = 1.1.6; | |||
| MARKETING_VERSION = 1.1.7; | |||
| PRODUCT_BUNDLE_IDENTIFIER = vn.azteam.farmdemo; | |||
| PRODUCT_NAME = "$(TARGET_NAME)"; | |||
| PROVISIONING_PROFILE_SPECIFIER = ""; | |||
| @@ -25,7 +25,9 @@ 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/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/supply_filter.dart'; | |||
| import 'package:farm_tpf/presentation/screens/task/models/task_request.dart'; | |||
| import 'package:farm_tpf/utils/const_common.dart'; | |||
| import 'package:flutter/material.dart'; | |||
| @@ -459,6 +461,7 @@ class Repository { | |||
| var url = '${ConstCommon.baseUrl}/api/tb-todo-lists/list?page=$page&size=$size'; | |||
| var res = await dio.post(url, data: { | |||
| // 'status': filter.status, | |||
| "crop_id": filter.cropId, | |||
| }); | |||
| return (res.data as List).map((e) => Task.fromJson(e)).toList(); | |||
| @@ -518,4 +521,45 @@ class Repository { | |||
| 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; | |||
| } | |||
| } | |||
| } | |||
| @@ -4,6 +4,7 @@ import 'dart:io'; | |||
| 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/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:flutter/material.dart'; | |||
| import 'package:flutter/services.dart'; | |||
| @@ -52,6 +53,11 @@ Future<void> main() async { | |||
| Repository(), | |||
| ), | |||
| ), | |||
| BlocProvider( | |||
| create: (_) => PlotBloc( | |||
| repository: Repository(), | |||
| ), | |||
| ), | |||
| BlocProvider( | |||
| create: (_) => DetailStampCubit(), | |||
| ), | |||
| @@ -2,10 +2,16 @@ import 'dart:async'; | |||
| import 'package:bloc/bloc.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/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 '../../../../models/item_dropdown.dart'; | |||
| part 'plot_event.dart'; | |||
| part 'plot_state.dart'; | |||
| @@ -14,6 +20,10 @@ class PlotBloc extends Bloc<PlotEvent, PlotState> { | |||
| PlotBloc({required this.repository}) : super(PlotInitial()); | |||
| static int pageSize = 20; | |||
| var supplyRaws = <SupplyFilter>[]; | |||
| var supplies = ValueNotifier(<ItemDropDown>[]); | |||
| var selectedSupply = ValueNotifier(<ItemDropDown>[]); | |||
| @override | |||
| Stream<PlotState> mapEventToState( | |||
| PlotEvent event, | |||
| @@ -22,13 +32,16 @@ class PlotBloc extends Bloc<PlotEvent, PlotState> { | |||
| try { | |||
| if (state is PlotInitial) { | |||
| 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); | |||
| } | |||
| if (state is PlotSuccess) { | |||
| final currentState = state as PlotSuccess; | |||
| if (currentState.hasReachedMax ?? false) { | |||
| return; | |||
| } | |||
| int page = (currentState.page ?? 0) + 1; | |||
| final response = await repository.getPlots(page: page, size: pageSize, searchString: ""); | |||
| final response = await getPlots(page); | |||
| yield response.isEmpty | |||
| ? currentState.copyWith(hasReachedMax: true) | |||
| : PlotSuccess( | |||
| @@ -45,7 +58,7 @@ class PlotBloc extends Bloc<PlotEvent, PlotState> { | |||
| if (event is OnRefresh) { | |||
| try { | |||
| 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); | |||
| } catch (e) { | |||
| yield PlotFailure(errorString: AppException.handleError(e)); | |||
| @@ -53,11 +66,41 @@ class PlotBloc extends Bloc<PlotEvent, PlotState> { | |||
| } else if (event is OnSearch) { | |||
| try { | |||
| 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); | |||
| } catch (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))); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| 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; | |||
| } | |||
| } | |||
| @@ -8,6 +8,9 @@ import 'package:flutter/material.dart'; | |||
| import 'package:flutter_bloc/flutter_bloc.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 'widgets/item_plot.dart'; | |||
| @@ -17,126 +20,28 @@ class PlotListScreen extends StatefulWidget { | |||
| } | |||
| class _PlotListScreenState extends State<PlotListScreen> { | |||
| final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>(); | |||
| var pref = LocalPref(); | |||
| var token; | |||
| var client; | |||
| String pushkey = ""; | |||
| 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 _scrollThreshold = 250.0; | |||
| PlotBloc? _plotBloc; | |||
| @override | |||
| void initState() { | |||
| super.initState(); | |||
| bloc.preparedData(); | |||
| bloc.add(DataFetched()); | |||
| _scrollController.addListener(() { | |||
| final maxScroll = _scrollController.position.maxScrollExtent; | |||
| final currentScroll = _scrollController.position.pixels; | |||
| 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 | |||
| @@ -144,4 +49,103 @@ class _InfinityViewState extends State<InfinityView> { | |||
| _scrollController.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(), | |||
| ); | |||
| }, | |||
| )) | |||
| ], | |||
| ), | |||
| ), | |||
| ); | |||
| } | |||
| } | |||
| @@ -37,6 +37,7 @@ class TaskBloc extends Bloc<TaskEvent, TaskState> { | |||
| .toList(), | |||
| ); | |||
| var sort = ValueNotifier(describeEnum(SortType.asc)); | |||
| var cropId = -1; | |||
| @override | |||
| Stream<TaskState> mapEventToState( | |||
| @@ -102,6 +103,7 @@ class TaskBloc extends Bloc<TaskEvent, TaskState> { | |||
| Future<List<Task>> getListTask(int page) async { | |||
| var filter = TaskFilterRequest() | |||
| ..cropId = cropId | |||
| ..sort = sort.value | |||
| ..status = selectedStatus.value.map((e) => e.key ?? '').toList(); | |||
| return await repository.tasks(page: 0, filter: filter); | |||
| @@ -116,7 +118,7 @@ class TaskBloc extends Bloc<TaskEvent, TaskState> { | |||
| await repository.updateTask( | |||
| (success) { | |||
| UtilWidget.hideDialog(); | |||
| Utils.showSnackBarSuccess(); | |||
| // Utils.showSnackBarSuccess(); | |||
| onSuccess(); | |||
| }, | |||
| (errorMessage) { | |||
| @@ -0,0 +1,47 @@ | |||
| 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, | |||
| ); | |||
| } | |||
| } | |||
| @@ -0,0 +1,23 @@ | |||
| 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); | |||
| } | |||
| @@ -0,0 +1,21 @@ | |||
| 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; | |||
| } | |||
| } | |||
| @@ -22,7 +22,7 @@ class Task { | |||
| id = json['id']; | |||
| description = json['detail']; | |||
| dueDate = json['deadline']; | |||
| executeDate = json['executeDate']; | |||
| executeDate = json['completedAt']; | |||
| assigned = json['assigned'] != null ? new Assigned.fromJson(json['assigned']) : null; | |||
| isCompleted = json['completed']; | |||
| } | |||
| @@ -33,7 +33,7 @@ class Task { | |||
| data['title'] = this.title; | |||
| data['detail'] = this.description; | |||
| data['deadline'] = this.dueDate; | |||
| data['executeDate'] = this.executeDate; | |||
| data['completedAt'] = this.executeDate; | |||
| if (this.assigned != null) { | |||
| data['assigned'] = this.assigned?.toJson(); | |||
| } | |||
| @@ -1,18 +1,21 @@ | |||
| class TaskFilterRequest { | |||
| List<String>? status; | |||
| String? sort; | |||
| int? cropId; | |||
| TaskFilterRequest({this.status, this.sort}); | |||
| TaskFilterRequest.fromJson(Map<String, dynamic> json) { | |||
| status = json['status'].cast<String>(); | |||
| sort = json['sort']; | |||
| cropId = json['cropId']; | |||
| } | |||
| Map<String, dynamic> toJson() { | |||
| final Map<String, dynamic> data = new Map<String, dynamic>(); | |||
| data['status'] = this.status; | |||
| data['sort'] = this.sort; | |||
| data['cropId'] = this.cropId; | |||
| return data; | |||
| } | |||
| } | |||
| @@ -0,0 +1,139 @@ | |||
| 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, | |||
| ), | |||
| ) | |||
| ], | |||
| ), | |||
| ); | |||
| } | |||
| } | |||
| @@ -24,6 +24,7 @@ import '../../custom_widgets/loading_list_page.dart'; | |||
| import '../plot/widget_search.dart'; | |||
| import 'bloc/task_bloc.dart'; | |||
| import 'create_task_page.dart'; | |||
| import 'task_detail_page.dart'; | |||
| import 'widgets/task_item.dart'; | |||
| class TaskPage extends StatefulWidget { | |||
| @@ -42,6 +43,7 @@ class _TaskPageState extends State<TaskPage> { | |||
| @override | |||
| void initState() { | |||
| bloc.cropId = widget.cropId; | |||
| bloc.add(DataFetched()); | |||
| _scrollController.addListener(() { | |||
| @@ -157,12 +159,15 @@ class _TaskPageState extends State<TaskPage> { | |||
| : ItemTask( | |||
| item: state.items?[index], | |||
| 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) { | |||
| bloc.updateStatusTask( | |||
| @@ -2,7 +2,7 @@ name: farm_tpf | |||
| description: A new Flutter project. | |||
| publish_to: 'none' | |||
| version: 1.1.7+23 | |||
| version: 1.1.8+24 | |||
| environment: | |||
| sdk: ">=2.17.0 <=3.0.0" | |||