| //Common | //Common | ||||
| @PUT("/api/update-fcmToken") | @PUT("/api/update-fcmToken") | ||||
| Future<void> updateFcmToken(@Body() String token); | Future<void> updateFcmToken(@Body() String token); | ||||
| @GET("/api/tb-crops?page={page}&size={size}") | |||||
| Future<List<Plot>> getPlots({@Path() int page = 0, @Path() int size = 20}); | |||||
| } | } |
| class _RestClient implements RestClient { | class _RestClient implements RestClient { | ||||
| _RestClient(this._dio, {this.baseUrl}) { | _RestClient(this._dio, {this.baseUrl}) { | ||||
| ArgumentError.checkNotNull(_dio, '_dio'); | ArgumentError.checkNotNull(_dio, '_dio'); | ||||
| this.baseUrl ??= ConstCommon.baseUrl; | |||||
| this.baseUrl ??= 'https://ironman.aztrace.vn'; | |||||
| } | } | ||||
| final Dio _dio; | final Dio _dio; | ||||
| data: _data); | data: _data); | ||||
| return null; | return null; | ||||
| } | } | ||||
| @override | |||||
| getPlots({page = 0, size = 20}) async { | |||||
| 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/tb-crops?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) => Plot.fromJson(i as Map<String, dynamic>)) | |||||
| .toList(); | |||||
| return value; | |||||
| } | |||||
| } | } |
| class Repository { | class Repository { | ||||
| final dio = DioProvider.instance(); | final dio = DioProvider.instance(); | ||||
| Future<List<Plot>> getPlots({int page, int size}) { | |||||
| final client = RestClient(dio); | |||||
| return client.getPlots(page: page, size: size); | |||||
| } | |||||
| Future<User> signInWithCredentials(String username, String password) { | Future<User> signInWithCredentials(String username, String password) { | ||||
| final client = RestClient(dio); | final client = RestClient(dio); | ||||
| return client.login(UserRequest(username: username, password: password)); | return client.login(UserRequest(username: username, password: password)); |
| String activityExecuteDate; | String activityExecuteDate; | ||||
| bool isExceedLimit; | bool isExceedLimit; | ||||
| Plot fromJson(Map<String, dynamic> json) => _$PlotFromJson(json); | |||||
| factory Plot.fromJson(Map<String, dynamic> json) => _$PlotFromJson(json); | |||||
| Map<String, dynamic> toJson() => _$PlotToJson(this); | Map<String, dynamic> toJson() => _$PlotToJson(this); | ||||
| } | } |
| 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_event.dart'; | |||||
| part 'plot_state.dart'; | |||||
| class PlotBloc extends Bloc<PlotEvent, PlotState> { | |||||
| final Repository repository; | |||||
| PlotBloc({@required this.repository}) : super(PlotInitial()); | |||||
| static int pageSize = 4; | |||||
| @override | |||||
| Stream<PlotState> mapEventToState( | |||||
| PlotEvent event, | |||||
| ) async* { | |||||
| if (event is DataFetched && | |||||
| !(state is PlotSuccess && (state as PlotSuccess).hasReachedMax)) { | |||||
| try { | |||||
| if (state is PlotInitial) { | |||||
| final response = await repository.getPlots(page: 0, size: pageSize); | |||||
| yield PlotSuccess( | |||||
| items: response, | |||||
| page: 0, | |||||
| hasReachedMax: response.length < pageSize ? true : false); | |||||
| } | |||||
| if (state is PlotSuccess) { | |||||
| final currentState = state as PlotSuccess; | |||||
| int page = currentState.page + 1; | |||||
| final response = | |||||
| await repository.getPlots(page: page, size: pageSize); | |||||
| yield response.isEmpty | |||||
| ? currentState.copyWith(hasReachedMax: true) | |||||
| : PlotSuccess( | |||||
| items: currentState.items + response, | |||||
| page: currentState.page + 1, | |||||
| hasReachedMax: false); | |||||
| } | |||||
| } catch (e) { | |||||
| var errorString = AppException.handleError(e); | |||||
| yield PlotFailure(errorString: errorString); | |||||
| } | |||||
| } | |||||
| if (event is OnRefresh) { | |||||
| try { | |||||
| final response = await repository.getPlots(page: 0, size: pageSize); | |||||
| yield PlotSuccess( | |||||
| items: response, | |||||
| page: 0, | |||||
| hasReachedMax: response.length < pageSize ? true : false); | |||||
| } catch (e) { | |||||
| yield PlotFailure(errorString: AppException.handleError(e)); | |||||
| } | |||||
| } | |||||
| } | |||||
| } |
| part of 'plot_bloc.dart'; | |||||
| abstract class PlotEvent extends Equatable { | |||||
| const PlotEvent(); | |||||
| @override | |||||
| List<Object> get props => []; | |||||
| } | |||||
| class DataFetched extends PlotEvent {} | |||||
| class OnRefresh extends PlotEvent {} |
| part of 'plot_bloc.dart'; | |||||
| abstract class PlotState extends Equatable { | |||||
| const PlotState(); | |||||
| @override | |||||
| List<Object> get props => []; | |||||
| } | |||||
| class PlotInitial extends PlotState {} | |||||
| class PlotFailure extends PlotState { | |||||
| final String errorString; | |||||
| PlotFailure({@required this.errorString}); | |||||
| } | |||||
| class PlotSuccess<T> extends PlotState { | |||||
| final List<T> items; | |||||
| final int page; | |||||
| final bool hasReachedMax; | |||||
| const PlotSuccess({this.items, this.page, this.hasReachedMax}); | |||||
| PlotSuccess copyWith({List<T> items, int page, bool hasReachedMax}) { | |||||
| return PlotSuccess( | |||||
| items: items ?? this.items, | |||||
| page: page ?? this.page, | |||||
| hasReachedMax: hasReachedMax ?? this.hasReachedMax); | |||||
| } | |||||
| @override | |||||
| List<Object> get props => [items, hasReachedMax]; | |||||
| } |
| import 'package:farm_tpf/models/Plot.dart'; | import 'package:farm_tpf/models/Plot.dart'; | ||||
| import 'package:farm_tpf/presentation/custom_widgets/bottom_loader.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/custom_widgets/loading_list_page.dart'; | ||||
| import 'package:farm_tpf/utils/bloc/infinity_scroll_bloc.dart'; | |||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||
| 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 'package:font_awesome_flutter/font_awesome_flutter.dart'; | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; | ||||
| import 'bloc/plot_bloc.dart'; | |||||
| class PlotListScreen extends StatefulWidget { | class PlotListScreen extends StatefulWidget { | ||||
| @override | @override | ||||
| _PlotListScreenState createState() => _PlotListScreenState(); | _PlotListScreenState createState() => _PlotListScreenState(); | ||||
| Widget build(BuildContext context) { | Widget build(BuildContext context) { | ||||
| return BlocProvider( | return BlocProvider( | ||||
| create: (context) => | create: (context) => | ||||
| InfinityScrollBloc(repository: Repository())..add(DataFetched()), | |||||
| PlotBloc(repository: Repository())..add(DataFetched()), | |||||
| child: HoldInfinityWidget(), | child: HoldInfinityWidget(), | ||||
| ); | ); | ||||
| } | } | ||||
| class _InfinityViewState extends State<InfinityView> { | class _InfinityViewState extends State<InfinityView> { | ||||
| final _scrollController = ScrollController(); | final _scrollController = ScrollController(); | ||||
| final _scrollThreshold = 250.0; | final _scrollThreshold = 250.0; | ||||
| InfinityScrollBloc _infinityScrollBloc; | |||||
| PlotBloc _plotBloc; | |||||
| @override | @override | ||||
| void initState() { | void initState() { | ||||
| 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) { | ||||
| _infinityScrollBloc.add(DataFetched()); | |||||
| _plotBloc.add(DataFetched()); | |||||
| } | } | ||||
| }); | }); | ||||
| _infinityScrollBloc = BlocProvider.of<InfinityScrollBloc>(context); | |||||
| _plotBloc = BlocProvider.of<PlotBloc>(context); | |||||
| super.initState(); | super.initState(); | ||||
| } | } | ||||
| @override | @override | ||||
| Widget build(BuildContext context) { | Widget build(BuildContext context) { | ||||
| return BlocBuilder<InfinityScrollBloc, InfinityScrollState>( | |||||
| return BlocBuilder<PlotBloc, PlotState>( | |||||
| builder: (context, state) { | builder: (context, state) { | ||||
| if (state is InfinityScrollFailure) { | |||||
| if (state is PlotFailure) { | |||||
| return Center(child: Text(state.errorString)); | return Center(child: Text(state.errorString)); | ||||
| } | } | ||||
| if (state is InfinityScrollSuccess) { | |||||
| if (state is PlotSuccess) { | |||||
| if (state.items.isEmpty) { | if (state.items.isEmpty) { | ||||
| return Center(child: Text(label_list_empty)); | return Center(child: Text(label_list_empty)); | ||||
| } | } | ||||
| controller: _scrollController, | controller: _scrollController, | ||||
| ), | ), | ||||
| onRefresh: () async { | onRefresh: () async { | ||||
| _infinityScrollBloc.add(OnRefresh()); | |||||
| _plotBloc.add(OnRefresh()); | |||||
| }); | }); | ||||
| } | } | ||||
| return Center( | return Center( |