| @@ -44,4 +44,10 @@ abstract class RestClient { | |||
| {@Path() int page = 0, @Path() int size = 20, @Path() String query = ""}); | |||
| @GET("/api/listActivityTypesOther") | |||
| 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}); | |||
| } | |||
| @@ -205,4 +205,24 @@ class _RestClient implements RestClient { | |||
| .toList(); | |||
| 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; | |||
| } | |||
| } | |||
| @@ -16,6 +16,11 @@ class Repository { | |||
| 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}) { | |||
| final client = RestClient(dio); | |||
| return client.getPlots(page: page, size: size, query: searchString); | |||
| @@ -26,5 +26,5 @@ Map<String, dynamic> _$CropToJson(Crop instance) => <String, dynamic>{ | |||
| 'seedIncubationTime': instance.seedIncubationTime, | |||
| 'numberPlants': instance.numberPlants, | |||
| 'numberCurrentPlants': instance.numberCurrentPlants, | |||
| 'endOfFarmingDate': instance.endOfFarmingDate | |||
| 'endOfFarmingDate': instance.endOfFarmingDate, | |||
| }; | |||
| @@ -25,5 +25,5 @@ Map<String, dynamic> _$HistoryActionToJson(HistoryAction instance) => | |||
| 'executeDate': instance.executeDate, | |||
| 'description': instance.description, | |||
| 'activityTypeId': instance.activityTypeId, | |||
| 'activityTypeName': instance.activityTypeName | |||
| 'activityTypeName': instance.activityTypeName, | |||
| }; | |||
| @@ -164,7 +164,8 @@ class ItemInfinityWidget extends StatelessWidget { | |||
| Navigator.push( | |||
| context, | |||
| MaterialPageRoute( | |||
| builder: (BuildContext context) => PlotDetailScreen())); | |||
| builder: (BuildContext context) => | |||
| PlotDetailScreen(cropId: item.id))); | |||
| }); | |||
| } | |||
| } | |||
| @@ -0,0 +1,68 @@ | |||
| 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)); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| 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); | |||
| } | |||
| @@ -0,0 +1,35 @@ | |||
| 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]; | |||
| } | |||
| @@ -1,10 +1,19 @@ | |||
| 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/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_string.dart'; | |||
| import 'package:flutter/material.dart'; | |||
| import 'package:flutter_bloc/flutter_bloc.dart'; | |||
| import 'package:farm_tpf/utils/formatter.dart'; | |||
| class PlotActionScreen extends StatefulWidget { | |||
| final int cropId; | |||
| PlotActionScreen({@required this.cropId}); | |||
| @override | |||
| _PlotActionScreenState createState() => _PlotActionScreenState(); | |||
| } | |||
| @@ -140,18 +149,112 @@ class _PlotActionScreenState extends State<PlotActionScreen> { | |||
| )) | |||
| ])) | |||
| ], | |||
| 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 { | |||
| Widget addScreen; | |||
| Widget listScreen; | |||
| @@ -6,6 +6,8 @@ import 'package:farm_tpf/utils/const_color.dart'; | |||
| import 'package:flutter/material.dart'; | |||
| class PlotDetailScreen extends StatefulWidget { | |||
| final int cropId; | |||
| PlotDetailScreen({@required this.cropId}); | |||
| @override | |||
| _PlotDetailScreenState createState() => _PlotDetailScreenState(); | |||
| } | |||
| @@ -28,7 +30,12 @@ class _PlotDetailScreenState extends State<PlotDetailScreen> { | |||
| child: new Scaffold( | |||
| backgroundColor: COLOR_CONST.ITEM_BG, | |||
| body: TabBarView( | |||
| children: [PlotParameterScreen(), PlotActionScreen()], | |||
| children: [ | |||
| PlotParameterScreen(), | |||
| PlotActionScreen( | |||
| cropId: widget.cropId, | |||
| ) | |||
| ], | |||
| ), | |||
| bottomNavigationBar: new TabBar( | |||
| tabs: [ | |||
| @@ -1,13 +1,20 @@ | |||
| # Generated by pub | |||
| # See https://dart.dev/tools/pub/glossary#lockfile | |||
| packages: | |||
| analyzer: | |||
| _fe_analyzer_shared: | |||
| dependency: transitive | |||
| description: | |||
| name: _fe_analyzer_shared | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "6.0.0" | |||
| analyzer: | |||
| dependency: "direct main" | |||
| description: | |||
| name: analyzer | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "0.36.4" | |||
| version: "0.39.14" | |||
| archive: | |||
| dependency: transitive | |||
| description: | |||
| @@ -56,14 +63,14 @@ packages: | |||
| name: build | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "1.1.6" | |||
| version: "1.3.0" | |||
| build_config: | |||
| dependency: transitive | |||
| description: | |||
| name: build_config | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "0.4.1+1" | |||
| version: "0.4.2" | |||
| build_daemon: | |||
| dependency: transitive | |||
| description: | |||
| @@ -77,21 +84,21 @@ packages: | |||
| name: build_resolvers | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "1.2.1" | |||
| version: "1.3.11" | |||
| build_runner: | |||
| dependency: "direct dev" | |||
| description: | |||
| name: build_runner | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "1.6.9" | |||
| version: "1.10.0" | |||
| build_runner_core: | |||
| dependency: transitive | |||
| description: | |||
| name: build_runner_core | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "3.1.1" | |||
| version: "5.2.0" | |||
| built_collection: | |||
| dependency: transitive | |||
| description: | |||
| @@ -127,6 +134,13 @@ packages: | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| 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: | |||
| dependency: transitive | |||
| description: | |||
| @@ -175,7 +189,7 @@ packages: | |||
| name: dart_style | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "1.2.9" | |||
| version: "1.3.6" | |||
| dio: | |||
| dependency: "direct main" | |||
| description: | |||
| @@ -296,13 +310,6 @@ packages: | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "0.3.0" | |||
| front_end: | |||
| dependency: transitive | |||
| description: | |||
| name: front_end | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "0.1.19" | |||
| get: | |||
| dependency: "direct main" | |||
| description: | |||
| @@ -400,28 +407,14 @@ packages: | |||
| name: json_annotation | |||
| url: "https://pub.dartlang.org" | |||
| 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: | |||
| dependency: "direct dev" | |||
| description: | |||
| name: json_serializable | |||
| url: "https://pub.dartlang.org" | |||
| 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: | |||
| dependency: "direct main" | |||
| description: | |||
| @@ -492,13 +485,6 @@ packages: | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "0.4.3" | |||
| package_resolver: | |||
| dependency: transitive | |||
| description: | |||
| name: package_resolver | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "1.0.10" | |||
| path: | |||
| dependency: transitive | |||
| description: | |||
| @@ -604,6 +590,20 @@ packages: | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| 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: | |||
| dependency: "direct main" | |||
| description: | |||
| @@ -678,7 +678,7 @@ packages: | |||
| name: source_gen | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "0.9.4+4" | |||
| version: "0.9.6" | |||
| source_span: | |||
| dependency: transitive | |||
| description: | |||
| @@ -735,6 +735,13 @@ packages: | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "0.1.1+2" | |||
| tuple: | |||
| dependency: transitive | |||
| description: | |||
| name: tuple | |||
| url: "https://pub.dartlang.org" | |||
| source: hosted | |||
| version: "1.0.3" | |||
| typed_data: | |||
| dependency: transitive | |||
| description: | |||
| @@ -37,14 +37,15 @@ dependencies: | |||
| get: ^3.8.0 | |||
| intl: ^0.16.1 | |||
| flutter_datetime_picker: ^1.3.8 | |||
| analyzer: ^0.39.14 | |||
| dev_dependencies: | |||
| flutter_test: | |||
| sdk: flutter | |||
| #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 | |||
| json_model: ^0.0.2 | |||
| # json_model: ^0.0.2 | |||
| build_runner: any | |||
| json_serializable: any | |||