Skip to content

Commit 1eff657

Browse files
authored
Merge pull request #74 from nimblehq/release/0.5.0
[Release] 0.5.0
2 parents 7e01764 + e1678b1 commit 1eff657

18 files changed

+466
-47
lines changed

assets/images/dummy_background.png

1.7 MB
Loading

assets/svg/close_button.svg

Lines changed: 14 additions & 0 deletions
Loading

lib/l10n/app_en.arb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,7 @@
1010
"genericError": "Something went wrong.\nPlease try again!",
1111
"loginFailAlertTitle": "Unable to log in",
1212
"today": "Today",
13-
"errorText": "Error"
13+
"errorText": "Error",
14+
"startSurveyText": "Start Survey",
15+
"submitText": "Submit"
1416
}

lib/main.dart

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@ import 'package:flutter_config/flutter_config.dart';
33
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
44
import 'package:flutter_riverpod/flutter_riverpod.dart';
55
import 'package:go_router/go_router.dart';
6+
import 'package:survey_flutter/model/survey_model.dart';
7+
import 'package:survey_flutter/screens/app_route.dart';
68
import 'package:survey_flutter/screens/home/home_screen.dart';
79
import 'package:survey_flutter/screens/login/login_screen.dart';
810
import 'package:survey_flutter/screens/splash/splash_screen.dart';
11+
import 'package:survey_flutter/screens/survey/survey_detail_screen.dart';
12+
import 'package:survey_flutter/screens/survey/survey_screen.dart';
913
import 'package:survey_flutter/storage/survey_storage.dart';
1014
import 'package:survey_flutter/theme/app_theme.dart';
1115

@@ -20,27 +24,35 @@ void main() async {
2024
);
2125
}
2226

