diff --git a/packages/smooth_app/lib/cards/product_cards/knowledge_panels/knowledge_panel_card.dart b/packages/smooth_app/lib/cards/product_cards/knowledge_panels/knowledge_panel_card.dart index 7712df6ccae..428069c0c78 100644 --- a/packages/smooth_app/lib/cards/product_cards/knowledge_panels/knowledge_panel_card.dart +++ b/packages/smooth_app/lib/cards/product_cards/knowledge_panels/knowledge_panel_card.dart @@ -2,11 +2,9 @@ import 'package:flutter/material.dart'; import 'package:openfoodfacts/model/KnowledgePanel.dart'; import 'package:openfoodfacts/model/KnowledgePanels.dart'; import 'package:smooth_app/cards/product_cards/knowledge_panels/knowledge_panel_expanded_card.dart'; +import 'package:smooth_app/cards/product_cards/knowledge_panels/knowledge_panel_full_page.dart'; import 'package:smooth_app/cards/product_cards/knowledge_panels/knowledge_panel_summary_card.dart'; -import 'package:smooth_app/generic_lib/design_constants.dart'; -import 'package:smooth_app/generic_lib/widgets/smooth_card.dart'; import 'package:smooth_app/helpers/analytics_helper.dart'; -import 'package:smooth_app/themes/smooth_theme.dart'; class KnowledgePanelCard extends StatelessWidget { const KnowledgePanelCard({ @@ -19,7 +17,6 @@ class KnowledgePanelCard extends StatelessWidget { @override Widget build(BuildContext context) { - final ThemeData themeData = Theme.of(context); // If [expanded] = true, render all panel elements (including summary), otherwise just renders panel summary. if (panel.expanded ?? false) { return KnowledgePanelExpandedCard( @@ -36,24 +33,9 @@ class KnowledgePanelCard extends StatelessWidget { Navigator.push( context, MaterialPageRoute( - builder: (BuildContext context) => Scaffold( - backgroundColor: SmoothTheme.getColor( - themeData.colorScheme, - SmoothTheme.getMaterialColor(context), - ColorDestination.SURFACE_BACKGROUND, - ), - appBar: AppBar(), - body: SingleChildScrollView( - child: SmoothCard( - padding: const EdgeInsets.all( - SMALL_SPACE, - ), - child: KnowledgePanelExpandedCard( - panel: panel, - allPanels: allPanels, - ), - ), - ), + builder: (BuildContext context) => KnowledgePanelFullPage( + panel: panel, + allPanels: allPanels, ), ), ); diff --git a/packages/smooth_app/lib/cards/product_cards/knowledge_panels/knowledge_panel_full_loading_page.dart b/packages/smooth_app/lib/cards/product_cards/knowledge_panels/knowledge_panel_full_loading_page.dart new file mode 100644 index 00000000000..460ff064269 --- /dev/null +++ b/packages/smooth_app/lib/cards/product_cards/knowledge_panels/knowledge_panel_full_loading_page.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:openfoodfacts/model/KnowledgePanel.dart'; +import 'package:openfoodfacts/model/KnowledgePanels.dart'; +import 'package:provider/provider.dart'; +import 'package:smooth_app/cards/product_cards/knowledge_panels/knowledge_panel_full_page.dart'; +import 'package:smooth_app/data_models/data_provider.dart'; + +class KnowledgePanelFullLoadingPage extends StatelessWidget { + const KnowledgePanelFullLoadingPage( + {required this.panelId, required this.barcode}); + + final String panelId; + final String barcode; + + @override + Widget build(BuildContext context) { + final KnowledgePanels? knowledgePanels = context + .select>, KnowledgePanels?>( + (DataProvider> value) => + value.value[barcode]); + + if (knowledgePanels == null) { + return Scaffold( + appBar: AppBar( + title: + Text(AppLocalizations.of(context)!.loading_dialog_default_title), + ), + body: const Center( + child: CircularProgressIndicator(), + ), + ); + } + + final KnowledgePanel? knowledgePanel = + knowledgePanels.panelIdToPanelMap[panelId]; + + if (knowledgePanel == null) { + Future.delayed(Duration.zero, () { + Navigator.pop(context); + }); + return Container(); + } + Future.delayed(Duration.zero, () { + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (BuildContext context) => KnowledgePanelFullPage( + panel: knowledgePanel, + allPanels: knowledgePanels, + ), + ), + ); + }); + + return Scaffold( + body: Container(), + ); + } +} diff --git a/packages/smooth_app/lib/cards/product_cards/knowledge_panels/knowledge_panel_full_page.dart b/packages/smooth_app/lib/cards/product_cards/knowledge_panels/knowledge_panel_full_page.dart new file mode 100644 index 00000000000..9e83cd74880 --- /dev/null +++ b/packages/smooth_app/lib/cards/product_cards/knowledge_panels/knowledge_panel_full_page.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:openfoodfacts/model/KnowledgePanel.dart'; +import 'package:openfoodfacts/model/KnowledgePanels.dart'; +import 'package:smooth_app/cards/product_cards/knowledge_panels/knowledge_panel_expanded_card.dart'; +import 'package:smooth_app/generic_lib/design_constants.dart'; +import 'package:smooth_app/generic_lib/widgets/smooth_card.dart'; +import 'package:smooth_app/themes/smooth_theme.dart'; + +class KnowledgePanelFullPage extends StatelessWidget { + const KnowledgePanelFullPage({ + required this.panel, + required this.allPanels, + }); + + final KnowledgePanel panel; + final KnowledgePanels allPanels; + + @override + Widget build(BuildContext context) { + final ThemeData themeData = Theme.of(context); + return Scaffold( + backgroundColor: SmoothTheme.getColor( + themeData.colorScheme, + SmoothTheme.getMaterialColor(context), + ColorDestination.SURFACE_BACKGROUND, + ), + appBar: AppBar( + title: Text(panel.titleElement?.title ?? ''), + ), + body: SingleChildScrollView( + child: SmoothCard( + padding: const EdgeInsets.all( + SMALL_SPACE, + ), + child: KnowledgePanelExpandedCard( + panel: panel, + allPanels: allPanels, + ), + ), + ), + ); + } +} diff --git a/packages/smooth_app/lib/data_models/data_provider.dart b/packages/smooth_app/lib/data_models/data_provider.dart new file mode 100644 index 00000000000..f86eca78b09 --- /dev/null +++ b/packages/smooth_app/lib/data_models/data_provider.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; + +class DataProvider with ChangeNotifier { + DataProvider(this._value); + + T _value; + + T get value => _value; + + void setValue(T newValue) { + _value = newValue; + notifyListeners(); + } +} diff --git a/packages/smooth_app/lib/data_models/user_management_provider.dart b/packages/smooth_app/lib/data_models/user_management_provider.dart index cf22d3d60f2..6ae960b0416 100644 --- a/packages/smooth_app/lib/data_models/user_management_provider.dart +++ b/packages/smooth_app/lib/data_models/user_management_provider.dart @@ -1,4 +1,4 @@ -import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:openfoodfacts/utils/OpenFoodAPIConfiguration.dart'; import 'package:smooth_app/database/dao_secured_string.dart'; diff --git a/packages/smooth_app/lib/helpers/smooth_matched_product.dart b/packages/smooth_app/lib/helpers/smooth_matched_product.dart index e8e7c28bd01..4e324b009be 100644 --- a/packages/smooth_app/lib/helpers/smooth_matched_product.dart +++ b/packages/smooth_app/lib/helpers/smooth_matched_product.dart @@ -4,8 +4,8 @@ import 'package:openfoodfacts/model/Product.dart'; import 'package:openfoodfacts/personalized_search/preference_importance.dart'; import 'package:openfoodfacts/personalized_search/product_preferences_manager.dart'; import 'package:smooth_app/data_models/user_preferences.dart'; +import 'package:smooth_app/helpers/attributes_card_helper.dart'; import 'package:smooth_app/pages/user_preferences_dev_mode.dart'; -import 'attributes_card_helper.dart'; /// Match and score of a [Product] vs. Preferences /// diff --git a/packages/smooth_app/lib/main.dart b/packages/smooth_app/lib/main.dart index f2131260796..10a23a8d142 100644 --- a/packages/smooth_app/lib/main.dart +++ b/packages/smooth_app/lib/main.dart @@ -8,12 +8,14 @@ import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart'; +import 'package:openfoodfacts/model/KnowledgePanels.dart'; import 'package:openfoodfacts/model/UserAgent.dart'; import 'package:openfoodfacts/personalized_search/product_preferences_selection.dart'; import 'package:openfoodfacts/utils/OpenFoodAPIConfiguration.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:provider/provider.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:smooth_app/data_models/data_provider.dart'; import 'package:smooth_app/data_models/product_preferences.dart'; import 'package:smooth_app/data_models/user_management_provider.dart'; import 'package:smooth_app/data_models/user_preferences.dart'; @@ -167,6 +169,12 @@ class _SmoothAppState extends State { provide(_localDatabase), provide(_themeProvider), provide(_userManagementProvider), + //Needs to be created here to be visible after calling Navigator.push + provide>>( + DataProvider>( + {}, + ), + ), ], builder: _buildApp, ); diff --git a/packages/smooth_app/lib/pages/product/new_product_page.dart b/packages/smooth_app/lib/pages/product/new_product_page.dart index 520c5097f42..f2ff5f4581c 100644 --- a/packages/smooth_app/lib/pages/product/new_product_page.dart +++ b/packages/smooth_app/lib/pages/product/new_product_page.dart @@ -4,8 +4,8 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:openfoodfacts/model/KnowledgePanels.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:provider/provider.dart'; -import 'package:smooth_app/cards/product_cards/knowledge_panels/knowledge_panels_builder.dart'; import 'package:smooth_app/cards/product_cards/product_image_carousel.dart'; +import 'package:smooth_app/data_models/data_provider.dart'; import 'package:smooth_app/data_models/fetched_product.dart'; import 'package:smooth_app/data_models/product_list.dart'; import 'package:smooth_app/data_models/product_preferences.dart'; @@ -23,7 +23,7 @@ import 'package:smooth_app/pages/product/category_picker_page.dart'; import 'package:smooth_app/pages/product/common/product_dialog_helper.dart'; import 'package:smooth_app/pages/product/common/product_list_page.dart'; import 'package:smooth_app/pages/product/edit_product_page.dart'; -import 'package:smooth_app/pages/product/knowledge_panel_product_cards.dart'; +import 'package:smooth_app/pages/product/product_knowledge_panels.dart'; import 'package:smooth_app/pages/product/summary_card.dart'; import 'package:smooth_app/pages/product_list_user_dialog_helper.dart'; import 'package:smooth_app/pages/user_preferences_dev_mode.dart'; @@ -39,8 +39,6 @@ class ProductPage extends StatefulWidget { State createState() => _ProductPageState(); } -enum ProductPageMenuItem { WEB, REFRESH } - class _ProductPageState extends State { late Product _product; late ProductPreferences _productPreferences; @@ -63,28 +61,48 @@ class _ProductPageState extends State { final ThemeData themeData = Theme.of(context); final ColorScheme colorScheme = themeData.colorScheme; final MaterialColor materialColor = SmoothTheme.getMaterialColor(context); - return Scaffold( - backgroundColor: SmoothTheme.getColor( - colorScheme, - materialColor, - ColorDestination.SURFACE_BACKGROUND, - ), - floatingActionButton: scrollingUp - ? FloatingActionButton( - backgroundColor: colorScheme.primary, - onPressed: () { - Navigator.maybePop(context); - }, - child: Icon( - ConstantIcons.instance.getBackIcon(), - color: Colors.white, - ), - ) - : null, - floatingActionButtonLocation: FloatingActionButtonLocation.startTop, - body: Stack( - children: [ - NotificationListener( + + final DataProvider> knowledgePanelsProvider = + context.read>>(); + + KnowledgePanelsQuery( + barcode: _product.barcode!, + ).getKnowledgePanels().then((KnowledgePanels value) { + final Map data = knowledgePanelsProvider.value; + data.putIfAbsent(_product.barcode!, () => value); + knowledgePanelsProvider.setValue(data); + }); + + return WillPopScope( + onWillPop: () async { + final Map data = + knowledgePanelsProvider.value; + data.remove(_product.barcode); + knowledgePanelsProvider.setValue(data); + return true; + }, + child: Scaffold( + backgroundColor: SmoothTheme.getColor( + colorScheme, + materialColor, + ColorDestination.SURFACE_BACKGROUND, + ), + floatingActionButton: scrollingUp + ? FloatingActionButton( + backgroundColor: colorScheme.primary, + onPressed: () { + Navigator.maybePop(context); + }, + child: Icon( + ConstantIcons.instance.getBackIcon(), + color: Colors.white, + ), + ) + : null, + floatingActionButtonLocation: FloatingActionButtonLocation.startTop, + body: Stack( + children: [ + NotificationListener( onNotification: (UserScrollNotification notification) { if (notification.direction == ScrollDirection.forward) { if (!scrollingUp) { @@ -101,8 +119,10 @@ class _ProductPageState extends State { } return true; }, - child: _buildProductBody(context)), - ], + child: _buildProductBody(context), + ), + ], + ), ), ); } @@ -173,7 +193,10 @@ class _ProductPageState extends State { ), ), ), - _buildKnowledgePanelCards(), + ProductPageKnowledgePanels( + product: _product, + setState: setState, + ), _buildActionBar(appLocalizations), if (productListNames.isNotEmpty) _buildListWidget(appLocalizations, productListNames, daoProductList), @@ -219,59 +242,12 @@ class _ProductPageState extends State { setState(() {}); } }, - child: const Text('Additional Button'), + child: const Text('Additional Button (CategoryPicker)'), ), ]), ); } - FutureBuilder _buildKnowledgePanelCards() { - // Note that this will make a new request on every rebuild. - // TODO(jasmeet): Avoid additional requests on rebuilds. - final Future knowledgePanels = KnowledgePanelsQuery( - barcode: _product.barcode!, - ).getKnowledgePanels(); - return FutureBuilder( - future: knowledgePanels, - builder: - (BuildContext context, AsyncSnapshot snapshot) { - List knowledgePanelWidgets = []; - if (snapshot.hasData) { - // Render all KnowledgePanels - knowledgePanelWidgets = - KnowledgePanelsBuilder(setState: () => setState(() {})) - .buildAll( - snapshot.data!, - context: context, - product: _product, - ); - } else if (snapshot.hasError) { - // TODO(jasmeet): Retry the request. - // Do nothing for now. - } else { - // Query results not available yet. - knowledgePanelWidgets = [_buildLoadingWidget()]; - } - return KnowledgePanelProductCards(knowledgePanelWidgets); - }); - } - - Widget _buildLoadingWidget() { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: const [ - SizedBox( - child: CircularProgressIndicator(), - width: 60, - height: 60, - ), - ], - ), - ); - } - Future _editList() async { final LocalDatabase localDatabase = context.read(); final DaoProductList daoProductList = DaoProductList(localDatabase); diff --git a/packages/smooth_app/lib/pages/product/product_knowledge_panels.dart b/packages/smooth_app/lib/pages/product/product_knowledge_panels.dart new file mode 100644 index 00000000000..fc94903ecf4 --- /dev/null +++ b/packages/smooth_app/lib/pages/product/product_knowledge_panels.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:openfoodfacts/model/KnowledgePanels.dart'; +import 'package:openfoodfacts/model/Product.dart'; +import 'package:provider/provider.dart'; +import 'package:smooth_app/cards/product_cards/knowledge_panels/knowledge_panels_builder.dart'; +import 'package:smooth_app/data_models/data_provider.dart'; +import 'package:smooth_app/pages/product/knowledge_panel_product_cards.dart'; + +// Just to be called from the product page with the right provider set up +class ProductPageKnowledgePanels extends StatelessWidget { + const ProductPageKnowledgePanels({ + required this.product, + required this.setState, + }); + + final Function(Function()) setState; + final Product product; + + @override + Widget build(BuildContext context) { + final KnowledgePanels? knowledgePanels = context + .select>, KnowledgePanels?>( + (DataProvider> value) => + value.value[product.barcode]); + + final List knowledgePanelWidgets; + + if (knowledgePanels != null) { + // Render all KnowledgePanels + knowledgePanelWidgets = + KnowledgePanelsBuilder(setState: () => setState(() {})).buildAll( + knowledgePanels, + context: context, + product: product, + ); + } else { + // Query results not available yet. + knowledgePanelWidgets = [ + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: const [ + SizedBox( + child: CircularProgressIndicator(), + width: 60, + height: 60, + ), + ], + ), + ), + ]; + } + return KnowledgePanelProductCards(knowledgePanelWidgets); + } +} diff --git a/packages/smooth_app/lib/pages/product/summary_card.dart b/packages/smooth_app/lib/pages/product/summary_card.dart index 3b082be69b6..1ad4535149e 100644 --- a/packages/smooth_app/lib/pages/product/summary_card.dart +++ b/packages/smooth_app/lib/pages/product/summary_card.dart @@ -2,10 +2,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:openfoodfacts/model/Attribute.dart'; import 'package:openfoodfacts/model/AttributeGroup.dart'; +import 'package:openfoodfacts/model/KnowledgePanels.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:openfoodfacts/personalized_search/preference_importance.dart'; import 'package:provider/provider.dart'; import 'package:smooth_app/cards/data_cards/score_card.dart'; +import 'package:smooth_app/cards/product_cards/knowledge_panels/knowledge_panel_full_loading_page.dart'; import 'package:smooth_app/cards/product_cards/product_title_card.dart'; import 'package:smooth_app/data_models/product_preferences.dart'; import 'package:smooth_app/data_models/user_preferences.dart'; @@ -76,10 +78,7 @@ class _SummaryCardState extends State { final Set _attributesToExcludeIfStatusIsUnknown = {}; bool _annotationVoted = false; - @override - void initState() { - super.initState(); - } + KnowledgePanels? knowledgePanels; @override Widget build(BuildContext context) { @@ -403,17 +402,39 @@ class _SummaryCardState extends State { return null; } return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - return SizedBox( - width: constraints.maxWidth / 2, - child: Row( + builder: (BuildContext context, BoxConstraints constraints) { + return InkWell( + enableFeedback: widget.isFullVersion, + onTap: () async { + if (!widget.isFullVersion || attribute.panelId == null) { + return; + } + + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => + KnowledgePanelFullLoadingPage( + panelId: attribute.panelId!, + barcode: widget._product.barcode!, + ), + ), + ); + }, + child: SizedBox( + width: constraints.maxWidth / 2, + child: Row( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ attributeIcon, Expanded(child: Text(attributeDisplayTitle).selectable()), - ])); - }); + ], + ), + ), + ); + }, + ); } /// Returns the mandatory attributes, ordered by attribute group order diff --git a/packages/smooth_app/pubspec.lock b/packages/smooth_app/pubspec.lock index e44b4953795..8e3099e5c3f 100644 --- a/packages/smooth_app/pubspec.lock +++ b/packages/smooth_app/pubspec.lock @@ -91,7 +91,7 @@ packages: name: camera_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.1.5" + version: "2.1.6" camera_web: dependency: transitive description: @@ -459,7 +459,7 @@ packages: name: image_picker_ios url: "https://pub.dartlang.org" source: hosted - version: "0.8.4+11" + version: "0.8.5" image_picker_platform_interface: dependency: transitive description: