Skip to content

Commit fa5e5e6

Browse files
authored
Merge pull request #71 from nimblehq/feature/17-scroll-to-load-more-surveys
[#17] [Integrate] As a logged-in user, I can scroll to load more surveys
2 parents 75f57a8 + 0f3a00b commit fa5e5e6

File tree

6 files changed

+85
-24
lines changed

6 files changed

+85
-24
lines changed

lib/screens/home/home_pages_widget.dart

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,51 @@ import '../../gen/assets.gen.dart';
77

88
const _imageOpacity = 0.6;
99

10-
class HomePagesWidget extends StatelessWidget {
10+
class HomePagesWidget extends StatefulWidget {
1111
final List<SurveyModel> surveys;
1212
final ValueNotifier<int> currentPage;
13-
final PageController _pageController = PageController();
1413
final VoidCallback onNextButtonPressed;
14+
final bool isRefreshing;
15+
final VoidCallback onLoadMore;
1516

16-
HomePagesWidget({
17+
const HomePagesWidget({
1718
Key? key,
1819
required this.surveys,
1920
required this.currentPage,
2021
required this.onNextButtonPressed,
22+
required this.onLoadMore,
23+
required this.isRefreshing,
2124
}) : super(key: key);
2225

26+
@override
27+
State<HomePagesWidget> createState() => _HomePagesWidgetState();
28+
}
29+
30+
class _HomePagesWidgetState extends State<HomePagesWidget> {
31+
final PageController _pageController = PageController(initialPage: 0);
32+
33+
@override
34+
void dispose() {
35+
_pageController.dispose();
36+
super.dispose();
37+
}
38+
39+
void _handlePageChanged(int index) {
40+
widget.currentPage.value = index;
41+
42+
if (index == widget.surveys.length - 1) {
43+
widget.onLoadMore();
44+
}
45+
}
46+
2347
@override
2448
Widget build(BuildContext context) {
49+
if (widget.isRefreshing && widget.surveys.isNotEmpty) {
50+
_pageController.jumpToPage(0);
51+
}
52+
2553
return PageView.builder(
26-
itemCount: surveys.length,
54+
itemCount: widget.surveys.length,
2755
controller: _pageController,
2856
itemBuilder: (_, int index) {
2957
return Container(
@@ -34,7 +62,7 @@ class HomePagesWidget extends StatelessWidget {
3462
opacity: _imageOpacity,
3563
child: FadeInImage.assetNetwork(
3664
placeholder: Assets.images.placeholder.path,
37-
image: surveys[index].coverImageUrl,
65+
image: widget.surveys[index].coverImageUrl,
3866
fit: BoxFit.cover,
3967
width: double.infinity,
4068
height: double.infinity,
@@ -51,8 +79,8 @@ class HomePagesWidget extends StatelessWidget {
5179
right: 0,
5280
),
5381
child: HomeFooterWidget(
54-
survey: surveys[index],
55-
onNextButtonPressed: onNextButtonPressed,
82+
survey: widget.surveys[index],
83+
onNextButtonPressed: widget.onNextButtonPressed,
5684
),
5785
),
5886
),
@@ -62,9 +90,7 @@ class HomePagesWidget extends StatelessWidget {
6290
),
6391
);
6492
},
65-
onPageChanged: (int index) {
66-
currentPage.value = index;
67-
},
93+
onPageChanged: _handlePageChanged,
6894
);
6995
}
7096
}

lib/screens/home/home_screen.dart

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,14 @@ class _HomeScreenState extends ConsumerState<HomeScreen> {
3333
_initData();
3434
}
3535

36-
Future<void> _initData() async {
37-
ref.read(homeViewModelProvider.notifier).loadSurveys(isRefreshing: false);
36+
void _initData() {
37+
_loadSurveys();
38+
}
39+
40+
Future<void> _loadSurveys({bool isRefreshing = false}) async {
41+
ref
42+
.read(homeViewModelProvider.notifier)
43+
.loadSurveys(isRefreshing: isRefreshing);
3844
}
3945

4046
@override
@@ -43,11 +49,15 @@ class _HomeScreenState extends ConsumerState<HomeScreen> {
4349
loading: () => _buildHomeScreen(isLoading: true),
4450
error: () => _buildHomeScreen(),
4551
loadCachedSurveysSuccess: () => _buildHomeScreen(),
46-
loadSurveysSuccess: () => _buildHomeScreen(),
52+
loadSurveysSuccess: (isRefreshing) =>
53+
_buildHomeScreen(isRefreshing: isRefreshing),
4754
);
4855
}
4956

50-
Widget _buildHomeScreen({bool isLoading = false}) {
57+
Widget _buildHomeScreen({
58+
bool isLoading = false,
59+
bool isRefreshing = false,
60+
}) {
5161
final surveys = ref.watch(_surveysStreamProvider).value ?? [];
5262
final errorMessage = ref.watch(_errorStreamProvider).value ?? "";
5363

@@ -92,6 +102,8 @@ class _HomeScreenState extends ConsumerState<HomeScreen> {
92102
extra: survey,
93103
);
94104
},
105+
onLoadMore: _loadSurveys,
106+
isRefreshing: isRefreshing,
95107
),
96108
const HomeHeaderWidget(),
97109
Align(

lib/screens/home/home_state.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ class HomeState with _$HomeState {
99
const factory HomeState.loadCachedSurveysSuccess() =
1010
_LoadCachedSurveysSuccess;
1111

12-
const factory HomeState.loadSurveysSuccess() = _LoadSurveysSuccess;
12+
const factory HomeState.loadSurveysSuccess(bool isRefreshing) =
13+
_LoadSurveysSuccess;
1314

1415
const factory HomeState.error() = _error;
1516
}

lib/screens/home/home_view_model.dart

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import 'package:survey_flutter/usecases/base/base_use_case.dart';
77
import 'package:survey_flutter/usecases/get_cached_surveys_use_case.dart';
88
import 'package:survey_flutter/usecases/get_surveys_use_case.dart';
99

10-
// TODO: Integrate load more
11-
const _pageNumber = 1;
10+
int _pageNumber = 1;
1211
const _pageSize = 10;
12+
List<SurveyModel> _loadedSurveys = [];
1313

1414
final homeViewModelProvider =
1515
StateNotifierProvider.autoDispose<HomeViewModel, HomeState>((ref) {
@@ -39,25 +39,43 @@ class HomeViewModel extends StateNotifier<HomeState> {
3939
final _error = StreamController<String>();
4040
Stream<String> get error => _error.stream;
4141

42+
void _handleError(Failed result) {
43+
if (result.isNotFoundError()) {
44+
_surveys.add(_loadedSurveys);
45+
state = const HomeState.loadSurveysSuccess(false);
46+
} else {
47+
_error.add(result.getErrorMessage());
48+
state = const HomeState.error();
49+
}
50+
}
51+
4252
Future<void> loadSurveys({required bool isRefreshing}) async {
4353
if (!isRefreshing) {
4454
_loadSurveysFromCache();
4555
}
46-
_loadSurveysFromRemote();
56+
_loadSurveysFromRemote(isRefreshing: isRefreshing);
4757
}
4858

49-
void _loadSurveysFromRemote() async {
59+
void _loadSurveysFromRemote({required bool isRefreshing}) async {
60+
if (isRefreshing) {
61+
_loadedSurveys.clear();
62+
_pageNumber = 1;
63+
}
5064
final result = await _getSurveysUseCase.call(SurveysParams(
5165
pageNumber: _pageNumber,
5266
pageSize: _pageSize,
5367
));
68+
5469
if (result is Success<List<SurveyModel>>) {
5570
final newSurveys = result.value;
56-
_surveys.add(newSurveys);
57-
state = const HomeState.loadSurveysSuccess();
71+
if (newSurveys.isNotEmpty) {
72+
_loadedSurveys.addAll(newSurveys);
73+
_surveys.add(_loadedSurveys);
74+
state = HomeState.loadSurveysSuccess(isRefreshing);
75+
_pageNumber++;
76+
}
5877
} else if (result is Failed) {
59-
_error.add((result as Failed).getErrorMessage());
60-
state = const HomeState.error();
78+
_handleError(result as Failed);
6179
}
6280
}
6381

lib/usecases/base/use_case_result.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,8 @@ class Failed<T> extends Result<T> {
2323

2424
String getErrorMessage() =>
2525
NetworkExceptions.getErrorMessage(exception.actualException);
26+
27+
bool isNotFoundError() =>
28+
this.exception.actualException ==
29+
const NetworkExceptions.notFound("Not found");
2630
}

test/screens/home/home_view_model_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ void main() {
5656
stateStream,
5757
emitsInOrder([
5858
const HomeState.loadCachedSurveysSuccess(),
59-
const HomeState.loadSurveysSuccess()
59+
const HomeState.loadSurveysSuccess(false),
6060
]));
6161
},
6262
);

0 commit comments

Comments
 (0)