diff --git a/android/app/build.gradle b/android/app/build.gradle index 092308f..486d0e7 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -42,7 +42,7 @@ android { buildTypes { debug { - + applicationIdSuffix ".debug" } release { // TODO: Add your own signing config for the release build. diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 04b4763..8b5c545 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,7 @@ + + diff --git a/lib/main.dart b/lib/main.dart index 7472acd..3732f08 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,9 +1,14 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; import 'page/flights_page.dart'; import 'util/colors.dart'; +import 'util/proxy_for_debug.dart'; void main() { + HttpOverrides.global = MyProxyHttpOverride(); runApp(MyApp()); } @@ -21,12 +26,16 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { final title = 'Gibraltar Flights'; - return MaterialApp( - title: title, - theme: ThemeData( - primarySwatch: generateMaterialColor(Palette.primary), - fontFamily: 'Georgia'), - home: FlightsPage(title: title), + return RefreshConfiguration( + headerBuilder: () => WaterDropMaterialHeader(), + + child: MaterialApp( + title: title, + theme: ThemeData( + primarySwatch: generateMaterialColor(Palette.primary), + fontFamily: 'Georgia'), + home: FlightsPage(title: title), + ), ); } } diff --git a/lib/page/flights_page.dart b/lib/page/flights_page.dart index ca2e2cc..27f58d1 100644 --- a/lib/page/flights_page.dart +++ b/lib/page/flights_page.dart @@ -1,3 +1,4 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:gibraltar_flights/main.dart'; @@ -5,6 +6,7 @@ import 'package:gibraltar_flights/model/flight.dart'; import 'package:gibraltar_flights/model/flights.dart'; import 'package:gibraltar_flights/util/extensions.dart'; import 'package:gibraltar_flights/util/scrappy.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; class FlightsPage extends StatefulWidget { const FlightsPage({Key key, @required this.title}) : super(key: key); @@ -15,12 +17,14 @@ class FlightsPage extends StatefulWidget { } class _FlightsPageState extends State { - Future futureFlights; + Flights flights; + RefreshController _refreshController = + RefreshController(initialRefresh: true); @override void initState() { super.initState(); - futureFlights = Scrapper.scrapeData(); + flights = Flights(items: []); } @override @@ -30,19 +34,18 @@ class _FlightsPageState extends State { title: Text(widget.title), ), body: Center( - child: FutureBuilder( - future: futureFlights, - builder: (context, snapshot) { - return RefreshIndicator( - child: _listView(snapshot), - onRefresh: _pullRefresh, - ); - }))); + child: SmartRefresher( + enablePullDown: true, + enablePullUp: false, + controller: _refreshController, + onRefresh: () => _onRefresh(context), + child: _listView(), + ))); } - Widget _listView(AsyncSnapshot snapshot) { - if (snapshot.hasData) { - List _items = processData(snapshot.data.items); + Widget _listView() { + if (flights.items.isNotEmpty) { + List _items = processData(flights.items); return ListView.builder( itemCount: _items.length, itemBuilder: (context, index) { @@ -59,15 +62,37 @@ class _FlightsPageState extends State { }, ); } else { - return const CircularProgressIndicator(); + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text("There are no flights in the list."), + Container( + margin: EdgeInsetsDirectional.only(top: 16), + child: TextButton( + onPressed: () => _refreshController.requestRefresh(), + child: Text("refresh".toUpperCase()), + style: TextButton.styleFrom( + primary: Colors.white, + backgroundColor: Theme.of(context).accentColor, + onSurface: Colors.grey, + )), + ) + ], + ); } } - Future _pullRefresh() async { - Future freshFutureFlights = Scrapper.scrapeData(); + Future _onRefresh(BuildContext context) async { + Future _freshFutureFlights = Scrapper.scrapeData(); + _freshFutureFlights.catchError((e) { + _refreshController.refreshFailed(); + _showError(context, "Couldn't load data, please try again."); + }); + Flights _freshFlights = await _freshFutureFlights; setState(() { - futureFlights = Future.value(freshFutureFlights); + flights = _freshFlights; }); + _refreshController.refreshCompleted(); } List processData(List flightList) { @@ -86,6 +111,11 @@ class _FlightsPageState extends State { }); return result; } + + void _showError(BuildContext context, String message) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(message), backgroundColor: Theme.of(context).errorColor)); + } } abstract class ListItem { diff --git a/lib/util/proxy_for_debug.dart b/lib/util/proxy_for_debug.dart new file mode 100644 index 0000000..fac44b6 --- /dev/null +++ b/lib/util/proxy_for_debug.dart @@ -0,0 +1,13 @@ +import 'dart:io'; + +class MyProxyHttpOverride extends HttpOverrides { + @override + HttpClient createHttpClient(SecurityContext context) { + return super.createHttpClient(context) + ..findProxy = (uri) { + return "PROXY 192.168.18.100:8888;"; + } + ..badCertificateCallback = + (X509Certificate cert, String host, int port) => true; + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 7c0870a..fecaeeb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -33,6 +33,7 @@ dependencies: html: ^0.14.0+3 jiffy: ^3.0.1 path_provider: ^1.6.24 + pull_to_refresh: ^1.6.3 scrapy: ^0.0.3 dev_dependencies: