| {@Path() int page = 0, @Path() int size = 20, @Path() String query = ""}); | {@Path() int page = 0, @Path() int size = 20, @Path() String query = ""}); | ||||
| @GET("/api/listActivityTypesOther") | @GET("/api/listActivityTypesOther") | ||||
| Future<List<ActionType>> getActionTypes(); | Future<List<ActionType>> getActionTypes(); | ||||
| //Crop | |||||
| @GET( | |||||
| "/api/tb-crops-detail/{cropId}?page={page}&size={size}&sort=executeDate,DESC") | |||||
| Future<Crop> getCropDetail(@Path() int cropId, | |||||
| {@Path() int page = 0, @Path() int size = 20}); | |||||
| } | } |
| .toList(); | .toList(); | ||||
| return value; | return value; | ||||
| } | } | ||||
| @override | |||||
| getCropDetail(cropId, {page = 0, size = 20}) async { | |||||
| ArgumentError.checkNotNull(cropId, 'cropId'); | |||||
| const _extra = <String, dynamic>{}; | |||||
| final queryParameters = <String, dynamic>{}; | |||||
| queryParameters.removeWhere((k, v) => v == null); | |||||
| final _data = <String, dynamic>{}; | |||||
| final Response<Map<String, dynamic>> _result = await _dio.request( | |||||
| '/api/tb-crops-detail/$cropId?page=$page&size=$size&sort=executeDate,DESC', | |||||
| queryParameters: queryParameters, | |||||
| options: RequestOptions( | |||||
| method: 'GET', | |||||
| headers: <String, dynamic>{}, | |||||
| extra: _extra, | |||||
| baseUrl: baseUrl), | |||||
| data: _data); | |||||
| final value = Crop.fromJson(_result.data); | |||||
| return value; | |||||
| } | |||||
| } | } |
| return client.getActionTypes(); | return client.getActionTypes(); | ||||
| } | } | ||||
| Future<Crop> getPlotDetail(int cropId, {int page, int size}) { | |||||
| final client = RestClient(dio); | |||||
| return client.getCropDetail(cropId, page: page, size: size); | |||||
| } | |||||
| Future<List<Plot>> getPlots({int page, int size, String searchString}) { | Future<List<Plot>> getPlots({int page, int size, String searchString}) { | ||||
| final client = RestClient(dio); | final client = RestClient(dio); | ||||
| return client.getPlots(page: page, size: size, query: searchString); | return client.getPlots(page: page, size: size, query: searchString); |
| 'seedIncubationTime': instance.seedIncubationTime, | 'seedIncubationTime': instance.seedIncubationTime, | ||||
| 'numberPlants': instance.numberPlants, | 'numberPlants': instance.numberPlants, | ||||
| 'numberCurrentPlants': instance.numberCurrentPlants, | 'numberCurrentPlants': instance.numberCurrentPlants, | ||||
| 'endOfFarmingDate': instance.endOfFarmingDate | |||||
| 'endOfFarmingDate': instance.endOfFarmingDate, | |||||
| }; | }; |
| 'executeDate': instance.executeDate, | 'executeDate': instance.executeDate, | ||||
| 'description': instance.description, | 'description': instance.description, | ||||
| 'activityTypeId': instance.activityTypeId, | 'activityTypeId': instance.activityTypeId, | ||||
| 'activityTypeName': instance.activityTypeName | |||||
| 'activityTypeName': instance.activityTypeName, | |||||
| }; | }; |
| Navigator.push( | Navigator.push( | ||||
| context, | context, | ||||
| MaterialPageRoute( | MaterialPageRoute( | ||||
| builder: (BuildContext context) => PlotDetailScreen())); | |||||
| builder: (BuildContext context) => | |||||
| PlotDetailScreen(cropId: item.id))); | |||||
| }); | }); | ||||
| } | } | ||||
| } | } |
| import 'dart:async'; | |||||
| import 'package:bloc/bloc.dart'; | |||||
| import 'package:equatable/equatable.dart'; | |||||
| import 'package:farm_tpf/data/api/app_exception.dart'; | |||||
| import 'package:farm_tpf/data/repository/repository.dart'; | |||||
| import 'package:meta/meta.dart'; | |||||
| part 'plot_detail_event.dart'; | |||||
| part 'plot_detail_state.dart'; | |||||
| class PlotDetailBloc extends Bloc<PlotDetailEvent, PlotDetailState> { | |||||
| final Repository repository; | |||||
| PlotDetailBloc({@required this.repository}) : super(PlotDetailInitial()); | |||||
| static int pageSize = 20; | |||||
| @override | |||||
| Stream<PlotDetailState> mapEventToState( | |||||
| PlotDetailEvent event, | |||||
| ) async* { | |||||
| if (event is DataFetched && | |||||
| !(state is PlotDetailSuccess && | |||||
| (state as PlotDetailSuccess).hasReachedMax)) { | |||||
| try { | |||||
| if (state is PlotDetailInitial) { | |||||
| yield PlotDetailLoading(); | |||||
| final response = await repository.getPlotDetail(event.cropId, | |||||
| page: 0, size: pageSize); | |||||
| yield PlotDetailSuccess( | |||||
| items: response.activities, | |||||
| page: 0, | |||||
| hasReachedMax: | |||||
| response.activities.length < pageSize ? true : false); | |||||
| } | |||||
| if (state is PlotDetailSuccess) { | |||||
| final currentState = state as PlotDetailSuccess; | |||||
| int page = currentState.page + 1; | |||||
| yield PlotDetailLoading(); | |||||
| final response = await repository.getPlotDetail(event.cropId, | |||||
| page: page, size: pageSize); | |||||
| yield response.activities.isEmpty | |||||
| ? currentState.copyWith(hasReachedMax: true) | |||||
| : PlotDetailSuccess( | |||||
| items: currentState.items + response.activities, | |||||
| page: currentState.page + 1, | |||||
| hasReachedMax: false); | |||||
| } | |||||
| } catch (e) { | |||||
| var errorString = AppException.handleError(e); | |||||
| yield PlotDetailFailure(errorString: errorString); | |||||
| } | |||||
| } | |||||
| if (event is OnRefresh) { | |||||
| try { | |||||
| yield PlotDetailLoading(); | |||||
| final response = await repository.getPlotDetail(event.cropId, | |||||
| page: 0, size: pageSize); | |||||
| yield PlotDetailSuccess( | |||||
| items: response.activities, | |||||
| page: 0, | |||||
| hasReachedMax: | |||||
| response.activities.length < pageSize ? true : false); | |||||
| } catch (e) { | |||||
| yield PlotDetailFailure(errorString: AppException.handleError(e)); | |||||
| } | |||||
| } | |||||
| } | |||||
| } |
| part of 'plot_detail_bloc.dart'; | |||||
| abstract class PlotDetailEvent extends Equatable { | |||||
| const PlotDetailEvent(); | |||||
| @override | |||||
| List<Object> get props => []; | |||||
| } | |||||
| class DataFetched extends PlotDetailEvent { | |||||
| final int cropId; | |||||
| DataFetched(this.cropId); | |||||
| } | |||||
| class OnRefresh extends PlotDetailEvent { | |||||
| final int cropId; | |||||
| OnRefresh(this.cropId); | |||||
| } |
| part of 'plot_detail_bloc.dart'; | |||||
| abstract class PlotDetailState extends Equatable { | |||||
| const PlotDetailState(); | |||||
| @override | |||||
| List<Object> get props => []; | |||||
| } | |||||
| class PlotDetailInitial extends PlotDetailState {} | |||||
| class PlotDetailLoading extends PlotDetailState {} | |||||
| class PlotDetailFailure extends PlotDetailState { | |||||
| final String errorString; | |||||
| PlotDetailFailure({@required this.errorString}); | |||||
| } | |||||
| class PlotDetailSuccess<T> extends PlotDetailState { | |||||
| final List<T> items; | |||||
| final int page; | |||||
| final bool hasReachedMax; | |||||
| const PlotDetailSuccess({this.items, this.page, this.hasReachedMax}); | |||||
| PlotDetailSuccess copyWith({List<T> items, int page, bool hasReachedMax}) { | |||||
| return PlotDetailSuccess( | |||||
| items: items ?? this.items, | |||||
| page: page ?? this.page, | |||||
| hasReachedMax: hasReachedMax ?? this.hasReachedMax); | |||||
| } | |||||
| @override | |||||
| List<Object> get props => [items, hasReachedMax]; | |||||
| } |
| import 'package:farm_tpf/data/repository/repository.dart'; | |||||
| import 'package:farm_tpf/models/index.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/actions/other/sc_edit_action_other.dart'; | import 'package:farm_tpf/presentation/screens/actions/other/sc_edit_action_other.dart'; | ||||
| import 'package:farm_tpf/presentation/screens/plot/sc_plot.dart'; | import 'package:farm_tpf/presentation/screens/plot/sc_plot.dart'; | ||||
| import 'package:farm_tpf/presentation/screens/plot_detail/bloc/plot_detail_bloc.dart'; | |||||
| import 'package:farm_tpf/utils/const_color.dart'; | import 'package:farm_tpf/utils/const_color.dart'; | ||||
| import 'package:farm_tpf/utils/const_string.dart'; | import 'package:farm_tpf/utils/const_string.dart'; | ||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_bloc/flutter_bloc.dart'; | |||||
| import 'package:farm_tpf/utils/formatter.dart'; | |||||
| class PlotActionScreen extends StatefulWidget { | class PlotActionScreen extends StatefulWidget { | ||||
| final int cropId; | |||||
| PlotActionScreen({@required this.cropId}); | |||||
| @override | @override | ||||
| _PlotActionScreenState createState() => _PlotActionScreenState(); | _PlotActionScreenState createState() => _PlotActionScreenState(); | ||||
| } | } | ||||
| )) | )) | ||||
| ])) | ])) | ||||
| ], | ], | ||||
| body: RefreshIndicator( | |||||
| backgroundColor: Colors.white, | |||||
| onRefresh: () async {}, | |||||
| child: ListView.builder( | |||||
| itemBuilder: (context, index) => ListTile(title: Text("Text $index")), | |||||
| itemCount: 20, | |||||
| body: BlocProvider( | |||||
| create: (context) => PlotDetailBloc(repository: Repository()) | |||||
| ..add(DataFetched(widget.cropId)), | |||||
| child: HoldInfinityWidget( | |||||
| cropId: widget.cropId, | |||||
| ), | ), | ||||
| ), | ), | ||||
| ); | ); | ||||
| } | } | ||||
| } | } | ||||
| class HoldInfinityWidget extends StatelessWidget { | |||||
| final int cropId; | |||||
| HoldInfinityWidget({@required this.cropId}); | |||||
| final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>(); | |||||
| @override | |||||
| Widget build(BuildContext context) { | |||||
| return Scaffold(key: _scaffoldKey, body: InfinityView(cropId: cropId)); | |||||
| } | |||||
| } | |||||
| class InfinityView extends StatefulWidget { | |||||
| final int cropId; | |||||
| InfinityView({@required this.cropId}); | |||||
| @override | |||||
| _InfinityViewState createState() => _InfinityViewState(); | |||||
| } | |||||
| class _InfinityViewState extends State<InfinityView> { | |||||
| final _scrollController = ScrollController(); | |||||
| final _scrollThreshold = 250.0; | |||||
| PlotDetailBloc _plotDetailBloc; | |||||
| @override | |||||
| void initState() { | |||||
| _scrollController.addListener(() { | |||||
| final maxScroll = _scrollController.position.maxScrollExtent; | |||||
| final currentScroll = _scrollController.position.pixels; | |||||
| if (maxScroll - currentScroll < _scrollThreshold) { | |||||
| _plotDetailBloc.add(DataFetched(widget.cropId)); | |||||
| } | |||||
| }); | |||||
| _plotDetailBloc = BlocProvider.of<PlotDetailBloc>(context); | |||||
| super.initState(); | |||||
| } | |||||
| @override | |||||
| Widget build(BuildContext context) { | |||||
| return BlocBuilder<PlotDetailBloc, PlotDetailState>( | |||||
| builder: (context, state) { | |||||
| if (state is PlotDetailFailure) { | |||||
| return Center(child: Text(state.errorString)); | |||||
| } | |||||
| if (state is PlotDetailSuccess) { | |||||
| 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() | |||||
| : ItemInfinityWidget(item: state.items[index]); | |||||
| }, | |||||
| itemCount: state.hasReachedMax | |||||
| ? state.items.length | |||||
| : state.items.length + 1, | |||||
| controller: _scrollController, | |||||
| ), | |||||
| onRefresh: () async { | |||||
| _plotDetailBloc.add(OnRefresh(widget.cropId)); | |||||
| }); | |||||
| } | |||||
| return Center( | |||||
| child: LoadingListPage(), | |||||
| ); | |||||
| }, | |||||
| ); | |||||
| } | |||||
| @override | |||||
| void dispose() { | |||||
| _scrollController.dispose(); | |||||
| super.dispose(); | |||||
| } | |||||
| } | |||||
| class ItemInfinityWidget extends StatelessWidget { | |||||
| final HistoryAction item; | |||||
| const ItemInfinityWidget({Key key, @required this.item}) : super(key: key); | |||||
| @override | |||||
| Widget build(BuildContext context) { | |||||
| return GestureDetector( | |||||
| child: Card( | |||||
| child: ListTile( | |||||
| title: Text(item.activityTypeName), | |||||
| subtitle: Text(item.executeDate.format_DDMMYY_HHmm()), | |||||
| ), | |||||
| ), | |||||
| onTap: () {}); | |||||
| } | |||||
| } | |||||
| class ActionType { | class ActionType { | ||||
| Widget addScreen; | Widget addScreen; | ||||
| Widget listScreen; | Widget listScreen; |
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||
| class PlotDetailScreen extends StatefulWidget { | class PlotDetailScreen extends StatefulWidget { | ||||
| final int cropId; | |||||
| PlotDetailScreen({@required this.cropId}); | |||||
| @override | @override | ||||
| _PlotDetailScreenState createState() => _PlotDetailScreenState(); | _PlotDetailScreenState createState() => _PlotDetailScreenState(); | ||||
| } | } | ||||
| child: new Scaffold( | child: new Scaffold( | ||||
| backgroundColor: COLOR_CONST.ITEM_BG, | backgroundColor: COLOR_CONST.ITEM_BG, | ||||
| body: TabBarView( | body: TabBarView( | ||||
| children: [PlotParameterScreen(), PlotActionScreen()], | |||||
| children: [ | |||||
| PlotParameterScreen(), | |||||
| PlotActionScreen( | |||||
| cropId: widget.cropId, | |||||
| ) | |||||
| ], | |||||
| ), | ), | ||||
| bottomNavigationBar: new TabBar( | bottomNavigationBar: new TabBar( | ||||
| tabs: [ | tabs: [ |
| # Generated by pub | # Generated by pub | ||||
| # See https://dart.dev/tools/pub/glossary#lockfile | # See https://dart.dev/tools/pub/glossary#lockfile | ||||
| packages: | packages: | ||||
| analyzer: | |||||
| _fe_analyzer_shared: | |||||
| dependency: transitive | dependency: transitive | ||||
| description: | |||||
| name: _fe_analyzer_shared | |||||
| url: "https://pub.dartlang.org" | |||||
| source: hosted | |||||
| version: "6.0.0" | |||||
| analyzer: | |||||
| dependency: "direct main" | |||||
| description: | description: | ||||
| name: analyzer | name: analyzer | ||||
| url: "https://pub.dartlang.org" | url: "https://pub.dartlang.org" | ||||
| source: hosted | source: hosted | ||||
| version: "0.36.4" | |||||
| version: "0.39.14" | |||||
| archive: | archive: | ||||
| dependency: transitive | dependency: transitive | ||||
| description: | description: | ||||
| name: build | name: build | ||||
| url: "https://pub.dartlang.org" | url: "https://pub.dartlang.org" | ||||
| source: hosted | source: hosted | ||||
| version: "1.1.6" | |||||
| version: "1.3.0" | |||||
| build_config: | build_config: | ||||
| dependency: transitive | dependency: transitive | ||||
| description: | description: | ||||
| name: build_config | name: build_config | ||||
| url: "https://pub.dartlang.org" | url: "https://pub.dartlang.org" | ||||
| source: hosted | source: hosted | ||||
| version: "0.4.1+1" | |||||
| version: "0.4.2" | |||||
| build_daemon: | build_daemon: | ||||
| dependency: transitive | dependency: transitive | ||||
| description: | description: | ||||
| name: build_resolvers | name: build_resolvers | ||||
| url: "https://pub.dartlang.org" | url: "https://pub.dartlang.org" | ||||
| source: hosted | source: hosted | ||||
| version: "1.2.1" | |||||
| version: "1.3.11" | |||||
| build_runner: | build_runner: | ||||
| dependency: "direct dev" | dependency: "direct dev" | ||||
| description: | description: | ||||
| name: build_runner | name: build_runner | ||||
| url: "https://pub.dartlang.org" | url: "https://pub.dartlang.org" | ||||
| source: hosted | source: hosted | ||||
| version: "1.6.9" | |||||
| version: "1.10.0" | |||||
| build_runner_core: | build_runner_core: | ||||
| dependency: transitive | dependency: transitive | ||||
| description: | description: | ||||
| name: build_runner_core | name: build_runner_core | ||||
| url: "https://pub.dartlang.org" | url: "https://pub.dartlang.org" | ||||
| source: hosted | source: hosted | ||||
| version: "3.1.1" | |||||
| version: "5.2.0" | |||||
| built_collection: | built_collection: | ||||
| dependency: transitive | dependency: transitive | ||||
| description: | description: | ||||
| url: "https://pub.dartlang.org" | url: "https://pub.dartlang.org" | ||||
| source: hosted | source: hosted | ||||
| version: "1.0.2" | version: "1.0.2" | ||||
| cli_util: | |||||
| dependency: transitive | |||||
| description: | |||||
| name: cli_util | |||||
| url: "https://pub.dartlang.org" | |||||
| source: hosted | |||||
| version: "0.1.4" | |||||
| code_builder: | code_builder: | ||||
| dependency: transitive | dependency: transitive | ||||
| description: | description: | ||||
| name: dart_style | name: dart_style | ||||
| url: "https://pub.dartlang.org" | url: "https://pub.dartlang.org" | ||||
| source: hosted | source: hosted | ||||
| version: "1.2.9" | |||||
| version: "1.3.6" | |||||
| dio: | dio: | ||||
| dependency: "direct main" | dependency: "direct main" | ||||
| description: | description: | ||||
| url: "https://pub.dartlang.org" | url: "https://pub.dartlang.org" | ||||
| source: hosted | source: hosted | ||||
| version: "0.3.0" | version: "0.3.0" | ||||
| front_end: | |||||
| dependency: transitive | |||||
| description: | |||||
| name: front_end | |||||
| url: "https://pub.dartlang.org" | |||||
| source: hosted | |||||
| version: "0.1.19" | |||||
| get: | get: | ||||
| dependency: "direct main" | dependency: "direct main" | ||||
| description: | description: | ||||
| name: json_annotation | name: json_annotation | ||||
| url: "https://pub.dartlang.org" | url: "https://pub.dartlang.org" | ||||
| source: hosted | source: hosted | ||||
| version: "2.3.0" | |||||
| json_model: | |||||
| dependency: "direct dev" | |||||
| description: | |||||
| name: json_model | |||||
| url: "https://pub.dartlang.org" | |||||
| source: hosted | |||||
| version: "0.0.2" | |||||
| version: "3.0.1" | |||||
| json_serializable: | json_serializable: | ||||
| dependency: "direct dev" | dependency: "direct dev" | ||||
| description: | description: | ||||
| name: json_serializable | name: json_serializable | ||||
| url: "https://pub.dartlang.org" | url: "https://pub.dartlang.org" | ||||
| source: hosted | source: hosted | ||||
| version: "2.3.0" | |||||
| kernel: | |||||
| dependency: transitive | |||||
| description: | |||||
| name: kernel | |||||
| url: "https://pub.dartlang.org" | |||||
| source: hosted | |||||
| version: "0.3.19" | |||||
| version: "3.4.1" | |||||
| keyboard_dismisser: | keyboard_dismisser: | ||||
| dependency: "direct main" | dependency: "direct main" | ||||
| description: | description: | ||||
| url: "https://pub.dartlang.org" | url: "https://pub.dartlang.org" | ||||
| source: hosted | source: hosted | ||||
| version: "0.4.3" | version: "0.4.3" | ||||
| package_resolver: | |||||
| dependency: transitive | |||||
| description: | |||||
| name: package_resolver | |||||
| url: "https://pub.dartlang.org" | |||||
| source: hosted | |||||
| version: "1.0.10" | |||||
| path: | path: | ||||
| dependency: transitive | dependency: transitive | ||||
| description: | description: | ||||
| url: "https://pub.dartlang.org" | url: "https://pub.dartlang.org" | ||||
| source: hosted | source: hosted | ||||
| version: "2.1.3" | version: "2.1.3" | ||||
| retrofit: | |||||
| dependency: transitive | |||||
| description: | |||||
| name: retrofit | |||||
| url: "https://pub.dartlang.org" | |||||
| source: hosted | |||||
| version: "1.3.4" | |||||
| retrofit_generator: | |||||
| dependency: "direct dev" | |||||
| description: | |||||
| name: retrofit_generator | |||||
| url: "https://pub.dartlang.org" | |||||
| source: hosted | |||||
| version: "1.3.7+6" | |||||
| rxdart: | rxdart: | ||||
| dependency: "direct main" | dependency: "direct main" | ||||
| description: | description: | ||||
| name: source_gen | name: source_gen | ||||
| url: "https://pub.dartlang.org" | url: "https://pub.dartlang.org" | ||||
| source: hosted | source: hosted | ||||
| version: "0.9.4+4" | |||||
| version: "0.9.6" | |||||
| source_span: | source_span: | ||||
| dependency: transitive | dependency: transitive | ||||
| description: | description: | ||||
| url: "https://pub.dartlang.org" | url: "https://pub.dartlang.org" | ||||
| source: hosted | source: hosted | ||||
| version: "0.1.1+2" | version: "0.1.1+2" | ||||
| tuple: | |||||
| dependency: transitive | |||||
| description: | |||||
| name: tuple | |||||
| url: "https://pub.dartlang.org" | |||||
| source: hosted | |||||
| version: "1.0.3" | |||||
| typed_data: | typed_data: | ||||
| dependency: transitive | dependency: transitive | ||||
| description: | description: |
| get: ^3.8.0 | get: ^3.8.0 | ||||
| intl: ^0.16.1 | intl: ^0.16.1 | ||||
| flutter_datetime_picker: ^1.3.8 | flutter_datetime_picker: ^1.3.8 | ||||
| analyzer: ^0.39.14 | |||||
| dev_dependencies: | dev_dependencies: | ||||
| flutter_test: | flutter_test: | ||||
| sdk: flutter | sdk: flutter | ||||
| #flutter packages pub run build_runner build --delete-conflicting-outputs | #flutter packages pub run build_runner build --delete-conflicting-outputs | ||||
| # retrofit_generator: ^1.3.7 | |||||
| retrofit_generator: ^1.3.7 | |||||
| # flutter packages pub run json_model | # flutter packages pub run json_model | ||||
| json_model: ^0.0.2 | |||||
| # json_model: ^0.0.2 | |||||
| build_runner: any | build_runner: any | ||||
| json_serializable: any | json_serializable: any | ||||