From 69c3dbf9602ad74abc56d3b2fa8bcabf9d8707c5 Mon Sep 17 00:00:00 2001 From: monsieurtanuki Date: Sat, 16 Jan 2021 12:31:04 +0100 Subject: [PATCH] feature/#21 - scan now uses local database, for product look up and for its history New file: * `scan_page.dart`: helper class to be used on top of `AlternativeContinuousScanPage` and `ContinuousScanPage` Impacted files: * `alternative_continuous_scan_page.dart`: `ContinuousScanModel` is now initialized upstream by `ScanPage` * `continuous_scan_model.dart`: now a `ChangeNotifier`, uses a `ProductList` and database cache * `continuous_scan_page.dart`: `ContinuousScanModel` is now initialized upstream by `ScanPage` * `contribution_page.dart`: uses the new `ScanPage` widget instead of the 2 scan pages * `local_database.dart`: removed the dummy notify listener method * `main.dart`: uses the new `ScanPage` widget instead of the 2 scan pages * `personalized_ranking_page.dart`: now we use a `ProductList` as input * `product_list.dart`: added a `getProduct` method and a "scan list" identifier * `product_page.dart`: unrelated UI fixes * `product_query_page.dart`: slight refactoring * `smooth_it_model.dart`: now we use `ProductList` * `smooth_product_carousel.dart`: added the new "CACHED" product possibility; fixed an init bug --- .../data_models/continuous_scan_model.dart | 121 +++++--- .../lib/data_models/product_list.dart | 22 ++ .../lib/data_models/smooth_it_model.dart | 4 +- .../lib/database/local_database.dart | 4 - .../lib/lists/smooth_product_carousel.dart | 6 +- packages/smooth_app/lib/main.dart | 22 +- .../alternative_continuous_scan_page.dart | 196 ++++++------- .../lib/pages/continuous_scan_page.dart | 265 ++++++++---------- .../lib/pages/contribution_page.dart | 13 +- .../lib/pages/personalized_ranking_page.dart | 8 +- .../smooth_app/lib/pages/product_page.dart | 24 +- .../lib/pages/product_query_page.dart | 11 +- packages/smooth_app/lib/pages/scan_page.dart | 71 +++++ 13 files changed, 413 insertions(+), 354 deletions(-) create mode 100644 packages/smooth_app/lib/pages/scan_page.dart diff --git a/packages/smooth_app/lib/data_models/continuous_scan_model.dart b/packages/smooth_app/lib/data_models/continuous_scan_model.dart index 4805c2b1179..aa7f8a085c2 100644 --- a/packages/smooth_app/lib/data_models/continuous_scan_model.dart +++ b/packages/smooth_app/lib/data_models/continuous_scan_model.dart @@ -5,103 +5,144 @@ import 'package:qr_code_scanner/qr_code_scanner.dart'; import 'package:smooth_app/database/barcode_product_query.dart'; import 'package:smooth_app/database/dao_product.dart'; import 'package:smooth_app/database/local_database.dart'; +import 'package:smooth_app/data_models/product_list.dart'; +import 'package:smooth_app/database/dao_product_list.dart'; enum ScannedProductState { FOUND, NOT_FOUND, LOADING, THANKS, + CACHED, } -class ContinuousScanModel { +class ContinuousScanModel with ChangeNotifier { ContinuousScanModel({@required bool contributionMode}) : _contributionMode = contributionMode; final Map _states = {}; - final Map _products = {}; final List _barcodes = []; + final ProductList _productList = + ProductList(listType: ProductList.LIST_TYPE_SCAN, parameters: ''); + bool _contributionMode; - QRViewController _scannerController; + String _barcodeLatest; String _barcodeTrustCheck; - LocalDatabase _localDatabase; + DaoProduct _daoProduct; + DaoProductList _daoProductList; bool get isNotEmpty => getBarcodes().isNotEmpty; bool get contributionMode => _contributionMode; + ProductList get productList => _productList; List getBarcodes() => _barcodes; + Future load(final LocalDatabase localDatabase) async { + try { + _daoProduct = DaoProduct(localDatabase); + _daoProductList = DaoProductList(localDatabase); + await _daoProductList.get(_productList); + for (final String barcode in _productList.barcodes) { + _barcodes.add(barcode); + _states[barcode] = ScannedProductState.CACHED; + _barcodeLatest = barcode; + } + return this; + } catch (e) { + print('exception: $e'); + } + return null; + } + void setBarcodeState( final String barcode, final ScannedProductState state, ) { _states[barcode] = state; - _localDatabase.dummyNotifyListeners(); + notifyListeners(); } ScannedProductState getBarcodeState(final String barcode) => _states[barcode]; - Product getProduct(final String barcode) => _products[barcode]; + Product getProduct(final String barcode) => _productList.getProduct(barcode); - void setLocalDatabase(final LocalDatabase localDatabase) => - _localDatabase = localDatabase; + void setupScanner(QRViewController controller) => + controller.scannedDataStream.listen((String barcode) => onScan(barcode)); - void setupScanner(QRViewController controller) { - _scannerController = controller; - _scannerController.scannedDataStream.listen( - (String barcode) => onScan(barcode), - ); - } + List getProducts() => _productList.getList(); - List getFoundProducts() { - final List result = []; - for (final String barcode in _barcodes) { - if (getBarcodeState(barcode) == ScannedProductState.FOUND) { - result.add(_products[barcode]); - } - } - return result; - } - - Future onScan(String code) async { - print('Barcode detected : $code'); + Future onScan(final String code) async { if (_barcodeTrustCheck != code) { _barcodeTrustCheck = code; return; } - _addBarcode(code); - } - - Future onScanAlt(String code, List offsets) async { - print('Barcode detected : $code'); + if (_barcodeLatest == code) { + return; + } + _barcodeLatest = code; _addBarcode(code); } void contributionModeSwitch(bool value) { if (_contributionMode != value) { _contributionMode = value; - _localDatabase.dummyNotifyListeners(); + notifyListeners(); } } - bool _addBarcode(final String barcode) { - if (getBarcodeState(barcode) == null) { + Future _addBarcode(final String barcode) async { + final ScannedProductState state = getBarcodeState(barcode); + if (state == null) { _barcodes.add(barcode); setBarcodeState(barcode, ScannedProductState.LOADING); - _loadBarcode(barcode); + _cacheOrLoadBarcode(barcode); + return true; + } + if (state == ScannedProductState.FOUND || + state == ScannedProductState.CACHED) { + final Product product = getProduct(barcode); + _barcodes.add(barcode); + _addProduct(product, state); return true; } return false; } - Future _loadBarcode(final String barcode) async { + Future _cacheOrLoadBarcode(final String barcode) async { + final bool cached = await _cachedBarcode(barcode); + if (!cached) { + _loadBarcode(barcode, ScannedProductState.NOT_FOUND); + } + } + + Future _cachedBarcode(final String barcode) async { + final int latestUpdate = await _daoProduct.getLastUpdate(barcode); + if (latestUpdate != null) { + _addProduct(await _daoProduct.get(barcode), ScannedProductState.CACHED); + return true; + } + return false; + } + + Future _loadBarcode( + final String barcode, + final ScannedProductState notFound, + ) async { final Product product = await BarcodeProductQuery(barcode).getProduct(); if (product != null) { - await DaoProduct(_localDatabase).put(product); - _products[barcode] = product; - setBarcodeState(barcode, ScannedProductState.FOUND); + _addProduct(product, ScannedProductState.FOUND); } else { - setBarcodeState(barcode, ScannedProductState.NOT_FOUND); + setBarcodeState(barcode, notFound); } } + + Future _addProduct( + final Product product, + final ScannedProductState state, + ) async { + _productList.add(product); + _daoProductList.put(_productList); + setBarcodeState(product.barcode, state); + } } diff --git a/packages/smooth_app/lib/data_models/product_list.dart b/packages/smooth_app/lib/data_models/product_list.dart index d9e9809eca6..d0ef8ac886c 100644 --- a/packages/smooth_app/lib/data_models/product_list.dart +++ b/packages/smooth_app/lib/data_models/product_list.dart @@ -15,6 +15,7 @@ class ProductList { static const String LIST_TYPE_HTTP_SEARCH_GROUP = 'http/search/group'; static const String LIST_TYPE_HTTP_SEARCH_KEYWORDS = 'http/search/keywords'; + static const String LIST_TYPE_SCAN = 'scan'; List get barcodes => _barcodes; @@ -25,6 +26,8 @@ class ProductList { _products.clear(); } + Product getProduct(final String barcode) => _products[barcode]; + void add(final Product product) { if (product == null) { throw Exception('null product'); @@ -59,4 +62,23 @@ class ProductList { } return result; } + + List getUniqueList({final bool ascending = true}) { + final List result = []; + final Set done = {}; + final Iterable orderedBarcodes = + ascending ? _barcodes : _barcodes.reversed; + for (final String barcode in orderedBarcodes) { + if (done.contains(barcode)) { + continue; + } + done.add(barcode); + final Product product = _products[barcode]; + if (product == null) { + throw Exception('no product for barcode $barcode'); + } + result.add(product); + } + return result; + } } diff --git a/packages/smooth_app/lib/data_models/smooth_it_model.dart b/packages/smooth_app/lib/data_models/smooth_it_model.dart index 2d20500fd46..6de16cbf64d 100644 --- a/packages/smooth_app/lib/data_models/smooth_it_model.dart +++ b/packages/smooth_app/lib/data_models/smooth_it_model.dart @@ -3,6 +3,7 @@ import 'package:smooth_app/structures/ranked_product.dart'; import 'package:smooth_app/data_models/user_preferences_model.dart'; import 'package:smooth_app/data_models/match.dart'; import 'package:smooth_app/temp/user_preferences.dart'; +import 'package:smooth_app/data_models/product_list.dart'; class SmoothItModel { static const int MATCH_INDEX_YES = 0; @@ -16,7 +17,7 @@ class SmoothItModel { bool _nextRefreshIsJustChangingTabs = false; void refresh( - final List unprocessedProducts, + final ProductList productList, final UserPreferences userPreferences, final UserPreferencesModel userPreferencesModel, ) { @@ -24,6 +25,7 @@ class SmoothItModel { _nextRefreshIsJustChangingTabs = false; return; } + final List unprocessedProducts = productList.getUniqueList(); _allProducts = Match.sort(unprocessedProducts, userPreferences, userPreferencesModel); _categorizedProducts.clear(); diff --git a/packages/smooth_app/lib/database/local_database.dart b/packages/smooth_app/lib/database/local_database.dart index cfc511e6ed5..430b549164f 100644 --- a/packages/smooth_app/lib/database/local_database.dart +++ b/packages/smooth_app/lib/database/local_database.dart @@ -40,10 +40,6 @@ class LocalDatabase extends ChangeNotifier { } static int nowInMillis() => DateTime.now().millisecondsSinceEpoch; - - void dummyNotifyListeners() { - notifyListeners(); // TODO(monsieurtanuki): create a ScanNotifier instead - } } class TableStats { diff --git a/packages/smooth_app/lib/lists/smooth_product_carousel.dart b/packages/smooth_app/lib/lists/smooth_product_carousel.dart index e34271dc0aa..47c43174384 100644 --- a/packages/smooth_app/lib/lists/smooth_product_carousel.dart +++ b/packages/smooth_app/lib/lists/smooth_product_carousel.dart @@ -32,7 +32,10 @@ class _SmoothProductCarouselState extends State { if (_length != barcodesLength) { _length = barcodesLength; if (_length > 1) { - _controller.animateToPage(_length - 1); + Future.delayed( + const Duration(seconds: 0), + () => _controller.animateToPage(_length - 1), + ); } } return CarouselSlider.builder( @@ -55,6 +58,7 @@ class _SmoothProductCarouselState extends State { final Product product = widget.continuousScanModel.getProduct(barcode); switch (widget.continuousScanModel.getBarcodeState(barcode)) { case ScannedProductState.FOUND: + case ScannedProductState.CACHED: if (widget.continuousScanModel.contributionMode) { return SmoothProductCardEdit(heroTag: barcode, product: product); } diff --git a/packages/smooth_app/lib/main.dart b/packages/smooth_app/lib/main.dart index cc89b721642..a878da3348e 100644 --- a/packages/smooth_app/lib/main.dart +++ b/packages/smooth_app/lib/main.dart @@ -10,10 +10,9 @@ import 'package:sentry/sentry.dart'; import 'package:provider/provider.dart'; import 'package:smooth_app/data_models/user_preferences_model.dart'; -import 'package:smooth_app/pages/alternative_continuous_scan_page.dart'; +import 'package:smooth_app/pages/scan_page.dart'; import 'package:smooth_app/pages/choose_page.dart'; import 'package:smooth_app/pages/contribution_page.dart'; -import 'package:smooth_app/pages/continuous_scan_page.dart'; import 'package:smooth_app/pages/profile_page.dart'; import 'package:smooth_app/pages/tracking_page.dart'; import 'package:smooth_app/themes/smooth_theme.dart'; @@ -222,16 +221,15 @@ class SmoothApp extends StatelessWidget { icon: 'assets/actions/scanner_alt_2.svg', iconPadding: _navigationIconPadding, iconSize: _navigationIconSize, - onTap: () { - final Widget newPage = mlKitState - ? const ContinuousScanPage() - : const AlternativeContinuousScanPage(); - Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => newPage), - ); - }, + onTap: () => Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => ScanPage( + contributionMode: false, + mlKit: mlKitState, + ), + ), + ), ), ); } diff --git a/packages/smooth_app/lib/pages/alternative_continuous_scan_page.dart b/packages/smooth_app/lib/pages/alternative_continuous_scan_page.dart index a578662c8af..abb9e3c8dc8 100644 --- a/packages/smooth_app/lib/pages/alternative_continuous_scan_page.dart +++ b/packages/smooth_app/lib/pages/alternative_continuous_scan_page.dart @@ -4,132 +4,120 @@ import 'package:provider/provider.dart'; import 'package:qr_code_scanner/qr_code_scanner.dart'; import 'package:smooth_app/data_models/continuous_scan_model.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:smooth_app/database/local_database.dart'; import 'package:smooth_app/lists/smooth_product_carousel.dart'; import 'package:smooth_app/pages/personalized_ranking_page.dart'; import 'package:smooth_ui_library/animations/smooth_reveal_animation.dart'; import 'package:smooth_ui_library/widgets/smooth_view_finder.dart'; -import 'package:smooth_app/pages/continuous_scan_page.dart'; +import 'package:smooth_app/pages/scan_page.dart'; -class AlternativeContinuousScanPage extends StatefulWidget { - const AlternativeContinuousScanPage( - {this.initializeWithContributionMode = false}); +class AlternativeContinuousScanPage extends StatelessWidget { + AlternativeContinuousScanPage(this._continuousScanModel); - final bool initializeWithContributionMode; + final ContinuousScanModel _continuousScanModel; - @override - _AlternativeContinuousScanPageState createState() => - _AlternativeContinuousScanPageState(); -} - -class _AlternativeContinuousScanPageState - extends State { - ContinuousScanModel _continuousScanModel; final GlobalKey _scannerViewKey = GlobalKey(debugLabel: 'Barcode Scanner'); - @override - void initState() { - super.initState(); - _continuousScanModel = ContinuousScanModel( - contributionMode: widget.initializeWithContributionMode); - } - @override Widget build(BuildContext context) { - final LocalDatabase localDatabase = context.watch(); - _continuousScanModel.setLocalDatabase(localDatabase); final Size screenSize = MediaQuery.of(context).size; final AppLocalizations appLocalizations = AppLocalizations.of(context); final ThemeData themeData = Theme.of(context); - return Scaffold( - floatingActionButton: SmoothRevealAnimation( - delay: 400, - animationCurve: Curves.easeInOutBack, - child: FloatingActionButton.extended( - icon: SvgPicture.asset( - 'assets/actions/smoothie.svg', - width: 24.0, - height: 24.0, - color: Colors.black, - ), - label: Text( - appLocalizations.myPersonalizedRanking, - style: const TextStyle(color: Colors.black), - ), - backgroundColor: Colors.white, - onPressed: () => Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => PersonalizedRankingPage( - input: _continuousScanModel.getFoundProducts(), - ), - ), - ), - ), - ), - body: Stack( - children: [ - ContinuousScanPage.getHero(screenSize), - SmoothRevealAnimation( + return ChangeNotifierProvider.value( + value: _continuousScanModel, + child: Consumer( + builder: + (BuildContext context, ContinuousScanModel dummy, Widget child) => + Scaffold( + floatingActionButton: SmoothRevealAnimation( delay: 400, - startOffset: const Offset(0.0, 0.0), animationCurve: Curves.easeInOutBack, - child: QRView( - key: _scannerViewKey, - onQRViewCreated: (QRViewController controller) => - _continuousScanModel.setupScanner(controller), + child: FloatingActionButton.extended( + icon: SvgPicture.asset( + 'assets/actions/smoothie.svg', + width: 24.0, + height: 24.0, + color: Colors.black, + ), + label: Text( + appLocalizations.myPersonalizedRanking, + style: const TextStyle(color: Colors.black), + ), + backgroundColor: Colors.white, + onPressed: () => Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => + PersonalizedRankingPage(_continuousScanModel.productList), + ), + ), ), ), - SmoothRevealAnimation( - delay: 400, - startOffset: const Offset(0.0, 0.1), - animationCurve: Curves.easeInOutBack, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.max, - children: [ - Container( - height: screenSize.height * 0.3, - padding: const EdgeInsets.only(top: 32.0), - child: Column( - children: [ - ContinuousScanPage.getContributeChooseToggle( - _continuousScanModel), - ], - ), + body: Stack( + children: [ + ScanPage.getHero(screenSize), + SmoothRevealAnimation( + delay: 400, + startOffset: const Offset(0.0, 0.0), + animationCurve: Curves.easeInOutBack, + child: QRView( + key: _scannerViewKey, + onQRViewCreated: (QRViewController controller) => + _continuousScanModel.setupScanner(controller), ), - Center( - child: Container( - child: SmoothViewFinder( - width: screenSize.width * 0.8, - height: screenSize.width * 0.4, - animationDuration: 1500, - ), - ), - ), - if (_continuousScanModel.isNotEmpty) - Container( - height: screenSize.height * 0.35, - padding: EdgeInsets.only(bottom: screenSize.height * 0.08), - child: SmoothProductCarousel( - continuousScanModel: _continuousScanModel, + ), + SmoothRevealAnimation( + delay: 400, + startOffset: const Offset(0.0, 0.1), + animationCurve: Curves.easeInOutBack, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.max, + children: [ + Container( + height: screenSize.height * 0.3, + padding: const EdgeInsets.only(top: 32.0), + child: Column( + children: [ + ScanPage.getContributeChooseToggle( + _continuousScanModel), + ], + ), ), - ) - else - Container( - width: screenSize.width, - height: screenSize.height * 0.35, - padding: EdgeInsets.only(top: screenSize.height * 0.08), - child: Text( - appLocalizations.scannerProductsEmpty, - style: themeData.textTheme.subtitle1, - textAlign: TextAlign.center, + Center( + child: Container( + child: SmoothViewFinder( + width: screenSize.width * 0.8, + height: screenSize.width * 0.4, + animationDuration: 1500, + ), + ), ), - ), - ], - ), + if (_continuousScanModel.isNotEmpty) + Container( + height: screenSize.height * 0.35, + padding: + EdgeInsets.only(bottom: screenSize.height * 0.08), + child: SmoothProductCarousel( + continuousScanModel: _continuousScanModel, + ), + ) + else + Container( + width: screenSize.width, + height: screenSize.height * 0.35, + padding: EdgeInsets.only(top: screenSize.height * 0.08), + child: Text( + appLocalizations.scannerProductsEmpty, + style: themeData.textTheme.subtitle1, + textAlign: TextAlign.center, + ), + ), + ], + ), + ), + ], ), - ], + ), ), ); } diff --git a/packages/smooth_app/lib/pages/continuous_scan_page.dart b/packages/smooth_app/lib/pages/continuous_scan_page.dart index ebf8d22997f..60a0bce31e2 100644 --- a/packages/smooth_app/lib/pages/continuous_scan_page.dart +++ b/packages/smooth_app/lib/pages/continuous_scan_page.dart @@ -5,183 +5,138 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:provider/provider.dart'; import 'package:smooth_app/data_models/continuous_scan_model.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:smooth_app/database/local_database.dart'; import 'package:smooth_app/lists/smooth_product_carousel.dart'; import 'package:smooth_app/pages/personalized_ranking_page.dart'; +import 'package:smooth_app/pages/scan_page.dart'; import 'package:smooth_ui_library/animations/smooth_reveal_animation.dart'; -import 'package:smooth_ui_library/widgets/smooth_toggle.dart'; - import 'package:smooth_ui_library/widgets/smooth_view_finder.dart'; -class ContinuousScanPage extends StatefulWidget { - const ContinuousScanPage({this.initializeWithContributionMode = false}); - - final bool initializeWithContributionMode; - - @override - _ContinuousScanPageState createState() => _ContinuousScanPageState(); - - static Widget getContributeChooseToggle(final ContinuousScanModel model) => - SmoothToggle( - value: model.contributionMode, - textLeft: ' CONTRIBUTE', - textRight: 'CHOOSE ', - colorLeft: Colors.black.withAlpha(160), - colorRight: Colors.black.withAlpha(160), - iconLeft: SvgPicture.asset('assets/ikonate_bold/add.svg'), - iconRight: SvgPicture.asset('assets/ikonate_bold/search.svg'), - textSize: 12.0, - animationDuration: const Duration(milliseconds: 320), - width: 150.0, - height: 50.0, - onChanged: (bool value) => model.contributionModeSwitch(value), - ); - - static Widget getHero(final Size screenSize) => Hero( - tag: 'action_button', - child: Container( - width: screenSize.width, - height: screenSize.height, - color: Colors.black, - child: Center( - child: SvgPicture.asset( - 'assets/actions/scanner_alt_2.svg', - width: 60.0, - height: 60.0, - color: Colors.white, - ), - ), - ), - ); -} +class ContinuousScanPage extends StatelessWidget { + const ContinuousScanPage(this._continuousScanModel); -class _ContinuousScanPageState extends State { - ContinuousScanModel _continuousScanModel; - - @override - void initState() { - super.initState(); - _continuousScanModel = ContinuousScanModel( - contributionMode: widget.initializeWithContributionMode); - } + final ContinuousScanModel _continuousScanModel; @override Widget build(BuildContext context) { - final LocalDatabase localDatabase = context.watch(); - _continuousScanModel.setLocalDatabase(localDatabase); final Size screenSize = MediaQuery.of(context).size; final AppLocalizations appLocalizations = AppLocalizations.of(context); final ThemeData themeData = Theme.of(context); - return Scaffold( - floatingActionButton: SmoothRevealAnimation( - delay: 400, - animationCurve: Curves.easeInOutBack, - child: FloatingActionButton.extended( - icon: SvgPicture.asset( - 'assets/actions/smoothie.svg', - width: 24.0, - height: 24.0, - color: Colors.black, - ), - label: Text( - appLocalizations.myPersonalizedRanking, - style: const TextStyle(color: Colors.black), - ), - backgroundColor: Colors.white, - onPressed: () => Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => PersonalizedRankingPage( - input: _continuousScanModel.getFoundProducts(), - ), - ), - ), - ), - ), - body: Stack( - children: [ - ContinuousScanPage.getHero(screenSize), - SmoothRevealAnimation( + return ChangeNotifierProvider.value( + value: _continuousScanModel, + child: Consumer( + builder: + (BuildContext context, ContinuousScanModel dummy, Widget child) => + Scaffold( + floatingActionButton: SmoothRevealAnimation( delay: 400, - startOffset: const Offset(0.0, 0.0), animationCurve: Curves.easeInOutBack, - child: QRBarScannerCamera( - formats: const [ - BarcodeFormats.EAN_8, - BarcodeFormats.EAN_13 - ], - qrCodeCallback: (String code) => - _continuousScanModel.onScan(code), - notStartedBuilder: (BuildContext context) => Container(), + child: FloatingActionButton.extended( + icon: SvgPicture.asset( + 'assets/actions/smoothie.svg', + width: 24.0, + height: 24.0, + color: Colors.black, + ), + label: Text( + appLocalizations.myPersonalizedRanking, + style: const TextStyle(color: Colors.black), + ), + backgroundColor: Colors.white, + onPressed: () => Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => + PersonalizedRankingPage(_continuousScanModel.productList), + ), + ), ), ), - SmoothRevealAnimation( - delay: 400, - startOffset: const Offset(0.0, 0.1), - animationCurve: Curves.easeInOutBack, - child: Column( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Column( - children: [ - Padding( - padding: const EdgeInsets.only(top: 36.0), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ContinuousScanPage.getContributeChooseToggle( - _continuousScanModel), - ], - ), - ), - Padding( - padding: const EdgeInsets.only(top: 14.0), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SmoothViewFinder( - width: screenSize.width * 0.8, - height: screenSize.width * 0.45, - animationDuration: 1500, - ) - ], - ), - ), - ], - ), + body: Stack( + children: [ + ScanPage.getHero(screenSize), + SmoothRevealAnimation( + delay: 400, + startOffset: const Offset(0.0, 0.0), + animationCurve: Curves.easeInOutBack, + child: QRBarScannerCamera( + formats: const [ + BarcodeFormats.EAN_8, + BarcodeFormats.EAN_13 + ], + qrCodeCallback: (String code) => + _continuousScanModel.onScan(code), + notStartedBuilder: (BuildContext context) => Container(), ), - Padding( - padding: const EdgeInsets.only(bottom: 80.0), - child: _continuousScanModel.isNotEmpty - ? Container( - width: screenSize.width, - child: SmoothProductCarousel( - continuousScanModel: _continuousScanModel, - height: _continuousScanModel.contributionMode - ? 160.0 - : 120.0, + ), + SmoothRevealAnimation( + delay: 400, + startOffset: const Offset(0.0, 0.1), + animationCurve: Curves.easeInOutBack, + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 36.0), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ScanPage.getContributeChooseToggle( + _continuousScanModel), + ], + ), ), - ) - : Container( - width: screenSize.width, - height: screenSize.height * 0.5, - child: Center( - child: Text( - appLocalizations.scannerProductsEmpty, - style: themeData.textTheme.subtitle1, - textAlign: TextAlign.start, + Padding( + padding: const EdgeInsets.only(top: 14.0), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SmoothViewFinder( + width: screenSize.width * 0.8, + height: screenSize.width * 0.45, + animationDuration: 1500, + ) + ], ), ), - ), + ], + ), + ), + Padding( + padding: const EdgeInsets.only(bottom: 80.0), + child: _continuousScanModel.isNotEmpty + ? Container( + width: screenSize.width, + child: SmoothProductCarousel( + continuousScanModel: _continuousScanModel, + height: _continuousScanModel.contributionMode + ? 160.0 + : 120.0, + ), + ) + : Container( + width: screenSize.width, + height: screenSize.height * 0.5, + child: Center( + child: Text( + appLocalizations.scannerProductsEmpty, + style: themeData.textTheme.subtitle1, + textAlign: TextAlign.start, + ), + ), + ), + ), + ], ), - ], - ), + ), + ], ), - ], + ), ), ); } diff --git a/packages/smooth_app/lib/pages/contribution_page.dart b/packages/smooth_app/lib/pages/contribution_page.dart index 814cf523197..f1298a0ad3f 100644 --- a/packages/smooth_app/lib/pages/contribution_page.dart +++ b/packages/smooth_app/lib/pages/contribution_page.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:smooth_app/pages/alternative_continuous_scan_page.dart'; -import 'package:smooth_app/pages/continuous_scan_page.dart'; +import 'package:smooth_app/pages/scan_page.dart'; import 'package:smooth_app/temp/user_preferences.dart'; import 'package:smooth_ui_library/animations/smooth_reveal_animation.dart'; import 'package:smooth_ui_library/buttons/smooth_simple_button.dart'; @@ -44,12 +43,10 @@ class CollaborationPage extends StatelessWidget { onPressed: () => Navigator.push( context, MaterialPageRoute( - builder: (BuildContext context) => - userPreferences.getMlKitState() - ? const ContinuousScanPage( - initializeWithContributionMode: true) - : const AlternativeContinuousScanPage( - initializeWithContributionMode: true), + builder: (BuildContext context) => ScanPage( + contributionMode: true, + mlKit: userPreferences.getMlKitState(), + ), ), ), ), diff --git a/packages/smooth_app/lib/pages/personalized_ranking_page.dart b/packages/smooth_app/lib/pages/personalized_ranking_page.dart index a5a5da5b59b..deba1f2694a 100644 --- a/packages/smooth_app/lib/pages/personalized_ranking_page.dart +++ b/packages/smooth_app/lib/pages/personalized_ranking_page.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:openfoodfacts/model/Product.dart'; +import 'package:smooth_app/data_models/product_list.dart'; import 'package:provider/provider.dart'; import 'package:smooth_app/bottom_sheet_views/user_preferences_view.dart'; import 'package:smooth_app/cards/product_cards/smooth_product_card_found.dart'; @@ -13,9 +13,9 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:smooth_app/themes/constant_icons.dart'; class PersonalizedRankingPage extends StatefulWidget { - const PersonalizedRankingPage({@required this.input}); + const PersonalizedRankingPage(this.productList); - final List input; + final ProductList productList; @override _PersonalizedRankingPageState createState() => @@ -74,7 +74,7 @@ class _PersonalizedRankingPageState extends State { final UserPreferences userPreferences = context.watch(); final UserPreferencesModel userPreferencesModel = context.watch(); - _model.refresh(widget.input, userPreferences, userPreferencesModel); + _model.refresh(widget.productList, userPreferences, userPreferencesModel); final List bottomNavigationBarItems = []; for (final int matchIndex in _ORDERED_MATCH_INDEXES) { diff --git a/packages/smooth_app/lib/pages/product_page.dart b/packages/smooth_app/lib/pages/product_page.dart index 0c0aee167fe..77daebc2ab9 100644 --- a/packages/smooth_app/lib/pages/product_page.dart +++ b/packages/smooth_app/lib/pages/product_page.dart @@ -17,7 +17,6 @@ class ProductPage extends StatelessWidget { final Size screenSize = MediaQuery.of(context).size; final double iconWidth = screenSize.width / 10; // TODO(monsieurtanuki): target size? - final TextStyle dividerTextStyle = Theme.of(context).textTheme.headline2; return Scaffold( body: Stack( children: [ @@ -98,9 +97,6 @@ class ProductPage extends StatelessWidget { ], ), ), - _getDivider( - Text(appLocalizations.nutrition, style: dividerTextStyle), - ), AttributeListExpandable( product: product, iconWidth: iconWidth, @@ -109,7 +105,7 @@ class ProductPage extends StatelessWidget { UserPreferencesModel.ATTRIBUTE_VEGETARIAN, UserPreferencesModel.ATTRIBUTE_PALM_OIL_FREE, ], - title: 'Nutrition levels', + title: appLocalizations.nutrition, ), AttributeListExpandable( product: product, @@ -123,9 +119,6 @@ class ProductPage extends StatelessWidget { ], title: 'Nutrition levels', ), - _getDivider( - Text(appLocalizations.ingredients, style: dividerTextStyle), - ), AttributeListExpandable( product: product, iconWidth: iconWidth, @@ -133,10 +126,7 @@ class ProductPage extends StatelessWidget { UserPreferencesModel.ATTRIBUTE_NOVA, UserPreferencesModel.ATTRIBUTE_ADDITIVES, ], - title: 'Labels', - ), - _getDivider( - Text(appLocalizations.ecology, style: dividerTextStyle), + title: appLocalizations.ingredients, ), AttributeListExpandable( product: product, @@ -146,7 +136,7 @@ class ProductPage extends StatelessWidget { UserPreferencesModel.ATTRIBUTE_ORGANIC, UserPreferencesModel.ATTRIBUTE_FAIR_TRADE, ], - title: 'Labels', + title: appLocalizations.ecology, ), ], ), @@ -156,12 +146,4 @@ class ProductPage extends StatelessWidget { ), ); } - - Widget _getDivider(final Widget child) => Padding( - padding: const EdgeInsets.only( - top: 14.0, right: 16.0, left: 16.0, bottom: 8.0), - child: Row( - children: [Flexible(child: child)], - ), - ); } diff --git a/packages/smooth_app/lib/pages/product_query_page.dart b/packages/smooth_app/lib/pages/product_query_page.dart index 90fcbbb21c0..f5233c7dce4 100644 --- a/packages/smooth_app/lib/pages/product_query_page.dart +++ b/packages/smooth_app/lib/pages/product_query_page.dart @@ -15,6 +15,7 @@ import 'package:smooth_app/pages/personalized_ranking_page.dart'; import 'package:smooth_ui_library/animations/smooth_reveal_animation.dart'; import 'package:smooth_app/themes/constant_icons.dart'; import 'package:smooth_app/pages/product_query_page_helper.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class ProductQueryPage extends StatefulWidget { const ProductQueryPage({ @@ -170,7 +171,7 @@ class _ProductQueryPageState extends State { color: widget.mainColor, ), label: Text( - 'My personalized ranking', + AppLocalizations.of(context).myPersonalizedRanking, style: TextStyle(color: widget.mainColor), ), backgroundColor: Colors.white, @@ -178,9 +179,11 @@ class _ProductQueryPageState extends State { Navigator.push( context, MaterialPageRoute( - builder: (BuildContext context) => - PersonalizedRankingPage( - input: _model.displayProducts)), + builder: (BuildContext context) => + PersonalizedRankingPage( + _model.supplier.getProductList(), + ), + ), ); }, ), diff --git a/packages/smooth_app/lib/pages/scan_page.dart b/packages/smooth_app/lib/pages/scan_page.dart new file mode 100644 index 00000000000..fdc60e9da3d --- /dev/null +++ b/packages/smooth_app/lib/pages/scan_page.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:smooth_app/data_models/continuous_scan_model.dart'; +import 'package:smooth_app/database/local_database.dart'; +import 'package:smooth_app/pages/alternative_continuous_scan_page.dart'; +import 'package:smooth_app/pages/continuous_scan_page.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:smooth_ui_library/widgets/smooth_toggle.dart'; + +class ScanPage extends StatelessWidget { + const ScanPage({ + @required this.contributionMode, + @required this.mlKit, + }); + + final bool contributionMode; + final bool mlKit; + + @override + Widget build(BuildContext context) { + final LocalDatabase localDatabase = context.watch(); + return FutureBuilder( + future: ContinuousScanModel(contributionMode: contributionMode) + .load(localDatabase), + builder: (BuildContext context, + AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + final ContinuousScanModel continuousScanModel = snapshot.data; + if (continuousScanModel != null) { + return mlKit + ? ContinuousScanPage(continuousScanModel) + : AlternativeContinuousScanPage(continuousScanModel); + } + } + return const Center(child: CircularProgressIndicator()); + }); + } + + static Widget getContributeChooseToggle(final ContinuousScanModel model) => + SmoothToggle( + value: model.contributionMode, + textLeft: ' CONTRIBUTE', + textRight: 'CHOOSE ', + colorLeft: Colors.black.withAlpha(160), + colorRight: Colors.black.withAlpha(160), + iconLeft: SvgPicture.asset('assets/ikonate_bold/add.svg'), + iconRight: SvgPicture.asset('assets/ikonate_bold/search.svg'), + textSize: 12.0, + animationDuration: const Duration(milliseconds: 320), + width: 150.0, + height: 50.0, + onChanged: (bool value) => model.contributionModeSwitch(value), + ); + + static Widget getHero(final Size screenSize) => Hero( + tag: 'action_button', + child: Container( + width: screenSize.width, + height: screenSize.height, + color: Colors.black, + child: Center( + child: SvgPicture.asset( + 'assets/actions/scanner_alt_2.svg', + width: 60.0, + height: 60.0, + color: Colors.white, + ), + ), + ), + ); +}