From dc6ec56e601f464c9858b34204b41fba6eb571f0 Mon Sep 17 00:00:00 2001 From: Luca Fluri Date: Wed, 15 Jul 2020 03:17:47 +0200 Subject: [PATCH] feat: :sparkles: settings screen fixes Settings screen #39 fixes add all contributors to credits page #25 --- lib/routes.dart | 4 +- lib/screens/credits/credits.dart | 57 ------- .../home/components/product_list_tile.dart | 2 + lib/screens/home/home.dart | 71 ++------ lib/screens/home/home_controller.dart | 19 --- lib/screens/settings/settings.dart | 154 ++++++++++++++++++ lib/screens/settings/settings_controller.dart | 54 ++++++ lib/services/backup.dart | 19 +-- lib/services/product_utils.dart | 8 + pubspec.lock | 7 + pubspec.yaml | 2 + 11 files changed, 247 insertions(+), 150 deletions(-) delete mode 100644 lib/screens/credits/credits.dart create mode 100644 lib/screens/settings/settings.dart create mode 100644 lib/screens/settings/settings_controller.dart diff --git a/lib/routes.dart b/lib/routes.dart index ffed2e1..ac8118e 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -1,10 +1,10 @@ import 'package:flutter/widgets.dart'; -import 'package:price_tracker/screens/credits/credits.dart'; import 'package:price_tracker/screens/home/home.dart'; import 'package:price_tracker/screens/intro/intro.dart'; +import 'package:price_tracker/screens/settings/settings.dart'; final Map routes = { "/": (BuildContext context) => HomeScreen(), "/intro": (context) => IntroScreen(), - "/credits": (context) => CreditsScreen(), + "/settings": (context) => SettingsScreen(), }; diff --git a/lib/screens/credits/credits.dart b/lib/screens/credits/credits.dart deleted file mode 100644 index ef01056..0000000 --- a/lib/screens/credits/credits.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:url_launcher/url_launcher.dart'; - -class CreditsScreen extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - elevation: 0, - brightness: Brightness.dark, - backgroundColor: Colors.transparent, - iconTheme: IconThemeData(color: Theme.of(context).primaryColor), - title: Text("Credits", - style: TextStyle(color: Theme.of(context).primaryColor)), - leading: BackButton( - onPressed: () { - Navigator.of(context) - .pushNamedAndRemoveUntil("/", (route) => false); - }, - ), - ), - body: Container( - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - GestureDetector( - onTap: () async { - if (await canLaunch("https://www.lucafluri.ch")) - await launch("https://www.lucafluri.ch"); - else - throw "Could not launch URL"; - }, - child: Column( - children: [ - Text( - "Made by Luca Fluri", - style: Theme.of(context).textTheme.headline5, - ), - Text( - "lucafluri.ch", - style: Theme.of(context).textTheme.headline5, - ) - ], - ), - ), - Padding( - padding: const EdgeInsets.only(top: 36.0), - child: Text("June 2020"), - ) - ], - ), - ), - ), - ); - } -} diff --git a/lib/screens/home/components/product_list_tile.dart b/lib/screens/home/components/product_list_tile.dart index 01e0e80..68329b4 100644 --- a/lib/screens/home/components/product_list_tile.dart +++ b/lib/screens/home/components/product_list_tile.dart @@ -2,6 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:price_tracker/models/product.dart'; +import 'package:price_tracker/services/product_utils.dart'; import 'package:price_tracker/screens/product_detail/product_detail.dart'; import 'package:share/share.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -133,6 +134,7 @@ class ProductListTile extends StatelessWidget { child: Container( color: _underTarget ? Colors.green[800] : Colors.transparent, child: ListTile( + enabled: refreshing, title: Text( product.getShortName(), overflow: TextOverflow.ellipsis, diff --git a/lib/screens/home/home.dart b/lib/screens/home/home.dart index e38cfb9..ccee5e0 100644 --- a/lib/screens/home/home.dart +++ b/lib/screens/home/home.dart @@ -3,14 +3,9 @@ import 'package:flutter/material.dart'; import 'package:price_tracker/components/widget_view/widget_view.dart'; import 'package:price_tracker/screens/home/components/product_list_tile.dart'; import 'package:price_tracker/screens/home/home_controller.dart'; -import 'package:price_tracker/services/backup.dart'; +import 'package:price_tracker/screens/settings/settings_controller.dart'; import 'package:price_tracker/services/product_utils.dart'; import 'package:toast/toast.dart'; -import 'package:workmanager/workmanager.dart'; - -/// Set this to 'true' if you want to have a red button to create a test-notification: -const NOTIFICATION_TEST_BUTTON = false; -const BACKGROUND_TEST_BUTTON = false; class HomeScreen extends StatefulWidget { HomeScreen({Key key}) : super(key: key); @@ -28,27 +23,14 @@ class HomeScreenView extends WidgetView { brightness: Brightness.dark, backgroundColor: Colors.transparent, iconTheme: IconThemeData(color: Theme.of(context).primaryColor), - title: Text('Price Tracker BETA', + title: Text(Settings.APP_NAME, style: TextStyle(color: Theme.of(context).primaryColor)), actions: [ - if (NOTIFICATION_TEST_BUTTON) - IconButton( - icon: Icon(Icons.speaker_notes), - onPressed: () => state.testNotification(), - color: Colors.redAccent, - ), - if (BACKGROUND_TEST_BUTTON) - IconButton( - icon: Icon(Icons.cloud_download), - onPressed: () => Workmanager.registerOneOffTask( - "manualPriceScraping", "Manual Price Tracker Scraper"), - color: Colors.redAccent, - ), // Show Reload Button if internet connection avialable or internet error icon if not state.iConnectivity ? IconButton( icon: Icon(Icons.refresh), - onPressed: () => state.onRefresh(), + onPressed: refreshing ? null : () => state.onRefresh(), ) : IconButton( icon: Icon( @@ -57,13 +39,12 @@ class HomeScreenView extends WidgetView { ), onPressed: () => state.checkInternet(), ), + IconButton( - icon: Icon(Icons.help_outline), - onPressed: () => Navigator.of(context).pushNamed("/intro"), - ), - IconButton( - icon: Icon(Icons.description), - onPressed: () => Navigator.of(context).pushNamed("/credits"), + icon: Icon(Icons.settings), + onPressed: refreshing + ? null + : () => Navigator.of(context).pushNamed("/settings"), ), ], ); @@ -78,7 +59,7 @@ class HomeScreenView extends WidgetView { duration: 3, gravity: Toast.BOTTOM); }, tooltip: state.iConnectivity ? 'Add Product' : 'No Internet', - backgroundColor: state.iConnectivity ? null : Colors.grey, + backgroundColor: state.iConnectivity && !refreshing ? null : Colors.grey, child: Icon(Icons.add), ); } @@ -100,36 +81,7 @@ class HomeScreenView extends WidgetView { controller: state.listviewController, child: Column( children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MaterialButton( - color: Colors.green, - child: Text("Backup"), - onPressed: () async { - BackupService.instance.backup(); - }), - Container( - width: 20, - ), - MaterialButton( - color: Colors.blue, - child: Text("Restore"), - onPressed: () async { - BackupService.instance.restore(); - }), - Container( - width: 20, - ), - MaterialButton( - color: Colors.redAccent, - child: Text("DELETE DB"), - onPressed: () async { - BackupService.instance.clearDB(); - }), - ], - ), - if (state.refreshing) + if (refreshing) Padding( padding: const EdgeInsets.symmetric(vertical: 12.0), child: Center( @@ -144,6 +96,9 @@ class HomeScreenView extends WidgetView { backgroundColor: Colors.grey[800], value: reloadProgress, ), + MaterialButton( + child: Text("Cancel"), + onPressed: () => cancelReload = true) ], )), ), diff --git a/lib/screens/home/home_controller.dart b/lib/screens/home/home_controller.dart index d756d66..456bb0a 100644 --- a/lib/screens/home/home_controller.dart +++ b/lib/screens/home/home_controller.dart @@ -17,7 +17,6 @@ class HomeScreenController extends State { ScrollController listviewController; bool loading = false; - bool refreshing = false; String refreshingText = "Refreshing Prices"; List products = []; bool iConnectivity = true; @@ -203,24 +202,6 @@ class HomeScreenController extends State { curve: Curves.easeInOut, duration: Duration(milliseconds: 1000)); } - testNotification() { - Product p = Product.fromMap({ - "_id": 5, - "name": "Apple iPad (10.2-inch, WiFi, 32GB) - Gold (latest model)", - "productUrl": "testUrl.com", - "prices": "[-1.0, 269.0, 260.0]", - "dates": - "[2020-07-02 00:00:00.000, 2020-07-03 15:43:12.345, 2020-07-04 04:00:45.000]", - "targetPrice": "261.0", - "imageUrl": "", - "parseSuccess": "true", - }); - // Only one can be send at a time (same id) - sendPriceFallNotification(p); - // sendUnderTargetNotification(p); - // sendAvailableAgainNotification(p); - } - @override Widget build(BuildContext context) => HomeScreenView(this); } diff --git a/lib/screens/settings/settings.dart b/lib/screens/settings/settings.dart new file mode 100644 index 0000000..abd4c15 --- /dev/null +++ b/lib/screens/settings/settings.dart @@ -0,0 +1,154 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:price_tracker/components/widget_view/widget_view.dart'; +import 'package:price_tracker/screens/settings/settings_controller.dart'; +import 'package:price_tracker/services/backup.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:settings_ui/settings_ui.dart'; + +class SettingsScreen extends StatefulWidget { + SettingsScreen({Key key}) : super(key: key); + + @override + Settings createState() => Settings(); +} + +class SettingsScreenView extends WidgetView { + SettingsScreenView(Settings state) : super(state); + + launchUrl(String url) async { + if (await canLaunch(url)) + await launch(url); + else + throw "Could not launch URL"; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + elevation: 0, + brightness: Brightness.dark, + backgroundColor: Colors.transparent, + iconTheme: IconThemeData(color: Theme.of(context).primaryColor), + title: Text("Settings", + style: TextStyle(color: Theme.of(context).primaryColor)), + leading: BackButton( + onPressed: () { + Navigator.of(context) + .pushNamedAndRemoveUntil("/", (route) => false); + }, + ), + ), + body: SettingsList( + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + sections: [ + SettingsSection( + title: 'Backup / Restore', + tiles: [ + SettingsTile( + title: 'Backup', + subtitle: 'Save Backup File', + leading: Icon(Icons.backup), + onTap: () async { + await BackupService.instance.backup(); + state.showToast("Backup file saved"); + }, + ), + SettingsTile( + title: 'Restore', + subtitle: + 'Restore from Backup File - loads all products into database', + leading: Icon(Icons.cloud_download), + onTap: () async { + await BackupService.instance.restore(); + state.showToast("Products added to database"); + }, + ), + ], + ), + SettingsSection( + title: 'Help', + tiles: [ + SettingsTile( + title: 'Tutorial', + subtitle: 'Open Tutorial', + leading: Icon(Icons.live_help), + onTap: () { + Navigator.of(context).pushNamed("/intro"); + }, + ), + ], + ), + SettingsSection( + title: 'DEVELOPER DANGER ZONE', + tiles: [ + SettingsTile( + title: 'Clear DB', + leading: Icon(Icons.warning), + onTap: () => state.clearDB(), + ), + SettingsTile( + title: 'Test Notification (Price Fall)', + leading: Icon(Icons.warning), + onTap: () => state.testPriceFallNotification(), + ), + SettingsTile( + title: 'Test Notification (Under Target)', + leading: Icon(Icons.warning), + onTap: () => state.testUnderTargetNotification(), + ), + SettingsTile( + title: 'Test Notification (Available Again)', + leading: Icon(Icons.warning), + onTap: () => state.testAvailableAgainNotification(), + ), + SettingsTile( + title: 'Test Background Service', + leading: Icon(Icons.warning), + onTap: () => state.testBackgroundService(), + ), + ], + ), + SettingsSection( + title: 'Credits', + tiles: [ + SettingsTile( + title: 'Luca Fluri', + subtitle: '@lucafluri, lucafluri.ch', + leading: Icon(Icons.person), + onTap: () => launchUrl("https://www.lucafluri.ch"), + ), + SettingsTile( + title: 'Andreas Ambühl', + subtitle: '@AndiSwiss, andiswiss.ch', + leading: Icon(Icons.person_outline), + onTap: () => launchUrl("https://andiswiss.ch/"), + ), + SettingsTile( + title: 'Dario Breitenstein', + subtitle: '@chdabre, imakethings.ch', + leading: Icon(Icons.person_outline), + onTap: () => launchUrl("https://www.imakethings.ch/"), + ), + SettingsTile( + title: 'Marc Schnydrig', + subtitle: '@marcschny', + leading: Icon(Icons.person_outline), + onTap: () => launchUrl("https://github.com/marcschny"), + ), + ], + ), + SettingsSection( + title: 'Version', + tiles: [ + SettingsTile( + title: Settings.VERSION, + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/screens/settings/settings_controller.dart b/lib/screens/settings/settings_controller.dart new file mode 100644 index 0000000..573a579 --- /dev/null +++ b/lib/screens/settings/settings_controller.dart @@ -0,0 +1,54 @@ +import 'package:flutter/widgets.dart'; +import 'package:price_tracker/models/product.dart'; +import 'package:price_tracker/screens/settings/settings.dart'; +import 'package:price_tracker/services/database.dart'; +import 'package:price_tracker/services/product_utils.dart'; +import 'package:toast/toast.dart'; +import 'package:workmanager/workmanager.dart'; + +class Settings extends State { + static const String VERSION = "0.1.3"; + static const String APP_NAME = "Price Tracker BETA"; + + Product testProduct = Product.fromMap({ + "_id": 1, + "name": + "Apple iPad (10.2-inch, WiFi, 32GB) - Gold (latest model) - with extra dolphins", + "productUrl": "testUrl.com", + "prices": "[-1.0, 269.0, 260.0]", + "dates": + "[2020-07-02 00:00:00.000, 2020-07-03 15:43:12.345, 2020-07-04 04:00:45.000]", + "targetPrice": "261.0", + "imageUrl": "", + "parseSuccess": "true", + }); + + showToast(String text, {int sec = 2}) => + Toast.show(text, context, duration: sec); + + clearDB() async { + DatabaseService _db = await DatabaseService.getInstance(); + await _db.deleteAll(); + showToast("Database cleared!"); + } + + testPriceFallNotification() { + sendPriceFallNotification(testProduct); + } + + testUnderTargetNotification() { + sendUnderTargetNotification(testProduct); + } + + testAvailableAgainNotification() { + sendAvailableAgainNotification(testProduct); + } + + testBackgroundService() { + Workmanager.registerOneOffTask( + "manualPriceScraping", "Manual Price Tracker Scraper"); + } + + @override + Widget build(BuildContext context) => SettingsScreenView(this); +} diff --git a/lib/services/backup.dart b/lib/services/backup.dart index 8e3a7a7..8c3b298 100644 --- a/lib/services/backup.dart +++ b/lib/services/backup.dart @@ -7,7 +7,6 @@ import 'package:path_provider/path_provider.dart'; import 'package:price_tracker/models/product.dart'; import 'package:price_tracker/services/database.dart'; import 'package:flutter_file_dialog/flutter_file_dialog.dart'; -import 'package:price_tracker/services/init.dart'; class BackupService { BackupService._privateConstructor(); @@ -66,11 +65,6 @@ class BackupService { restore() async { DatabaseService _db = await DatabaseService.getInstance(); - // File file = await FilePicker.getFile( - // type: FileType.custom, - // allowedExtensions: [".pt"], - // ); - final params = OpenFileDialogParams( dialogType: OpenFileDialogType.document, sourceType: SourceType.photoLibrary, @@ -86,28 +80,25 @@ class BackupService { debugPrint("ERROR Getting File"); return; } + String string; + try { string = await file.readAsString(); } catch (e) { debugPrint("Can't read file as string!"); return; } + List products = _buildProducts(string); + if (products == null) { debugPrint("Error building Products"); return; } + for (Product p in products) { await _db.insert(p); } - - navigatorKey.currentState.pushNamedAndRemoveUntil("/", (route) => false); - } - - clearDB() async { - DatabaseService _db = await DatabaseService.getInstance(); - await _db.deleteAll(); - navigatorKey.currentState.pushNamedAndRemoveUntil("/", (route) => false); } } diff --git a/lib/services/product_utils.dart b/lib/services/product_utils.dart index 16a760a..8047305 100644 --- a/lib/services/product_utils.dart +++ b/lib/services/product_utils.dart @@ -7,7 +7,10 @@ import 'package:flutter/material.dart'; import 'package:price_tracker/services/init.dart'; import 'package:price_tracker/services/notifications.dart'; +bool refreshing = false; + double reloadProgress; +bool cancelReload = false; double roundToPlace(double d, int places) { double mod = pow(10.0, places); @@ -36,6 +39,11 @@ Future updatePrices(Function perUpdate, {test: false}) async { List products = await _db.getAllProducts(); for (int i = 0; i < products.length; i++) { + if (cancelReload) { + cancelReload = false; + break; + } + double lastPrice = products[i].latestPrice; await products[i].update(test: test); await _db.update(products[i]); diff --git a/pubspec.lock b/pubspec.lock index 950afab..cee5e86 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -380,6 +380,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.24.1" + settings_ui: + dependency: "direct main" + description: + name: settings_ui + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.0" share: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 3b00221..edee3bb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -47,6 +47,8 @@ dependencies: receive_sharing_intent: ^1.4.0+2 double_back_to_close_app: ^1.2.0 flutter_file_dialog: ^0.0.5 + settings_ui: ^0.3.0 +