| @@ -0,0 +1,6 @@ | |||
| { | |||
| "id": 1578, | |||
| "times": 20, | |||
| "activityExecuteDate": "2020-08-20T11:53:20Z", | |||
| "isExceedLimit": false | |||
| } | |||
| @@ -1,7 +1,10 @@ | |||
| import 'package:farm_tpf/data/api/dio_provider.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_request.dart'; | |||
| import 'package:farm_tpf/utils/const_common.dart'; | |||
| class Repository { | |||
| final dio = DioProvider.instance(); | |||
| @@ -10,4 +13,23 @@ class Repository { | |||
| final client = RestClient(dio); | |||
| 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; | |||
| } | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| 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); | |||
| } | |||
| } | |||
| @@ -0,0 +1,24 @@ | |||
| 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; | |||
| } | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| 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, | |||
| ), | |||
| ), | |||
| ), | |||
| ); | |||
| } | |||
| } | |||
| @@ -0,0 +1,80 @@ | |||
| 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, | |||
| ), | |||
| ), | |||
| ), | |||
| ], | |||
| ), | |||
| ), | |||
| ); | |||
| } | |||
| } | |||
| @@ -1,5 +1,6 @@ | |||
| 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/plot/sc_plot.dart'; | |||
| import 'package:farm_tpf/presentation/screens/plot_detail/sc_plot_detail.dart'; | |||
| import 'package:flutter/material.dart'; | |||
| @@ -35,6 +36,12 @@ class HomePage extends StatelessWidget { | |||
| Navigator.of(context).push( | |||
| MaterialPageRoute(builder: (_) => PlotDetailScreen())); | |||
| }), | |||
| MaterialButton( | |||
| child: Text("Danh sách lô"), | |||
| onPressed: () { | |||
| Navigator.of(context).push( | |||
| MaterialPageRoute(builder: (_) => PlotListScreen())); | |||
| }), | |||
| ], | |||
| ), | |||
| ), | |||
| @@ -0,0 +1,133 @@ | |||
| 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: () {}); | |||
| } | |||
| } | |||
| @@ -0,0 +1,64 @@ | |||
| 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(); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,12 @@ | |||
| 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 {} | |||
| @@ -0,0 +1,31 @@ | |||
| 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]; | |||
| } | |||
| @@ -21,3 +21,5 @@ const String label_select_video_from_library = "Chọn video từ thư viện"; | |||
| const String label_record_video = "Quay video"; | |||
| 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"; | |||
| @@ -374,7 +374,7 @@ packages: | |||
| source: hosted | |||
| version: "0.12.6" | |||
| meta: | |||
| dependency: transitive | |||
| dependency: "direct main" | |||
| description: | |||
| name: meta | |||
| url: "https://pub.dartlang.org" | |||
| @@ -15,6 +15,7 @@ dependencies: | |||
| sdk: flutter | |||
| cupertino_icons: ^0.1.3 | |||
| meta: ^1.1.8 | |||
| shared_preferences: ^0.5.8 | |||
| flutter_bloc: ^6.0.1 | |||
| equatable: ^1.2.0 | |||