From 1a2a8ab38979f0a3a866f9b089f6498f78a5cecd Mon Sep 17 00:00:00 2001 From: Pierre Slamich Date: Wed, 30 Oct 2024 16:21:50 +0100 Subject: [PATCH 01/16] ci: Update and rename github-projects-for-openfoodfacts-design.yml to github-projects-ventilation.yml --- ...design.yml => github-projects-ventilation.yml} | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) rename .github/workflows/{github-projects-for-openfoodfacts-design.yml => github-projects-ventilation.yml} (89%) diff --git a/.github/workflows/github-projects-for-openfoodfacts-design.yml b/.github/workflows/github-projects-ventilation.yml similarity index 89% rename from .github/workflows/github-projects-for-openfoodfacts-design.yml rename to .github/workflows/github-projects-ventilation.yml index 72f2ef027c11..03c383f1f4e3 100644 --- a/.github/workflows/github-projects-for-openfoodfacts-design.yml +++ b/.github/workflows/github-projects-ventilation.yml @@ -4,8 +4,21 @@ on: issues: types: - opened - - labeled - edited + - deleted + - transferred + - pinned + - unpinned + - closed + - reopened + - assigned + - unassigned + - labeled + - unlabeled + - locked + - unlocked + - milestoned + - demilestoned jobs: add-to-project: name: Add issues to the relevant GitHub projects From 767fccb0dba00362324417a5e858b311c0b96825 Mon Sep 17 00:00:00 2001 From: Pierre Slamich Date: Fri, 1 Nov 2024 14:50:31 +0100 Subject: [PATCH 02/16] ci: Update labeler.yml --- .github/labeler.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/labeler.yml b/.github/labeler.yml index 0000fd33d44c..8766331ba561 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -287,3 +287,7 @@ Prices: router: - changed-files: - any-glob-to-any-file: 'packages/smooth_app/lib/pages/navigator/app_navigator.dart' + +🛣️ Road to scores: +- changed-files: + - any-glob-to-any-file: 'packages/smooth_app/lib/pages/product/product_incomplete_card.dart' From 66a3492531a84c0fa1593e0b2e9ef8526386d613 Mon Sep 17 00:00:00 2001 From: monsieurtanuki Date: Fri, 1 Nov 2024 14:58:15 +0100 Subject: [PATCH 03/16] feat: 5621 - "road to scores" label now depends on OxF (#5772) Impacted files: * `app_en.arb`: added labels for OxF versions * `product_incomplete_card.dart`: button label depends on OxF * `product_query.dart`: new `getRoadToScoreLabel` method --- packages/smooth_app/lib/l10n/app_en.arb | 3 +++ .../lib/pages/product/product_incomplete_card.dart | 7 ++++++- packages/smooth_app/lib/query/product_query.dart | 11 +++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/smooth_app/lib/l10n/app_en.arb b/packages/smooth_app/lib/l10n/app_en.arb index 5548df21bad4..9199049a4f01 100644 --- a/packages/smooth_app/lib/l10n/app_en.arb +++ b/packages/smooth_app/lib/l10n/app_en.arb @@ -682,6 +682,9 @@ "description": "Button at the end of new product page, that takes you to completed product" }, "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", + "hey_incomplete_product_message_beauty": "Tap now to answer 2 questions to help analyze this cosmetic!", + "hey_incomplete_product_message_pet_food": "Tap now to answer 3 questions to help analyze this pet food product!", + "hey_incomplete_product_message_product": "Tap now to help complete this product!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling photo", diff --git a/packages/smooth_app/lib/pages/product/product_incomplete_card.dart b/packages/smooth_app/lib/pages/product/product_incomplete_card.dart index 9b9468f40298..36a86d0f845f 100644 --- a/packages/smooth_app/lib/pages/product/product_incomplete_card.dart +++ b/packages/smooth_app/lib/pages/product/product_incomplete_card.dart @@ -6,6 +6,7 @@ import 'package:smooth_app/helpers/analytics_helper.dart'; import 'package:smooth_app/pages/product/add_new_product_page.dart'; import 'package:smooth_app/pages/product/product_field_editor.dart'; import 'package:smooth_app/pages/product/simple_input_page_helpers.dart'; +import 'package:smooth_app/query/product_query.dart'; /// "Incomplete product!" card to be displayed in product summary, if relevant. /// @@ -99,7 +100,11 @@ class ProductIncompleteCard extends StatelessWidget { child: ElevatedButton.icon( label: Padding( padding: const EdgeInsets.symmetric(vertical: SMALL_SPACE), - child: Text(appLocalizations.hey_incomplete_product_message), + child: Text( + (product.productType ?? ProductType.food).getRoadToScoreLabel( + appLocalizations, + ), + ), ), icon: const Icon( Icons.bolt, diff --git a/packages/smooth_app/lib/query/product_query.dart b/packages/smooth_app/lib/query/product_query.dart index 315942d1ecf7..76169110f7f6 100644 --- a/packages/smooth_app/lib/query/product_query.dart +++ b/packages/smooth_app/lib/query/product_query.dart @@ -307,4 +307,15 @@ extension ProductTypeExtension on ProductType { ProductType.petFood => appLocalizations.product_type_label_pet_food, ProductType.product => appLocalizations.product_type_label_product, }; + + String getRoadToScoreLabel(final AppLocalizations appLocalizations) => + switch (this) { + ProductType.food => appLocalizations.hey_incomplete_product_message, + ProductType.beauty => + appLocalizations.hey_incomplete_product_message_beauty, + ProductType.petFood => + appLocalizations.hey_incomplete_product_message_pet_food, + ProductType.product => + appLocalizations.hey_incomplete_product_message_product, + }; } From ef4b2e9141074a07cde96974bcd06952e474ac0a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 1 Nov 2024 15:29:00 +0100 Subject: [PATCH 04/16] Update assets (#5769) Co-authored-by: M123-dev --- .../smooth_app/assets/onboarding/sample_product_data.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/smooth_app/assets/onboarding/sample_product_data.json b/packages/smooth_app/assets/onboarding/sample_product_data.json index c364b4013e54..96286db761e6 100644 --- a/packages/smooth_app/assets/onboarding/sample_product_data.json +++ b/packages/smooth_app/assets/onboarding/sample_product_data.json @@ -1269,7 +1269,7 @@ { "element_type" : "text", "text_element" : { - "html" : "\n Life cycle analysis score: 98
\n Sum of bonuses and maluses:\n \n +13\n

\n Final score: 100/100\n \n ", + "html" : "\n Life cycle analysis score: 98
\n Sum of bonuses and maluses:\n \n +13\n

\n Final score: 100/100\n ", "type" : "summary" } } @@ -2169,7 +2169,7 @@ }, { "evaluation" : "good", - "text" : "-68%" + "text" : "-69%" } ] }, @@ -2185,7 +2185,7 @@ }, { "evaluation" : "good", - "text" : "-41%" + "text" : "-42%" } ] }, From 8608fa4ad3db76d57e50f59d6b4878fbac1be74f Mon Sep 17 00:00:00 2001 From: Edouard Marquez Date: Sat, 2 Nov 2024 09:11:41 +0100 Subject: [PATCH 05/16] fix: Robotoff banner: bring back the pre-Material 3 visual (#5774) * Robotoff banner: bring back the pre-Material 3 visual * Light shadow * Remove the falsy claim about badges --- packages/smooth_app/lib/l10n/app_en.arb | 4 +- .../product/product_questions_widget.dart | 113 ++++++++++-------- 2 files changed, 63 insertions(+), 54 deletions(-) diff --git a/packages/smooth_app/lib/l10n/app_en.arb b/packages/smooth_app/lib/l10n/app_en.arb index 9199049a4f01..2f2a499b7125 100644 --- a/packages/smooth_app/lib/l10n/app_en.arb +++ b/packages/smooth_app/lib/l10n/app_en.arb @@ -408,9 +408,9 @@ "@saving_answer": { "description": "Dialog shown to users after they have answered a question, while the answer is being saved in the BE." }, - "contribute_to_get_rewards": "Help improve food transparency and get rewards", + "contribute_to_get_rewards": "Become an actor of food transparency", "@contribute_to_get_rewards": { - "description": "Button description shown on a product, clicking the button opens a card with unanswered product questions, users can answer these to contribute to Open Food Facts and gain rewards." + "description": "Button description shown on a product, clicking the button opens a card with unanswered product questions, users can answer these to contribute to Open Food Facts." }, "question_sign_in_text": "Sign in to your Open Food Facts account to get credit for your contributions", "question_yes_button_accessibility_value": "Answer with yes", diff --git a/packages/smooth_app/lib/pages/product/product_questions_widget.dart b/packages/smooth_app/lib/pages/product/product_questions_widget.dart index 5bcb07d2ff02..fe0a55fc846e 100644 --- a/packages/smooth_app/lib/pages/product/product_questions_widget.dart +++ b/packages/smooth_app/lib/pages/product/product_questions_widget.dart @@ -12,6 +12,7 @@ import 'package:smooth_app/helpers/analytics_helper.dart'; import 'package:smooth_app/helpers/robotoff_insight_helper.dart'; import 'package:smooth_app/pages/hunger_games/question_page.dart'; import 'package:smooth_app/query/product_questions_query.dart'; +import 'package:smooth_app/themes/theme_provider.dart'; class ProductQuestionsWidget extends StatefulWidget { const ProductQuestionsWidget( @@ -313,12 +314,13 @@ class _ProductQuestionBanner extends StatelessWidget { @override Widget build(BuildContext context) { final AppLocalizations appLocalizations = AppLocalizations.of(context); - final bool isDarkMode = Theme.of(context).brightness == Brightness.dark; - final Color contentColor = isDarkMode ? Colors.black : WHITE_COLOR; + final bool darkTheme = context.darkTheme(); + final Color contentColor = darkTheme ? Colors.black : WHITE_COLOR; // We need to differentiate with / without a Shimmer, because // [Shimmer] doesn't support [Ink] - final Color backgroundColor = Theme.of(context).colorScheme.primary; + final ThemeData theme = Theme.of(context); + final Color backgroundColor = theme.colorScheme.primary; final Widget child; if (state is! _ProductQuestionsWithQuestions) { @@ -327,59 +329,66 @@ class _ProductQuestionBanner extends StatelessWidget { child: EMPTY_WIDGET, ); } else { - child = Semantics( - value: appLocalizations.tap_to_answer_hint, - button: true, - excludeSemantics: true, - child: Material( - type: MaterialType.transparency, - child: InkWell( - onTap: openQuestionsCallback, - child: Ink( - width: double.infinity, - color: backgroundColor, - padding: const EdgeInsetsDirectional.symmetric( - vertical: SMALL_SPACE, - horizontal: VERY_LARGE_SPACE, - ).add( - EdgeInsetsDirectional.only( - bottom: MediaQuery.viewPaddingOf(context).bottom, + child = DecoratedBox( + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: theme.shadowColor.withOpacity(darkTheme ? 0.5 : 0.2), + blurRadius: 2.5, + offset: const Offset(0.0, -4.0), + ), + ], + ), + child: Semantics( + value: appLocalizations.tap_to_answer_hint, + button: true, + excludeSemantics: true, + child: Material( + type: MaterialType.transparency, + child: InkWell( + onTap: openQuestionsCallback, + child: Ink( + width: double.infinity, + color: backgroundColor, + padding: const EdgeInsetsDirectional.symmetric( + vertical: SMALL_SPACE, + horizontal: MEDIUM_SPACE, + ).add( + EdgeInsetsDirectional.only( + bottom: MediaQuery.viewPaddingOf(context).bottom, + ), ), - ), - child: Row( - children: [ - const _ProductQuestionIcon(), - Expanded( - child: RichText( - text: TextSpan( - text: '${appLocalizations.tap_to_answer}\n', - style: Theme.of(context) - .primaryTextTheme - .bodyLarge! - .copyWith( - color: contentColor, - height: 1.5, - ), - children: [ - TextSpan( - text: appLocalizations.contribute_to_get_rewards, - style: Theme.of(context) - .primaryTextTheme - .bodyMedium! - .copyWith( - color: contentColor, - ), + child: Row( + children: [ + const _ProductQuestionIcon(), + Expanded( + child: RichText( + text: TextSpan( + text: '${appLocalizations.tap_to_answer}\n', + style: theme.primaryTextTheme.bodyLarge!.copyWith( + color: contentColor, + height: 1.5, + fontWeight: FontWeight.bold, ), - ], + children: [ + TextSpan( + text: appLocalizations.contribute_to_get_rewards, + style: + theme.primaryTextTheme.bodyMedium!.copyWith( + color: contentColor, + ), + ), + ], + ), ), ), - ), - Icon( - Icons.arrow_circle_right_outlined, - color: contentColor, - size: 20.0, - ) - ], + Icon( + Icons.arrow_circle_right_outlined, + color: contentColor, + size: 20.0, + ) + ], + ), ), ), ), From 93b3503c1a5a175b7eb4126ae94f6f978db4703a Mon Sep 17 00:00:00 2001 From: Edouard Marquez Date: Sat, 2 Nov 2024 09:48:09 +0100 Subject: [PATCH 06/16] Location map page appbar fixes (#5775) --- .../pages/locations/location_map_page.dart | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/packages/smooth_app/lib/pages/locations/location_map_page.dart b/packages/smooth_app/lib/pages/locations/location_map_page.dart index 16e4bdd2845b..7b8367a4a122 100644 --- a/packages/smooth_app/lib/pages/locations/location_map_page.dart +++ b/packages/smooth_app/lib/pages/locations/location_map_page.dart @@ -26,21 +26,14 @@ class LocationMapPage extends StatelessWidget { return SmoothScaffold( appBar: SmoothAppBar( title: title == null ? null : Text(title), - subTitle: subtitle == null ? null : Text(subtitle), + subTitle: subtitle == null + ? null + : Text( + subtitle, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), actions: [ - IconButton( - icon: const Icon(Icons.check), - onPressed: () { - // pops that map page - Navigator.of(context).pop(); - if (popFirst) { - // pops the result page - Navigator.of(context).pop(); - } - // returns the result - Navigator.of(context).pop(osmLocation); - }, - ), IconButton( icon: const Icon(Icons.info), onPressed: () => showCupertinoModalPopup( @@ -66,6 +59,19 @@ class LocationMapPage extends StatelessWidget { ), ), ), + IconButton( + icon: const Icon(Icons.check), + onPressed: () { + // pops that map page + Navigator.of(context).pop(); + if (popFirst) { + // pops the result page + Navigator.of(context).pop(); + } + // returns the result + Navigator.of(context).pop(osmLocation); + }, + ), ], ), body: FlutterMap( From 0cc9e47c3fe9006623c48ca8cc149ba1c1cb2aa1 Mon Sep 17 00:00:00 2001 From: Edouard Marquez Date: Sat, 2 Nov 2024 09:48:23 +0100 Subject: [PATCH 07/16] Visual improvements for the scan page (#5773) --- packages/smooth_app/ios/Podfile.lock | 4 +- .../product_cards/product_title_card.dart | 37 +++- .../smooth_product_base_card.dart | 4 +- .../product/product_compatibility_header.dart | 2 + .../lib/pages/product/summary_card.dart | 87 ++++++--- .../smooth_app/lib/pages/scan/scan_page.dart | 180 +++++++++--------- .../lib/pages/scan/scan_product_card.dart | 9 + 7 files changed, 197 insertions(+), 126 deletions(-) diff --git a/packages/smooth_app/ios/Podfile.lock b/packages/smooth_app/ios/Podfile.lock index 657bc696e02d..ba40bf624b91 100644 --- a/packages/smooth_app/ios/Podfile.lock +++ b/packages/smooth_app/ios/Podfile.lock @@ -291,7 +291,7 @@ SPEC CHECKSUMS: mobile_scanner: 38dcd8a49d7d485f632b7de65e4900010187aef2 MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb nanopb: 438bc412db1928dac798aa6fd75726007be04262 - package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c + package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 @@ -302,7 +302,7 @@ SPEC CHECKSUMS: SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380 Sentry: f8374b5415bc38dfb5645941b3ae31230fbeae57 sentry_flutter: 0eb93e5279eb41e2392212afe1ccd2fecb4f8cbe - share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad + share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 sqflite_darwin: a553b1fd6fe66f53bbb0fe5b4f5bab93f08d7a13 url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe diff --git a/packages/smooth_app/lib/cards/product_cards/product_title_card.dart b/packages/smooth_app/lib/cards/product_cards/product_title_card.dart index b2eb5c68f069..f6c1938ee7d0 100644 --- a/packages/smooth_app/lib/cards/product_cards/product_title_card.dart +++ b/packages/smooth_app/lib/cards/product_cards/product_title_card.dart @@ -3,6 +3,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:provider/provider.dart'; import 'package:smooth_app/cards/product_cards/smooth_product_base_card.dart'; +import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/helpers/extension_on_text_helper.dart'; import 'package:smooth_app/helpers/product_cards_helper.dart'; @@ -52,15 +53,30 @@ class ProductTitleCard extends StatelessWidget { ]; } else { children = [ - _ProductTitleCardName( - selectable: isSelectable, - dense: dense, - ), - _ProductTitleCardBrand( - removable: isRemovable, - selectable: isSelectable, + Padding( + padding: const EdgeInsetsDirectional.only(top: VERY_SMALL_SPACE), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _ProductTitleCardName( + selectable: isSelectable, + dense: dense, + ), + _ProductTitleCardBrand( + removable: isRemovable, + selectable: isSelectable, + ), + ], + ), + ), + title, + ], + ), ), - title, ]; } @@ -154,6 +170,11 @@ class _ProductTitleCardTrailing extends StatelessWidget { alignment: AlignmentDirectional.centerEnd, child: ProductCardCloseButton( onRemove: onRemove, + padding: const EdgeInsetsDirectional.only( + start: SMALL_SPACE, + top: SMALL_SPACE, + bottom: SMALL_SPACE, + ), ), ); } else { diff --git a/packages/smooth_app/lib/cards/product_cards/smooth_product_base_card.dart b/packages/smooth_app/lib/cards/product_cards/smooth_product_base_card.dart index d80f0a34fe22..eebc0ccf806b 100644 --- a/packages/smooth_app/lib/cards/product_cards/smooth_product_base_card.dart +++ b/packages/smooth_app/lib/cards/product_cards/smooth_product_base_card.dart @@ -59,10 +59,12 @@ class ProductCardCloseButton extends StatelessWidget { const ProductCardCloseButton({ this.onRemove, this.iconData = Icons.clear_rounded, + this.padding, }); final OnRemoveCallback? onRemove; final IconData iconData; + final EdgeInsetsGeometry? padding; @override Widget build(BuildContext context) { @@ -79,7 +81,7 @@ class ProductCardCloseButton extends StatelessWidget { child: Tooltip( message: appLocalizations.product_card_remove_product_tooltip, child: Padding( - padding: const EdgeInsets.all(SMALL_SPACE), + padding: padding ?? const EdgeInsets.all(SMALL_SPACE), child: Icon( iconData, size: DEFAULT_ICON_SIZE, diff --git a/packages/smooth_app/lib/pages/product/product_compatibility_header.dart b/packages/smooth_app/lib/pages/product/product_compatibility_header.dart index 83797ff35b2c..ebd720c34d5e 100644 --- a/packages/smooth_app/lib/pages/product/product_compatibility_header.dart +++ b/packages/smooth_app/lib/pages/product/product_compatibility_header.dart @@ -52,6 +52,8 @@ class ProductCompatibilityHeader extends StatelessWidget { helper.getHeaderText(appLocalizations), style: themeData.textTheme.titleMedium?.copyWith( color: helper.getHeaderForegroundColor(isDarkMode), + fontSize: 15.0, + fontWeight: FontWeight.w600, ), ), ), diff --git a/packages/smooth_app/lib/pages/product/summary_card.dart b/packages/smooth_app/lib/pages/product/summary_card.dart index 7adf1c2c28d2..43fa4841c773 100644 --- a/packages/smooth_app/lib/pages/product/summary_card.dart +++ b/packages/smooth_app/lib/pages/product/summary_card.dart @@ -24,6 +24,8 @@ import 'package:smooth_app/pages/product/product_field_editor.dart'; import 'package:smooth_app/pages/product/product_incomplete_card.dart'; import 'package:smooth_app/pages/product/product_questions_widget.dart'; import 'package:smooth_app/pages/product/summary_attribute_group.dart'; +import 'package:smooth_app/resources/app_icons.dart' as icons; +import 'package:smooth_app/themes/smooth_theme_colors.dart'; const List _ATTRIBUTE_GROUP_ORDER = [ AttributeGroup.ATTRIBUTE_GROUP_ALLERGENS, @@ -45,6 +47,7 @@ class SummaryCard extends StatefulWidget { this.isProductEditable = true, this.attributeGroupsClickable = true, this.padding, + this.shadow, }); final Product _product; @@ -73,6 +76,9 @@ class SummaryCard extends StatefulWidget { final EdgeInsetsGeometry? padding; + /// An optional shadow to apply to the card + final BoxShadow? shadow; + @override State createState() => _SummaryCardState(); } @@ -120,6 +126,9 @@ class _SummaryCardState extends State with UpToDateMixin { } Widget _buildLimitedSizeSummaryCard(double parentHeight) { + final SmoothColorsThemeExtension? themeExtension = + Theme.of(context).extension(); + return Padding( padding: widget.padding ?? const EdgeInsets.symmetric( @@ -128,23 +137,30 @@ class _SummaryCardState extends State with UpToDateMixin { ), child: Stack( children: [ - ClipRRect( - borderRadius: ROUNDED_BORDER_RADIUS, - child: OverflowBox( - alignment: AlignmentDirectional.topStart, - minHeight: parentHeight, - maxHeight: double.infinity, - child: buildProductSmoothCard( - header: ProductCompatibilityHeader( - product: upToDateProduct, - productPreferences: widget._productPreferences, - isSettingVisible: widget.isSettingVisible, - ), - body: Padding( - padding: SMOOTH_CARD_PADDING, - child: _buildSummaryCardContent(context), + DecoratedBox( + decoration: BoxDecoration( + boxShadow: + widget.shadow != null ? [widget.shadow!] : null, + borderRadius: ROUNDED_BORDER_RADIUS, + ), + child: ClipRRect( + borderRadius: ROUNDED_BORDER_RADIUS, + child: OverflowBox( + alignment: AlignmentDirectional.topStart, + minHeight: parentHeight, + maxHeight: double.infinity, + child: buildProductSmoothCard( + header: ProductCompatibilityHeader( + product: upToDateProduct, + productPreferences: widget._productPreferences, + isSettingVisible: widget.isSettingVisible, + ), + body: Padding( + padding: SMOOTH_CARD_PADDING, + child: _buildSummaryCardContent(context), + ), + margin: EdgeInsets.zero, ), - margin: EdgeInsets.zero, ), ), ), @@ -157,18 +173,39 @@ class _SummaryCardState extends State with UpToDateMixin { vertical: SMALL_SPACE, ), decoration: BoxDecoration( - color: Theme.of(context).cardColor, + color: themeExtension!.primaryNormal, borderRadius: const BorderRadius.vertical(bottom: ROUNDED_RADIUS), ), - child: Center( - child: Text( - AppLocalizations.of(context).tap_for_more, - style: - Theme.of(context).primaryTextTheme.bodyLarge?.copyWith( - color: PRIMARY_BLUE_COLOR, - ), - ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsetsDirectional.only(bottom: 2.0), + child: Text( + AppLocalizations.of(context).tap_for_more, + style: const TextStyle( + color: Colors.white, + fontSize: 15.0, + fontWeight: FontWeight.w600, + ), + ), + ), + const SizedBox( + width: BALANCED_SPACE, + ), + Container( + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Colors.white, + ), + padding: const EdgeInsets.all(VERY_SMALL_SPACE), + child: icons.Arrow.right( + color: themeExtension.primaryNormal, + size: 12.0, + ), + ) + ], ), ), ], diff --git a/packages/smooth_app/lib/pages/scan/scan_page.dart b/packages/smooth_app/lib/pages/scan/scan_page.dart index 4f467cf54c93..a8a3f1025d14 100644 --- a/packages/smooth_app/lib/pages/scan/scan_page.dart +++ b/packages/smooth_app/lib/pages/scan/scan_page.dart @@ -16,6 +16,8 @@ import 'package:smooth_app/helpers/haptic_feedback_helper.dart'; import 'package:smooth_app/helpers/permission_helper.dart'; import 'package:smooth_app/pages/scan/camera_scan_page.dart'; import 'package:smooth_app/pages/scan/carousel/scan_carousel.dart'; +import 'package:smooth_app/themes/smooth_theme_colors.dart'; +import 'package:smooth_app/themes/theme_provider.dart'; import 'package:smooth_app/widgets/smooth_scaffold.dart'; class ScanPage extends StatefulWidget { @@ -52,6 +54,8 @@ class _ScanPageState extends State { } final AppLocalizations appLocalizations = AppLocalizations.of(context); + final SmoothColorsThemeExtension themeExtension = + Theme.of(context).extension()!; final TextDirection direction = Directionality.of(context); final bool hasACamera = CameraHelper.hasACamera; @@ -60,86 +64,81 @@ class _ScanPageState extends State { Theme.of(context).brightness == Brightness.light && Platform.isIOS ? Brightness.dark : null, - body: Container( - color: Colors.white, - child: SafeArea( - child: Container( - color: Theme.of(context).colorScheme.surface, - child: Column( - children: [ - if (hasACamera) - Expanded( - flex: 100 - _carouselHeightPct, - child: Consumer( - builder: ( - BuildContext context, - PermissionListener listener, - _, - ) { - switch (listener.value.status) { - case DevicePermissionStatus.checking: - return EMPTY_WIDGET; - case DevicePermissionStatus.granted: - // TODO(m123): change - return const CameraScannerPage(); - default: - return const _PermissionDeniedCard(); - } - }, - ), - ), - Expanded( - flex: _carouselHeightPct, - child: Padding( - padding: const EdgeInsetsDirectional.only( - bottom: BALANCED_SPACE), - child: ScanPageCarousel( - onPageChangedTo: (int page, String? barcode) async { - if (barcode == null) { - // We only notify for new products - return; - } + backgroundColor: + context.lightTheme() ? themeExtension.primaryLight : null, + body: SafeArea( + child: Column( + children: [ + if (hasACamera) + Expanded( + flex: 100 - _carouselHeightPct, + child: Consumer( + builder: ( + BuildContext context, + PermissionListener listener, + _, + ) { + switch (listener.value.status) { + case DevicePermissionStatus.checking: + return EMPTY_WIDGET; + case DevicePermissionStatus.granted: + // TODO(m123): change + return const CameraScannerPage(); + default: + return const _PermissionDeniedCard(); + } + }, + ), + ), + Expanded( + flex: _carouselHeightPct, + child: Padding( + padding: + const EdgeInsetsDirectional.only(bottom: BALANCED_SPACE), + child: ScanPageCarousel( + onPageChangedTo: (int page, String? barcode) async { + if (barcode == null) { + // We only notify for new products + return; + } - // Both are Future methods, but it doesn't matter to wait here - SmoothHapticFeedback.lightNotification(); + // Both are Future methods, but it doesn't matter to wait here + SmoothHapticFeedback.lightNotification(); - if (_userPreferences.playCameraSound) { - await _initSoundManagerIfNecessary(); - await _musicPlayer!.stop(); - await _musicPlayer!.play( - AssetSource('audio/beep.wav'), - volume: 0.5, - ctx: AudioContext( - android: const AudioContextAndroid( - isSpeakerphoneOn: false, - stayAwake: false, - contentType: AndroidContentType.sonification, - usageType: AndroidUsageType.notification, - audioFocus: - AndroidAudioFocus.gainTransientMayDuck, - ), - iOS: AudioContextIOS( - category: AVAudioSessionCategory.soloAmbient, - options: const { - AVAudioSessionOptions.mixWithOthers, - }, - ), - ), - ); - } + if (_userPreferences.playCameraSound) { + await _initSoundManagerIfNecessary(); + await _musicPlayer!.stop(); + await _musicPlayer!.play( + AssetSource('audio/beep.wav'), + volume: 0.5, + ctx: AudioContext( + android: const AudioContextAndroid( + isSpeakerphoneOn: false, + stayAwake: false, + contentType: AndroidContentType.sonification, + usageType: AndroidUsageType.notification, + audioFocus: AndroidAudioFocus.gainTransientMayDuck, + ), + iOS: AudioContextIOS( + category: AVAudioSessionCategory.soloAmbient, + options: const { + AVAudioSessionOptions.mixWithOthers, + }, + ), + ), + ); + } - SemanticsService.announce( - appLocalizations.scan_announce_new_barcode(barcode), - direction, - assertiveness: Assertiveness.assertive, - ); - }, - ), - ), + SemanticsService.announce( + appLocalizations.scan_announce_new_barcode(barcode), + direction, + assertiveness: Assertiveness.assertive, + ); + }, ), - ], + ), ), - ), + ], ), ), ); @@ -191,9 +190,12 @@ class _PermissionDeniedCard extends StatelessWidget { end: SMALL_SPACE, bottom: 5.0, ), + borderRadius: BorderRadius.zero, + margin: EdgeInsets.zero, child: Align( alignment: Alignment.topCenter, child: Column( + mainAxisAlignment: MainAxisAlignment.center, children: [ Text( localizations.permission_photo_denied_title, @@ -203,22 +205,20 @@ class _PermissionDeniedCard extends StatelessWidget { ), textAlign: TextAlign.center, ), - Expanded( - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: BALANCED_SPACE, - vertical: BALANCED_SPACE, + SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: BALANCED_SPACE, + vertical: BALANCED_SPACE, + ), + child: Text( + localizations.permission_photo_denied_message( + APP_NAME, ), - child: Text( - localizations.permission_photo_denied_message( - APP_NAME, - ), - textAlign: TextAlign.center, - style: const TextStyle( - height: 1.4, - fontSize: 15.5, - ), + textAlign: TextAlign.center, + style: const TextStyle( + height: 1.4, + fontSize: 15.5, ), ), ), diff --git a/packages/smooth_app/lib/pages/scan/scan_product_card.dart b/packages/smooth_app/lib/pages/scan/scan_product_card.dart index a4a362b66812..7603f3fd4244 100644 --- a/packages/smooth_app/lib/pages/scan/scan_product_card.dart +++ b/packages/smooth_app/lib/pages/scan/scan_product_card.dart @@ -6,6 +6,7 @@ import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/pages/navigator/app_navigator.dart'; import 'package:smooth_app/pages/product/hideable_container.dart'; import 'package:smooth_app/pages/product/summary_card.dart'; +import 'package:smooth_app/themes/theme_provider.dart'; class ScanProductCard extends StatelessWidget { ScanProductCard(this.product) @@ -38,6 +39,14 @@ class ScanProductCard extends StatelessWidget { padding: const EdgeInsets.symmetric( vertical: VERY_SMALL_SPACE, ), + shadow: BoxShadow( + color: Theme.of(context) + .shadowColor + .withOpacity(context.lightTheme() ? 0.08 : 0.3), + offset: const Offset(0.0, 2.0), + blurRadius: 5.0, + spreadRadius: 1.0, + ), ), ), ), From 0f0ba5d62fad0e97dedef2591795e210aa727394 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 Nov 2024 10:40:27 +0100 Subject: [PATCH 08/16] chore(deps): bump path_provider from 2.1.4 to 2.1.5 in /packages/smooth_app (#5770) Bumps [path_provider](https://github.com/flutter/packages/tree/main/packages/path_provider) from 2.1.4 to 2.1.5. - [Release notes](https://github.com/flutter/packages/releases) - [Commits](https://github.com/flutter/packages/commits/path_provider-v2.1.5/packages/path_provider) --- updated-dependencies: - dependency-name: path_provider dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/smooth_app/pubspec.lock | 4 ++-- packages/smooth_app/pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/smooth_app/pubspec.lock b/packages/smooth_app/pubspec.lock index 9a1087c51223..8fc91966a41b 100644 --- a/packages/smooth_app/pubspec.lock +++ b/packages/smooth_app/pubspec.lock @@ -1133,10 +1133,10 @@ packages: dependency: "direct main" description: name: path_provider - sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" path_provider_android: dependency: transitive description: diff --git a/packages/smooth_app/pubspec.yaml b/packages/smooth_app/pubspec.yaml index 603e85381253..c5eb4503ae0f 100644 --- a/packages/smooth_app/pubspec.yaml +++ b/packages/smooth_app/pubspec.yaml @@ -56,7 +56,7 @@ dependencies: intl: 0.19.0 collection: 1.18.0 path: 1.9.0 - path_provider: 2.1.4 + path_provider: 2.1.5 share_plus: 10.1.1 fimber: 0.7.0 shimmer: ^3.0.0 From fc38f6ed644bdb63d2c95d67cec9f521760c21ae Mon Sep 17 00:00:00 2001 From: monsieurtanuki Date: Sat, 2 Nov 2024 10:52:43 +0100 Subject: [PATCH 09/16] fix: 5681 - startActivityAndCollapse + android 14 (#5684) * fix: 5681 - startActivityAndCollapse + android 14 * Fixed import --- .../kotlin/org/openfoodfacts/app/AppMainTile.kt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/smooth_app/android/app/src/main/kotlin/org/openfoodfacts/app/AppMainTile.kt b/packages/smooth_app/android/app/src/main/kotlin/org/openfoodfacts/app/AppMainTile.kt index 5a9d15f6eca9..1f322a39af74 100644 --- a/packages/smooth_app/android/app/src/main/kotlin/org/openfoodfacts/app/AppMainTile.kt +++ b/packages/smooth_app/android/app/src/main/kotlin/org/openfoodfacts/app/AppMainTile.kt @@ -1,5 +1,6 @@ package org.openfoodfacts.app +import android.app.PendingIntent import android.content.Intent import android.os.Build import android.service.quicksettings.Tile @@ -24,7 +25,18 @@ class AppMainTile : TileService() { flags += Intent.FLAG_ACTIVITY_NEW_TASK } - startActivityAndCollapse(intent) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + startActivityAndCollapse( + PendingIntent.getActivity( + applicationContext, + 0, + intent, + PendingIntent.FLAG_IMMUTABLE + ) + ) + } else { + startActivityAndCollapse(intent) + } } } \ No newline at end of file From e7c58b6431fd34df327c1bca80690db24ad69353 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 Nov 2024 10:53:05 +0100 Subject: [PATCH 10/16] chore(deps): bump flutter_svg in /packages/scanner/shared (#5776) Bumps [flutter_svg](https://github.com/flutter/packages/tree/main/third_party/packages) from 2.0.10+1 to 2.0.11. - [Release notes](https://github.com/flutter/packages/releases) - [Commits](https://github.com/flutter/packages/commits/flutter_svg-v2.0.11/third_party/packages) --- updated-dependencies: - dependency-name: flutter_svg dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/scanner/shared/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/scanner/shared/pubspec.yaml b/packages/scanner/shared/pubspec.yaml index 31a4b877580f..f5dfe06c9880 100644 --- a/packages/scanner/shared/pubspec.yaml +++ b/packages/scanner/shared/pubspec.yaml @@ -9,7 +9,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_svg: 2.0.10+1 + flutter_svg: 2.0.11 visibility_detector: 0.4.0+2 provider: 6.1.2 From 066204153f0b26ae6e604892ca763594646d6a89 Mon Sep 17 00:00:00 2001 From: Edouard Marquez Date: Sat, 2 Nov 2024 11:51:51 +0100 Subject: [PATCH 11/16] feat: Product page - manage user lists in a bottom sheet (#5778) * Add to user lists in a bottom sheet * Better support dark mode --- .../bottom_sheets/smooth_bottom_sheet.dart | 106 +++- .../smooth_draggable_bottom_sheet.dart | 30 +- packages/smooth_app/lib/l10n/app_en.arb | 8 + .../pages/product/product_list_helper.dart | 498 ++++++++++++++++++ .../product_page/new_product_footer.dart | 30 +- .../smooth_app/lib/themes/smooth_theme.dart | 14 +- .../lib/widgets/smooth_checkbox.dart | 75 +++ 7 files changed, 711 insertions(+), 50 deletions(-) create mode 100644 packages/smooth_app/lib/pages/product/product_list_helper.dart create mode 100644 packages/smooth_app/lib/widgets/smooth_checkbox.dart diff --git a/packages/smooth_app/lib/generic_lib/bottom_sheets/smooth_bottom_sheet.dart b/packages/smooth_app/lib/generic_lib/bottom_sheets/smooth_bottom_sheet.dart index 38341b9f6e89..07b86f0722c0 100644 --- a/packages/smooth_app/lib/generic_lib/bottom_sheets/smooth_bottom_sheet.dart +++ b/packages/smooth_app/lib/generic_lib/bottom_sheets/smooth_bottom_sheet.dart @@ -4,6 +4,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/semantics.dart'; import 'package:smooth_app/generic_lib/bottom_sheets/smooth_draggable_bottom_sheet_route.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; +import 'package:smooth_app/resources/app_icons.dart' as icons; +import 'package:smooth_app/themes/smooth_theme_colors.dart'; Future showSmoothModalSheet({ required BuildContext context, @@ -90,47 +92,65 @@ class SmoothModalSheet extends StatelessWidget { class SmoothModalSheetHeader extends StatelessWidget implements SizeWidget { const SmoothModalSheetHeader({ required this.title, + this.prefix, this.suffix, + this.foregroundColor, + this.backgroundColor, }); static const double MIN_HEIGHT = 50.0; final String title; + final SizeWidget? prefix; final SizeWidget? suffix; + final Color? foregroundColor; + final Color? backgroundColor; @override Widget build(BuildContext context) { final Color primaryColor = Theme.of(context).primaryColor; - return Container( - height: suffix is SmoothModalSheetHeaderButton ? double.infinity : null, - color: primaryColor.withOpacity(0.2), - constraints: const BoxConstraints(minHeight: MIN_HEIGHT), - padding: EdgeInsetsDirectional.only( - start: VERY_LARGE_SPACE, - top: VERY_SMALL_SPACE, - bottom: VERY_SMALL_SPACE, - end: VERY_LARGE_SPACE - - (suffix?.requiresPadding == true ? 0 : LARGE_SPACE), - ), - child: IntrinsicHeight( - child: Row( - children: [ - Expanded( - child: Semantics( - sortKey: const OrdinalSortKey(1.0), - child: Text( - title, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.titleLarge?.copyWith( - fontWeight: FontWeight.bold, - ), + return IconTheme( + data: IconThemeData(color: foregroundColor), + child: Container( + height: suffix is SmoothModalSheetHeaderButton ? double.infinity : null, + color: backgroundColor ?? primaryColor.withOpacity(0.2), + constraints: const BoxConstraints(minHeight: MIN_HEIGHT), + padding: EdgeInsetsDirectional.only( + start: (prefix?.requiresPadding == true ? 0 : VERY_LARGE_SPACE), + top: VERY_SMALL_SPACE, + bottom: VERY_SMALL_SPACE, + end: VERY_LARGE_SPACE - + (suffix?.requiresPadding == true ? 0 : LARGE_SPACE), + ), + child: IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (prefix != null) + Padding( + padding: + const EdgeInsetsDirectional.only(end: BALANCED_SPACE), + child: prefix, + ), + Expanded( + child: Semantics( + sortKey: const OrdinalSortKey(1.0), + child: Text( + title, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.bold, + fontSize: 18.0, + color: foregroundColor, + ), + ), ), ), - ), - if (suffix != null) suffix! - ], + if (suffix != null) suffix! + ], + ), ), ), ); @@ -245,9 +265,11 @@ class SmoothModalSheetHeaderCloseButton extends StatelessWidget implements SizeWidget { const SmoothModalSheetHeaderCloseButton({ this.semanticsOrder, + this.addPadding, }); final double? semanticsOrder; + final bool? addPadding; @override Widget build(BuildContext context) { @@ -264,7 +286,9 @@ class SmoothModalSheetHeaderCloseButton extends StatelessWidget customBorder: const CircleBorder(), child: const Padding( padding: EdgeInsets.all(MEDIUM_SPACE), - child: Icon(Icons.clear), + child: icons.Close( + size: 15.0, + ), ), ), ), @@ -275,8 +299,34 @@ class SmoothModalSheetHeaderCloseButton extends StatelessWidget double widgetHeight(BuildContext context) => (MEDIUM_SPACE * 2) + (Theme.of(context).iconTheme.size ?? 20.0); + @override + bool get requiresPadding => addPadding ?? false; +} + +class SmoothModalSheetHeaderPrefixIndicator extends StatelessWidget + implements SizeWidget { + const SmoothModalSheetHeaderPrefixIndicator({super.key}); + + @override + Widget build(BuildContext context) { + final SmoothColorsThemeExtension extension = + Theme.of(context).extension()!; + + return Container( + width: 10.0, + height: 10.0, + decoration: BoxDecoration( + color: extension.secondaryNormal, + shape: BoxShape.circle, + ), + ); + } + @override bool get requiresPadding => false; + + @override + double widgetHeight(BuildContext context) => 10.0; } abstract class SizeWidget implements Widget { diff --git a/packages/smooth_app/lib/generic_lib/bottom_sheets/smooth_draggable_bottom_sheet.dart b/packages/smooth_app/lib/generic_lib/bottom_sheets/smooth_draggable_bottom_sheet.dart index d23491686d39..9d1b0790887c 100644 --- a/packages/smooth_app/lib/generic_lib/bottom_sheets/smooth_draggable_bottom_sheet.dart +++ b/packages/smooth_app/lib/generic_lib/bottom_sheets/smooth_draggable_bottom_sheet.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:provider/provider.dart'; class SmoothDraggableBottomSheet extends StatefulWidget { const SmoothDraggableBottomSheet({ @@ -172,20 +173,23 @@ class _SmoothDraggableContentState extends State<_SmoothDraggableContent> { Widget build(BuildContext context) { return Scrollbar( controller: widget.scrollController, - child: CustomScrollView( - cacheExtent: widget.cacheExtent, - key: _contentKey, - controller: widget.scrollController, - slivers: [ - SliverPersistentHeader( - pinned: true, - delegate: _SliverHeader( - child: widget.headerBuilder(context), - height: widget.headerHeight, + child: ChangeNotifierProvider.value( + value: widget.scrollController, + child: CustomScrollView( + cacheExtent: widget.cacheExtent, + key: _contentKey, + controller: widget.scrollController, + slivers: [ + SliverPersistentHeader( + pinned: true, + delegate: _SliverHeader( + child: widget.headerBuilder(context), + height: widget.headerHeight, + ), ), - ), - widget.bodyBuilder(context), - ], + widget.bodyBuilder(context), + ], + ), ), ); } diff --git a/packages/smooth_app/lib/l10n/app_en.arb b/packages/smooth_app/lib/l10n/app_en.arb index 2f2a499b7125..7daf6dde94b0 100644 --- a/packages/smooth_app/lib/l10n/app_en.arb +++ b/packages/smooth_app/lib/l10n/app_en.arb @@ -1572,6 +1572,10 @@ "@user_list_subtitle_product": { "description": "Subtitle of a paragraph about user lists in a product context" }, + "user_list_title": "Your lists", + "@user_list_title": { + "description": "Label for the user lists (when the user wants to add a product to a list)" + }, "user_list_add_product": "Add the product to your lists", "@user_list_add_product": { "description": "Label for the dialog to add a product to a list" @@ -1616,6 +1620,10 @@ "@user_list_name_error_same": { "description": "Validation error about the renamed name that is the same as the initial list name" }, + "user_list_name_input_hint": "Name of the list", + "@user_list_name_input_hint": { + "description": "A hint to indicate that the user should input a name of a list" + }, "try_again": "Try Again", "@try_again": { "description": "Label for buttons that try to repeat a failed action" diff --git a/packages/smooth_app/lib/pages/product/product_list_helper.dart b/packages/smooth_app/lib/pages/product/product_list_helper.dart new file mode 100644 index 000000000000..728ee6611e8a --- /dev/null +++ b/packages/smooth_app/lib/pages/product/product_list_helper.dart @@ -0,0 +1,498 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; +import 'package:smooth_app/data_models/product_list.dart'; +import 'package:smooth_app/database/dao_product_list.dart'; +import 'package:smooth_app/database/local_database.dart'; +import 'package:smooth_app/generic_lib/buttons/smooth_simple_button.dart'; +import 'package:smooth_app/generic_lib/design_constants.dart'; +import 'package:smooth_app/helpers/collections_helper.dart'; +import 'package:smooth_app/helpers/haptic_feedback_helper.dart'; +import 'package:smooth_app/helpers/provider_helper.dart'; +import 'package:smooth_app/pages/product_list_user_dialog_helper.dart'; +import 'package:smooth_app/resources/app_icons.dart'; +import 'package:smooth_app/themes/smooth_theme_colors.dart'; +import 'package:smooth_app/widgets/smooth_checkbox.dart'; + +class AddProductToListContainer extends StatelessWidget { + const AddProductToListContainer({required this.barcode, super.key}); + + final String barcode; + + @override + Widget build(BuildContext context) { + return ChangeNotifierProvider<_ProductUserListsProvider>( + create: (BuildContext context) => _ProductUserListsProvider( + DaoProductList(context.read()), + barcode, + ), + child: Consumer<_ProductUserListsProvider>( + builder: ( + final BuildContext context, + final _ProductUserListsProvider productUserListsProvider, + final Widget? child, + ) { + return switch (productUserListsProvider.value) { + _ProductUserListsLoadingState _ => const _AddToProductListLoading(), + _ProductUserListsEmptyState _ => + const _AddToProductListNoListAvailable(), + _ProductUserListsWithState _ => const _AddToProductListWithLists(), + }; + }, + ), + ); + } +} + +/// Widget when the [_ProductUserListsProvider] is loading lists +class _AddToProductListLoading extends StatelessWidget { + const _AddToProductListLoading(); + + @override + Widget build(BuildContext context) { + return const SliverFillRemaining( + child: Center( + child: CircularProgressIndicator.adaptive(), + ), + ); + } +} + +/// Widget when there is no user list +/// A button to create a new list (in a dialog) +class _AddToProductListNoListAvailable extends StatelessWidget { + const _AddToProductListNoListAvailable(); + + @override + Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + + return SliverFillRemaining( + child: Center( + child: Padding( + padding: const EdgeInsetsDirectional.only( + start: LARGE_SPACE, + end: LARGE_SPACE, + bottom: VERY_LARGE_SPACE, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ExcludeSemantics( + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Theme.of(context) + .extension()! + .secondaryLight, + ), + padding: const EdgeInsets.all(VERY_LARGE_SPACE), + child: const Milk( + size: 40.0, + color: Colors.white, + ), + ), + ), + const SizedBox(height: VERY_LARGE_SPACE), + Text( + appLocalizations.user_list_empty_label, + textAlign: TextAlign.center, + style: const TextStyle(fontWeight: FontWeight.w600), + ), + const SizedBox(height: VERY_LARGE_SPACE), + SmoothSimpleButton( + onPressed: () async { + final ProductList? list = await ProductListUserDialogHelper( + DaoProductList(context.read())) + .showCreateUserListDialog( + context, + ); + + if (list != null && context.mounted) { + final _ProductUserListsProvider provider = + context.read<_ProductUserListsProvider>(); + await provider.addAProductToAList(list.parameters); + await provider.reloadLists(); + } + }, + child: Text( + appLocalizations.user_list_button_new, + style: const TextStyle(fontWeight: FontWeight.w600), + ), + ) + ], + ), + ), + ), + ); + } +} + +/// Widget when there are user lists +/// A list of: +/// -> Add new list button +/// -> List of user lists +class _AddToProductListWithLists extends StatelessWidget { + const _AddToProductListWithLists(); + + static const double MIN_ITEM_HEIGHT = 48.0; + + @override + Widget build(BuildContext context) { + final _ProductUserListsWithState state = context + .watch<_ProductUserListsProvider>() + .value as _ProductUserListsWithState; + + return SliverList( + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) { + if (index == 0) { + return _AddToProductListAddNewList( + userLists: state.userLists.map((MapEntry entry) { + return entry.key; + }), + ); + } else if (index <= state.userLists.length) { + final MapEntry entry = state.userLists[index - 1]; + return _AddToProductListItem( + listId: entry.key, + selected: entry.value, + includeDivider: index < state.userLists.length, + ); + } else { + return SizedBox(height: MediaQuery.viewPaddingOf(context).bottom); + } + }, + childCount: state.userLists.length + 2, + ), + ); + } +} + +/// An item showing a user list and handling the click +class _AddToProductListItem extends StatelessWidget { + const _AddToProductListItem({ + required this.listId, + required this.selected, + required this.includeDivider, + }); + + final String listId; + final bool selected; + final bool includeDivider; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + InkWell( + onTap: () { + SmoothHapticFeedback.lightNotification(); + Provider.of<_ProductUserListsProvider>(context, listen: false) + .toggleProductToList(listId); + }, + child: ConstrainedBox( + constraints: const BoxConstraints( + minHeight: _AddToProductListWithLists.MIN_ITEM_HEIGHT, + ), + child: Padding( + padding: EdgeInsetsDirectional.symmetric( + horizontal: LARGE_SPACE, + // The CupertinoCheckbox has huge paddings + // (that we can't override) + vertical: + (Platform.isIOS || Platform.isMacOS) ? 2.0 : MEDIUM_SPACE, + ), + child: Row( + children: [ + IgnorePointer( + child: SmoothCheckbox( + value: selected, + onChanged: (_) {}, + ), + ), + const SizedBox(width: MEDIUM_SPACE), + Expanded( + child: Text( + listId, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + ), + ), + if (includeDivider) const _AddToProductListDivider() + ], + ); + } +} + +/// Widget that shows an inline way to create a list +class _AddToProductListAddNewList extends StatefulWidget { + const _AddToProductListAddNewList({required this.userLists}); + + final Iterable userLists; + + @override + State<_AddToProductListAddNewList> createState() => + _AddToProductListAddNewListState(); +} + +class _AddToProductListAddNewListState + extends State<_AddToProductListAddNewList> { + final TextEditingController _controller = TextEditingController(); + bool _editMode = false; + bool _inputValid = false; + + @override + Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + final Color? mainColor = Theme.of(context) + .checkboxTheme + .fillColor! + .resolve({WidgetState.selected}); + + return Column( + children: [ + IconTheme( + data: IconThemeData(color: mainColor), + child: InkWell( + onTap: () { + setState(() { + if (!_editMode) { + _controller.clear(); + _editMode = true; + _inputValid = false; + } + }); + }, + child: ConstrainedBox( + constraints: const BoxConstraints( + minHeight: _AddToProductListWithLists.MIN_ITEM_HEIGHT, + ), + child: Padding( + padding: EdgeInsetsDirectional.symmetric( + horizontal: + (Platform.isIOS || Platform.isMacOS) ? 25.5 : 28.0, + ), + child: Row( + children: [ + const Icon(Icons.add_circle_rounded), + const SizedBox(width: VERY_LARGE_SPACE), + Expanded( + child: _editMode + ? Padding( + padding: + const EdgeInsetsDirectional.only(bottom: 1.0), + child: TextField( + controller: _controller, + autofocus: true, + decoration: InputDecoration( + isDense: true, + hintText: appLocalizations + .user_list_name_input_hint, + hintStyle: TextStyle( + fontStyle: FontStyle.italic, + color: Theme.of(context).hintColor, + ), + contentPadding: EdgeInsets.zero, + border: InputBorder.none, + ), + textInputAction: TextInputAction.done, + maxLines: 1, + textAlignVertical: TextAlignVertical.top, + style: DefaultTextStyle.of(context).style, + onChanged: _checkInput, + onSubmitted: (_) => _inputValid + ? () => _addList(context) + : null, + ), + ) + : Text( + appLocalizations.user_list_button_new, + ), + ), + if (_editMode) + IconButton( + icon: const Icon(Icons.cancel), + onPressed: () => setState(() => _editMode = false), + ), + if (_editMode) + IconButton( + icon: const Icon(Icons.check_circle), + onPressed: _inputValid ? () => _addList(context) : null, + ), + ], + ), + ), + ), + ), + ), + const _AddToProductListDivider(), + ], + ); + } + + void _checkInput(String value) { + final bool validInput = + value.trim().isNotEmpty && !widget.userLists.containsIgnoreCase(value); + + if (validInput != _inputValid) { + setState(() { + _inputValid = validInput; + }); + } + } + + void _addList(BuildContext context) { + if (!_inputValid) { + return; + } + + SmoothHapticFeedback.lightNotification(); + Provider.of<_ProductUserListsProvider>(context, listen: false) + .createUserList(_controller.value.text); + + setState(() => _editMode = false); + } +} + +class _AddToProductListDivider extends StatelessWidget { + const _AddToProductListDivider(); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsetsDirectional.symmetric( + horizontal: LARGE_SPACE, + ), + child: SizedBox( + height: 1.0, + width: double.infinity, + child: ColoredBox( + color: Theme.of(context) + .extension()! + .primaryLight, + ), + ), + ); + } +} + +/// Logic for the user lists +class _ProductUserListsProvider extends ValueNotifier<_ProductUserListsState> { + _ProductUserListsProvider(this.dao, this.barcode) + : super(const _ProductUserListsLoadingState()) { + reloadLists(); + } + + final DaoProductList dao; + final String barcode; + + Future reloadLists() async { + emit(const _ProductUserListsLoadingState()); + + final List lists = dao.getUserLists(); + if (lists.isEmpty) { + emit(const _ProductUserListsEmptyState()); + return; + } + + // Sort by ignoring case + lists.sort( + (String a, String b) => a.toLowerCase().compareTo(b.toLowerCase()), + ); + + // Create a list of user lists with a boolean if the product is in it + final List listsWithProduct = + await dao.getUserListsWithBarcodes([barcode]); + + final List> userLists = >[]; + for (final String listId in lists) { + userLists.add( + MapEntry(listId, listsWithProduct.contains(listId)), + ); + } + + emit(_ProductUserListsWithState(userLists)); + } + + Future toggleProductToList(String listId) async { + if (value is! _ProductUserListsWithState) { + return false; + } + + /// Fake the UI first (otherwise there is a slight delay) + final bool selected = !(value as _ProductUserListsWithState) + .userLists + .firstWhere((MapEntry item) => item.key == listId) + .value; + + _fakeStateChange( + listId, + selected, + ); + + return dao.set( + ProductList.user(listId), + barcode, + selected, + ); + } + + /// Create a new user list and add the product to it + Future createUserList(String listId) async { + /// Fake the UI first (otherwise there is a slight delay) + _fakeStateChange(listId, true); + + final ProductList userList = ProductList.user(listId); + await dao.put(userList); + return addAProductToAList(listId); + } + + Future addAProductToAList(String listId) { + return dao.set(ProductList.user(listId), barcode, true); + } + + /// Force reload the UI. + /// If [listId] doesn't exist, it will be added at the top. + bool _fakeStateChange(String listId, bool selected) { + final List> lists = List>.of( + (value as _ProductUserListsWithState).userLists); + + final int position = + lists.indexWhere((MapEntry item) => item.key == listId); + + if (position >= 0) { + lists[position] = MapEntry(listId, selected); + } else { + lists.insert(0, MapEntry(listId, selected)); + } + + emit(_ProductUserListsWithState(lists)); + return true; + } +} + +sealed class _ProductUserListsState { + const _ProductUserListsState(); +} + +class _ProductUserListsLoadingState extends _ProductUserListsState { + const _ProductUserListsLoadingState(); +} + +class _ProductUserListsEmptyState extends _ProductUserListsState { + const _ProductUserListsEmptyState(); +} + +class _ProductUserListsWithState extends _ProductUserListsState { + _ProductUserListsWithState(this.userLists); + + final List> userLists; +} diff --git a/packages/smooth_app/lib/pages/product/product_page/new_product_footer.dart b/packages/smooth_app/lib/pages/product/product_page/new_product_footer.dart index bfc44778cf6f..19f91e8ab183 100644 --- a/packages/smooth_app/lib/pages/product/product_page/new_product_footer.dart +++ b/packages/smooth_app/lib/pages/product/product_page/new_product_footer.dart @@ -6,8 +6,8 @@ import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:provider/provider.dart'; import 'package:share_plus/share_plus.dart'; import 'package:smooth_app/data_models/preferences/user_preferences.dart'; -import 'package:smooth_app/database/dao_product_list.dart'; import 'package:smooth_app/database/local_database.dart'; +import 'package:smooth_app/generic_lib/bottom_sheets/smooth_bottom_sheet.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/generic_lib/widgets/smooth_snackbar.dart'; import 'package:smooth_app/helpers/analytics_helper.dart'; @@ -16,8 +16,8 @@ import 'package:smooth_app/pages/prices/price_meta_product.dart'; import 'package:smooth_app/pages/prices/product_price_add_page.dart'; import 'package:smooth_app/pages/product/common/product_query_page_helper.dart'; import 'package:smooth_app/pages/product/edit_product_page.dart'; +import 'package:smooth_app/pages/product/product_list_helper.dart'; import 'package:smooth_app/pages/product/product_page/new_product_page.dart'; -import 'package:smooth_app/pages/product_list_user_dialog_helper.dart'; import 'package:smooth_app/query/category_product_query.dart'; import 'package:smooth_app/query/product_query.dart'; import 'package:smooth_app/resources/app_icons.dart' as icons; @@ -117,13 +117,27 @@ class _ProductAddToListButton extends StatelessWidget { } Future _editList(BuildContext context, Product product) async { - final LocalDatabase localDatabase = context.read(); - final DaoProductList daoProductList = DaoProductList(localDatabase); - return ProductListUserDialogHelper(daoProductList) - .showUserAddProductsDialog( - context, - {product.barcode!}, + final AppLocalizations appLocalizations = AppLocalizations.of(context); + final SmoothColorsThemeExtension? extension = + Theme.of(context).extension(); + + showSmoothDraggableModalSheet( + context: context, + header: SmoothModalSheetHeader( + backgroundColor: extension!.primaryDark, + foregroundColor: Colors.white, + prefix: const SmoothModalSheetHeaderPrefixIndicator(), + title: appLocalizations.user_list_title, + suffix: const SmoothModalSheetHeaderCloseButton( + addPadding: true, + ), + ), + bodyBuilder: (BuildContext context) => AddProductToListContainer( + barcode: product.barcode!, + ), ); + + return true; } } diff --git a/packages/smooth_app/lib/themes/smooth_theme.dart b/packages/smooth_app/lib/themes/smooth_theme.dart index 26fd823da2fb..a6b00aa2ea4d 100644 --- a/packages/smooth_app/lib/themes/smooth_theme.dart +++ b/packages/smooth_app/lib/themes/smooth_theme.dart @@ -116,10 +116,22 @@ class SmoothTheme { return null; } if (states.contains(WidgetState.selected)) { - return myColorScheme.primary; + return brightness == Brightness.light + ? smoothExtension.primarySemiDark + : smoothExtension.primaryNormal; } return null; }), + side: BorderSide( + color: brightness == Brightness.light + ? smoothExtension.primaryBlack + : smoothExtension.primarySemiDark, + width: 2.0, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(3.0), + ), + checkColor: const WidgetStatePropertyAll(Colors.white), ), radioTheme: RadioThemeData( fillColor: diff --git a/packages/smooth_app/lib/widgets/smooth_checkbox.dart b/packages/smooth_app/lib/widgets/smooth_checkbox.dart new file mode 100644 index 000000000000..13547d879a1c --- /dev/null +++ b/packages/smooth_app/lib/widgets/smooth_checkbox.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; + +/// A [Checkbox.adaptive] that ensures that the Cupertino variant follows +/// the same theme as the Material variant. +/// (Active color = fill color) +class SmoothCheckbox extends StatelessWidget { + const SmoothCheckbox({ + super.key, + required this.value, + this.tristate = false, + required this.onChanged, + this.mouseCursor, + this.activeColor, + this.fillColor, + this.checkColor, + this.focusColor, + this.hoverColor, + this.overlayColor, + this.splashRadius, + this.materialTapTargetSize, + this.visualDensity, + this.focusNode, + this.autofocus = false, + this.shape, + this.side, + this.isError = false, + this.semanticLabel, + }); + + final bool? value; + final ValueChanged? onChanged; + final MouseCursor? mouseCursor; + final Color? activeColor; + final WidgetStateProperty? fillColor; + final Color? checkColor; + final bool tristate; + final MaterialTapTargetSize? materialTapTargetSize; + final VisualDensity? visualDensity; + final Color? focusColor; + final Color? hoverColor; + final WidgetStateProperty? overlayColor; + final double? splashRadius; + final FocusNode? focusNode; + final bool autofocus; + final OutlinedBorder? shape; + final BorderSide? side; + final bool isError; + final String? semanticLabel; + + @override + Widget build(BuildContext context) { + return Checkbox.adaptive( + value: value, + onChanged: onChanged, + mouseCursor: mouseCursor, + activeColor: (fillColor ?? Theme.of(context).checkboxTheme.fillColor!) + .resolve({WidgetState.selected}), + fillColor: fillColor, + checkColor: checkColor, + tristate: tristate, + materialTapTargetSize: materialTapTargetSize, + visualDensity: visualDensity ?? VisualDensity.standard, + focusColor: focusColor, + hoverColor: hoverColor, + overlayColor: overlayColor, + splashRadius: splashRadius, + focusNode: focusNode, + autofocus: autofocus, + shape: shape, + side: side, + isError: isError, + semanticLabel: semanticLabel, + ); + } +} From 88ca4e0cbef7eac95bfc5b94dcd4d1ce9331fd66 Mon Sep 17 00:00:00 2001 From: Pierre Slamich Date: Sat, 2 Nov 2024 11:52:05 +0100 Subject: [PATCH 12/16] ci: Unblock the iOS build at the expense of release please ? (#5747) * ci: Unblock the iOS build at the expense of release please ? * Update version.txt Co-authored-by: Edouard Marquez --------- Co-authored-by: Edouard Marquez --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index ecbc3b03079a..152b2727836c 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.16.0 +4.16.9 From dea18989ec761ca8791f06034db8fbec36bcfa70 Mon Sep 17 00:00:00 2001 From: Edouard Marquez Date: Sat, 2 Nov 2024 13:20:02 +0100 Subject: [PATCH 13/16] Bump flutter_svg for smooth_app (#5779) --- packages/smooth_app/pubspec.lock | 4 ++-- packages/smooth_app/pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/smooth_app/pubspec.lock b/packages/smooth_app/pubspec.lock index 8fc91966a41b..dc198e56c2c4 100644 --- a/packages/smooth_app/pubspec.lock +++ b/packages/smooth_app/pubspec.lock @@ -696,10 +696,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" + sha256: "1b7723a814d84fb65869ea7115cdb3ee7c3be5a27a755c1ec60e049f6b9fcbb2" url: "https://pub.dev" source: hosted - version: "2.0.10+1" + version: "2.0.11" flutter_test: dependency: "direct dev" description: flutter diff --git a/packages/smooth_app/pubspec.yaml b/packages/smooth_app/pubspec.yaml index c5eb4503ae0f..1ef42549ba7d 100644 --- a/packages/smooth_app/pubspec.yaml +++ b/packages/smooth_app/pubspec.yaml @@ -17,7 +17,7 @@ dependencies: barcode_widget: 2.0.4 carousel_slider: 5.0.0 cupertino_icons: 1.0.8 - flutter_svg: 2.0.10+1 + flutter_svg: 2.0.11 flutter_map: 7.0.2 html: 0.15.4 flutter_widget_from_html_core: 0.8.3+1 From 2fe8185ba89f2eb8851999438d84e9c52189519b Mon Sep 17 00:00:00 2001 From: Edouard Marquez Date: Sun, 3 Nov 2024 10:43:31 +0100 Subject: [PATCH 14/16] Edit page: fix regressions after migration to Material 3 (#5781) --- .../lib/pages/product/edit_product_page.dart | 25 ++++++++++++---- .../lib/widgets/smooth_app_bar.dart | 29 ++++++++++++++----- 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/packages/smooth_app/lib/pages/product/edit_product_page.dart b/packages/smooth_app/lib/pages/product/edit_product_page.dart index cbf058063254..eb3ace52cb25 100644 --- a/packages/smooth_app/lib/pages/product/edit_product_page.dart +++ b/packages/smooth_app/lib/pages/product/edit_product_page.dart @@ -22,6 +22,8 @@ import 'package:smooth_app/pages/product/product_field_editor.dart'; import 'package:smooth_app/pages/product/product_image_gallery_view.dart'; import 'package:smooth_app/pages/product/simple_input_page.dart'; import 'package:smooth_app/pages/product/simple_input_page_helpers.dart'; +import 'package:smooth_app/themes/smooth_theme_colors.dart'; +import 'package:smooth_app/themes/theme_provider.dart'; import 'package:smooth_app/widgets/smooth_app_bar.dart'; import 'package:smooth_app/widgets/smooth_floating_message.dart'; import 'package:smooth_app/widgets/smooth_scaffold.dart'; @@ -53,6 +55,8 @@ class _EditProductPageState extends State with UpToDateMixin { context.watch(); refreshUpToDate(); final ThemeData theme = Theme.of(context); + final bool lightTheme = context.lightTheme(); + final String productName = getProductName( upToDateProduct, appLocalizations, @@ -61,9 +65,13 @@ class _EditProductPageState extends State with UpToDateMixin { getProductBrands(upToDateProduct, appLocalizations); return SmoothScaffold( + backgroundColor: lightTheme + ? theme.extension()!.primaryLight + : null, appBar: SmoothAppBar( centerTitle: false, leading: const SmoothBackButton(), + backgroundColor: lightTheme ? Colors.white : null, title: Semantics( value: productName, child: ExcludeSemantics( @@ -174,8 +182,10 @@ class _EditProductPageState extends State with UpToDateMixin { ), if (upToDateProduct.productType != ProductType.product) _ListTitleItem( - leading: - const SvgIcon('assets/cacheTintable/ingredients.svg'), + leading: const SvgIcon( + 'assets/cacheTintable/ingredients.svg', + dontAddColor: true, + ), title: appLocalizations.edit_product_form_item_ingredients_title, onTap: () async => ProductFieldOcrIngredientEditor().edit( @@ -191,8 +201,10 @@ class _EditProductPageState extends State with UpToDateMixin { if (upToDateProduct.productType != ProductType.beauty && upToDateProduct.productType != ProductType.product) _ListTitleItem( - leading: - const SvgIcon('assets/cacheTintable/scale-balance.svg'), + leading: const SvgIcon( + 'assets/cacheTintable/scale-balance.svg', + dontAddColor: true, + ), title: appLocalizations .edit_product_form_item_nutrition_facts_title, subtitle: appLocalizations @@ -219,7 +231,10 @@ class _EditProductPageState extends State with UpToDateMixin { }), _getSimpleListTileItem(SimpleInputPageLabelHelper()), _ListTitleItem( - leading: const SvgIcon('assets/cacheTintable/packaging.svg'), + leading: const SvgIcon( + 'assets/cacheTintable/packaging.svg', + dontAddColor: true, + ), title: appLocalizations.edit_packagings_title, onTap: () async => ProductFieldPackagingEditor().edit( context: context, diff --git a/packages/smooth_app/lib/widgets/smooth_app_bar.dart b/packages/smooth_app/lib/widgets/smooth_app_bar.dart index a08bc81e44bc..14d73672f177 100644 --- a/packages/smooth_app/lib/widgets/smooth_app_bar.dart +++ b/packages/smooth_app/lib/widgets/smooth_app_bar.dart @@ -18,9 +18,10 @@ class SmoothAppBar extends StatelessWidget implements PreferredSizeWidget { this.flexibleSpace, this.bottom, this.elevation, - this.scrolledUnderElevation, + this.scrolledUnderElevation = 0.0, + this.notificationPredicate, this.shadowColor, - this.surfaceTintColor, + // this.surfaceTintColor, this.backgroundColor, this.foregroundColor, this.iconTheme, @@ -41,6 +42,8 @@ class SmoothAppBar extends StatelessWidget implements PreferredSizeWidget { this.actionModeCloseTooltip, this.onLeaveActionMode, this.ignoreSemanticsForSubtitle = false, + this.forceMaterialTransparency = false, + this.clipBehavior, super.key, }) : assert(!actionMode || actionModeTitle != null), preferredSize = @@ -58,8 +61,11 @@ class SmoothAppBar extends StatelessWidget implements PreferredSizeWidget { final PreferredSizeWidget? bottom; final double? elevation; final double? scrolledUnderElevation; + final ScrollNotificationPredicate? notificationPredicate; final Color? shadowColor; - final Color? surfaceTintColor; + + // Disabled as it will do unexpected things with [backgroundColor] + // final Color? surfaceTintColor; final Color? backgroundColor; final Color? foregroundColor; final IconThemeData? iconTheme; @@ -79,6 +85,8 @@ class SmoothAppBar extends StatelessWidget implements PreferredSizeWidget { final TextStyle? titleTextStyle; final SystemUiOverlayStyle? systemOverlayStyle; final bool? ignoreSemanticsForSubtitle; + final bool forceMaterialTransparency; + final Clip? clipBehavior; final VoidCallback? onLeaveActionMode; final String? actionModeCloseTooltip; @@ -101,11 +109,11 @@ class SmoothAppBar extends StatelessWidget implements PreferredSizeWidget { }, child: actionMode ? _createActionModeAppBar(context) - : _createAppBar(parentRoute), + : _createAppBar(context, parentRoute), ); } - Widget _createAppBar(ModalRoute? parentRoute) { + Widget _createAppBar(BuildContext context, ModalRoute? parentRoute) { final bool useCloseButton = parentRoute is PageRoute && parentRoute.fullscreenDialog; Widget? leadingWidget = leading; @@ -127,8 +135,11 @@ class SmoothAppBar extends StatelessWidget implements PreferredSizeWidget { bottom: bottom, elevation: elevation, scrolledUnderElevation: scrolledUnderElevation, + notificationPredicate: + notificationPredicate ?? defaultScrollNotificationPredicate, shadowColor: shadowColor, - surfaceTintColor: surfaceTintColor, + surfaceTintColor: + backgroundColor ?? Theme.of(context).appBarTheme.backgroundColor, backgroundColor: backgroundColor, foregroundColor: foregroundColor, iconTheme: iconTheme, @@ -143,8 +154,9 @@ class SmoothAppBar extends StatelessWidget implements PreferredSizeWidget { toolbarHeight: toolbarHeight, leadingWidth: leadingWidth, toolbarTextStyle: toolbarTextStyle, - titleTextStyle: titleTextStyle, systemOverlayStyle: systemOverlayStyle, + forceMaterialTransparency: forceMaterialTransparency, + clipBehavior: clipBehavior, ); } @@ -171,7 +183,8 @@ class SmoothAppBar extends StatelessWidget implements PreferredSizeWidget { elevation: elevation, scrolledUnderElevation: scrolledUnderElevation, shadowColor: shadowColor, - surfaceTintColor: surfaceTintColor, + surfaceTintColor: + backgroundColor ?? Theme.of(context).appBarTheme.backgroundColor, backgroundColor: backgroundColor, foregroundColor: foregroundColor, iconTheme: iconTheme, From 148948d9e1d21bb3c4aaee09b3a0ef95dae1f0de Mon Sep 17 00:00:00 2001 From: Pierre Slamich Date: Sun, 3 Nov 2024 10:46:18 +0100 Subject: [PATCH 15/16] ci: labeler reorg for editing --- .github/labeler.yml | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/.github/labeler.yml b/.github/labeler.yml index 8766331ba561..527cd2c340f7 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -106,10 +106,6 @@ History: - any-glob-to-any-file: 'packages/smooth_app/lib/pages/onboarding/**/*' - any-glob-to-any-file: 'packages/smooth_app/assets/onboarding/**/*' -✏️ Editing - Nutrition input: -- changed-files: - - any-glob-to-any-file: 'packages/smooth_app/lib/pages/product/nutrition_page_loaded.dart' - 🤳🥫 Scan: - changed-files: - any-glob-to-any-file: 'packages/smooth_app/lib/pages/scan/**/*' @@ -206,18 +202,6 @@ GitHub: - any-glob-to-any-file: 'packages/smooth_app/lib/pages/user_preferences_attribute_group.dart' - any-glob-to-any-file: 'packages/smooth_app/lib/helpers/user_management_helper.dart' -#tag picker: - -✏️ Editing - Category picker: -- changed-files: - - any-glob-to-any-file: 'packages/smooth_app/lib/data_models/category_tree_supplier.dart' - - any-glob-to-any-file: 'packages/smooth_app/example/smooth_category_picker_example.dart' - - any-glob-to-any-file: 'packages/smooth_app/generic_lib/dialogs/smooth_category_picker.dart' - - any-glob-to-any-file: 'packages/smooth_app/test/dialogs/smooth_category_picker_test.dart' - - any-glob-to-any-file: 'packages/smooth_app/lib/data_models/category_query_model.dart' - - any-glob-to-any-file: 'packages/smooth_app/lib/data_models/query_category_tree_supplier.dart' - - any-glob-to-any-file: 'packages/smooth_app/lib/data_models/smooth_category.dart' - Ranking: - changed-files: - any-glob-to-any-file: 'packages/smooth_app/lib/helpers/product_compatibility_helper.dart' @@ -242,11 +226,35 @@ Product scan carousel: - changed-files: - any-glob-to-any-file: 'packages/smooth_app/lib/pages/scan/carousel/scan_carousel.dart' +✏️ Editing: +- changed-files: + - any-glob-to-any-file: 'packages/smooth_app/lib/pages/product/edit_product_page.dart' + +✏️ Editing - Nutrition input: +- changed-files: + - any-glob-to-any-file: 'packages/smooth_app/lib/pages/product/nutrition_page_loaded.dart' + +#tag picker: + +✏️ Editing - Category picker: +- changed-files: + - any-glob-to-any-file: 'packages/smooth_app/lib/data_models/category_tree_supplier.dart' + - any-glob-to-any-file: 'packages/smooth_app/example/smooth_category_picker_example.dart' + - any-glob-to-any-file: 'packages/smooth_app/generic_lib/dialogs/smooth_category_picker.dart' + - any-glob-to-any-file: 'packages/smooth_app/test/dialogs/smooth_category_picker_test.dart' + - any-glob-to-any-file: 'packages/smooth_app/lib/data_models/category_query_model.dart' + - any-glob-to-any-file: 'packages/smooth_app/lib/data_models/query_category_tree_supplier.dart' + - any-glob-to-any-file: 'packages/smooth_app/lib/data_models/smooth_category.dart' + ✏️ Editing - 📦 Packaging input: - changed-files: - any-glob-to-any-file: 'packages/smooth_app/lib/pages/product/edit_new_packagings.dart' - any-glob-to-any-file: 'packages/smooth_app/lib/pages/product/ocr_packaging_helper.dart' +✏️ Editing - Basic info input: +- changed-files: + - any-glob-to-any-file: 'packages/smooth_app/lib/pages/product/add_basic_details_page.dart' + OCR page: - changed-files: - any-glob-to-any-file: 'packages/smooth_app/lib/pages/product/edit_ocr/edit_ocr_main_action.dart' @@ -254,10 +262,6 @@ OCR page: - any-glob-to-any-file: 'packages/smooth_app/lib/pages/product/edit_ocr/ocr_ingredients_helper.dart' - any-glob-to-any-file: 'packages/smooth_app/lib/pages/product/edit_ocr/ocr_packaging_helper.dart' -✏️ Editing - Basic info input: -- changed-files: - - any-glob-to-any-file: 'packages/smooth_app/lib/pages/product/add_basic_details_page.dart' - Prices: - changed-files: - any-glob-to-any-file: 'packages/smooth_app/lib/background/background_task_add_price.dart' From 104b3bf0404d156f3674e5856abea4d5ae3463a5 Mon Sep 17 00:00:00 2001 From: Edouard Marquez Date: Tue, 5 Nov 2024 08:51:03 +0100 Subject: [PATCH 16/16] Adopt iOS code for Android status bar (#5784) --- .../lib/pages/onboarding/reinvention_page.dart | 3 ++- .../lib/pages/preferences/user_preferences_page.dart | 9 +++------ packages/smooth_app/lib/widgets/smooth_scaffold.dart | 3 +-- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/smooth_app/lib/pages/onboarding/reinvention_page.dart b/packages/smooth_app/lib/pages/onboarding/reinvention_page.dart index 31cbedb74400..cfb0840107b5 100644 --- a/packages/smooth_app/lib/pages/onboarding/reinvention_page.dart +++ b/packages/smooth_app/lib/pages/onboarding/reinvention_page.dart @@ -9,6 +9,7 @@ import 'package:smooth_app/database/local_database.dart'; import 'package:smooth_app/pages/onboarding/onboarding_flow_navigator.dart'; import 'package:smooth_app/pages/onboarding/v2/onboarding_bottom_hills.dart'; import 'package:smooth_app/themes/smooth_theme_colors.dart'; +import 'package:smooth_app/widgets/smooth_scaffold.dart'; import 'package:smooth_app/widgets/smooth_text.dart'; /// Onboarding page: "reinvention" @@ -17,7 +18,7 @@ class OnboardingHomePage extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( + return SmoothScaffold( backgroundColor: const Color(0xFFE3F3FE), body: Provider.value( value: OnboardingConfig._(MediaQuery.sizeOf(context)), diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_page.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_page.dart index fd47fd12dc21..2d64551bc9ef 100644 --- a/packages/smooth_app/lib/pages/preferences/user_preferences_page.dart +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_page.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -267,10 +265,9 @@ class _UserPreferencesPageState extends State ); return SmoothScaffold( statusBarBackgroundColor: dark ? null : headerColor, - brightness: - Theme.of(context).brightness == Brightness.light && Platform.isIOS - ? Brightness.dark - : Brightness.light, + brightness: Theme.of(context).brightness == Brightness.light + ? Brightness.dark + : Brightness.light, contentBehindStatusBar: false, spaceBehindStatusBar: false, appBar: SmoothAppBar( diff --git a/packages/smooth_app/lib/widgets/smooth_scaffold.dart b/packages/smooth_app/lib/widgets/smooth_scaffold.dart index 6fc2ff670608..08ef268854fc 100644 --- a/packages/smooth_app/lib/widgets/smooth_scaffold.dart +++ b/packages/smooth_app/lib/widgets/smooth_scaffold.dart @@ -146,8 +146,7 @@ class SmoothScaffoldState extends ScaffoldState { SystemUiOverlayStyle get _overlayStyle { final Brightness? brightness; - // Invert brightness on iOS devices - if (Platform.isIOS && _brightness == null) { + if (_brightness == null) { switch (Theme.of(context).brightness) { case Brightness.dark: brightness = Brightness.light;