23-
const routePathSplashScreen = '/';
24-
2527
class App extends StatelessWidget {
2628
App({Key? key}) : super(key: key);
2729

2830
final GoRouter _router = GoRouter(
2931
routes: <GoRoute>[
3032
GoRoute(
31-
path: routePathSplashScreen,
33+
path: AppRoute.splash.path,
3234
builder: (_, __) => const SplashScreen(),
3335
),
3436
GoRoute(
35-
path: routePathLoginScreen,
37+
path: AppRoute.login.path,
3638
pageBuilder: (_, __) => const NoTransitionPage<void>(
3739
child: LoginScreen(),
3840
),
3941
),
4042
GoRoute(
41-
path: routePathHomeScreen,
43+
path: AppRoute.home.path,
4244
builder: (_, __) => const HomeScreen(),
4345
),
46+
GoRoute(
47+
path: AppRoute.survey.path,
48+
builder: (_, state) => SurveyDetailScreen(
49+
survey: state.extra as SurveyModel,
50+
),
51+
),
52+
GoRoute(
53+
path: AppRoute.questions.path,
54+
builder: (_, __) => SurveyScreen(),
55+
),
4456
],
4557
);
4658

lib/screens/app_route.dart

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
enum AppRoute {
2+
splash,
3+
login,
4+
home,
5+
survey,
6+
questions,
7+
}
8+
9+
extension AppRoutePath on AppRoute {
10+
String get path {
11+
switch (this) {
12+
case AppRoute.splash:
13+
return '/';
14+
case AppRoute.login:
15+
return '/login';
16+
case AppRoute.home:
17+
return '/home';
18+
case AppRoute.survey:
19+
return '/survey';
20+
case AppRoute.questions:
21+
return '/questions';
22+
}
23+
}
24+
}

lib/screens/home/home_pages_widget.dart

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +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();
13+
final VoidCallback onNextButtonPressed;
14+
final bool isRefreshing;
15+
final VoidCallback onLoadMore;
1416

15-
HomePagesWidget({
17+
const HomePagesWidget({
1618
Key? key,
1719
required this.surveys,
1820
required this.currentPage,
21+
required this.onNextButtonPressed,
22+
required this.onLoadMore,
23+
required this.isRefreshing,
1924
}) : super(key: key);
2025

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+
2147
@override
2248
Widget build(BuildContext context) {
49+
if (widget.isRefreshing && widget.surveys.isNotEmpty) {
50+
_pageController.jumpToPage(0);
51+
}
52+
2353
return PageView.builder(
24-
itemCount: surveys.length,
54+
itemCount: widget.surveys.length,
2555
controller: _pageController,
2656
itemBuilder: (_, int index) {
2757
return Container(
@@ -32,7 +62,7 @@ class HomePagesWidget extends StatelessWidget {
3262
opacity: _imageOpacity,
3363
child: FadeInImage.assetNetwork(
3464
placeholder: Assets.images.placeholder.path,
35-
image: surveys[index].coverImageUrl,
65+
image: widget.surveys[index].coverImageUrl,
3666
fit: BoxFit.cover,
3767
width: double.infinity,
3868
height: double.infinity,
@@ -49,13 +79,8 @@ class HomePagesWidget extends StatelessWidget {
4979
right: 0,
5080
),
5181
child: HomeFooterWidget(
52-
survey: surveys[index],
53-
onNextButtonPressed: () {
54-
if (currentPage.value < surveys.length - 1) {
55-
currentPage.value = index + 1;
56-
_pageController.jumpToPage(currentPage.value);
57-
}
58-
},
82+
survey: widget.surveys[index],
83+
onNextButtonPressed: widget.onNextButtonPressed,
5984
),
6085
),
6186
),
@@ -65,9 +90,7 @@ class HomePagesWidget extends StatelessWidget {
6590
),
6691
);
6792
},
68-
onPageChanged: (int index) {
69-
currentPage.value = index;
70-
},
93+
onPageChanged: _handlePageChanged,
7194
);
7295
}
7396
}

lib/screens/home/home_screen.dart

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter_riverpod/flutter_riverpod.dart';
3+
import 'package:go_router/go_router.dart';
34
import 'package:survey_flutter/model/survey_model.dart';
5+
import 'package:survey_flutter/screens/app_route.dart';
46
import 'package:survey_flutter/screens/home/home_header_widget.dart';
5-
import 'package:survey_flutter/screens/home/home_pages_widget.dart';
67
import 'package:survey_flutter/screens/home/home_page_indicator_widget.dart';
8+
import 'package:survey_flutter/screens/home/home_pages_widget.dart';
79
import 'package:survey_flutter/screens/home/home_shimmer_loading.dart';
810
import 'package:survey_flutter/screens/home/home_view_model.dart';
911
import 'package:survey_flutter/utils/build_context_ext.dart';
1012
import 'package:survey_flutter/widgets/alert_dialog.dart';
1113

12-
const routePathHomeScreen = '/home';
13-
1414
class HomeScreen extends ConsumerStatefulWidget {
1515
const HomeScreen({Key? key}) : super(key: key);
1616

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

@@ -85,6 +95,15 @@ class _HomeScreenState extends ConsumerState<HomeScreen> {
8595
HomePagesWidget(
8696
surveys: surveys,
8797
currentPage: _currentPage,
98+
onNextButtonPressed: () {
99+
final survey = surveys[_currentPage.value];
100+
context.push(
101+
AppRoute.survey.path,
102+
extra: survey,
103+
);
104+
},
105+
onLoadMore: _loadSurveys,
106+
isRefreshing: isRefreshing,
88107
),
89108
const HomeHeaderWidget(),
90109
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/screens/login/login_screen.dart

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,14 @@ import 'package:flutter/material.dart';
44
import 'package:flutter_riverpod/flutter_riverpod.dart';
55
import 'package:go_router/go_router.dart';
66
import 'package:survey_flutter/gen/assets.gen.dart';
7-
import 'package:survey_flutter/screens/home/home_screen.dart';
7+
import 'package:survey_flutter/screens/app_route.dart';
88
import 'package:survey_flutter/screens/login/login_form.dart';
99
import 'package:survey_flutter/screens/login/login_view_model.dart';
1010
import 'package:survey_flutter/theme/app_constants.dart';
1111
import 'package:survey_flutter/uimodels/app_error.dart';
1212
import 'package:survey_flutter/utils/build_context_ext.dart';
1313
import 'package:survey_flutter/widgets/alert_dialog.dart';
1414

15-
const routePathLoginScreen = '/login';
16-
1715
class LoginScreen extends ConsumerStatefulWidget {
1816
const LoginScreen({Key? key}) : super(key: key);
1917

@@ -117,7 +115,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
117115
_setUpListener(BuildContext context) {
118116
ref.listen<AsyncValue<void>>(loginViewModelProvider, (_, next) {
119117
next.maybeWhen(
120-
data: (_) => context.go(routePathHomeScreen),
118+
data: (_) => context.go(AppRoute.home.path),
121119
error: (error, _) {
122120
showAlertDialog(
123121
context: context,

0 commit comments

Comments
 (0)