1+ import 'dart:async' ;
2+ import 'dart:io' ;
3+ 14import 'package:cached_network_image/cached_network_image.dart' ;
25import 'package:flutter/cupertino.dart' ;
36import 'package:flutter/material.dart' ;
@@ -7,6 +10,7 @@ import 'package:flutter_news_app/feature/data/model/topheadlinesnews/top_headlin
710import 'package:flutter_news_app/feature/presentation/bloc/topheadlinesnews/bloc.dart' ;
811import 'package:flutter_news_app/injection_container.dart' ;
912import 'package:flutter_screenutil/flutter_screenutil.dart' ;
13+ import 'package:flutter_svg/flutter_svg.dart' ;
1014import 'package:intl/intl.dart' ;
1115
1216class HomePage extends StatefulWidget {
@@ -25,13 +29,25 @@ class _HomePageState extends State<HomePage> {
2529 CategoryNewsModel (image: 'assets/images/img_sport.png' , title: 'Sport' ),
2630 CategoryNewsModel (image: 'assets/images/img_technology.png' , title: 'Technology' ),
2731 ];
32+ final refreshIndicatorState = GlobalKey <RefreshIndicatorState >();
33+ 34+ bool isLoadingCenterIOS = false ;
35+ Completer completerRefresh;
2836 var indexCategorySelected = 0 ;
2937
3038 @override
3139 void initState () {
32- topHeadlinesNewsBloc.add (
33- LoadTopHeadlinesNewsEvent (category: listCategories[indexCategorySelected].title.toLowerCase ()),
34- );
40+ WidgetsBinding .instance.addPostFrameCallback ((_) {
41+ if (Platform .isIOS) {
42+ isLoadingCenterIOS = true ;
43+ topHeadlinesNewsBloc.add (
44+ LoadTopHeadlinesNewsEvent (category: listCategories[indexCategorySelected].title.toLowerCase ()),
45+ );
46+ } else {
47+ completerRefresh = Completer ();
48+ refreshIndicatorState.currentState.show ();
49+ }
50+ });
3551 super .initState ();
3652 }
3753
@@ -46,7 +62,11 @@ class _HomePageState extends State<HomePage> {
4662 create: (context) => topHeadlinesNewsBloc,
4763 child: BlocListener <TopHeadlinesNewsBloc , TopHeadlinesNewsState >(
4864 listener: (context, state) {
49- // TODO: handle listener state yang diperlukan
65+ if (state is FailureTopHeadlinesNewsState ) {
66+ _resetRefreshIndicator ();
67+ } else if (state is LoadedTopHeadlinesNewsState ) {
68+ _resetRefreshIndicator ();
69+ }
5070 },
5171 child: Container (
5272 width: double .infinity,
@@ -84,7 +104,7 @@ class _HomePageState extends State<HomePage> {
84104 _buildWidgetListCategory (),
85105 SizedBox (height: 24. h),
86106 Expanded (
87- child: _buildWidgetContentNews (),
107+ child: Platform .isIOS ? _buildWidgetContentNewsIOS () : _buildWidgetContentNewsAndroid (),
88108 ),
89109 SizedBox (height: paddingBottom),
90110 ],
@@ -95,43 +115,163 @@ class _HomePageState extends State<HomePage> {
95115 );
96116 }
97117
98- Widget _buildWidgetContentNews () {
118+ void _resetRefreshIndicator () {
119+ if (isLoadingCenterIOS) {
120+ isLoadingCenterIOS = false ;
121+ }
122+ completerRefresh? .complete ();
123+ completerRefresh = Completer ();
124+ }
125+ 126+ Widget _buildWidgetContentNewsIOS () {
99127 return BlocBuilder <TopHeadlinesNewsBloc , TopHeadlinesNewsState >(
100128 builder: (context, state) {
101- if (state is LoadingTopHeadlinesNewsState ) {
102- return Center (
103- child: CircularProgressIndicator (),
104- );
105- } else if (state is FailureTopHeadlinesNewsState ) {
129+ var listArticles = < ItemArticleTopHeadlinesNewsResponseModel > [];
130+ if (state is LoadedTopHeadlinesNewsState ) {
131+ listArticles.addAll (state.listArticles);
132+ } else if (isLoadingCenterIOS) {
106133 return Center (
107- child: Text (state.errorMessage ),
134+ child: CupertinoActivityIndicator ( ),
108135 );
109- } else if (state is LoadedTopHeadlinesNewsState ) {
110- var listArticles = state.listArticles;
111- return ListView .builder (
112- padding: EdgeInsets .symmetric (horizontal: 48. w),
113- itemBuilder: (context, index) {
114- var itemArticle = listArticles[index];
115- var dateTimePublishedAt = DateFormat ('yyyy-MM-ddTHH:mm:ssZ' ).parse (itemArticle.publishedAt, true );
116- var strPublishedAt = DateFormat ('MMM dd, yyyy HH:mm' ).format (dateTimePublishedAt);
117- if (index == 0 ) {
118- return _buildWidgetItemLatestNews (itemArticle, strPublishedAt);
136+ }
137+ return Stack (
138+ children: < Widget > [
139+ Padding (
140+ padding: EdgeInsets .symmetric (horizontal: 48. w),
141+ child: CustomScrollView (
142+ physics: BouncingScrollPhysics (
143+ parent: AlwaysScrollableScrollPhysics (),
144+ ),
145+ slivers: < Widget > [
146+ CupertinoSliverRefreshControl (
147+ onRefresh: () {
148+ topHeadlinesNewsBloc.add (
149+ LoadTopHeadlinesNewsEvent (category: listCategories[indexCategorySelected].title.toLowerCase ()),
150+ );
151+ return completerRefresh.future;
152+ },
153+ ),
154+ SliverList (
155+ delegate: SliverChildBuilderDelegate (
156+ (context, index) {
157+ var itemArticle = listArticles[index];
158+ var dateTimePublishedAt = DateFormat ('yyyy-MM-ddTHH:mm:ssZ' ).parse (itemArticle.publishedAt, true );
159+ var strPublishedAt = DateFormat ('MMM dd, yyyy HH:mm' ).format (dateTimePublishedAt);
160+ if (index == 0 ) {
161+ return _buildWidgetItemLatestNews (itemArticle, strPublishedAt);
162+ } else {
163+ return _buildWidgetItemNews (index, itemArticle, strPublishedAt);
164+ }
165+ },
166+ childCount: listArticles.length,
167+ ),
168+ ),
169+ ],
170+ ),
171+ ),
172+ listArticles.isEmpty && state is ! LoadingTopHeadlinesNewsState
173+ ? _buildWidgetFailureLoadData ()
174+ : Container (),
175+ ],
176+ );
177+ },
178+ );
179+ }
180+ 181+ Widget _buildWidgetContentNewsAndroid () {
182+ return BlocBuilder <TopHeadlinesNewsBloc , TopHeadlinesNewsState >(
183+ builder: (context, state) {
184+ var listArticles = < ItemArticleTopHeadlinesNewsResponseModel > [];
185+ if (state is LoadedTopHeadlinesNewsState ) {
186+ listArticles.addAll (state.listArticles);
187+ }
188+ return Stack (
189+ children: < Widget > [
190+ RefreshIndicator (
191+ key: refreshIndicatorState,
192+ onRefresh: () {
193+ topHeadlinesNewsBloc.add (
194+ LoadTopHeadlinesNewsEvent (category: listCategories[indexCategorySelected].title.toLowerCase ()),
195+ );
196+ return completerRefresh.future;
197+ },
198+ child: ListView .builder (
199+ padding: EdgeInsets .symmetric (horizontal: 48. w),
200+ itemBuilder: (context, index) {
201+ var itemArticle = listArticles[index];
202+ var dateTimePublishedAt = DateFormat ('yyyy-MM-ddTHH:mm:ssZ' ).parse (itemArticle.publishedAt, true );
203+ var strPublishedAt = DateFormat ('MMM dd, yyyy HH:mm' ).format (dateTimePublishedAt);
204+ if (index == 0 ) {
205+ return _buildWidgetItemLatestNews (itemArticle, strPublishedAt);
206+ } else {
207+ return _buildWidgetItemNews (index, itemArticle, strPublishedAt);
208+ }
209+ },
210+ itemCount: listArticles.length,
211+ ),
212+ ),
213+ listArticles.isEmpty && state is ! LoadingTopHeadlinesNewsState
214+ ? _buildWidgetFailureLoadData ()
215+ : Container (),
216+ ],
217+ );
218+ },
219+ );
220+ }
221+ 222+ Widget _buildWidgetFailureLoadData () {
223+ return Center (
224+ child: Column (
225+ mainAxisAlignment: MainAxisAlignment .center,
226+ children: < Widget > [
227+ SvgPicture .asset (
228+ 'assets/svg/undraw_newspaper.svg' ,
229+ width: ScreenUtil .screenWidthDp / 3 ,
230+ height: ScreenUtil .screenWidthDp / 3 ,
231+ ),
232+ SizedBox (height: 24. h),
233+ Text (
234+ 'You seem to be offline' ,
235+ style: TextStyle (
236+ fontSize: 48. sp,
237+ fontWeight: FontWeight .w500,
238+ ),
239+ ),
240+ Text (
241+ 'Check your wi-fi connection or cellular data \n and try again.' ,
242+ style: TextStyle (
243+ fontSize: 36. sp,
244+ ),
245+ textAlign: TextAlign .center,
246+ ),
247+ RaisedButton (
248+ onPressed: () {
249+ if (Platform .isIOS) {
250+ isLoadingCenterIOS = true ;
251+ topHeadlinesNewsBloc.add (
252+ LoadTopHeadlinesNewsEvent (category: listCategories[indexCategorySelected].title.toLowerCase ()),
253+ );
119254 } else {
120- return _buildWidgetItemNews (index, itemArticle, strPublishedAt );
255+ refreshIndicatorState.currentState. show ( );
121256 }
122257 },
123- itemCount: listArticles.length,
124- );
125- } else {
126- return Container ();
127- }
128- },
258+ child: Text (
259+ 'Try Again' .toUpperCase (),
260+ style: TextStyle (
261+ color: Colors .white,
262+ fontSize: 36. sp,
263+ ),
264+ ),
265+ color: Colors .grey[700 ],
266+ ),
267+ ],
268+ ),
129269 );
130270 }
131271
132272 Widget _buildWidgetItemNews (
133273 int index,
134- ItemArticleTopHeadlinesNewsResponseModel itemArticleTopHeadlinesNewsResponseModel ,
274+ ItemArticleTopHeadlinesNewsResponseModel itemArticle ,
135275 String strPublishedAt,
136276 ) {
137277 return Padding (
@@ -147,7 +287,7 @@ class _HomePageState extends State<HomePage> {
147287 ClipRRect (
148288 borderRadius: BorderRadius .circular (8.0 ),
149289 child: CachedNetworkImage (
150- imageUrl: itemArticleTopHeadlinesNewsResponseModel .urlToImage,
290+ imageUrl: itemArticle .urlToImage,
151291 fit: BoxFit .cover,
152292 width: 200. w,
153293 height: 200. w,
@@ -182,18 +322,18 @@ class _HomePageState extends State<HomePage> {
182322 children: < Widget > [
183323 Expanded (
184324 child: Text (
185- itemArticleTopHeadlinesNewsResponseModel .title,
325+ itemArticle .title,
186326 maxLines: 2 ,
187327 overflow: TextOverflow .ellipsis,
188328 style: TextStyle (
189329 fontSize: 36. sp,
190330 ),
191331 ),
192332 ),
193- itemArticleTopHeadlinesNewsResponseModel .author == null
333+ itemArticle .author == null
194334 ? Container ()
195335 : Text (
196- itemArticleTopHeadlinesNewsResponseModel .author,
336+ itemArticle .author,
197337 style: TextStyle (
198338 color: Colors .grey,
199339 fontSize: 28. sp,
@@ -217,7 +357,7 @@ class _HomePageState extends State<HomePage> {
217357 ),
218358 ),
219359 Text (
220- itemArticleTopHeadlinesNewsResponseModel .source.name,
360+ itemArticle .source.name,
221361 style: TextStyle (
222362 color: Colors .grey,
223363 fontSize: 24. sp,
0 commit comments