| @@ -1 +1 @@ | |||
| 40af80373277a16a0592f58ccbdc7f07 | |||
| eb141365fb69a4e0decadd2584d53516 | |||
| @@ -0,0 +1,36 @@ | |||
| class EnvironmentParameter { | |||
| int id; | |||
| String name; | |||
| num index; | |||
| int activityId; | |||
| String executeDate; | |||
| bool status; | |||
| EnvironmentParameter( | |||
| {this.id, | |||
| this.name, | |||
| this.index, | |||
| this.activityId, | |||
| this.executeDate, | |||
| this.status}); | |||
| EnvironmentParameter.fromJson(Map<String, dynamic> json) { | |||
| id = json['id']; | |||
| name = json['name']; | |||
| index = json['index']; | |||
| activityId = json['activityId']; | |||
| executeDate = json['executeDate']; | |||
| status = json['status']; | |||
| } | |||
| Map<String, dynamic> toJson() { | |||
| final Map<String, dynamic> data = new Map<String, dynamic>(); | |||
| data['id'] = this.id; | |||
| data['name'] = this.name; | |||
| data['index'] = this.index; | |||
| data['activityId'] = this.activityId; | |||
| data['executeDate'] = this.executeDate; | |||
| data['status'] = this.status; | |||
| return data; | |||
| } | |||
| } | |||
| @@ -1,6 +1,7 @@ | |||
| import 'package:dio/dio.dart'; | |||
| import 'package:farm_tpf/custom_model/CropPlot.dart'; | |||
| import 'package:farm_tpf/custom_model/Device.dart'; | |||
| import 'package:farm_tpf/custom_model/EnvironmentParameter.dart'; | |||
| import 'package:farm_tpf/custom_model/Harvest.dart'; | |||
| import 'package:farm_tpf/custom_model/WaterType.dart'; | |||
| import 'package:farm_tpf/custom_model/account.dart'; | |||
| @@ -71,4 +72,10 @@ abstract class RestClient { | |||
| //Device | |||
| @GET("/api/listDeviceOfUserCustomers") | |||
| Future<List<Device>> getDevices(); | |||
| //Get environment parameter | |||
| @GET("/api/list-environment-updates-display/{cropId}?page={page}&size={size}") | |||
| Future<List<EnvironmentParameter>> getEnvironmentParameters( | |||
| @Path() int cropId, | |||
| {@Path() int page = 0, | |||
| @Path() int size = 20}); | |||
| } | |||
| @@ -324,6 +324,29 @@ class _RestClient implements RestClient { | |||
| return value; | |||
| } | |||
| @override | |||
| getEnvironmentParameters(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<List<dynamic>> _result = await _dio.request( | |||
| '/api/list-environment-updates-display/$cropId?page=$page&size=$size', | |||
| queryParameters: queryParameters, | |||
| options: RequestOptions( | |||
| method: 'GET', | |||
| headers: <String, dynamic>{}, | |||
| extra: _extra, | |||
| baseUrl: baseUrl), | |||
| data: _data); | |||
| var value = _result.data | |||
| .map((dynamic i) => | |||
| EnvironmentParameter.fromJson(i as Map<String, dynamic>)) | |||
| .toList(); | |||
| return value; | |||
| } | |||
| RequestOptions newRequestOptions(Options options) { | |||
| if (options is RequestOptions) { | |||
| return options; | |||
| @@ -4,6 +4,7 @@ import 'package:dio/dio.dart'; | |||
| import 'package:dio_http_cache/dio_http_cache.dart'; | |||
| import 'package:farm_tpf/custom_model/CropPlot.dart'; | |||
| import 'package:farm_tpf/custom_model/Device.dart'; | |||
| import 'package:farm_tpf/custom_model/EnvironmentParameter.dart'; | |||
| import 'package:farm_tpf/custom_model/Harvest.dart'; | |||
| import 'package:farm_tpf/custom_model/WaterType.dart'; | |||
| import 'package:farm_tpf/custom_model/user.dart'; | |||
| @@ -144,4 +145,11 @@ class Repository { | |||
| final client = RestClient(dio); | |||
| return client.getDevices(); | |||
| } | |||
| //Environment Parameter | |||
| Future<List<EnvironmentParameter>> getEnvironmentParameters( | |||
| {@required int cropId, int page, int size}) { | |||
| final client = RestClient(dio); | |||
| return client.getEnvironmentParameters(cropId, page: page, size: size); | |||
| } | |||
| } | |||
| @@ -32,7 +32,6 @@ class PlotBloc extends Bloc<PlotEvent, PlotState> { | |||
| if (state is PlotSuccess) { | |||
| final currentState = state as PlotSuccess; | |||
| int page = currentState.page + 1; | |||
| yield PlotLoading(); | |||
| final response = | |||
| await repository.getPlots(page: page, size: pageSize); | |||
| yield response.isEmpty | |||
| @@ -239,7 +239,7 @@ class ItemInfinityWidget extends StatelessWidget { | |||
| MaterialPageRoute( | |||
| builder: (BuildContext context) => PlotDetailScreen( | |||
| cropId: item.id, | |||
| initialIndex: 1, | |||
| initialIndex: 0, | |||
| ))); | |||
| }); | |||
| } | |||
| @@ -0,0 +1,67 @@ | |||
| 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_parameter_event.dart'; | |||
| part 'plot_parameter_state.dart'; | |||
| class PlotParameterBloc extends Bloc<PlotParameterEvent, PlotParameterState> { | |||
| final Repository repository; | |||
| PlotParameterBloc({@required this.repository}) | |||
| : super(PlotParameterInitial()); | |||
| static int pageSize = 20; | |||
| @override | |||
| Stream<PlotParameterState> mapEventToState( | |||
| PlotParameterEvent event, | |||
| ) async* { | |||
| if (event is DataFetched && | |||
| !(state is PlotParameterSuccess && | |||
| (state as PlotParameterSuccess).hasReachedMax)) { | |||
| try { | |||
| if (state is PlotParameterInitial) { | |||
| yield PlotParameterLoading(); | |||
| final response = await repository.getEnvironmentParameters( | |||
| cropId: event.cropId, page: 0, size: pageSize); | |||
| yield PlotParameterSuccess( | |||
| items: response, | |||
| page: 0, | |||
| hasReachedMax: response.length < pageSize ? true : false); | |||
| } | |||
| //TODO: check paging api | |||
| // if (state is PlotParameterSuccess) { | |||
| // final currentState = state as PlotParameterSuccess; | |||
| // int page = currentState.page + 1; | |||
| // final response = await repository.getEnvironmentParameters( | |||
| // cropId: event.cropId, page: page, size: pageSize); | |||
| // yield response.isEmpty | |||
| // ? currentState.copyWith(hasReachedMax: true) | |||
| // : PlotParameterSuccess( | |||
| // items: currentState.items + response, | |||
| // page: currentState.page + 1, | |||
| // hasReachedMax: false); | |||
| // } | |||
| } catch (e) { | |||
| var errorString = AppException.handleError(e); | |||
| yield PlotParameterFailure(errorString: errorString); | |||
| } | |||
| } | |||
| if (event is OnRefresh) { | |||
| try { | |||
| yield PlotParameterLoading(); | |||
| final response = await repository.getEnvironmentParameters( | |||
| cropId: event.cropId, page: 0, size: pageSize); | |||
| yield PlotParameterSuccess( | |||
| items: response, | |||
| page: 0, | |||
| hasReachedMax: response.length < pageSize ? true : false); | |||
| } catch (e) { | |||
| yield PlotParameterFailure(errorString: AppException.handleError(e)); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| part of 'plot_parameter_bloc.dart'; | |||
| abstract class PlotParameterEvent extends Equatable { | |||
| const PlotParameterEvent(); | |||
| @override | |||
| List<Object> get props => []; | |||
| } | |||
| class DataFetched extends PlotParameterEvent { | |||
| final int cropId; | |||
| DataFetched({@required this.cropId}); | |||
| } | |||
| class OnRefresh extends PlotParameterEvent { | |||
| final int cropId; | |||
| OnRefresh({@required this.cropId}); | |||
| } | |||
| @@ -0,0 +1,35 @@ | |||
| part of 'plot_parameter_bloc.dart'; | |||
| abstract class PlotParameterState extends Equatable { | |||
| const PlotParameterState(); | |||
| @override | |||
| List<Object> get props => []; | |||
| } | |||
| class PlotParameterInitial extends PlotParameterState {} | |||
| class PlotParameterLoading extends PlotParameterState {} | |||
| class PlotParameterFailure extends PlotParameterState { | |||
| final String errorString; | |||
| PlotParameterFailure({@required this.errorString}); | |||
| } | |||
| class PlotParameterSuccess<T> extends PlotParameterState { | |||
| final List<T> items; | |||
| final int page; | |||
| final bool hasReachedMax; | |||
| const PlotParameterSuccess({this.items, this.page, this.hasReachedMax}); | |||
| PlotParameterSuccess copyWith({List<T> items, int page, bool hasReachedMax}) { | |||
| return PlotParameterSuccess( | |||
| items: items ?? this.items, | |||
| page: page ?? this.page, | |||
| hasReachedMax: hasReachedMax ?? this.hasReachedMax); | |||
| } | |||
| @override | |||
| List<Object> get props => [items, hasReachedMax]; | |||
| } | |||
| @@ -45,7 +45,9 @@ class _PlotDetailScreenState extends State<PlotDetailScreen> { | |||
| backgroundColor: COLOR_CONST.ITEM_BG, | |||
| body: TabBarView( | |||
| children: [ | |||
| PlotParameterScreen(), | |||
| PlotParameterScreen( | |||
| cropId: widget.cropId, | |||
| ), | |||
| PlotActionScreen( | |||
| cropId: widget.cropId, | |||
| cropCode: widget.cropCode, | |||
| @@ -1,6 +1,17 @@ | |||
| import 'package:farm_tpf/custom_model/EnvironmentParameter.dart'; | |||
| import 'package:farm_tpf/data/repository/repository.dart'; | |||
| import 'package:farm_tpf/presentation/custom_widgets/bottom_loader.dart'; | |||
| import 'package:farm_tpf/presentation/custom_widgets/loading_list_page.dart'; | |||
| import 'package:farm_tpf/utils/const_string.dart'; | |||
| import 'package:flutter/material.dart'; | |||
| import 'package:flutter_bloc/flutter_bloc.dart'; | |||
| import 'package:farm_tpf/utils/formatter.dart'; | |||
| import 'bloc/plot_parameter_bloc.dart'; | |||
| class PlotParameterScreen extends StatefulWidget { | |||
| final int cropId; | |||
| PlotParameterScreen({@required this.cropId}); | |||
| @override | |||
| _PlotParameterScreenState createState() => _PlotParameterScreenState(); | |||
| } | |||
| @@ -8,6 +19,120 @@ class PlotParameterScreen extends StatefulWidget { | |||
| class _PlotParameterScreenState extends State<PlotParameterScreen> { | |||
| @override | |||
| Widget build(BuildContext context) { | |||
| return Container(); | |||
| return BlocProvider( | |||
| create: (context) => PlotParameterBloc(repository: Repository()) | |||
| ..add(DataFetched(cropId: 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; | |||
| PlotParameterBloc _plotParameterBloc; | |||
| @override | |||
| void initState() { | |||
| _scrollController.addListener(() { | |||
| final maxScroll = _scrollController.position.maxScrollExtent; | |||
| final currentScroll = _scrollController.position.pixels; | |||
| if (maxScroll - currentScroll < _scrollThreshold) { | |||
| _plotParameterBloc.add(DataFetched(cropId: widget.cropId)); | |||
| } | |||
| }); | |||
| _plotParameterBloc = BlocProvider.of<PlotParameterBloc>(context); | |||
| super.initState(); | |||
| } | |||
| @override | |||
| Widget build(BuildContext context) { | |||
| return Column( | |||
| children: <Widget>[ | |||
| Expanded(child: BlocBuilder<PlotParameterBloc, PlotParameterState>( | |||
| builder: (context, state) { | |||
| if (state is PlotParameterFailure) { | |||
| return Center(child: Text(state.errorString)); | |||
| } | |||
| if (state is PlotParameterSuccess) { | |||
| 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 { | |||
| _plotParameterBloc.add(OnRefresh(cropId: widget.cropId)); | |||
| }); | |||
| } | |||
| return Center( | |||
| child: LoadingListPage(), | |||
| ); | |||
| }, | |||
| )) | |||
| ], | |||
| ); | |||
| } | |||
| @override | |||
| void dispose() { | |||
| _scrollController.dispose(); | |||
| super.dispose(); | |||
| } | |||
| } | |||
| class ItemInfinityWidget extends StatelessWidget { | |||
| final EnvironmentParameter item; | |||
| const ItemInfinityWidget({Key key, @required this.item}) : super(key: key); | |||
| @override | |||
| Widget build(BuildContext context) { | |||
| return Card( | |||
| child: ListTile( | |||
| title: Text( | |||
| "${item.name ?? ''}", | |||
| style: TextStyle(fontWeight: FontWeight.bold), | |||
| ), | |||
| subtitle: Text( | |||
| item.index.formatNumtoStringDecimal(), | |||
| style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold), | |||
| ), | |||
| trailing: Text(item.executeDate.format_DDMMYY_HHmm()), | |||
| ), | |||
| ); | |||
| } | |||
| } | |||