@@ -12,7 +12,6 @@ import '../routes/settings_page.dart';
12
12
import '../utils/bus.dart' ;
13
13
import '../utils/bus_api.dart' ;
14
14
import '../utils/bus_stop.dart' ;
15
- import '../utils/bus_utils.dart' ;
16
15
import '../utils/database_utils.dart' ;
17
16
import '../utils/location_utils.dart' ;
18
17
import '../utils/reorder_status_notification.dart' ;
@@ -40,6 +39,7 @@ class _HomePageState extends BottomSheetPageState<HomePage> {
40
39
Widget _busStopOverviewList;
41
40
int _bottomNavIndex;
42
41
Map <String , dynamic > _nearestBusStops;
42
+ List <Bus > _followedBuses;
43
43
ScrollController _scrollController;
44
44
bool canScroll;
45
45
AnimationController _fabScaleAnimationController;
@@ -188,96 +188,146 @@ class _HomePageState extends BottomSheetPageState<HomePage> {
188
188
}
189
189
190
190
Widget _buildBody () {
191
- return RefreshIndicator (
192
- onRefresh: refreshLocation,
193
- child: Stack (
194
- children: < Widget > [
195
- CustomScrollView (
196
- controller: _scrollController,
197
- scrollDirection: Axis .vertical,
198
- physics: canScroll ? const AlwaysScrollableScrollPhysics () : const NeverScrollableScrollPhysics (),
199
- slivers: < Widget > [
200
- SliverToBoxAdapter (
201
- child: Container (
202
- alignment: Alignment .topCenter,
203
- height: 64.0 + MediaQuery .of (context).padding.top,
204
- ),
205
- ),
206
- SliverToBoxAdapter (
207
- child: HomePageContentSwitcher (
208
- scrollController: _scrollController,
209
- child: _buildContent (),
210
- ),
191
+ return Stack (
192
+ children: < Widget > [
193
+ CustomScrollView (
194
+ controller: _scrollController,
195
+ scrollDirection: Axis .vertical,
196
+ physics: canScroll ? const AlwaysScrollableScrollPhysics () : const NeverScrollableScrollPhysics (),
197
+ slivers: < Widget > [
198
+ SliverToBoxAdapter (
199
+ child: Container (
200
+ alignment: Alignment .topCenter,
201
+ height: 64.0 + MediaQuery .of (context).padding.top,
211
202
),
212
- ],
213
- ),
214
- // Hide the overscroll contents from the status bar
215
- Container (
216
- height: kToolbarHeight / 2 + MediaQuery .of (context).padding.top,
217
- color: Theme .of (context).scaffoldBackgroundColor,
218
- ),
219
- Positioned (
220
- top: 0 ,
221
- left: 0 ,
222
- right: 0 ,
223
- child: AppBar (
224
- brightness: Theme .of (context).brightness,
225
- backgroundColor: Colors .transparent,
226
- leading: null ,
227
- automaticallyImplyLeading: false ,
228
- titleSpacing: 8.0 ,
229
- elevation: 0.0 ,
230
- title: Container (
231
- child: _buildSearchField (),
203
+ ),
204
+ SliverToBoxAdapter (
205
+ child: HomePageContentSwitcher (
206
+ scrollController: _scrollController,
207
+ child: _buildContent (),
232
208
),
233
209
),
210
+ ],
211
+ ),
212
+ // Hide the overscroll contents from the status bar
213
+ Container (
214
+ height: kToolbarHeight / 2 + MediaQuery .of (context).padding.top,
215
+ color: Theme .of (context).scaffoldBackgroundColor,
216
+ ),
217
+ Positioned (
218
+ top: 8 ,
219
+ left: 0 ,
220
+ right: 0 ,
221
+ child: AppBar (
222
+ brightness: Theme .of (context).brightness,
223
+ backgroundColor: Colors .transparent,
224
+ leading: null ,
225
+ automaticallyImplyLeading: false ,
226
+ titleSpacing: 8.0 ,
227
+ elevation: 0.0 ,
228
+ title: Container (
229
+ child: _buildSearchField (),
230
+ ),
234
231
),
235
- ] ,
236
- ) ,
232
+ ) ,
233
+ ] ,
237
234
);
238
235
}
239
236
240
237
Widget _buildTrackedBuses () {
241
- return FutureBuilder <List <Bus >>(
242
- future: getFollowedBuses (),
243
- builder: (BuildContext context, AsyncSnapshot <List <Bus >> snapshot) {
244
- return Card (
245
- margin: const EdgeInsets .all (8.0 ),
246
- child: Padding (
247
- padding: const EdgeInsets .all (16.0 ),
248
- child: Column (
249
- crossAxisAlignment: CrossAxisAlignment .start,
250
- children: < Widget > [
251
- Text ('Tracked buses' , style: Theme .of (context).textTheme.headline4),
252
- ListView .builder (
253
- shrinkWrap: true ,
254
- physics: const NeverScrollableScrollPhysics (),
255
- itemBuilder: (BuildContext context, int position) {
256
- final Bus bus = snapshot.data[position];
257
-
258
- return ListTile (
259
- leading: Text (
260
- bus.busService.number.padAsServiceNumber (),
261
- style: Theme .of (context).textTheme.headline6.copyWith (fontFamily: 'B612 Mono' )
262
- ),
263
- title: FutureBuilder <DateTime >(
264
- future: BusAPI ().getArrivalTime (bus.busStop, bus.busService.number),
265
- builder: (BuildContext context, AsyncSnapshot <DateTime > snapshot) {
266
- return Text (snapshot.hasData ? '${snapshot .data .getMinutesFromNow ()} min' : '' ,
267
- style: Theme .of (context).textTheme.subtitle1,
238
+ return AnimatedSize (
239
+ alignment: Alignment .topCenter,
240
+ vsync: this ,
241
+ duration: const Duration (milliseconds: 400 ),
242
+ curve: Curves .easeInOutCubic,
243
+ child: StreamBuilder <List <Bus >>(
244
+ initialData: _followedBuses,
245
+ stream: followedBusesStream (),
246
+ builder: (BuildContext context, AsyncSnapshot <List <Bus >> snapshot) {
247
+ final bool isLoaded = snapshot.hasData && snapshot.data.isNotEmpty;
248
+ if (isLoaded)
249
+ _followedBuses = snapshot.data;
250
+
251
+ return AnimatedOpacity (
252
+ opacity: isLoaded ? 1 : 0 ,
253
+ duration: isLoaded ? const Duration (milliseconds: 650 ) : Duration .zero,
254
+ curve: const Interval (0.66 , 1 ),
255
+ child: isLoaded ? Card (
256
+ margin: const EdgeInsets .all (8.0 ),
257
+ child: Padding (
258
+ padding: const EdgeInsets .only (top: 16.0 , bottom: 8.0 ),
259
+ child: Column (
260
+ crossAxisAlignment: CrossAxisAlignment .start,
261
+ children: < Widget > [
262
+ Padding (
263
+ padding: const EdgeInsets .symmetric (horizontal: 16.0 ),
264
+ child: Text ('Tracked buses' , style: Theme .of (context).textTheme.headline4),
265
+ ),
266
+ AnimatedSize (
267
+ alignment: Alignment .topCenter,
268
+ vsync: this ,
269
+ duration: const Duration (milliseconds: 400 ),
270
+ curve: Curves .easeInOutCubic,
271
+ child: ListView .builder (
272
+ shrinkWrap: true ,
273
+ physics: const NeverScrollableScrollPhysics (),
274
+ itemBuilder: (BuildContext context, int position) {
275
+ final Bus bus = snapshot.data[position];
276
+
277
+ return ListTile (
278
+ onTap: () {
279
+ showBusDetailSheet (bus.busStop, UserRoute .home);
280
+ },
281
+ title: FutureBuilder <DateTime >(
282
+ future: BusAPI ().getArrivalTime (bus.busStop, bus.busService.number),
283
+ builder: (BuildContext context, AsyncSnapshot <DateTime > snapshot) {
284
+ return Text (snapshot.hasData ? '${bus .busService .number } - ${snapshot .data .getMinutesFromNow ()} min' : '' ,
285
+ style: Theme .of (context).textTheme.headline6,
286
+ );
287
+ },
288
+ ),
289
+ subtitle: Text (bus.busStop.displayName),
268
290
);
269
291
},
292
+ itemCount: snapshot.data.length,
270
293
),
271
- subtitle: Text (bus.busStop.displayName),
272
- );
273
- },
274
- itemCount: snapshot? .data? .length ?? 0 ,
294
+ ),
295
+ Row (
296
+ children: < Widget > [
297
+ FlatButton .icon (
298
+ icon: const Icon (Icons .notifications_off),
299
+ label: const Text (
300
+ 'STOP TRACKING ALL BUSES' ,
301
+ style: TextStyle (fontWeight: FontWeight .bold),
302
+ ),
303
+ textColor: Theme .of (context).accentColor,
304
+ onPressed: () async {
305
+ final List <Map <String , dynamic >> trackedBuses = await unfollowAllBuses ();
306
+ Scaffold .of (context).showSnackBar (SnackBar (
307
+ content: const Text ('Stopped tracking all buses' ),
308
+ action: SnackBarAction (
309
+ label: 'Undo' ,
310
+ onPressed: () async {
311
+ for (Map <String , dynamic > trackedBus in trackedBuses) {
312
+ await followBus (stop: trackedBus['stop' ], bus: trackedBus['bus' ], arrivalTime: trackedBus['arrivalTime' ]);
313
+ }
314
+
315
+ // Update the bus stop detail sheet to reflect change in bus stop follow status
316
+ widget.bottomSheetKey.currentState.setState (() {});
317
+ },
318
+ ),
319
+ ));
320
+ },
321
+ ),
322
+ ],
323
+ ),
324
+ ],
275
325
),
276
- ] ,
277
- ),
278
- ),
279
- );
280
- } ,
326
+ ) ,
327
+ ) : Container () ,
328
+ );
329
+ },
330
+ ) ,
281
331
);
282
332
}
283
333
@@ -286,12 +336,27 @@ class _HomePageState extends BottomSheetPageState<HomePage> {
286
336
future: _getNearestBusStops (),
287
337
initialData: _nearestBusStops,
288
338
builder: (BuildContext context, AsyncSnapshot <Map <String , dynamic >> snapshot) {
289
- if (snapshot.hasData)
339
+ if (snapshot.hasData && snapshot.connectionState == ConnectionState .done )
290
340
_nearestBusStops = snapshot.data;
291
341
else if (snapshot.connectionState == ConnectionState .done || ! LocationUtils .isLocationAllowed ()) {
292
342
return Container ();
293
343
}
294
- final bool isLoaded = snapshot.hasData && snapshot.data['busStops' ].length == 5 ;
344
+ final bool isLoaded = _nearestBusStops != null && _nearestBusStops['busStops' ].length == 5 ;
345
+
346
+ final Widget refreshButton = Row (
347
+ children: < Widget > [
348
+ FlatButton .icon (
349
+ icon: const Icon (Icons .refresh),
350
+ label: const Text (
351
+ 'REFRESH LOCATION' ,
352
+ style: TextStyle (fontWeight: FontWeight .bold),
353
+ ),
354
+ textColor: Theme .of (context).accentColor,
355
+ onPressed: refreshLocation,
356
+ ),
357
+ ],
358
+ );
359
+
295
360
return Card (
296
361
elevation: 0.0 ,
297
362
shape: RoundedRectangleBorder (
@@ -304,36 +369,42 @@ class _HomePageState extends BottomSheetPageState<HomePage> {
304
369
margin: const EdgeInsets .all (8.0 ),
305
370
child: Padding (
306
371
padding: const EdgeInsets .only (bottom: 8.0 ),
307
- child: ExpandablePanel (
308
- tapHeaderToExpand: isLoaded,
309
- hasIcon: isLoaded,
310
- headerAlignment: ExpandablePanelHeaderAlignment .center,
311
- header: Container (
312
- alignment: Alignment .centerLeft,
313
- padding: const EdgeInsets .all (16.0 ),
314
- child: Text ('Nearby stops' , style: Theme .of (context).textTheme.headline4),
315
- ),
316
- collapsed: Column (
317
- crossAxisAlignment: CrossAxisAlignment .stretch,
318
- children: < Widget > [
319
- if (isLoaded)
320
- _buildSuggestionItem (snapshot.data['busStops' ][0 ], snapshot.data['distances' ][0 ]),
321
- if (! isLoaded)
322
- _buildSuggestionItem (null , null ),
323
- ],
324
- ),
325
- expanded: isLoaded ? ListView .separated (
326
- physics: const NeverScrollableScrollPhysics (),
327
- scrollDirection: Axis .vertical,
328
- shrinkWrap: true ,
329
- itemCount: 5 ,
330
- separatorBuilder: (BuildContext context, int position) => const Divider (),
331
- itemBuilder: (BuildContext context, int position) {
332
- final BusStop busStop = snapshot.data['busStops' ][position ];
333
- final double distanceInMeters = snapshot.data['distances' ][position];
334
- return _buildSuggestionItem (busStop, distanceInMeters);
335
- },
336
- ) : Container (),
372
+ child: Column (
373
+ children: [
374
+ ExpandablePanel (
375
+ tapHeaderToExpand: true ,
376
+ hasIcon: true ,
377
+ headerAlignment: ExpandablePanelHeaderAlignment .center,
378
+ header: Container (
379
+ alignment: Alignment .centerLeft,
380
+ padding: const EdgeInsets .all (16.0 ),
381
+ child: Text ('Nearby stops' , style: Theme .of (context).textTheme.headline4),
382
+ ),
383
+ collapsed: Column (
384
+ mainAxisAlignment: MainAxisAlignment .start,
385
+ crossAxisAlignment: CrossAxisAlignment .stretch,
386
+ children: < Widget > [
387
+ if (isLoaded)
388
+ _buildSuggestionItem (_nearestBusStops['busStops' ][0 ], _nearestBusStops['distances' ][0 ]),
389
+ if (! isLoaded)
390
+ _buildSuggestionItem (null , null ),
391
+ ],
392
+ ),
393
+ expanded: ListView .separated (
394
+ physics: const NeverScrollableScrollPhysics (),
395
+ scrollDirection: Axis .vertical,
396
+ shrinkWrap: true ,
397
+ itemCount: 5 ,
398
+ separatorBuilder: (BuildContext context, int position) => const Divider (),
399
+ itemBuilder: (BuildContext context, int position) {
400
+ final BusStop busStop = isLoaded ? _nearestBusStops['busStops' ][position] : null ;
401
+ final double distanceInMeters = isLoaded ? _nearestBusStops['distances' ][position] : null ;
402
+ return _buildSuggestionItem (busStop, distanceInMeters);
403
+ },
404
+ ),
405
+ ),
406
+ refreshButton,
407
+ ],
337
408
),
338
409
),
339
410
);
@@ -449,6 +520,12 @@ class _HomePageState extends BottomSheetPageState<HomePage> {
449
520
}
450
521
451
522
Future <void > refreshLocation () async {
523
+ setState ((){
524
+ _nearestBusStops = null ;
525
+ });
526
+ }
527
+
528
+ Future <void > refresh () async {
452
529
setState (() {});
453
530
}
454
531
0 commit comments