Skip to content

Commit

Permalink
Merge pull request #71 from nimblehq/feature/17-scroll-to-load-more-s…
Browse files Browse the repository at this point in the history
…urveys

[#17] [Integrate] As a logged-in user, I can scroll to load more surveys
  • Loading branch information
nkhanh44 authored Aug 31, 2023
2 parents 75f57a8 + 0f3a00b commit fa5e5e6
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 24 deletions.
46 changes: 36 additions & 10 deletions lib/screens/home/home_pages_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,51 @@ import '../../gen/assets.gen.dart';

const _imageOpacity = 0.6;

class HomePagesWidget extends StatelessWidget {
class HomePagesWidget extends StatefulWidget {
final List<SurveyModel> surveys;
final ValueNotifier<int> currentPage;
final PageController _pageController = PageController();
final VoidCallback onNextButtonPressed;
final bool isRefreshing;
final VoidCallback onLoadMore;

HomePagesWidget({
const HomePagesWidget({
Key? key,
required this.surveys,
required this.currentPage,
required this.onNextButtonPressed,
required this.onLoadMore,
required this.isRefreshing,
}) : super(key: key);

@override
State<HomePagesWidget> createState() => _HomePagesWidgetState();
}

class _HomePagesWidgetState extends State<HomePagesWidget> {
final PageController _pageController = PageController(initialPage: 0);

@override
void dispose() {
_pageController.dispose();
super.dispose();
}

void _handlePageChanged(int index) {
widget.currentPage.value = index;

if (index == widget.surveys.length - 1) {
widget.onLoadMore();
}
}

@override
Widget build(BuildContext context) {
if (widget.isRefreshing && widget.surveys.isNotEmpty) {
_pageController.jumpToPage(0);
}

return PageView.builder(
itemCount: surveys.length,
itemCount: widget.surveys.length,
controller: _pageController,
itemBuilder: (_, int index) {
return Container(
Expand All @@ -34,7 +62,7 @@ class HomePagesWidget extends StatelessWidget {
opacity: _imageOpacity,
child: FadeInImage.assetNetwork(
placeholder: Assets.images.placeholder.path,
image: surveys[index].coverImageUrl,
image: widget.surveys[index].coverImageUrl,
fit: BoxFit.cover,
width: double.infinity,
height: double.infinity,
Expand All @@ -51,8 +79,8 @@ class HomePagesWidget extends StatelessWidget {
right: 0,
),
child: HomeFooterWidget(
survey: surveys[index],
onNextButtonPressed: onNextButtonPressed,
survey: widget.surveys[index],
onNextButtonPressed: widget.onNextButtonPressed,
),
),
),
Expand All @@ -62,9 +90,7 @@ class HomePagesWidget extends StatelessWidget {
),
);
},
onPageChanged: (int index) {
currentPage.value = index;
},
onPageChanged: _handlePageChanged,
);
}
}
20 changes: 16 additions & 4 deletions lib/screens/home/home_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,14 @@ class _HomeScreenState extends ConsumerState<HomeScreen> {
_initData();
}

Future<void> _initData() async {
ref.read(homeViewModelProvider.notifier).loadSurveys(isRefreshing: false);
void _initData() {
_loadSurveys();
}

Future<void> _loadSurveys({bool isRefreshing = false}) async {
ref
.read(homeViewModelProvider.notifier)
.loadSurveys(isRefreshing: isRefreshing);
}

@override
Expand All @@ -43,11 +49,15 @@ class _HomeScreenState extends ConsumerState<HomeScreen> {
loading: () => _buildHomeScreen(isLoading: true),
error: () => _buildHomeScreen(),
loadCachedSurveysSuccess: () => _buildHomeScreen(),
loadSurveysSuccess: () => _buildHomeScreen(),
loadSurveysSuccess: (isRefreshing) =>
_buildHomeScreen(isRefreshing: isRefreshing),
);
}

Widget _buildHomeScreen({bool isLoading = false}) {
Widget _buildHomeScreen({
bool isLoading = false,
bool isRefreshing = false,
}) {
final surveys = ref.watch(_surveysStreamProvider).value ?? [];
final errorMessage = ref.watch(_errorStreamProvider).value ?? "";

Expand Down Expand Up @@ -92,6 +102,8 @@ class _HomeScreenState extends ConsumerState<HomeScreen> {
extra: survey,
);
},
onLoadMore: _loadSurveys,
isRefreshing: isRefreshing,
),
const HomeHeaderWidget(),
Align(
Expand Down
3 changes: 2 additions & 1 deletion lib/screens/home/home_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ class HomeState with _$HomeState {
const factory HomeState.loadCachedSurveysSuccess() =
_LoadCachedSurveysSuccess;

const factory HomeState.loadSurveysSuccess() = _LoadSurveysSuccess;
const factory HomeState.loadSurveysSuccess(bool isRefreshing) =
_LoadSurveysSuccess;

const factory HomeState.error() = _error;
}
34 changes: 26 additions & 8 deletions lib/screens/home/home_view_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import 'package:survey_flutter/usecases/base/base_use_case.dart';
import 'package:survey_flutter/usecases/get_cached_surveys_use_case.dart';
import 'package:survey_flutter/usecases/get_surveys_use_case.dart';

// TODO: Integrate load more
const _pageNumber = 1;
int _pageNumber = 1;
const _pageSize = 10;
List<SurveyModel> _loadedSurveys = [];

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

void _handleError(Failed result) {
if (result.isNotFoundError()) {
_surveys.add(_loadedSurveys);
state = const HomeState.loadSurveysSuccess(false);
} else {
_error.add(result.getErrorMessage());
state = const HomeState.error();
}
}

Future<void> loadSurveys({required bool isRefreshing}) async {
if (!isRefreshing) {
_loadSurveysFromCache();
}
_loadSurveysFromRemote();
_loadSurveysFromRemote(isRefreshing: isRefreshing);
}

void _loadSurveysFromRemote() async {
void _loadSurveysFromRemote({required bool isRefreshing}) async {
if (isRefreshing) {
_loadedSurveys.clear();
_pageNumber = 1;
}
final result = await _getSurveysUseCase.call(SurveysParams(
pageNumber: _pageNumber,
pageSize: _pageSize,
));

if (result is Success<List<SurveyModel>>) {
final newSurveys = result.value;
_surveys.add(newSurveys);
state = const HomeState.loadSurveysSuccess();
if (newSurveys.isNotEmpty) {
_loadedSurveys.addAll(newSurveys);
_surveys.add(_loadedSurveys);
state = HomeState.loadSurveysSuccess(isRefreshing);
_pageNumber++;
}
} else if (result is Failed) {
_error.add((result as Failed).getErrorMessage());
state = const HomeState.error();
_handleError(result as Failed);
}
}

Expand Down
4 changes: 4 additions & 0 deletions lib/usecases/base/use_case_result.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,8 @@ class Failed<T> extends Result<T> {

String getErrorMessage() =>
NetworkExceptions.getErrorMessage(exception.actualException);

bool isNotFoundError() =>
this.exception.actualException ==
const NetworkExceptions.notFound("Not found");
}
2 changes: 1 addition & 1 deletion test/screens/home/home_view_model_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ void main() {
stateStream,
emitsInOrder([
const HomeState.loadCachedSurveysSuccess(),
const HomeState.loadSurveysSuccess()
const HomeState.loadSurveysSuccess(false),
]));
},
);
Expand Down

0 comments on commit fa5e5e6

Please sign in to comment.