| { | |||||
| "id": 1578, | |||||
| "times": 20, | |||||
| "activityExecuteDate": "2020-08-20T11:53:20Z", | |||||
| "isExceedLimit": false | |||||
| } |
| import 'package:farm_tpf/data/api/dio_provider.dart'; | import 'package:farm_tpf/data/api/dio_provider.dart'; | ||||
| import 'package:farm_tpf/data/api/rest_client.dart'; | import 'package:farm_tpf/data/api/rest_client.dart'; | ||||
| import 'package:farm_tpf/models/PagedResult.dart'; | |||||
| import 'package:farm_tpf/models/Plot.dart'; | |||||
| import 'package:farm_tpf/models/user.dart'; | import 'package:farm_tpf/models/user.dart'; | ||||
| import 'package:farm_tpf/models/user_request.dart'; | import 'package:farm_tpf/models/user_request.dart'; | ||||
| import 'package:farm_tpf/utils/const_common.dart'; | |||||
| class Repository { | class Repository { | ||||
| final dio = DioProvider.instance(); | final dio = DioProvider.instance(); | ||||
| final client = RestClient(dio); | final client = RestClient(dio); | ||||
| return client.login(UserRequest(username: username, password: password)); | return client.login(UserRequest(username: username, password: password)); | ||||
| } | } | ||||
| Future<PagedResult<T>> getInfinityList<T>(String url, | |||||
| {int page = 0, int size = 20}) async { | |||||
| var initUrl = "/api/activities/latest-env-by-activity-type/1/2"; | |||||
| var url = | |||||
| ConstCommon.baseUrl + initUrl + "?page=$page&paged=true&size=$size"; | |||||
| var response = await dio.get(url); | |||||
| final value = PagedResult<T>.fromJson(response.data, getInstanceClass()); | |||||
| return value; | |||||
| } | |||||
| Object getInstanceClass() { | |||||
| var instanceClass; | |||||
| if (1 == 1) { | |||||
| instanceClass = new Plot(); | |||||
| } | |||||
| return instanceClass; | |||||
| } | |||||
| } | } |
| class PagedResult<T> { | |||||
| final int totalElements; | |||||
| final List<T> results; | |||||
| PagedResult({this.totalElements, this.results}); | |||||
| factory PagedResult.fromJson(Map<String, dynamic> json, object) { | |||||
| final items = json['content'].cast<Map<String, dynamic>>(); | |||||
| final listItems = | |||||
| new List<T>.from(items.map((itemsJson) => object.fromJson(itemsJson))); | |||||
| final total = json['totalElements'] as num; | |||||
| return PagedResult<T>(totalElements: total, results: listItems); | |||||
| } | |||||
| } |
| class Plot { | |||||
| int id; | |||||
| int times; | |||||
| String activityExecuteDate; | |||||
| bool isExceedLimit; | |||||
| Plot({this.id, this.times, this.activityExecuteDate, this.isExceedLimit}); | |||||
| Plot fromJson(Map<String, dynamic> json) { | |||||
| id = json['id']; | |||||
| times = json['times']; | |||||
| activityExecuteDate = json['activityExecuteDate']; | |||||
| isExceedLimit = json['isExceedLimit']; | |||||
| } | |||||
| Map<String, dynamic> toJson() { | |||||
| final Map<String, dynamic> data = new Map<String, dynamic>(); | |||||
| data['id'] = this.id; | |||||
| data['times'] = this.times; | |||||
| data['activityExecuteDate'] = this.activityExecuteDate; | |||||
| data['isExceedLimit'] = this.isExceedLimit; | |||||
| return data; | |||||
| } | |||||
| } |
| import 'package:flutter/material.dart'; | |||||
| class BottomLoader extends StatelessWidget { | |||||
| @override | |||||
| Widget build(BuildContext context) { | |||||
| return Container( | |||||
| alignment: Alignment.center, | |||||
| child: Center( | |||||
| child: SizedBox( | |||||
| width: 33, | |||||
| height: 33, | |||||
| child: CircularProgressIndicator( | |||||
| strokeWidth: 1.5, | |||||
| ), | |||||
| ), | |||||
| ), | |||||
| ); | |||||
| } | |||||
| } |
| import 'package:flutter/material.dart'; | |||||
| import 'package:shimmer/shimmer.dart'; | |||||
| class LoadingListPage extends StatefulWidget { | |||||
| @override | |||||
| _LoadingListPageState createState() => _LoadingListPageState(); | |||||
| } | |||||
| class _LoadingListPageState extends State<LoadingListPage> { | |||||
| bool _enabled = true; | |||||
| @override | |||||
| Widget build(BuildContext context) { | |||||
| return Scaffold( | |||||
| body: Container( | |||||
| width: double.infinity, | |||||
| padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), | |||||
| child: Column( | |||||
| mainAxisSize: MainAxisSize.max, | |||||
| children: <Widget>[ | |||||
| Expanded( | |||||
| child: Shimmer.fromColors( | |||||
| baseColor: Colors.grey[300], | |||||
| highlightColor: Colors.grey[100], | |||||
| enabled: _enabled, | |||||
| child: ListView.builder( | |||||
| itemBuilder: (_, __) => Padding( | |||||
| padding: const EdgeInsets.only(bottom: 8.0), | |||||
| child: Row( | |||||
| crossAxisAlignment: CrossAxisAlignment.start, | |||||
| children: [ | |||||
| Container( | |||||
| width: 48.0, | |||||
| height: 48.0, | |||||
| color: Colors.white, | |||||
| ), | |||||
| const Padding( | |||||
| padding: EdgeInsets.symmetric(horizontal: 8.0), | |||||
| ), | |||||
| Expanded( | |||||
| child: Column( | |||||
| crossAxisAlignment: CrossAxisAlignment.start, | |||||
| children: <Widget>[ | |||||
| Container( | |||||
| width: double.infinity, | |||||
| height: 8.0, | |||||
| color: Colors.white, | |||||
| ), | |||||
| const Padding( | |||||
| padding: EdgeInsets.symmetric(vertical: 2.0), | |||||
| ), | |||||
| Container( | |||||
| width: double.infinity, | |||||
| height: 8.0, | |||||
| color: Colors.white, | |||||
| ), | |||||
| const Padding( | |||||
| padding: EdgeInsets.symmetric(vertical: 2.0), | |||||
| ), | |||||
| Container( | |||||
| width: 40.0, | |||||
| height: 8.0, | |||||
| color: Colors.white, | |||||
| ), | |||||
| ], | |||||
| ), | |||||
| ) | |||||
| ], | |||||
| ), | |||||
| ), | |||||
| itemCount: 20, | |||||
| ), | |||||
| ), | |||||
| ), | |||||
| ], | |||||
| ), | |||||
| ), | |||||
| ); | |||||
| } | |||||
| } |
| import 'package:farm_tpf/presentation/screens/actions/nursery/sc_nursery.dart'; | import 'package:farm_tpf/presentation/screens/actions/nursery/sc_nursery.dart'; | ||||
| import 'package:farm_tpf/presentation/screens/actions/plant/sc_plant.dart'; | import 'package:farm_tpf/presentation/screens/actions/plant/sc_plant.dart'; | ||||
| import 'package:farm_tpf/presentation/screens/plot/sc_plot.dart'; | |||||
| import 'package:farm_tpf/presentation/screens/plot_detail/sc_plot_detail.dart'; | import 'package:farm_tpf/presentation/screens/plot_detail/sc_plot_detail.dart'; | ||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||
| Navigator.of(context).push( | Navigator.of(context).push( | ||||
| MaterialPageRoute(builder: (_) => PlotDetailScreen())); | MaterialPageRoute(builder: (_) => PlotDetailScreen())); | ||||
| }), | }), | ||||
| MaterialButton( | |||||
| child: Text("Danh sách lô"), | |||||
| onPressed: () { | |||||
| Navigator.of(context).push( | |||||
| MaterialPageRoute(builder: (_) => PlotListScreen())); | |||||
| }), | |||||
| ], | ], | ||||
| ), | ), | ||||
| ), | ), |
| import 'package:dio/dio.dart'; | |||||
| import 'package:farm_tpf/data/repository/repository.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/loading_list_page.dart'; | |||||
| import 'package:farm_tpf/utils/bloc/infinity_scroll_bloc.dart'; | |||||
| import 'package:flutter/material.dart'; | |||||
| import 'package:flutter_bloc/flutter_bloc.dart'; | |||||
| import 'package:farm_tpf/utils/const_string.dart'; | |||||
| class PlotListScreen extends StatefulWidget { | |||||
| @override | |||||
| _PlotListScreenState createState() => _PlotListScreenState(); | |||||
| } | |||||
| class _PlotListScreenState extends State<PlotListScreen> { | |||||
| @override | |||||
| Widget build(BuildContext context) { | |||||
| return BlocProvider( | |||||
| create: (context) => | |||||
| InfinityScrollBloc(repository: Repository())..add(DataFetched()), | |||||
| child: HoldInfinityWidget(), | |||||
| ); | |||||
| } | |||||
| } | |||||
| class HoldInfinityWidget extends StatelessWidget { | |||||
| final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>(); | |||||
| @override | |||||
| Widget build(BuildContext context) { | |||||
| return Scaffold( | |||||
| key: _scaffoldKey, | |||||
| appBar: AppBar( | |||||
| centerTitle: true, | |||||
| title: Text("Danh sách lô"), | |||||
| actions: <Widget>[ | |||||
| IconButton(icon: Icon(Icons.add), onPressed: () {}) | |||||
| ], | |||||
| ), | |||||
| body: InfinityView()); | |||||
| } | |||||
| } | |||||
| class InfinityView extends StatefulWidget { | |||||
| @override | |||||
| _InfinityViewState createState() => _InfinityViewState(); | |||||
| } | |||||
| class _InfinityViewState extends State<InfinityView> { | |||||
| final _scrollController = ScrollController(); | |||||
| final _scrollThreshold = 250.0; | |||||
| InfinityScrollBloc _infinityScrollBloc; | |||||
| @override | |||||
| void initState() { | |||||
| _scrollController.addListener(() { | |||||
| final maxScroll = _scrollController.position.maxScrollExtent; | |||||
| final currentScroll = _scrollController.position.pixels; | |||||
| if (maxScroll - currentScroll < _scrollThreshold) { | |||||
| _infinityScrollBloc.add(DataFetched()); | |||||
| } | |||||
| }); | |||||
| _infinityScrollBloc = BlocProvider.of<InfinityScrollBloc>(context); | |||||
| super.initState(); | |||||
| } | |||||
| @override | |||||
| Widget build(BuildContext context) { | |||||
| return BlocBuilder<InfinityScrollBloc, InfinityScrollState>( | |||||
| builder: (context, state) { | |||||
| if (state is InfinityScrollFailure) { | |||||
| return Center(child: Text(label_error_get_data)); | |||||
| } | |||||
| if (state is InfinityScrollSuccess) { | |||||
| if (state.items.isEmpty) { | |||||
| return Center(child: Text(label_list_empty)); | |||||
| } | |||||
| return RefreshIndicator( | |||||
| child: ListView.builder( | |||||
| 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 { | |||||
| _infinityScrollBloc.add(OnRefresh()); | |||||
| }); | |||||
| } | |||||
| return Center( | |||||
| child: LoadingListPage(), | |||||
| ); | |||||
| }, | |||||
| ); | |||||
| } | |||||
| @override | |||||
| void dispose() { | |||||
| _scrollController.dispose(); | |||||
| super.dispose(); | |||||
| } | |||||
| } | |||||
| class ItemInfinityWidget extends StatelessWidget { | |||||
| final Plot item; | |||||
| const ItemInfinityWidget({Key key, @required this.item}) : super(key: key); | |||||
| @override | |||||
| Widget build(BuildContext context) { | |||||
| return GestureDetector( | |||||
| child: Card( | |||||
| child: Container( | |||||
| padding: EdgeInsets.all(8.0), | |||||
| child: Column( | |||||
| crossAxisAlignment: CrossAxisAlignment.start, | |||||
| children: <Widget>[ | |||||
| Text("Ngày giờ: " + item.activityExecuteDate.toString()), | |||||
| SizedBox( | |||||
| height: 8.0, | |||||
| ), | |||||
| Text("Thời gian: " + item.id.toString() + " giây"), | |||||
| ], | |||||
| ), | |||||
| ), | |||||
| ), | |||||
| onTap: () {}); | |||||
| } | |||||
| } |
| import 'dart:async'; | |||||
| import 'package:bloc/bloc.dart'; | |||||
| import 'package:equatable/equatable.dart'; | |||||
| import 'package:farm_tpf/data/repository/repository.dart'; | |||||
| import 'package:meta/meta.dart'; | |||||
| part 'infinity_scroll_event.dart'; | |||||
| part 'infinity_scroll_state.dart'; | |||||
| class InfinityScrollBloc | |||||
| extends Bloc<InfinityScrollEvent, InfinityScrollState> { | |||||
| final Repository repository; | |||||
| InfinityScrollBloc({@required this.repository}) | |||||
| : super(InfinityScrollInitial()); | |||||
| static int pageSize = 20; | |||||
| @override | |||||
| Stream<InfinityScrollState> mapEventToState( | |||||
| InfinityScrollEvent event, | |||||
| ) async* { | |||||
| if (event is DataFetched && | |||||
| !(state is InfinityScrollSuccess && | |||||
| (state as InfinityScrollSuccess).hasReachedMax)) { | |||||
| try { | |||||
| if (state is InfinityScrollInitial) { | |||||
| final response = | |||||
| await repository.getInfinityList("url", page: 0, size: pageSize); | |||||
| yield InfinityScrollSuccess( | |||||
| items: response.results, | |||||
| page: 0, | |||||
| hasReachedMax: response.results.length < pageSize ? true : false); | |||||
| } | |||||
| if (state is InfinityScrollSuccess) { | |||||
| final currentState = state as InfinityScrollSuccess; | |||||
| int page = currentState.page + 1; | |||||
| final response = await repository.getInfinityList("url", | |||||
| page: page, size: pageSize); | |||||
| yield response.results.isEmpty | |||||
| ? currentState.copyWith(hasReachedMax: true) | |||||
| : InfinityScrollSuccess( | |||||
| items: currentState.items + response.results, | |||||
| page: currentState.page + 1, | |||||
| hasReachedMax: false); | |||||
| } | |||||
| } catch (e) { | |||||
| print(e); | |||||
| yield InfinityScrollFailure(); | |||||
| } | |||||
| } | |||||
| if (event is OnRefresh) { | |||||
| try { | |||||
| final response = | |||||
| await repository.getInfinityList("url", page: 0, size: pageSize); | |||||
| yield InfinityScrollSuccess( | |||||
| items: response.results, | |||||
| page: 0, | |||||
| hasReachedMax: response.results.length < pageSize ? true : false); | |||||
| } catch (_) { | |||||
| yield InfinityScrollFailure(); | |||||
| } | |||||
| } | |||||
| } | |||||
| } |
| part of 'infinity_scroll_bloc.dart'; | |||||
| abstract class InfinityScrollEvent extends Equatable { | |||||
| const InfinityScrollEvent(); | |||||
| @override | |||||
| List<Object> get props => []; | |||||
| } | |||||
| class DataFetched extends InfinityScrollEvent {} | |||||
| class OnRefresh extends InfinityScrollEvent {} |
| part of 'infinity_scroll_bloc.dart'; | |||||
| abstract class InfinityScrollState extends Equatable { | |||||
| const InfinityScrollState(); | |||||
| @override | |||||
| List<Object> get props => []; | |||||
| } | |||||
| class InfinityScrollInitial extends InfinityScrollState {} | |||||
| class InfinityScrollFailure extends InfinityScrollState {} | |||||
| class InfinityScrollSuccess<T> extends InfinityScrollState { | |||||
| final List<T> items; | |||||
| final int page; | |||||
| final bool hasReachedMax; | |||||
| const InfinityScrollSuccess({this.items, this.page, this.hasReachedMax}); | |||||
| InfinityScrollSuccess copyWith( | |||||
| {List<T> items, int page, bool hasReachedMax}) { | |||||
| return InfinityScrollSuccess( | |||||
| items: items ?? this.items, | |||||
| page: page ?? this.page, | |||||
| hasReachedMax: hasReachedMax ?? this.hasReachedMax); | |||||
| } | |||||
| @override | |||||
| List<Object> get props => [items, hasReachedMax]; | |||||
| } |
| const String label_record_video = "Quay video"; | const String label_record_video = "Quay video"; | ||||
| const String label_cancel = "Huỷ"; | const String label_cancel = "Huỷ"; | ||||
| const String label_list_empty = "Dữ liệu rỗng"; | |||||
| const String label_error_get_data = "Lỗi tải dữ liệu"; |
| source: hosted | source: hosted | ||||
| version: "0.12.6" | version: "0.12.6" | ||||
| meta: | meta: | ||||
| dependency: transitive | |||||
| dependency: "direct main" | |||||
| description: | description: | ||||
| name: meta | name: meta | ||||
| url: "https://pub.dartlang.org" | url: "https://pub.dartlang.org" |
| sdk: flutter | sdk: flutter | ||||
| cupertino_icons: ^0.1.3 | cupertino_icons: ^0.1.3 | ||||
| meta: ^1.1.8 | |||||
| shared_preferences: ^0.5.8 | shared_preferences: ^0.5.8 | ||||
| flutter_bloc: ^6.0.1 | flutter_bloc: ^6.0.1 | ||||
| equatable: ^1.2.0 | equatable: ^1.2.0 |