Browse Source

add infinity scroll bloc

master
daivph 5 years ago
parent
commit
e03a01aec3
14 changed files with 416 additions and 1 deletions
  1. +6
    -0
      jsons/Plot.json
  2. +22
    -0
      lib/data/repository/repository.dart
  3. +14
    -0
      lib/models/PagedResult.dart
  4. +24
    -0
      lib/models/Plot.dart
  5. +19
    -0
      lib/presentation/custom_widgets/bottom_loader.dart
  6. +80
    -0
      lib/presentation/custom_widgets/loading_list_page.dart
  7. +7
    -0
      lib/presentation/screens/home/view/home_page.dart
  8. +133
    -0
      lib/presentation/screens/plot/sc_plot.dart
  9. +64
    -0
      lib/utils/bloc/infinity_scroll_bloc.dart
  10. +12
    -0
      lib/utils/bloc/infinity_scroll_event.dart
  11. +31
    -0
      lib/utils/bloc/infinity_scroll_state.dart
  12. +2
    -0
      lib/utils/const_string.dart
  13. +1
    -1
      pubspec.lock
  14. +1
    -0
      pubspec.yaml

+ 6
- 0
jsons/Plot.json View File

@@ -0,0 +1,6 @@
{
"id": 1578,
"times": 20,
"activityExecuteDate": "2020-08-20T11:53:20Z",
"isExceedLimit": false
}

+ 22
- 0
lib/data/repository/repository.dart View File

@@ -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;
}
}

+ 14
- 0
lib/models/PagedResult.dart View File

@@ -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);
}
}

+ 24
- 0
lib/models/Plot.dart View File

@@ -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;
}
}

+ 19
- 0
lib/presentation/custom_widgets/bottom_loader.dart View File

@@ -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,
),
),
),
);
}
}

+ 80
- 0
lib/presentation/custom_widgets/loading_list_page.dart View File

@@ -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,
),
),
),
],
),
),
);
}
}

+ 7
- 0
lib/presentation/screens/home/view/home_page.dart View File

@@ -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()));
}),
],
),
),

+ 133
- 0
lib/presentation/screens/plot/sc_plot.dart View File

@@ -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: () {});
}
}

+ 64
- 0
lib/utils/bloc/infinity_scroll_bloc.dart View File

@@ -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();
}
}
}
}

+ 12
- 0
lib/utils/bloc/infinity_scroll_event.dart View File

@@ -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 {}

+ 31
- 0
lib/utils/bloc/infinity_scroll_state.dart View File

@@ -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];
}

+ 2
- 0
lib/utils/const_string.dart View File

@@ -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";

+ 1
- 1
pubspec.lock View File

@@ -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"

+ 1
- 0
pubspec.yaml View File

@@ -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

Loading…
Cancel
Save