diff --git a/.github/workflows/waldo_sessions.yml b/.github/workflows/waldo_sessions.yml new file mode 100644 index 00000000000..51e8548a30b --- /dev/null +++ b/.github/workflows/waldo_sessions.yml @@ -0,0 +1,61 @@ +name: Upload builds to Waldo + +on: + pull_request_target: + types: + - opened + - edited + - synchronize + +jobs: + build: + runs-on: macos-latest + defaults: + run: + shell: bash + steps: + - name: "Checkout code" + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup Java JDK + uses: actions/setup-java@v3.11.0 + with: + distribution: 'zulu' + java-version: 11 + + # Get the flutter version from ./flutter-version.txt + - run: echo "FLUTTER_VERSION=$(cat flutter-version.txt)" >> $GITHUB_OUTPUT + id: flutter-version + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + #channel: stable + cache: true + flutter-version: ${{ steps.flutter-version.outputs.FLUTTER_VERSION }} + cache-key: flutter-${{ hashFiles('flutter-version.txt')}}-${{ hashFiles('packages\smooth_app\pubspec.lock')}} + + - run: flutter --version + + - name: Get dependencies + run: ci/pub_upgrade.sh + + # Build apk. + - name: Build APK + run: flutter build apk --debug -t lib/entrypoints/android/main_google_play.dart + working-directory: ./packages/smooth_app + + - name: Upload APK to Waldo + uses: waldoapp/gh-action-upload@v1 + with: + build_path: packages/smooth_app/build/app/outputs/flutter-apk/app-debug.apk + upload_token: ${{ secrets.WALDO_SESSIONS_ANDROID }} + + - name: Write comment + uses: mshick/add-pr-comment@v2 + with: + message: "You can test this PR on: [https://app.waldo.com/applications/app-19d476740ba1bb36/sessions](Android)" + + # TODO Build the iOS variant and upload it \ No newline at end of file diff --git a/.run/iOS app.run.xml b/.run/iOS app.run.xml index fdc39c324ec..93d440e0c05 100644 --- a/.run/iOS app.run.xml +++ b/.run/iOS app.run.xml @@ -1,5 +1,5 @@ - + diff --git a/.vscode/launch.json b/.vscode/launch.json index d016cd97e75..93b48adcb0f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,7 +7,7 @@ "program": "packages/smooth_app/lib/entrypoints/android/main_google_play.dart" }, { - "name": "iOS", + "name": "iOS/macOS", "type": "dart", "request": "launch", "program": "packages/smooth_app/lib/entrypoints/ios/main_ios.dart" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d42f7324c5f..e992b149b20 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,11 +3,11 @@ We have predefined run configurations for Android Studio and Visual Studio Code -In order to run the application, make sure you are in the `packages/smooth_app` directory and run these commands : +In order to run the application, make sure you are in the `packages/smooth_app` directory and run these commands: - `flutter pub get .` - On Android 🤖: flutter run -t lib/entrypoints/android/main_google_play.dart -- On iOS 🍎: flutter run -t lib/entrypoints/ios/main_ios.dart +- On iOS/macOS 🍎: flutter run -t lib/entrypoints/ios/main_ios.dart ## Contributing @@ -15,7 +15,7 @@ In order to run the application, make sure you are in the `packages/smooth_app` - Please ensure to add a before/after screenshot when doing a PR that has visual impacts. -- Please name your pull request following this scheme: `type: What you did` this allows us to automatically generate the changelog +- Please name your pull request following this scheme: `type: What you did` this allows us to automatically generate the changelog. Following `type`s are allowed: - `feat`, for Features @@ -45,19 +45,17 @@ Following `type`s are allowed: [Track crashes](https://sentry.io/organizations/openfoodfacts/issues/?project=5376745) -

- ## Contributing - What can I work on ? Are you a developer? A graphic designer? Full of innovative ideas to help users improve their mode of consumption? Then join us! We are always looking for new contributors, if you're willing to help please let us know, we'll be pleased to introduce you to the project. - On GitHub, [you can start here to get some inspiration](https://github.com/openfoodfacts/smooth-app/issues/525) -- You can join the Open Food Facts's Slack here : [Get an invite](https://slack.openfoodfacts.org) - [Open our Slack](https://openfoodfacts.slack.com). +- You can join the Open Food Facts's Slack here: [Get an invite](https://slack.openfoodfacts.org) - [Open our Slack](https://openfoodfacts.slack.com). ### Weekly meetings - We usually meet on Thursdays at 15:30 GMT (UTC) at . Please email pierre@openfoodfacts.org if you want to be added to the Calendar invite for convenience + We usually meet on Thursdays at 15:30 GMT (UTC) at . Please email pierre@openfoodfacts.org if you want to be added to the Calendar invite for convenience. ## Wiki & Doc @@ -70,10 +68,6 @@ We are always looking for new contributors, if you're willing to help please let - [Project Smoothie Landing page](https://github.com/openfoodfacts/smoothielanding) - Private app signing for iOS certificates repository - please ask @teolemon -## Custom dependencies (forked versions) - -- [g123k/plugins](https://github.com/g123k/plugins) - We use our own fork of the camera plugin to be able to hotfix problems in it we find. - ## V1 Roadmap (Shipped on June 15th 2022 for Vivatech) - [x] We should be able to ship the Smoothie code to the main listing on Android and iOS diff --git a/README.md b/README.md index 45ac3de0013..2d21b4acc13 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@
-## Smooth App : The new Open Food Facts mobile app for Android and iPhone +## Smooth App: The new Open Food Facts mobile app for Android and iPhone [![SmoothApp Post-Submit Tests](https://github.com/openfoodfacts/smooth-app/actions/workflows/postsubmit.yml/badge.svg)](https://github.com/openfoodfacts/smooth-app/actions/workflows/postsubmit.yml) [![Create internal releases](https://github.com/openfoodfacts/smooth-app/actions/workflows/internal-release.yml/badge.svg)](https://github.com/openfoodfacts/smooth-app/actions/workflows/internal-release.yml) @@ -39,23 +39,24 @@ Starting this April, we invite all users and contributors to build a vision for ## Current Release -Latest commit deployed to Apple App Store: Released on Nov 29th as Build 835 -Latest commit deployed to PlayStore: 792 from Nov 6th 11AM +- Latest commit deployed to Apple App Store: Released on Nov 29th as Build 835 +- Latest commit deployed to PlayStore: 792 from Nov 6th 11AM ## Presentation - This new mobile application aims to showcase Open Food Facts's power to a broad range of users through a smooth user experience and sleek user interface. It is a Flutter application by [Open Food Facts](https://github.com/openfoodfacts). - We pioneered the collaborative scanning app in 2012. With this experimental app, we’re reinventing it from the ground up. -- Install it on [Android](https://play.google.com/store/apps/details?id=org.openfoodfacts.scanner) or [iPhone/iPad](https://apps.apple.com/app/open-food-facts/id588797948). Note that a internal development build ([Android](https://play.google.com/apps/internaltest/4699092342921529278) or [iPhone/iPad](https://testflight.apple.com/join/c2tiBHgd) )if you'd like to use the results of your PRs quicker. +- Install it on **Android** ([Google Play](https://play.google.com/store/apps/details?id=org.openfoodfacts.scanner), [F-Droid](https://f-droid.org/fr/packages/openfoodfacts.github.scrachx.openfood/) or [Amazon App Store](https://www.amazon.com/Open-Food-Facts-food-Nutriscore/dp/B00U49IVIU)) or [iPhone/iPad](https://apps.apple.com/app/open-food-facts/id588797948). Note that a internal development build ([Android](https://play.google.com/apps/internaltest/4699092342921529278) or **iPhone/iPad** ([App Store](https://testflight.apple.com/join/c2tiBHgd)) if you'd like to use the results of your PRs quicker. app showcase - Smooth-app is developed in parallel to the [openfoodfacts-dart](https://github.com/openfoodfacts/openfoodfacts-dart) plugin, which provides a high level interface with the Open Food Facts API and [openfoodfacts_flutter_lints](https://github.com/openfoodfacts/openfoodfacts_flutter_lints) which provides specific linting - Every new interaction with the API should be implemented in the plugin in order to provide these new features to other developers. +- We support desktop platforms (Linux, macOS and Windows), but **only for development**
-

Features of the app

+

Features of the app

## Features @@ -95,17 +96,17 @@ Latest commit deployed to PlayStore: 792 from Nov 6th 11AM We have predefined run configurations for Android Studio and Visual Studio Code -In order to run the application, make sure you are in the `packages/smooth_app` directory and run these commands : +In order to run the application, make sure you are in the `packages/smooth_app` directory and run these commands: - `flutter pub get .` - On Android 🤖: `flutter run -t lib/entrypoints/android/main_google_play.dart` -- On iOS 🍎: `flutter run -t lib/entrypoints/ios/main_ios.dart` +- On iOS/macOS 🍎: `flutter run -t lib/entrypoints/ios/main_ios.dart` - Troubleshooting🚀: If you get an error like `App depends on scanner shared from path which depends on camera_platform_interface from git, version solving failed.` then run - `flutter pub cache clean` or manually delete the - - `C:\Users\~\AppData\Local\Pub\Cache` file . + - `C:\Users\~\AppData\Local\Pub\Cache` file. Then redo the above procedure to run the app. - [Contributing Guidelines](https://github.com/openfoodfacts/smooth-app/blob/develop/CONTRIBUTING.md) diff --git a/packages/app_store/apple_app_store/lib/src/apple_app_store.dart b/packages/app_store/apple_app_store/lib/src/apple_app_store.dart index 00b21f21660..0513aef47cd 100644 --- a/packages/app_store/apple_app_store/lib/src/apple_app_store.dart +++ b/packages/app_store/apple_app_store/lib/src/apple_app_store.dart @@ -9,7 +9,7 @@ class AppleAppStore extends AppStore { AppleAppStore(this.appId) : assert(appId.isNotEmpty), assert(!appId.startsWith('id')), - assert(Platform.isIOS); + assert(Platform.isIOS || Platform.isMacOS); final String appId; final InAppReview _inAppReview = InAppReview.instance; diff --git a/packages/scanner/ml_kit/lib/src/scanner_ml_kit.dart b/packages/scanner/ml_kit/lib/src/scanner_ml_kit.dart index c34384ace10..2b67d7fd0a8 100644 --- a/packages/scanner/ml_kit/lib/src/scanner_ml_kit.dart +++ b/packages/scanner/ml_kit/lib/src/scanner_ml_kit.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:async/async.dart'; import 'package:flutter/material.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:scanner_shared/scanner_shared.dart'; @@ -83,7 +84,6 @@ class _SmoothBarcodeScannerMLKitState extends State<_SmoothBarcodeScannerMLKit> static const ValueKey _visibilityKey = ValueKey('VisibilityDetector'); - static const double _cornerPadding = 26.0; bool _isStarted = true; @@ -100,6 +100,11 @@ class _SmoothBarcodeScannerMLKitState extends State<_SmoothBarcodeScannerMLKit> autoStart: true, ); + // Stores a background operation when the screen isn't visible + CancelableOperation? _autoStopCameraOperation; + // Stores the latest visibility value of the screen + VisibilityInfo? _latestVisibilityInfoEvent; + @override void initState() { super.initState(); @@ -113,25 +118,64 @@ class _SmoothBarcodeScannerMLKitState extends State<_SmoothBarcodeScannerMLKit> if (state == AppLifecycleState.paused) { _stop(); } else if (state == AppLifecycleState.resumed) { - /// When the app is resumed (from the launcher for example), the camera is - /// always started and we can't prevent this behavior. - /// - /// To fix it, we check when the app is resumed if the camera is the - /// visible page and if that's not the case, we wait for the camera to be - /// initialized to stop it - WidgetsBinding.instance.addPostFrameCallback((_) { - if (ScreenVisibilityDetector.invisible(context)) { - _pauseCameraWhenInitialized(); - } - }); + _autoStopCameraOperation?.cancel(); + _checkIfAppIsRestarting(); + } + } + + void _checkIfAppIsRestarting([int retry = 0]) { + /// When the app is resumed (from the launcher for example), the camera is + /// always started due to the [autostart] feature and we can't + /// prevent this behavior. + /// + /// To fix it, we check when the app is resumed if the camera is the + /// visible page and if that's not the case, we wait for the camera to be + /// initialized to stop it. + /// + /// Comment from @g123k: This is a very hacky way (temporary I hope) and + /// more explanation are available on the PR: + /// [https://github.com/openfoodfacts/smooth-app/pull/4292] + /// + // ignore: prefer_function_declarations_over_variables + final Function fn = () { + if (ScreenVisibilityDetector.invisible(context)) { + _pauseCameraWhenInitialized(); + } else if (retry < 1) { + // In 99% of cases, this won't happen, but if for some reason, we are + // "considered" as visible, we will retry in a few milliseconds + // and if we are still invisible -> force stop the camera + _autoStopCameraOperation = CancelableOperation.fromFuture( + Future.delayed( + const Duration(milliseconds: 500), + () => _checkIfAppIsRestarting(retry + 1), + ), + ); + } else if (_latestVisibilityInfoEvent?.visible == false) { + _pauseCameraWhenInitialized(); + } + }; + + // Ensure to wait for the first frame + if (retry == 0) { + // ignore: avoid_dynamic_calls + WidgetsBinding.instance.addPostFrameCallback((_) => fn.call()); + } else { + // ignore: avoid_dynamic_calls + scheduleMicrotask(() => fn.call()); } } Future _pauseCameraWhenInitialized() async { + if (!mounted) { + return; + } + if (_controller.isStarting) { - return Future.delayed( - const Duration(milliseconds: 250), - () => _pauseCameraWhenInitialized(), + _autoStopCameraOperation = CancelableOperation.fromFuture( + Future.delayed( + const Duration(milliseconds: 250), + () => _pauseCameraWhenInitialized(), + ), ); } @@ -155,6 +199,7 @@ class _SmoothBarcodeScannerMLKitState extends State<_SmoothBarcodeScannerMLKit> } Future _stop() async { + _autoStopCameraOperation?.cancel(); if (!_isStarted) { return; } @@ -172,6 +217,7 @@ class _SmoothBarcodeScannerMLKitState extends State<_SmoothBarcodeScannerMLKit> return VisibilityDetector( key: _visibilityKey, onVisibilityChanged: (final VisibilityInfo info) async { + _latestVisibilityInfoEvent = info; if (info.visibleBounds.height > 0.0) { await _start(); } else { @@ -200,14 +246,15 @@ class _SmoothBarcodeScannerMLKitState extends State<_SmoothBarcodeScannerMLKit> ), Center( child: SmoothBarcodeScannerVisor( - cornerRadius: _cornerPadding, contentPadding: widget.contentPadding, ), ), Align( alignment: Alignment.bottomCenter, child: Padding( - padding: const EdgeInsets.all(_cornerPadding), + padding: const EdgeInsets.all( + SmoothBarcodeScannerVisor.CORNER_PADDING, + ), child: Row( mainAxisAlignment: _showFlipCameraButton ? MainAxisAlignment.spaceBetween @@ -215,12 +262,14 @@ class _SmoothBarcodeScannerMLKitState extends State<_SmoothBarcodeScannerMLKit> crossAxisAlignment: CrossAxisAlignment.center, children: [ if (_showFlipCameraButton) - IconButton( - enableFeedback: true, + VisorButton( + onTap: () async { + widget.hapticFeedback.call(); + await _controller.switchCamera(); + }, tooltip: widget.toggleCameraModeTooltip ?? 'Switch between back and front camera', - color: Colors.white, - icon: ValueListenableBuilder( + child: ValueListenableBuilder( valueListenable: _controller.cameraFacingState, builder: ( BuildContext context, @@ -235,10 +284,6 @@ class _SmoothBarcodeScannerMLKitState extends State<_SmoothBarcodeScannerMLKit> } }, ), - onPressed: () async { - widget.hapticFeedback.call(); - await _controller.switchCamera(); - }, ), ValueListenableBuilder( valueListenable: _controller.hasTorchState, @@ -250,12 +295,19 @@ class _SmoothBarcodeScannerMLKitState extends State<_SmoothBarcodeScannerMLKit> if (state != true) { return const SizedBox.shrink(); } - return IconButton( - enableFeedback: true, + return VisorButton( tooltip: widget.toggleFlashModeTooltip ?? 'Turn ON or OFF the flash of the camera', - color: Colors.white, - icon: ValueListenableBuilder( + onTap: () async { + widget.hapticFeedback.call(); + + try { + await _controller.toggleTorch(); + } catch (err) { + widget.onCameraFlashError?.call(context); + } + }, + child: ValueListenableBuilder( valueListenable: _controller.torchState, builder: ( BuildContext context, @@ -276,15 +328,6 @@ class _SmoothBarcodeScannerMLKitState extends State<_SmoothBarcodeScannerMLKit> } }, ), - onPressed: () async { - widget.hapticFeedback.call(); - - try { - await _controller.toggleTorch(); - } catch (err) { - widget.onCameraFlashError?.call(context); - } - }, ); }, ), @@ -300,6 +343,7 @@ class _SmoothBarcodeScannerMLKitState extends State<_SmoothBarcodeScannerMLKit> @override void dispose() { WidgetsBinding.instance.removeObserver(this); + _autoStopCameraOperation?.cancel(); _controller.dispose(); super.dispose(); } diff --git a/packages/scanner/ml_kit/pubspec.yaml b/packages/scanner/ml_kit/pubspec.yaml index 9acba17987f..2a1f8e93a1b 100644 --- a/packages/scanner/ml_kit/pubspec.yaml +++ b/packages/scanner/ml_kit/pubspec.yaml @@ -11,6 +11,8 @@ dependencies: sdk: flutter visibility_detector: 0.4.0+2 + async: 2.11.0 + mobile_scanner: git: url: https://github.com/openfoodfacts/mobile_scanner.git diff --git a/packages/scanner/shared/lib/src/scanner_visor.dart b/packages/scanner/shared/lib/src/scanner_visor.dart index ee903f9f8d5..3eb78bf710f 100644 --- a/packages/scanner/shared/lib/src/scanner_visor.dart +++ b/packages/scanner/shared/lib/src/scanner_visor.dart @@ -3,12 +3,13 @@ import 'package:flutter_svg/flutter_svg.dart'; class SmoothBarcodeScannerVisor extends StatelessWidget { const SmoothBarcodeScannerVisor({ - required this.cornerRadius, this.contentPadding, super.key, }); - final double cornerRadius; + static const double CORNER_PADDING = 26.0; + static const double STROKE_WIDTH = 3.0; + final EdgeInsetsGeometry? contentPadding; @override @@ -19,7 +20,7 @@ class SmoothBarcodeScannerVisor extends StatelessWidget { padding: contentPadding, // The duration is twice the time required to hide the header duration: const Duration(milliseconds: 250), - curve: contentPadding.horizontal > cornerRadius * 2 + curve: contentPadding.horizontal > CORNER_PADDING * 2 ? Curves.easeOutQuad : Curves.decelerate, child: SizedBox.expand( @@ -39,13 +40,13 @@ class SmoothBarcodeScannerVisor extends StatelessWidget { EdgeInsetsGeometry _computePadding() { if (contentPadding == null) { - return EdgeInsets.all(cornerRadius); + return const EdgeInsets.all(CORNER_PADDING); } else { - return EdgeInsets.only( - top: cornerRadius / 4.0, - left: cornerRadius, - right: cornerRadius, - bottom: cornerRadius, + return const EdgeInsets.only( + top: CORNER_PADDING / 4.0, + left: CORNER_PADDING, + right: CORNER_PADDING, + bottom: CORNER_PADDING, ).add(contentPadding!); } } @@ -54,13 +55,12 @@ class SmoothBarcodeScannerVisor extends StatelessWidget { class _ScanVisorPainter extends CustomPainter { _ScanVisorPainter(); - static const double strokeWidth = 3.0; static const double _fullCornerSize = 31.0; static const double _halfCornerSize = _fullCornerSize / 2; static const Radius _borderRadius = Radius.circular(_halfCornerSize); final Paint _paint = Paint() - ..strokeWidth = strokeWidth + ..strokeWidth = SmoothBarcodeScannerVisor.STROKE_WIDTH ..color = Colors.white ..style = PaintingStyle.stroke; @@ -79,7 +79,7 @@ class _ScanVisorPainter extends CustomPainter { static Path getPath(Rect rect, bool includeLineBetweenCorners) { final double bottomPosition; if (includeLineBetweenCorners) { - bottomPosition = rect.bottom - strokeWidth; + bottomPosition = rect.bottom - SmoothBarcodeScannerVisor.STROKE_WIDTH; } else { bottomPosition = rect.bottom; } @@ -146,3 +146,41 @@ class _ScanVisorPainter extends CustomPainter { return path; } } + +class VisorButton extends StatelessWidget { + const VisorButton({ + required this.child, + required this.onTap, + required this.tooltip, + }); + + final VoidCallback onTap; + final String tooltip; + final Widget child; + + @override + Widget build(BuildContext context) { + return Material( + type: MaterialType.transparency, + child: InkWell( + onTap: onTap, + borderRadius: const BorderRadius.all( + Radius.circular( + SmoothBarcodeScannerVisor.CORNER_PADDING, + ), + ), + child: Tooltip( + message: tooltip, + enableFeedback: true, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: IconTheme( + data: const IconThemeData(color: Colors.white), + child: child, + ), + ), + ), + ), + ); + } +} diff --git a/packages/scanner/zxing/lib/src/scanner_zxing.dart b/packages/scanner/zxing/lib/src/scanner_zxing.dart index 448f9921a73..2a62a25e2e3 100644 --- a/packages/scanner/zxing/lib/src/scanner_zxing.dart +++ b/packages/scanner/zxing/lib/src/scanner_zxing.dart @@ -78,8 +78,6 @@ class _SmoothBarcodeScannerZXingState BarcodeFormat.upcE, ]; - static const double _cornerPadding = 26; - bool _visible = false; final GlobalKey _qrKey = GlobalKey(debugLabel: 'QR'); QRViewController? _controller; @@ -131,14 +129,15 @@ class _SmoothBarcodeScannerZXingState ), Center( child: SmoothBarcodeScannerVisor( - cornerRadius: _cornerPadding, contentPadding: widget.contentPadding, ), ), Align( alignment: Alignment.bottomCenter, child: Padding( - padding: const EdgeInsets.all(_cornerPadding), + padding: const EdgeInsets.all( + SmoothBarcodeScannerVisor.CORNER_PADDING, + ), child: Row( mainAxisAlignment: _showFlipCameraButton ? MainAxisAlignment.spaceBetween @@ -146,16 +145,15 @@ class _SmoothBarcodeScannerZXingState crossAxisAlignment: CrossAxisAlignment.center, children: [ if (_showFlipCameraButton) - IconButton( - icon: Icon(getCameraFlip()), + VisorButton( tooltip: widget.toggleCameraModeTooltip ?? 'Switch between back and front camera', - color: Colors.white, - onPressed: () async { + onTap: () async { widget.hapticFeedback.call(); await _controller?.flipCamera(); setState(() {}); }, + child: Icon(getCameraFlip()), ), FutureBuilder( future: _controller?.getFlashStatus(), @@ -164,14 +162,10 @@ class _SmoothBarcodeScannerZXingState if (flashOn == null) { return EMPTY_WIDGET; } - return IconButton( - icon: - Icon(flashOn ? Icons.flash_on : Icons.flash_off), - enableFeedback: true, + return VisorButton( tooltip: widget.toggleFlashModeTooltip ?? 'Turn ON or OFF the flash of the camera', - color: Colors.white, - onPressed: () async { + onTap: () async { widget.hapticFeedback.call(); try { @@ -181,6 +175,9 @@ class _SmoothBarcodeScannerZXingState widget.onCameraFlashError?.call(context); } }, + child: Icon( + flashOn ? Icons.flash_on : Icons.flash_off, + ), ); }, ), diff --git a/packages/smooth_app/android/app/build.gradle b/packages/smooth_app/android/app/build.gradle index a9b1054d711..32e739346a4 100644 --- a/packages/smooth_app/android/app/build.gradle +++ b/packages/smooth_app/android/app/build.gradle @@ -27,6 +27,7 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 33 + ndkVersion "25.1.8937393" compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 diff --git a/packages/smooth_app/assets/onboarding/hill_end.svg b/packages/smooth_app/assets/onboarding/hill_end.svg new file mode 100644 index 00000000000..8a568c76e8c --- /dev/null +++ b/packages/smooth_app/assets/onboarding/hill_end.svg @@ -0,0 +1,9 @@ + + + Artboard + + + + + + \ No newline at end of file diff --git a/packages/smooth_app/assets/onboarding/hill_start.svg b/packages/smooth_app/assets/onboarding/hill_start.svg new file mode 100644 index 00000000000..7272afc0b78 --- /dev/null +++ b/packages/smooth_app/assets/onboarding/hill_start.svg @@ -0,0 +1,9 @@ + + + reinvention + + + + + + \ No newline at end of file diff --git a/packages/smooth_app/assets/onboarding/onboarding.riv b/packages/smooth_app/assets/onboarding/onboarding.riv new file mode 100644 index 00000000000..de201145d07 Binary files /dev/null and b/packages/smooth_app/assets/onboarding/onboarding.riv differ diff --git a/packages/smooth_app/fastlane/metadata/en-US/full_description.txt b/packages/smooth_app/fastlane/metadata/en-US/full_description.txt index 06b62a81647..39277dcdae5 100644 --- a/packages/smooth_app/fastlane/metadata/en-US/full_description.txt +++ b/packages/smooth_app/fastlane/metadata/en-US/full_description.txt @@ -24,12 +24,12 @@ Eco-Score

👉 Choose products that are good for you ⚕️

-➜ The Nutri-Score grade, from A to E : Nutritional quality -➜ The NOVA group, from 1 to 4 : Avoid ultra-processed foods (Group 4) +➜ The Nutri-Score grade, from A to E: Nutritional quality +➜ The NOVA group, from 1 to 4: Avoid ultra-processed foods (Group 4)

👉 Choose products that are good the planet 🌍

➜ Food causes over 1/3 of global greenhouse gas emissions -➜ The Eco-Score grade, from A to E : a synthesis of 16 environmental impacts +➜ The Eco-Score grade, from A to E: a synthesis of 16 environmental impacts

👉 Contribute to Food Transparency

@@ -67,6 +67,6 @@ We decipher products labels for you. You'll find:

👉 More

Web version: https://world.openfoodfacts.org -Questions, feedback : contact@openfoodfacts.org +Questions, feedback: contact@openfoodfacts.org For cosmetics, you can install Open Beauty Facts. diff --git a/packages/smooth_app/ios/Runner/Info.plist b/packages/smooth_app/ios/Runner/Info.plist index 65c63f92a6b..7e8191d8b63 100644 --- a/packages/smooth_app/ios/Runner/Info.plist +++ b/packages/smooth_app/ios/Runner/Info.plist @@ -59,5 +59,7 @@ FlutterDeepLinkingEnabled + ITSAppUsesNonExemptEncryption + 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 c42898bddd0..0e45efce003 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 @@ -130,7 +130,7 @@ class _ProductTitleCardTrailing extends StatelessWidget { if (removable && !selectable) { return Align( - alignment: Alignment.centerRight, + alignment: AlignmentDirectional.centerEnd, child: ProductCardCloseButton( onRemove: onRemove, ), 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 8f77f3acc9a..94aeaff7797 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 @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.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/haptic_feedback_helper.dart'; /// A common Widget for carrousel item cards. /// It allows to have the correct width/height and also a scale down feature, @@ -12,11 +13,13 @@ class SmoothProductBaseCard extends StatelessWidget { const SmoothProductBaseCard({ required this.child, this.backgroundColorOpacity, + this.margin, super.key, }); final Widget child; final double? backgroundColorOpacity; + final EdgeInsets? margin; @override Widget build(BuildContext context) { @@ -34,6 +37,7 @@ class SmoothProductBaseCard extends StatelessWidget { color: themeData.brightness == Brightness.light ? Colors.white.withOpacity(backgroundColorOpacity ?? 1.0) : Colors.black.withOpacity(backgroundColorOpacity ?? 1.0), + margin: margin, padding: padding, child: FittedBox( fit: BoxFit.scaleDown, @@ -66,7 +70,10 @@ class ProductCardCloseButton extends StatelessWidget { return InkWell( customBorder: const CircleBorder(), - onTap: () => onRemove?.call(context), + onTap: () { + onRemove?.call(context); + SmoothHapticFeedback.lightNotification(); + }, child: Tooltip( message: appLocalizations.product_card_remove_product_tooltip, child: Padding( diff --git a/packages/smooth_app/lib/cards/product_cards/smooth_product_card_not_found.dart b/packages/smooth_app/lib/cards/product_cards/smooth_product_card_not_found.dart index e98940e9bec..22c8c933a98 100644 --- a/packages/smooth_app/lib/cards/product_cards/smooth_product_card_not_found.dart +++ b/packages/smooth_app/lib/cards/product_cards/smooth_product_card_not_found.dart @@ -25,6 +25,9 @@ class SmoothProductCardNotFound extends StatelessWidget { final TextTheme textTheme = Theme.of(context).textTheme; return SmoothProductBaseCard( + margin: const EdgeInsets.symmetric( + vertical: VERY_SMALL_SPACE, + ), child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.center, diff --git a/packages/smooth_app/lib/cards/product_cards/smooth_product_card_template.dart b/packages/smooth_app/lib/cards/product_cards/smooth_product_card_template.dart index bd112f2c984..0ba342006a5 100644 --- a/packages/smooth_app/lib/cards/product_cards/smooth_product_card_template.dart +++ b/packages/smooth_app/lib/cards/product_cards/smooth_product_card_template.dart @@ -66,7 +66,8 @@ class SmoothProductCardTemplate extends StatelessWidget { height: screenSize.width * 0.20, color: itemColor, ), - const Padding(padding: EdgeInsets.only(left: VERY_SMALL_SPACE)), + const Padding( + padding: EdgeInsetsDirectional.only(start: VERY_SMALL_SPACE)), Expanded( child: SizedBox( height: screenSize.width * 0.2, @@ -83,7 +84,8 @@ class SmoothProductCardTemplate extends StatelessWidget { if (message == null) textWidget, if (message != null) Padding( - padding: const EdgeInsets.only(top: SMALL_SPACE), + padding: const EdgeInsetsDirectional.only( + top: SMALL_SPACE), child: AutoSizeText( message!, maxLines: 3, @@ -94,7 +96,8 @@ class SmoothProductCardTemplate extends StatelessWidget { ), ), ), - const Padding(padding: EdgeInsets.only(left: VERY_SMALL_SPACE)), + const Padding( + padding: EdgeInsetsDirectional.only(start: VERY_SMALL_SPACE)), Padding( padding: const EdgeInsets.all(VERY_SMALL_SPACE), child: actionButton == null diff --git a/packages/smooth_app/lib/data_models/query_product_list_supplier.dart b/packages/smooth_app/lib/data_models/query_product_list_supplier.dart index 6ba65218736..e762a032f88 100644 --- a/packages/smooth_app/lib/data_models/query_product_list_supplier.dart +++ b/packages/smooth_app/lib/data_models/query_product_list_supplier.dart @@ -21,7 +21,7 @@ class QueryProductListSupplier extends ProductListSupplier { partialProductList.clear(); if (searchResult.products != null) { productList.setAll(searchResult.products!); - productList.totalSize = searchResult.count!; + productList.totalSize = searchResult.count ?? 0; partialProductList.add(productList); await DaoProduct(localDatabase).putAll(searchResult.products!); } diff --git a/packages/smooth_app/lib/data_models/up_to_date_interest.dart b/packages/smooth_app/lib/data_models/up_to_date_interest.dart new file mode 100644 index 00000000000..d107fde7ee3 --- /dev/null +++ b/packages/smooth_app/lib/data_models/up_to_date_interest.dart @@ -0,0 +1,28 @@ +/// Management of the interest for a key. +class UpToDateInterest { + /// Number of time an interest was shown for a given key. + final Map _interestCounts = {}; + + /// Shows an interest for a key. + void add(final String key) { + final int result = (_interestCounts[key] ?? 0) + 1; + _interestCounts[key] = result; + } + + /// Loses an interest for a key. + /// + /// Returns true if completely lost interest. + bool remove(final String key) { + final int result = (_interestCounts[key] ?? 0) - 1; + if (result <= 0) { + _interestCounts.remove(key); + return true; + } + _interestCounts[key] = result; + return false; + } + + bool get isEmpty => _interestCounts.isEmpty; + + bool containsKey(final String key) => _interestCounts.containsKey(key); +} diff --git a/packages/smooth_app/lib/data_models/up_to_date_mixin.dart b/packages/smooth_app/lib/data_models/up_to_date_mixin.dart new file mode 100644 index 00000000000..1e9941c17f3 --- /dev/null +++ b/packages/smooth_app/lib/data_models/up_to_date_mixin.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:openfoodfacts/openfoodfacts.dart'; +import 'package:smooth_app/background/background_task_manager.dart'; +import 'package:smooth_app/database/local_database.dart'; + +/// Provides the most up-to-date local product data for a StatefulWidget. +/// +/// Typically we have +/// * a product from the database (downloaded from the server) +/// * potentially pending changes to apply on top while they're being uploaded +/// +/// With this mixin +/// * we get the most up-to-date local product data +/// * we re-launch the task manager if relevant +/// * we track the barcodes currently "opened" by the app +@optionalTypeArgs +mixin UpToDateMixin on State { + /// To be used in the `initState` method. + void initUpToDate( + final Product initialProduct, + final LocalDatabase localDatabase, + ) { + this.initialProduct = initialProduct; + _localDatabase = localDatabase; + localDatabase.upToDate.showInterest(barcode); + } + + @protected + late final Product initialProduct; + + late final LocalDatabase _localDatabase; + + late Product _product; + + @protected + String get barcode => initialProduct.barcode!; + + @protected + Product get upToDateProduct => _product; + + @override + void dispose() { + _localDatabase.upToDate.loseInterest(barcode); + super.dispose(); + } + + /// Refreshes [upToDateProduct] with the latest available local data. + /// + /// To be used in the `build` method, after a call to + /// `context.watch()`. + void refreshUpToDate() { + BackgroundTaskManager.getInstance(_localDatabase).run(); // no await + _product = _localDatabase.upToDate.getLocalUpToDate(initialProduct); + } +} diff --git a/packages/smooth_app/lib/data_models/up_to_date_product_list_mixin.dart b/packages/smooth_app/lib/data_models/up_to_date_product_list_mixin.dart new file mode 100644 index 00000000000..eda77dc288a --- /dev/null +++ b/packages/smooth_app/lib/data_models/up_to_date_product_list_mixin.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.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'; + +/// Provides the most up-to-date local product list data for a StatefulWidget. +@optionalTypeArgs +mixin UpToDateProductListMixin on State { + /// To be used in the `initState` method. + void initUpToDate( + final ProductList initialProductList, + final LocalDatabase localDatabase, + ) { + _productList = initialProductList; + _localDatabase = localDatabase; + _localDatabase.upToDateProductList.showInterest(initialProductList); + _localDatabase.upToDateProductList.setLocalUpToDate( + DaoProductList.getKey(_productList), + _productList.barcodes, + ); + } + + late final LocalDatabase _localDatabase; + + late ProductList _productList; + + ProductList get productList => _productList; + + set productList(final ProductList productList) { + final ProductList previous = _productList; + _productList = productList; + _localDatabase.upToDateProductList.showInterest(_productList); + _localDatabase.upToDateProductList.loseInterest(previous); + _localDatabase.upToDateProductList.setLocalUpToDate( + DaoProductList.getKey(_productList), + _productList.barcodes, + ); + } + + @override + void dispose() { + _localDatabase.upToDateProductList.loseInterest(_productList); + super.dispose(); + } + + /// Refreshes [upToDateProduct] with the latest available local data. + /// + /// To be used in the `build` method, after a call to + /// `context.watch()`. + void refreshUpToDate() { + final List barcodes = + _localDatabase.upToDateProductList.getLocalUpToDate(_productList); + _productList.set(barcodes); + } +} diff --git a/packages/smooth_app/lib/data_models/up_to_date_product_list_provider.dart b/packages/smooth_app/lib/data_models/up_to_date_product_list_provider.dart new file mode 100644 index 00000000000..422c54a86a5 --- /dev/null +++ b/packages/smooth_app/lib/data_models/up_to_date_product_list_provider.dart @@ -0,0 +1,54 @@ +import 'package:smooth_app/data_models/product_list.dart'; +import 'package:smooth_app/data_models/up_to_date_interest.dart'; +import 'package:smooth_app/database/dao_product_list.dart'; +import 'package:smooth_app/database/local_database.dart'; + +/// Provider that reflects the latest barcode lists on [ProductList]s. +class UpToDateProductListProvider { + UpToDateProductListProvider(this.localDatabase); + + final LocalDatabase localDatabase; + + /// Product lists currently displayed in the app. + /// + /// We need to know which product lists are "interesting" because we need to + /// cache barcode lists in memory for instant access. And we should cache only + /// them, because we cannot cache all product lists in memory. + final UpToDateInterest _interest = UpToDateInterest(); + + final Map> _barcodes = >{}; + + /// Shows an interest for a product list. + /// + /// Typically, to be used by a widget in `initState`. + void showInterest(final ProductList productList) => + _interest.add(_getKey(productList)); + + /// Loses interest for a product list. + /// + /// Typically, to be used by a widget in `dispose`. + void loseInterest(final ProductList productList) { + final String key = _getKey(productList); + if (!_interest.remove(key)) { + return; + } + _barcodes.remove(key); + } + + String _getKey(final ProductList productList) => + DaoProductList.getKey(productList); + + void setLocalUpToDate( + final String key, + final List barcodes, + ) { + if (!_interest.containsKey(key)) { + return; + } + _barcodes[key] = List.from(barcodes); // need to copy + } + + /// Returns the latest barcodes. + List getLocalUpToDate(final ProductList productList) => + _barcodes[_getKey(productList)] ?? []; +} diff --git a/packages/smooth_app/lib/data_models/up_to_date_product_provider.dart b/packages/smooth_app/lib/data_models/up_to_date_product_provider.dart index 4170b737997..f8a38e3e4d8 100644 --- a/packages/smooth_app/lib/data_models/up_to_date_product_provider.dart +++ b/packages/smooth_app/lib/data_models/up_to_date_product_provider.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:smooth_app/data_models/up_to_date_changes.dart'; +import 'package:smooth_app/data_models/up_to_date_interest.dart'; import 'package:smooth_app/database/dao_transient_operation.dart'; import 'package:smooth_app/database/local_database.dart'; @@ -26,7 +27,7 @@ class UpToDateProductProvider { /// We need to know which barcodes are "interesting" because we need to cache /// products in memory for instant access. And we should cache only them, /// because we cannot cache all products in memory. - final Map _interestingBarcodes = {}; + final UpToDateInterest _interest = UpToDateInterest(); /// Returns true if at least one barcode was refreshed after the [timestamp]. bool needsRefresh(final int? latestTimestamp, final List barcodes) { @@ -46,23 +47,18 @@ class UpToDateProductProvider { /// Shows an interest for a barcode. /// /// Typically, to be used by a widget in `initState`. - void showInterest(final String barcode) { - final int result = (_interestingBarcodes[barcode] ?? 0) + 1; - _interestingBarcodes[barcode] = result; - } + void showInterest(final String barcode) => _interest.add(barcode); /// Loses interest for a barcode. /// /// Typically, to be used by a widget in `dispose`. void loseInterest(final String barcode) { - final int result = (_interestingBarcodes[barcode] ?? 0) - 1; - if (result <= 0) { - _interestingBarcodes.remove(barcode); - _latestDownloadedProducts.remove(barcode); - _timestamps.remove(barcode); - } else { - _interestingBarcodes[barcode] = result; + final bool lostInterest = _interest.remove(barcode); + if (!lostInterest) { + return; } + _latestDownloadedProducts.remove(barcode); + _timestamps.remove(barcode); } /// Typical use-case: a product page is refreshed through a pull-gesture. @@ -82,12 +78,12 @@ class UpToDateProductProvider { final Iterable products, { final bool notify = true, }) { - if (_interestingBarcodes.isEmpty) { + if (_interest.isEmpty) { return; } bool atLeastOne = false; for (final Product product in products) { - if (_interestingBarcodes.containsKey(product.barcode)) { + if (_interest.containsKey(product.barcode!)) { atLeastOne = true; setLatestDownloadedProduct(product, notify: false); } diff --git a/packages/smooth_app/lib/database/dao_product_list.dart b/packages/smooth_app/lib/database/dao_product_list.dart index 2e84d7ad4fc..134408ed586 100644 --- a/packages/smooth_app/lib/database/dao_product_list.dart +++ b/packages/smooth_app/lib/database/dao_product_list.dart @@ -84,8 +84,16 @@ class DaoProductList extends AbstractDao { LazyBox<_BarcodeList> _getBox() => Hive.lazyBox<_BarcodeList>(_hiveBoxName); - Future<_BarcodeList?> _get(final ProductList productList) => - _getBox().get(_getKey(productList)); + Future<_BarcodeList?> _get(final ProductList productList) async { + final _BarcodeList? result = await _getBox().get(getKey(productList)); + if (result != null) { + localDatabase.upToDateProductList.setLocalUpToDate( + getKey(productList), + result.barcodes, + ); + } + return result; + } Future getTimestamp(final ProductList productList) async => (await _get(productList))?.timestamp; @@ -95,7 +103,7 @@ class DaoProductList extends AbstractDao { // Encoding the parameter part in base64 makes us safe regarding ASCII. // As it's a list of keywords, there's a fairly high probability // that we'll be under the 255 character length. - static String _getKey(final ProductList productList) => + static String getKey(final ProductList productList) => '${productList.listType.key}' '$_keySeparator' '${base64.encode(utf8.encode(productList.getParametersKey()))}'; @@ -126,15 +134,21 @@ class DaoProductList extends AbstractDao { throw Exception('Unknown product list type: "$value" from "$key"'); } - Future _put(final String key, final _BarcodeList barcodeList) async => - _getBox().put(key, barcodeList); + Future _put(final String key, final _BarcodeList barcodeList) async { + await _getBox().put(key, barcodeList); + localDatabase.upToDateProductList.setLocalUpToDate( + key, + barcodeList.barcodes, + ); + } Future put(final ProductList productList) async => - _put(_getKey(productList), _BarcodeList.fromProductList(productList)); + _put(getKey(productList), _BarcodeList.fromProductList(productList)); Future delete(final ProductList productList) async { final LazyBox<_BarcodeList> box = _getBox(); - final String key = _getKey(productList); + final String key = getKey(productList); + localDatabase.upToDateProductList.setLocalUpToDate(key, []); if (!box.containsKey(key)) { return false; } @@ -182,12 +196,12 @@ class DaoProductList extends AbstractDao { barcodes.remove(barcode); // removes a potential duplicate barcodes.add(barcode); final _BarcodeList newList = _BarcodeList.now(barcodes); - await _put(_getKey(productList), newList); + await _put(getKey(productList), newList); } Future clear(final ProductList productList) async { final _BarcodeList newList = _BarcodeList.now([]); - await _put(_getKey(productList), newList); + await _put(getKey(productList), newList); } /// Adds or removes a barcode within a product list (depending on [include]) @@ -217,7 +231,7 @@ class DaoProductList extends AbstractDao { barcodes.add(barcode); } final _BarcodeList newList = _BarcodeList.now(barcodes); - await _put(_getKey(productList), newList); + await _put(getKey(productList), newList); return true; } @@ -249,7 +263,7 @@ class DaoProductList extends AbstractDao { } final _BarcodeList newList = _BarcodeList.now(allBarcodes); - await _put(_getKey(productList), newList); + await _put(getKey(productList), newList); } Future rename( @@ -259,7 +273,7 @@ class DaoProductList extends AbstractDao { final ProductList newList = ProductList.user(newName); final _BarcodeList list = await _get(initialList) ?? _BarcodeList.now([]); - await _put(_getKey(newList), list); + await _put(getKey(newList), list); await delete(initialList); await get(newList); return newList; @@ -287,10 +301,23 @@ class DaoProductList extends AbstractDao { } /// Returns the names of the user lists. - /// - /// Possibly restricted to the user lists that contains ALL the given barcodes. - Future> getUserLists({List? withBarcodes}) async { - // TODO(m123): change return type to a set + List getUserLists() { + final List result = []; + for (final dynamic key in _getBox().keys) { + final String tmp = key.toString(); + final ProductListType productListType = getProductListType(tmp); + if (productListType != ProductListType.USER) { + continue; + } + result.add(getProductListParameters(tmp)); + } + return result; + } + + /// Returns the names of the user lists that contains ALL the given barcodes. + Future> getUserListsWithBarcodes( + final List withBarcodes, + ) async { final List result = []; for (final dynamic key in _getBox().keys) { final String tmp = key.toString(); @@ -298,22 +325,18 @@ class DaoProductList extends AbstractDao { if (productListType != ProductListType.USER) { continue; } - if (withBarcodes != null) { - final _BarcodeList? barcodeList = await _getBox().get(key); - if (barcodeList == null) { - continue; + final _BarcodeList? barcodeList = await _getBox().get(key); + if (barcodeList == null) { + continue; + } + for (final String barcode in withBarcodes) { + if (!barcodeList.barcodes.contains(barcode)) { + break; } - for (final String barcode in withBarcodes) { - if (!barcodeList.barcodes.contains(barcode)) { - break; - } - if (withBarcodes.last == barcode) { - result.add(getProductListParameters(tmp)); - break; - } + if (withBarcodes.last == barcode) { + result.add(getProductListParameters(tmp)); + break; } - } else { - result.add(getProductListParameters(tmp)); } } return result; diff --git a/packages/smooth_app/lib/database/local_database.dart b/packages/smooth_app/lib/database/local_database.dart index cab27cba19b..21db1927c71 100644 --- a/packages/smooth_app/lib/database/local_database.dart +++ b/packages/smooth_app/lib/database/local_database.dart @@ -6,6 +6,7 @@ import 'package:hive_flutter/hive_flutter.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:smooth_app/background/background_task_manager.dart'; +import 'package:smooth_app/data_models/up_to_date_product_list_provider.dart'; import 'package:smooth_app/data_models/up_to_date_product_provider.dart'; import 'package:smooth_app/database/abstract_dao.dart'; import 'package:smooth_app/database/dao_hive_product.dart'; @@ -25,14 +26,18 @@ import 'package:sqflite/sqflite.dart'; class LocalDatabase extends ChangeNotifier { LocalDatabase._(final Database database) : _database = database { _upToDateProductProvider = UpToDateProductProvider(this); + _upToDateProductListProvider = UpToDateProductListProvider(this); } final Database _database; late final UpToDateProductProvider _upToDateProductProvider; + late final UpToDateProductListProvider _upToDateProductListProvider; Database get database => _database; UpToDateProductProvider get upToDate => _upToDateProductProvider; + UpToDateProductListProvider get upToDateProductList => + _upToDateProductListProvider; @override void notifyListeners() { diff --git a/packages/smooth_app/lib/entrypoints/ios/main_ios.dart b/packages/smooth_app/lib/entrypoints/ios/main_ios.dart index 9f303fa6b41..1621c70eee3 100644 --- a/packages/smooth_app/lib/entrypoints/ios/main_ios.dart +++ b/packages/smooth_app/lib/entrypoints/ios/main_ios.dart @@ -5,7 +5,9 @@ import 'package:smooth_app/main.dart'; /// App Store/TestFlight version with: /// - Barcode decoding algorithm: MLKit -/// - iOS SDK to open the store +/// - iOS/macOS SDK to open the store +/// +/// This version is compatible both with iOS and macOS void main() { launchSmoothApp( barcodeScanner: const ScannerMLKit(), diff --git a/packages/smooth_app/lib/generic_lib/buttons/smooth_large_button_with_icon.dart b/packages/smooth_app/lib/generic_lib/buttons/smooth_large_button_with_icon.dart index b9f71b36f5d..07b157dc8fd 100644 --- a/packages/smooth_app/lib/generic_lib/buttons/smooth_large_button_with_icon.dart +++ b/packages/smooth_app/lib/generic_lib/buttons/smooth_large_button_with_icon.dart @@ -11,6 +11,7 @@ class SmoothLargeButtonWithIcon extends StatelessWidget { this.trailing, this.backgroundColor, this.foregroundColor, + this.textAlign, }); final String text; @@ -20,6 +21,7 @@ class SmoothLargeButtonWithIcon extends StatelessWidget { final IconData? trailing; final Color? backgroundColor; final Color? foregroundColor; + final TextAlign? textAlign; Color _getBackgroundColor(final ThemeData themeData) => backgroundColor ?? themeData.colorScheme.secondary; @@ -48,6 +50,7 @@ class SmoothLargeButtonWithIcon extends StatelessWidget { child: AutoSizeText( text, maxLines: 2, + textAlign: textAlign, style: themeData.textTheme.bodyMedium!.copyWith( color: _getForegroundColor(themeData), ), diff --git a/packages/smooth_app/lib/generic_lib/dialogs/smooth_alert_dialog.dart b/packages/smooth_app/lib/generic_lib/dialogs/smooth_alert_dialog.dart index 3cbd9a7ea7f..be77eea6e9f 100644 --- a/packages/smooth_app/lib/generic_lib/dialogs/smooth_alert_dialog.dart +++ b/packages/smooth_app/lib/generic_lib/dialogs/smooth_alert_dialog.dart @@ -1,7 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:flutter_svg/flutter_svg.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/generic_lib/widgets/smooth_responsive.dart'; +import 'package:smooth_app/helpers/app_helper.dart'; /// Custom Dialog to use in the app /// @@ -28,6 +31,7 @@ class SmoothAlertDialog extends StatelessWidget { this.actionsAxis, this.actionsOrder, this.close = false, + this.contentPadding, }); final String? title; @@ -37,26 +41,29 @@ class SmoothAlertDialog extends StatelessWidget { final SmoothActionButton? negativeAction; final Axis? actionsAxis; final SmoothButtonsBarOrder? actionsOrder; + final EdgeInsetsDirectional? contentPadding; - static const EdgeInsets _smallContentPadding = EdgeInsets.only( - left: SMALL_SPACE, + static const EdgeInsetsDirectional _smallContentPadding = + EdgeInsetsDirectional.only( + start: SMALL_SPACE, top: MEDIUM_SPACE, - right: SMALL_SPACE, + end: SMALL_SPACE, bottom: SMALL_SPACE, ); - static const EdgeInsets _contentPadding = EdgeInsets.only( - left: 22.0, + static const EdgeInsetsDirectional _contentPadding = + EdgeInsetsDirectional.only( + start: 22.0, top: VERY_LARGE_SPACE, - right: 22.0, + end: 22.0, bottom: 22.0, ); @override Widget build(BuildContext context) { final Widget content = _buildContent(context); - final EdgeInsets padding = - context.isSmallDevice() ? _smallContentPadding : _contentPadding; + final EdgeInsetsDirectional padding = contentPadding ?? + (context.isSmallDevice() ? _smallContentPadding : _contentPadding); return AlertDialog( scrollable: false, @@ -82,11 +89,14 @@ class SmoothAlertDialog extends StatelessWidget { ); } - Padding _buildBottomBar(EdgeInsets padding) { + Padding _buildBottomBar(EdgeInsetsDirectional padding) { return Padding( padding: EdgeInsetsDirectional.only( top: padding.bottom, start: SMALL_SPACE, + end: positiveAction != null && negativeAction != null + ? 0.0 + : SMALL_SPACE, ), child: SmoothActionButtonsBar( positiveAction: positiveAction, @@ -197,8 +207,9 @@ class _SmoothDialogCrossButton extends StatelessWidget { } enum SmoothButtonsBarOrder { - /// If the [axis] is [Axis.horizontal], the positive button will be on the end - /// If the [axis] is [Axis.vertical], the positive button will be on the start + /// If the [axis] is [Axis.horizontal], the positive button will be at the end + /// If the [axis] is [Axis.vertical], the positive button will be at the first + /// position auto, /// Whatever the [axis] is, the positive button will always be at first place @@ -458,3 +469,68 @@ class _SmoothActionFlatButton extends StatelessWidget { ); } } + +/// A custom dialog where you only have to pass a [title] and a [message]. +/// By default an "OK" button will be show., but you can override it by passing +/// a [positiveAction] and/or [negativeAction] +class SmoothSimpleErrorAlertDialog extends StatelessWidget { + const SmoothSimpleErrorAlertDialog({ + required this.title, + required this.message, + this.positiveAction, + this.negativeAction, + this.actionsAxis, + this.actionsOrder, + this.contentPadding, + }); + + final String title; + final String message; + final SmoothActionButton? positiveAction; + final SmoothActionButton? negativeAction; + final Axis? actionsAxis; + final SmoothButtonsBarOrder? actionsOrder; + final EdgeInsetsDirectional? contentPadding; + + @override + Widget build(BuildContext context) { + final Widget content = Column( + children: [ + SvgPicture.asset( + 'assets/misc/error.svg', + width: MINIMUM_TOUCH_SIZE * 2, + package: AppHelper.APP_PACKAGE, + ), + const SizedBox(height: MEDIUM_SPACE), + Text( + title, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox(height: LARGE_SPACE), + Text( + message, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyMedium?.copyWith(height: 1.3), + ), + ], + ); + + SmoothActionButton? positiveButton = positiveAction; + if (positiveAction == null && negativeAction == null) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + positiveButton = SmoothActionButton( + text: appLocalizations.okay, + onPressed: () => Navigator.of(context).maybePop(), + ); + } + + return SmoothAlertDialog( + body: content, + positiveAction: positiveButton, + negativeAction: negativeAction, + actionsOrder: actionsOrder, + contentPadding: contentPadding, + ); + } +} diff --git a/packages/smooth_app/lib/generic_lib/widgets/images/smooth_image.dart b/packages/smooth_app/lib/generic_lib/widgets/images/smooth_image.dart index ba67308efe6..9aa42fe3a48 100644 --- a/packages/smooth_app/lib/generic_lib/widgets/images/smooth_image.dart +++ b/packages/smooth_app/lib/generic_lib/widgets/images/smooth_image.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/generic_lib/widgets/picture_not_found.dart'; @@ -32,6 +33,7 @@ class SmoothImage extends StatelessWidget { image: imageProvider!, fit: fit ?? BoxFit.cover, loadingBuilder: _loadingBuilder, + errorBuilder: _errorBuilder, ); if (heroTag != null) { @@ -51,24 +53,45 @@ class SmoothImage extends StatelessWidget { } Widget _loadingBuilder( - BuildContext _, + BuildContext context, Widget child, ImageChunkEvent? progress, ) { - if (progress == null) { - return child; + final double? progressValue; + + if (progress != null) { + progressValue = + progress.cumulativeBytesLoaded / progress.expectedTotalBytes!; + } else { + progressValue = null; } - final double progressValue = - progress.cumulativeBytesLoaded / progress.expectedTotalBytes!; + final ThemeData theme = Theme.of(context); - return Center( - child: CircularProgressIndicator( - strokeWidth: 2.5, - valueColor: const AlwaysStoppedAnimation( - Colors.white, + return Container( + color: theme.primaryColor.withOpacity(0.1), + child: Center( + child: CircularProgressIndicator( + strokeWidth: 2.5, + valueColor: theme.brightness == Brightness.dark + ? const AlwaysStoppedAnimation(Colors.white) + : null, + value: progressValue, ), - value: progressValue, + ), + ); + } + + Widget _errorBuilder( + BuildContext context, + Object _, + StackTrace? __, + ) { + return Container( + color: Theme.of(context).primaryColor.withOpacity(0.1), + padding: const EdgeInsets.all(MEDIUM_SPACE), + child: Center( + child: SvgPicture.asset('assets/misc/error.svg'), ), ); } diff --git a/packages/smooth_app/lib/generic_lib/widgets/smooth_bottom_sheet.dart b/packages/smooth_app/lib/generic_lib/widgets/smooth_bottom_sheet.dart new file mode 100644 index 00000000000..9f20947aee5 --- /dev/null +++ b/packages/smooth_app/lib/generic_lib/widgets/smooth_bottom_sheet.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/semantics.dart'; +import 'package:smooth_app/generic_lib/design_constants.dart'; + +Future showSmoothModalSheet({ + required BuildContext context, + required WidgetBuilder builder, +}) { + return showModalBottomSheet( + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: ROUNDED_RADIUS), + ), + builder: builder, + ); +} + +class SmoothModalSheet extends StatelessWidget { + const SmoothModalSheet({ + required this.title, + required this.body, + this.closeButton = true, + this.bodyPadding, + this.closeButtonSemanticsOrder = 2.0, + }); + + final String title; + final bool closeButton; + final double closeButtonSemanticsOrder; + final Widget body; + final EdgeInsetsGeometry? bodyPadding; + + @override + Widget build(BuildContext context) { + final Color primaryColor = Theme.of(context).primaryColor; + + return ClipRRect( + borderRadius: const BorderRadius.vertical(top: ROUNDED_RADIUS), + child: DecoratedBox( + decoration: const BoxDecoration( + borderRadius: BorderRadius.vertical(top: ROUNDED_RADIUS), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + color: primaryColor.withOpacity(0.2), + padding: EdgeInsetsDirectional.only( + start: VERY_LARGE_SPACE, + top: VERY_SMALL_SPACE, + bottom: VERY_SMALL_SPACE, + end: VERY_LARGE_SPACE - (closeButton ? LARGE_SPACE : 0), + ), + 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, + ), + ), + ), + if (closeButton) + Semantics( + value: MaterialLocalizations.of(context) + .closeButtonTooltip, + button: true, + excludeSemantics: true, + onScrollDown: () {}, + sortKey: OrdinalSortKey(closeButtonSemanticsOrder), + child: Tooltip( + message: MaterialLocalizations.of(context) + .closeButtonTooltip, + enableFeedback: true, + child: InkWell( + onTap: () => Navigator.of(context).pop(), + customBorder: const CircleBorder(), + child: const Padding( + padding: EdgeInsets.all(MEDIUM_SPACE), + child: Icon(Icons.clear), + ), + ), + ), + ) + ], + ), + ), + Padding( + padding: bodyPadding ?? const EdgeInsets.all(MEDIUM_SPACE), + child: body, + ), + ], + )), + ); + } +} diff --git a/packages/smooth_app/lib/helpers/analytics_helper.dart b/packages/smooth_app/lib/helpers/analytics_helper.dart index b6ba131284a..8d300225152 100644 --- a/packages/smooth_app/lib/helpers/analytics_helper.dart +++ b/packages/smooth_app/lib/helpers/analytics_helper.dart @@ -6,7 +6,6 @@ import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:smooth_app/data_models/user_preferences.dart'; -import 'package:smooth_app/helpers/entry_points_helper.dart'; import 'package:smooth_app/helpers/global_vars.dart'; /// Category for Matomo Events @@ -194,21 +193,14 @@ class AnalyticsHelper { /// Don't call this method directly, it is automatically updated via the /// [UserPreferences] static Future _setAnalyticsReports(final bool allow) async { - if (GlobalVars.storeLabel == StoreLabel.FDroid) { - _analyticsReporting = _AnalyticsTrackingMode.disabled; - await MatomoTracker.instance.setOptOut(optout: true); + if (allow) { + _analyticsReporting = _AnalyticsTrackingMode.enabled; } else { - if (allow) { - _analyticsReporting = _AnalyticsTrackingMode.enabled; - } else { - _analyticsReporting = _AnalyticsTrackingMode.anonymous; - } - - await MatomoTracker.instance.setOptOut(optout: false); + _analyticsReporting = _AnalyticsTrackingMode.anonymous; + } - if (MatomoTracker.instance.initialized) { - MatomoTracker.instance.setVisitorUserId(_uuid); - } + if (MatomoTracker.instance.initialized) { + MatomoTracker.instance.setVisitorUserId(_uuid); } } @@ -330,6 +322,10 @@ class AnalyticsHelper { } } + static void sendException(dynamic throwable, {dynamic stackTrace}) { + Sentry.captureException(throwable, stackTrace: stackTrace); + } + static String? get matomoVisitorId => MatomoTracker.instance.visitor.id; } diff --git a/packages/smooth_app/lib/helpers/image_compute_container.dart b/packages/smooth_app/lib/helpers/image_compute_container.dart index d3fff0db7b2..8fd11fbd285 100644 --- a/packages/smooth_app/lib/helpers/image_compute_container.dart +++ b/packages/smooth_app/lib/helpers/image_compute_container.dart @@ -49,10 +49,16 @@ Future saveBmp({ height: source.height, ); if (container.isIsolatePossible) { - await compute(_saveBmp, container); - } else { - await _saveBmp(container); + try { + // with an isolate if possible + await compute(_saveBmp, container); + } catch (e) { + // fallback version: async (cf. https://github.com/openfoodfacts/smooth-app/issues/4304) + await _saveBmp(container, withIsolate: false); + } + return; } + await _saveBmp(container, withIsolate: false); } /// Saves an image to a JPEG file. @@ -76,10 +82,16 @@ Future saveJpeg({ height: source.height, ); if (container.isIsolatePossible) { - await compute(_saveJpeg, container); - } else { - await _saveJpeg(container); + try { + // with an isolate if possible + await compute(_saveJpeg, container); + } catch (e) { + // fallback version: async (cf. https://github.com/openfoodfacts/smooth-app/issues/4304) + await _saveJpeg(container, withIsolate: false); + } + return; } + await _saveJpeg(container, withIsolate: false); } Future _convertImageFromUI( @@ -96,8 +108,13 @@ Future _convertImageFromUI( ); /// Saves an image to a BMP file. As BMP for better performances. -Future _saveBmp(final _ImageComputeContainer container) async { - container.ensureIsolate(); +Future _saveBmp( + final _ImageComputeContainer container, { + final bool withIsolate = true, +}) async { + if (withIsolate) { + container.ensureIsolate(); + } final image.Image rawImage = await _convertImageFromUI( container.rawData, container.width, @@ -113,8 +130,13 @@ Future _saveBmp(final _ImageComputeContainer container) async { /// /// It's faster to encode as BMP and then compress to JPEG, instead of directly /// compressing the image to JPEG (standard flutter being slow). -Future _saveJpeg(final _ImageComputeContainer container) async { - container.ensureIsolate(); +Future _saveJpeg( + final _ImageComputeContainer container, { + final bool withIsolate = true, +}) async { + if (withIsolate) { + container.ensureIsolate(); + } image.Image? rawImage = await _convertImageFromUI( container.rawData, container.width, diff --git a/packages/smooth_app/lib/helpers/keyboard_helper.dart b/packages/smooth_app/lib/helpers/keyboard_helper.dart new file mode 100644 index 00000000000..a478f05e226 --- /dev/null +++ b/packages/smooth_app/lib/helpers/keyboard_helper.dart @@ -0,0 +1,5 @@ +import 'package:flutter/widgets.dart'; + +extension KeyboardContextExtention on BuildContext { + bool get keyboardVisible => MediaQuery.of(this).viewInsets.bottom > 0.0; +} diff --git a/packages/smooth_app/lib/helpers/product_cards_helper.dart b/packages/smooth_app/lib/helpers/product_cards_helper.dart index fcffea5f6ae..34b4ba7760e 100644 --- a/packages/smooth_app/lib/helpers/product_cards_helper.dart +++ b/packages/smooth_app/lib/helpers/product_cards_helper.dart @@ -157,6 +157,7 @@ List getFilteredAttributes( Widget addPanelButton( final String label, { final IconData? iconData, + final String? textAlign, required final Function() onPressed, }) => Padding( @@ -165,6 +166,7 @@ Widget addPanelButton( text: label, icon: iconData ?? Icons.add, onPressed: onPressed, + textAlign: iconData == null ? TextAlign.center : null, ), ); @@ -256,21 +258,6 @@ List> getSelectedImages( return result.entries.toList(); } -// TODO(monsieurtanuki): move to off-dart in ImageHelper -String _getBarcodeSubPath(final String barcode) { - if (barcode.length < 9) { - return barcode; - } - final String p1 = barcode.substring(0, 3); - final String p2 = barcode.substring(3, 6); - final String p3 = barcode.substring(6, 9); - if (barcode.length == 9) { - return '$p1/$p2/$p3'; - } - final String p4 = barcode.substring(9); - return '$p1/$p2/$p3/$p4'; -} - String _getImageRoot() => OpenFoodAPIConfiguration.globalQueryType == QueryType.PROD ? 'https://images.openfoodfacts.org/images/products' @@ -281,7 +268,7 @@ String getLocalizedProductImageUrl( final ProductImage productImage, ) => '${_getImageRoot()}/' - '${_getBarcodeSubPath(product.barcode!)}/' + '${ImageHelper.getBarcodeSubPath(product.barcode!)}/' '${ImageHelper.getProductImageFilename(productImage, imageSize: ImageSize.DISPLAY)}'; /// Returns the languages for which [imageField] has images for that [product]. diff --git a/packages/smooth_app/lib/knowledge_panel/knowledge_panels/knowledge_panel_page.dart b/packages/smooth_app/lib/knowledge_panel/knowledge_panels/knowledge_panel_page.dart index c17c890dda9..6d8ca02b540 100644 --- a/packages/smooth_app/lib/knowledge_panel/knowledge_panels/knowledge_panel_page.dart +++ b/packages/smooth_app/lib/knowledge_panel/knowledge_panels/knowledge_panel_page.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:matomo_tracker/matomo_tracker.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:provider/provider.dart'; +import 'package:smooth_app/data_models/up_to_date_mixin.dart'; import 'package:smooth_app/database/local_database.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/generic_lib/widgets/smooth_card.dart'; @@ -26,29 +27,17 @@ class KnowledgePanelPage extends StatefulWidget { } class _KnowledgePanelPageState extends State - with TraceableClientMixin { + with TraceableClientMixin, UpToDateMixin { @override String get traceTitle => 'knowledge_panel_page'; @override String get traceName => 'Opened full knowledge panel page'; - late Product _product; - late final Product _initialProduct; - late final LocalDatabase _localDatabase; - @override void initState() { super.initState(); - _initialProduct = widget.product; - _localDatabase = context.read(); - _localDatabase.upToDate.showInterest(_initialProduct.barcode!); - } - - @override - void dispose() { - _localDatabase.upToDate.loseInterest(_initialProduct.barcode!); - super.dispose(); + initUpToDate(widget.product, context.read()); } static KnowledgePanelPanelGroupElement? _groupElementOf( @@ -63,7 +52,7 @@ class _KnowledgePanelPageState extends State @override Widget build(BuildContext context) { context.watch(); - _product = _localDatabase.upToDate.getLocalUpToDate(_initialProduct); + refreshUpToDate(); return SmoothScaffold( appBar: SmoothAppBar( title: Text( @@ -81,7 +70,7 @@ class _KnowledgePanelPageState extends State ), child: KnowledgePanelExpandedCard( panelId: widget.panelId, - product: _product, + product: upToDateProduct, isInitiallyExpanded: true, ), ), @@ -112,8 +101,10 @@ class _KnowledgePanelPageState extends State groupElement?.title!.isNotEmpty == true) { return groupElement!.title!; } - final KnowledgePanel? panel = - KnowledgePanelWidget.getKnowledgePanel(_product, widget.panelId); + final KnowledgePanel? panel = KnowledgePanelWidget.getKnowledgePanel( + upToDateProduct, + widget.panelId, + ); if (panel?.titleElement?.title.isNotEmpty == true) { return (panel?.titleElement?.title)!; } diff --git a/packages/smooth_app/lib/knowledge_panel/knowledge_panels/knowledge_panel_table_card.dart b/packages/smooth_app/lib/knowledge_panel/knowledge_panels/knowledge_panel_table_card.dart index 4f50479c8a9..b239bdcc49a 100644 --- a/packages/smooth_app/lib/knowledge_panel/knowledge_panels/knowledge_panel_table_card.dart +++ b/packages/smooth_app/lib/knowledge_panel/knowledge_panels/knowledge_panel_table_card.dart @@ -95,9 +95,13 @@ class _KnowledgePanelTableCardState extends State { return Column( children: [ for (List row in rowsWidgets) - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: row, + Semantics( + excludeSemantics: true, + value: _buildSemanticsValue(row), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: row, + ), ), if (withPortionCalculator) const Divider(), if (withPortionCalculator) PortionCalculator(widget.product) @@ -253,6 +257,21 @@ class _KnowledgePanelTableCardState extends State { } } } + + String _buildSemanticsValue(List row) { + final StringBuffer buffer = StringBuffer(); + + for (final Widget widget in row) { + if (widget is TableCellWidget && widget.cell.text.isNotEmpty) { + if (buffer.isNotEmpty) { + buffer.write(' - '); + } + buffer.write(widget.cell.text); + } + } + + return buffer.toString(); + } } class TableCellWidget extends StatefulWidget { diff --git a/packages/smooth_app/lib/knowledge_panel/knowledge_panels/knowledge_panel_world_map_card.dart b/packages/smooth_app/lib/knowledge_panel/knowledge_panels/knowledge_panel_world_map_card.dart index d0f766320e8..4bfd5c0cd84 100644 --- a/packages/smooth_app/lib/knowledge_panel/knowledge_panels/knowledge_panel_world_map_card.dart +++ b/packages/smooth_app/lib/knowledge_panel/knowledge_panels/knowledge_panel_world_map_card.dart @@ -42,7 +42,7 @@ class KnowledgePanelWorldMapCard extends StatelessWidget { AttributionWidget( attributionBuilder: (BuildContext context) { return Align( - alignment: Alignment.bottomRight, + alignment: AlignmentDirectional.bottomEnd, child: ColoredBox( color: const Color(0xCCFFFFFF), child: GestureDetector( diff --git a/packages/smooth_app/lib/l10n/app_aa.arb b/packages/smooth_app/lib/l10n/app_aa.arb index 508e46034f2..773e24cd051 100644 --- a/packages/smooth_app/lib/l10n/app_aa.arb +++ b/packages/smooth_app/lib/l10n/app_aa.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_af.arb b/packages/smooth_app/lib/l10n/app_af.arb index f3dc516e802..b44e6b7c3db 100644 --- a/packages/smooth_app/lib/l10n/app_af.arb +++ b/packages/smooth_app/lib/l10n/app_af.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_ak.arb b/packages/smooth_app/lib/l10n/app_ak.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_ak.arb +++ b/packages/smooth_app/lib/l10n/app_ak.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_am.arb b/packages/smooth_app/lib/l10n/app_am.arb index 7cc7cd35436..304c68b3732 100644 --- a/packages/smooth_app/lib/l10n/app_am.arb +++ b/packages/smooth_app/lib/l10n/app_am.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_ar.arb b/packages/smooth_app/lib/l10n/app_ar.arb index 4950be6d394..8706cb7fb56 100644 --- a/packages/smooth_app/lib/l10n/app_ar.arb +++ b/packages/smooth_app/lib/l10n/app_ar.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "نتاج", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "مستوى معالجة الطعام غير معروف", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "صورة معلومات إعادة التدوير", diff --git a/packages/smooth_app/lib/l10n/app_as.arb b/packages/smooth_app/lib/l10n/app_as.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_as.arb +++ b/packages/smooth_app/lib/l10n/app_as.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_az.arb b/packages/smooth_app/lib/l10n/app_az.arb index 5d35d7db54d..6878c33707a 100644 --- a/packages/smooth_app/lib/l10n/app_az.arb +++ b/packages/smooth_app/lib/l10n/app_az.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Məhsul", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_be.arb b/packages/smooth_app/lib/l10n/app_be.arb index d06c0cb594f..80f564937c7 100644 --- a/packages/smooth_app/lib/l10n/app_be.arb +++ b/packages/smooth_app/lib/l10n/app_be.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Прадукт", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Фота звестак аб пажыўнасці запампавана", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Фота з інфармацыяй аб перапрацоўцы", @@ -1365,7 +1366,7 @@ "@user_list_popup_rename": { "description": "Short label of a 'rename list' popup" }, - "user_list_name_hint": "Мой спіс", + "user_list_name_hint": "My list", "@user_list_name_hint": { "description": "Hint of a user list name text-field in a 'user list' dialog" }, diff --git a/packages/smooth_app/lib/l10n/app_bg.arb b/packages/smooth_app/lib/l10n/app_bg.arb index 77ba433d351..c549c33dd61 100644 --- a/packages/smooth_app/lib/l10n/app_bg.arb +++ b/packages/smooth_app/lib/l10n/app_bg.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Докосни, за повече информация…", + "tap_for_more": "Докосни, за повече информация…", "@Product": {}, "product": "Продукт", "@product": {}, @@ -506,7 +506,7 @@ "@new_product_dialog_title": { "description": "Please keep it short, like 50 characters. Title of the dialog when the user searched for an unknown barcode." }, - "new_product_leave_message": "It looks like you didn't input anything. Do you really want to leave this page?", + "new_product_leave_message": "Изглежда, че не си въвел нищо. Наистина ли искаш да напуснеш тази страница?", "@new_product_leave_message": { "description": "Alert dialog message when a user landed on the 'add new product' page, didn't input anything and tried to leave the page." }, @@ -534,7 +534,7 @@ "@crop_page_action_local": { "description": "Action being performed on the crop page" }, - "crop_page_too_small_image_title": "The image is too small!", + "crop_page_too_small_image_title": "Снимката е прекалено малка!", "@crop_page_too_small_image_title": { "description": "Title of a dialog warning the user that the image is too small for upload" }, @@ -582,11 +582,12 @@ "new_product_title_ecoscore": "Изчисли Eco-Score", "new_product_subtitle_ecoscore": "Get it by filling at least a category", "new_product_additional_ecoscore": "Направи изчисляването на Eco-Score по-прецизно с произход, опаковка и други", - "new_product_title_nova": "Compute the food processing level (NOVA)", + "new_product_title_nova": "Изчисли нивото на обработка на храната (NOVA)", "new_product_subtitle_nova": "Get it by filling the food category and ingredients", "new_product_desc_nova_unknown": "Нивото на обработка на храни не е известно", "new_product_title_pictures": "Да направим няколко снимки!", "new_product_title_misc": "И някои основни данни…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Снимката с хранителните стойности е качена", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Снимка с информацията за рециклиране", @@ -1357,7 +1358,7 @@ "@added_to_list_msg": { "description": "Message when products have been successfully added to a list" }, - "user_list_popup_clear": "Clear your history", + "user_list_popup_clear": "Изчистване на историята", "@user_list_popup_clear": { "description": "Short label of a 'clear your history list' popup" }, @@ -1398,11 +1399,11 @@ } } }, - "camera_toggle_camera": "Switch between back and front camera", + "camera_toggle_camera": "Превключване между задна и предна камера", "@camera_toggle_camera": { "description": "Explanation for the icon to switch between cameras" }, - "camera_toggle_flash": "Turn ON or OFF the flash of the camera", + "camera_toggle_flash": "Включване или изключване на светкавицата на камерата", "@camera_toggle_flash": { "description": "Explanation for the icon to turn on/off the flash" }, @@ -1723,8 +1724,8 @@ "@confirm_clear": { "description": "Asking about whether to clear the history list or not" }, - "alert_clear_selected_user_list": "You're about to clear selected items in your history", - "confirm_clear_selected_user_list": "Are you sure you want to continue?", + "alert_clear_selected_user_list": "На път си да изчистиш избраните елементи от хронологията", + "confirm_clear_selected_user_list": "Сигурен ли си, че искаш да продължиш?", "alert_select_items_to_clear": "Please select one or more items to clear", "confirm_clear_user_list": "На път си да изчистиш този списък ({name}): сигурен ли си, че искаш да продължиш?", "@confirm_clear_user_list": { @@ -1833,11 +1834,11 @@ "@image_upload_queued": { "description": "Message when a photo is queued for upload" }, - "background_task_title_full_refresh": "Starting the refresh of all the products locally stored", + "background_task_title_full_refresh": "Стартиране на опресняването на всички продукти, съхранявани локално", "@background_task_title_full_refresh": { "description": "Snackbar message when a full refresh is started" }, - "background_task_title_top_n": "Starting the download of the most popular products", + "background_task_title_top_n": "Започва изтеглянето на най-популярните продукти", "@background_task_title_top_n": { "description": "Snackbar message when a download of the most popular products is started" }, @@ -1904,7 +1905,7 @@ "@product_card_remove_product_tooltip": { "description": "Tooltip (message visible with a long-press) on a product item in the carousel" }, - "scan_announce_new_barcode": "New barcode scanned: {barcode}", + "scan_announce_new_barcode": "Нов сканиран баркод: {barcode}", "@scan_announce_new_barcode": { "description": "Text to pronounce by the Accessibility tool when a new barcode is decoded", "placeholders": { @@ -1918,11 +1919,11 @@ "@scan_header_clear_button_tooltip": { "description": "Tooltip (message visible with a long-press) on the Clear button on top of the scanner" }, - "scan_header_compare_button_invalid_state_tooltip": "Please scan at least two products to compare them", + "scan_header_compare_button_invalid_state_tooltip": "Моля, сканирай поне два продукта, за да ги сравниш", "@scan_header_compare_button_invalid_state_tooltip": { "description": "Tooltip (message visible with a long-press) on the Compare button on top of the scanner, when there is just one product scanned" }, - "scan_header_compare_button_valid_state_tooltip": "Click to compare the products you have scanned", + "scan_header_compare_button_valid_state_tooltip": "Кликни, за да сравниш продуктите, които си сканирал", "@scan_header_compare_button_valid_state_tooltip": { "description": "Tooltip (message visible with a long-press) on the Compare button on top of the scanner, when there is at least two prodiucts" }, diff --git a/packages/smooth_app/lib/l10n/app_bm.arb b/packages/smooth_app/lib/l10n/app_bm.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_bm.arb +++ b/packages/smooth_app/lib/l10n/app_bm.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_bn.arb b/packages/smooth_app/lib/l10n/app_bn.arb index b22afb2f8ab..5489c86d747 100644 --- a/packages/smooth_app/lib/l10n/app_bn.arb +++ b/packages/smooth_app/lib/l10n/app_bn.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_bo.arb b/packages/smooth_app/lib/l10n/app_bo.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_bo.arb +++ b/packages/smooth_app/lib/l10n/app_bo.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_br.arb b/packages/smooth_app/lib/l10n/app_br.arb index 1111f996f37..ddaaf1a0458 100644 --- a/packages/smooth_app/lib/l10n/app_br.arb +++ b/packages/smooth_app/lib/l10n/app_br.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Produ", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_bs.arb b/packages/smooth_app/lib/l10n/app_bs.arb index f100644926e..70dbb6eb136 100644 --- a/packages/smooth_app/lib/l10n/app_bs.arb +++ b/packages/smooth_app/lib/l10n/app_bs.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_ca.arb b/packages/smooth_app/lib/l10n/app_ca.arb index a4e33542e1f..996a5b77378 100644 --- a/packages/smooth_app/lib/l10n/app_ca.arb +++ b/packages/smooth_app/lib/l10n/app_ca.arb @@ -64,51 +64,51 @@ "@unknown": { "description": "Short label for product list view: the compatibility of that product with your preferences is unknown" }, - "match_very_good": "Very good match", + "match_very_good": "Molt bona coincidència", "@match_very_good": { "description": "Label for product page regarding product compatibility with the user preferences: very good match" }, - "match_good": "Good match", + "match_good": "Bona coincidència", "@match_good": { "description": "Label for product page regarding product compatibility with the user preferences: good match" }, - "match_poor": "Poor match", + "match_poor": "Poca coincidència", "@match_poor": { "description": "Label for product page regarding product compatibility with the user preferences: poor match" }, - "match_may_not": "May not match", + "match_may_not": "Podria no coincidir", "@match_may_not": { "description": "Label for product page regarding product compatibility with the user preferences: may not match" }, - "match_does_not": "Does not match", + "match_does_not": "No coincideix", "@match_does_not": { "description": "Label for product page regarding product compatibility with the user preferences: does not match" }, - "match_unknown": "Unknown match", + "match_unknown": "Coincidència desconeguda", "@match_unknown": { "description": "Label for product page regarding product compatibility with the user preferences: unknown match" }, - "match_short_very_good": "Very good match", + "match_short_very_good": "Molt bona coincidència", "@match_short_very_good": { "description": "Short label for product list view regarding product compatibility with the user preferences: very good match" }, - "match_short_good": "Good match", + "match_short_good": "Bona coincidència", "@match_short_good": { "description": "Short label for product list view regarding product compatibility with the user preferences: good match" }, - "match_short_poor": "Poor match", + "match_short_poor": "Poca coincidència", "@match_short_poor": { "description": "Short label for product list view regarding product compatibility with the user preferences: poor match" }, - "match_short_may_not": "May not match", + "match_short_may_not": "Podria no coincidir", "@match_short_may_not": { "description": "Short label for product list view regarding product compatibility with the user preferences: may not match" }, - "match_short_does_not": "Does not match", + "match_short_does_not": "No coincideix", "@match_short_does_not": { "description": "Short label for product list view regarding product compatibility with the user preferences: does not match" }, - "match_short_unknown": "Unknown match", + "match_short_unknown": "Coincidència desconeguda", "@match_short_unknown": { "description": "Short label for product list view regarding product compatibility with the user preferences: unknown match" }, @@ -302,7 +302,7 @@ "description": "Button label: Opens a pop up window which shows information about the app" }, "@About this app section": {}, - "contribute": "Contribuir", + "contribute": "Contribueix", "@contribute": { "description": "Button label: Shows multiple ways how users can contribute to OFF" }, @@ -402,7 +402,7 @@ "ranking_tab_all": "Tots", "ranking_subtitle_match_yes": "Una bona coincidència per a vós", "ranking_subtitle_match_no": "Coincidència molt pobra", - "ranking_subtitle_match_maybe": "Unknown match", + "ranking_subtitle_match_maybe": "Coincidència desconeguda", "refresh_with_new_preferences": "Actualitzeu la llista amb les vostres noves preferències", "@refresh_with_new_preferences": { "description": "Action button label: Refresh the list with your new preferences" @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Toqueu per veure més informació…", + "tap_for_more": "Toqueu per veure més informació…", "@Product": {}, "product": "Producte", "@product": {}, @@ -581,12 +581,13 @@ "new_product_subtitle_nutriscore": "Aconsegueix-ho omplint la categoria de l'aliment i els valors nutricionals", "new_product_title_ecoscore": "Calculeu l'Eco-Score", "new_product_subtitle_ecoscore": "Aconsegueix-ho omplint almenys una categoria", - "new_product_additional_ecoscore": "Make Eco-Score computation more precise with origins, packaging & more", - "new_product_title_nova": "Compute the food processing level (NOVA)", - "new_product_subtitle_nova": "Get it by filling the food category and ingredients", + "new_product_additional_ecoscore": "Feu que el càlcul Eco-Score sigui més precís amb els orígens, l'embalatge i molt més", + "new_product_title_nova": "Calcula el nivell de processament d'aliments (NOVA)", + "new_product_subtitle_nova": "Aconsegueix-ho omplint la categoria de l'aliment i els ingredients", "new_product_desc_nova_unknown": "Nivell de processament d'aliments desconegut", - "new_product_title_pictures": "Let's take some pictures!", - "new_product_title_misc": "And some basic data…", + "new_product_title_pictures": "Fem unes fotos!", + "new_product_title_misc": "I algunes dades bàsiques…", + "hey_incomplete_product_message": "Toqueu per respondre 3 preguntes ARA per calcular el Nutri-Score, l'Eco-Score i l'Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "S'ha penjat una foto de dades nutricionals", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Foto informativa sobre el reciclatge", @@ -641,16 +642,16 @@ "score_add_missing_product_emb": "Afegiu els codis de traçabilitat dels productes que falten", "score_add_missing_product_labels": "Afegiu les etiquetes dels productes que falten", "score_add_missing_product_origins": "Afegiu els orígens dels productes que falten", - "score_add_missing_product_stores": "Add missing product stores", - "score_update_nutrition_facts": "Update nutrition facts", + "score_add_missing_product_stores": "Afegeix les botigues de productes que falten", + "score_update_nutrition_facts": "Actualitzar les dades nutricionals", "nutrition_page_title": "Informació nutricional", "nutrition_page_unspecified": "Les dades nutricionals no estan especificades en el producte", "nutrition_page_per_100g": "per 100 g", "nutrition_page_per_serving": "per porció", "nutrition_page_add_nutrient": "Afegir un nutrient", "nutrition_page_serving_size": "Mida de la porció", - "nutrition_page_invalid_number": "Invalid number", - "nutrition_page_update_running": "Updating the product on the server…", + "nutrition_page_invalid_number": "Número no vàlid", + "nutrition_page_update_running": "Actualització del producte al servidor…", "nutrition_page_update_done": "Producte actualitzat!", "more_photos": "Més fotos interessants", "@more_photos": {}, @@ -671,7 +672,7 @@ "@deprecated_header": { "description": "Confirmation, that the user can upgrade to new version of the app" }, - "click_here": "Clicau aquí", + "click_here": "Feu clic aquí", "@click_here": { "description": "Confirmation click to download new version of the app" }, @@ -691,23 +692,23 @@ "@Product Addition": {}, "added_product_thanks": "Gràcies per afegir aquest producte!", "@added_product_thanks": {}, - "product_search_same_category": "Compare to Category", + "product_search_same_category": "Compara amb la categoria", "@product_search_same_category": { "description": "Button looking for the other products within the same category. Less than 30 characters" }, - "product_improvement_add_category": "Add a category to calculate the Nutri-Score.", + "product_improvement_add_category": "Afegiu una categoria per calcular el Nutri-Score.", "@product_improvement_add_category": { "description": "Message for ProductImprovement.ADD_CATEGORY" }, - "product_improvement_add_nutrition_facts": "Add nutrition facts to calculate the Nutri-Score.", + "product_improvement_add_nutrition_facts": "Afegiu dades nutricionals per calcular el Nutri-Score.", "@product_improvement_add_nutrition_facts": { "description": "Message for ProductImprovement.ADD_NUTRITION_FACTS" }, - "product_improvement_add_nutrition_facts_and_category": "Add nutrition facts and a category to calculate the Nutri-Score.", + "product_improvement_add_nutrition_facts_and_category": "Afegiu dades nutricionals i una categoria per calcular el Nutri-Score.", "@product_improvement_add_nutrition_facts_and_category": { "description": "Message for ProductImprovement.ADD_NUTRITION_FACTS_AND_CATEGORY" }, - "product_improvement_categories_but_no_nutriscore": "The Nutri-Score for this product can't be calculated, which may be due to e.g. a non-standard category. If this is considered an error, please contact us.", + "product_improvement_categories_but_no_nutriscore": "La Nutri-score d'aquest producte no es pot calcular, la qual cosa pot ser degut, per exemple, a una categoria no estàndard. Si ho consideres un error, posa't en contacte amb nosaltres.", "@product_improvement_categories_but_no_nutriscore": { "description": "Message for ProductImprovement.CATEGORIES_BUT_NO_NUTRISCORE" }, @@ -727,23 +728,23 @@ "@country_label": { "description": "Explanation as to why users should select their country." }, - "product_removed_comparison": "Product removed from comparison", + "product_removed_comparison": "Producte eliminat de la comparació", "@product_removed_comparison": { "description": "Product got removed from comparison list" }, - "native_app_settings": "Native App Settings", + "native_app_settings": "Configuració de l'aplicació nativa", "@native_app_settings": { "description": "Native App Settings in app settings" }, - "native_app_description": "Open systems settings for Open Food Facts", + "native_app_description": "Configuració del sistema obert per a Open Food Facts", "@native_app_description": { "description": "Native App description in app settings" }, - "product_removed_history": "Product removed from history", + "product_removed_history": "Producte eliminat de l'historial", "@product_removed_history": { "description": "Product got removed from history" }, - "product_could_not_remove": "Could not remove product", + "product_could_not_remove": "No s'ha pogut eliminar el producte", "@product_could_not_remove": { "description": "Could not remove product from a list" }, @@ -757,49 +758,49 @@ }, "really_clear": "Realment voleu suprimir aquesta llista?", "@Plural": {}, - "pct_match": "{percent}% match", + "pct_match": "{percent}% de coincidència", "@pct_match": { "description": "This product has a x percent match with your preferences", "placeholders": { "percent": {} } }, - "plural_ago_days": "{count,plural, =1{one day ago} other{{count} days ago}}", + "plural_ago_days": "{count,plural, =1{fa un dia} other{fa {count} dies}}", "@plural_ago_days": { "description": "Cached results from: x days ago", "placeholders": { "count": {} } }, - "plural_ago_hours": "{count,plural, =1{one hour ago} other{{count} hours ago}}", + "plural_ago_hours": "{count,plural, =1{Fa una hora} other{Fa {count} hores}}", "@plural_ago_hours": { "description": "Cached results from: x hours ago", "placeholders": { "count": {} } }, - "plural_ago_minutes": "{count,plural, =0{less than a minute ago} =1{one minute ago} other{{count} minutes ago}}", + "plural_ago_minutes": "{count,plural, =0{fa menys d'un minut} =1{fa un minut} other{fa {count} minuts}}", "@plural_ago_minutes": { "description": "Cached results from: x minutes ago", "placeholders": { "count": {} } }, - "plural_ago_months": "{count,plural, =1{one month ago} other{{count} months ago}}", + "plural_ago_months": "{count,plural, =1{fa un mes} other{fa {count} mesos}}", "@plural_ago_months": { "description": "Cached results from: x months ago", "placeholders": { "count": {} } }, - "plural_ago_weeks": "{count,plural, =1{one week ago} other{{count} weeks ago}}", + "plural_ago_weeks": "{count,plural, =1{fa una setmana} other{fa {count} setmanes}}", "@plural_ago_weeks": { "description": "Cached results from: x weeks ago", "placeholders": { "count": {} } }, - "plural_compare_x_products": "{count,plural, =1{Compare one Product} other{Compare {count} Products}}", + "plural_compare_x_products": "{count,plural, =1{Compara un producte} other{Compara {count} Productes}}", "@plural_compare_x_products": { "description": "Button label to open a page to compare all selected products to each other", "placeholders": { @@ -810,7 +811,7 @@ "@compare_products_mode": { "description": "Button to switch to 'compare products mode'" }, - "compare_products_appbar_title": "Compare products", + "compare_products_appbar_title": "Comparar productes", "@compare_products_appbar_title": { "description": "AppBar title when in comparison mode " }, @@ -827,7 +828,7 @@ "blog": "Bloc", "faq": "FAQ", "discover": "Descobrir", - "how_to_contribute": "How to Contribute", + "how_to_contribute": "Com contribuir", "hint_knowledge_panel_message": "Podeu fer clic a qualsevol part de la targeta per obtenir més detalls del que veieu. Proveu-ho ara!", "@hint_knowledge_panel_message": { "description": "Hint popup indicating the card is clickable during onboarding" @@ -844,11 +845,11 @@ "@consent_analytics_body2": { "description": "second paragraph for the consent analytics UI Page" }, - "permissions_page_title": "Camera access", + "permissions_page_title": "Accés a la càmera", "@permissions_page_title": { "description": "Title for the camera permission's page (onboarding)" }, - "permissions_page_body1": "To scan barcodes with your phone's camera, please Authorise the access.", + "permissions_page_body1": "Per escanejar codis de barres amb la càmera del telèfon, si us plau, autoritzeu l'accés.", "@permissions_page_body1": { "description": "first paragraph for the camera permission's page (onboarding)" }, @@ -856,7 +857,7 @@ "@permissions_page_body2": { "description": "second paragraph for the camera permission's page (onboarding)" }, - "contact_form_body_android": "OS: Android (SDK Int: {sdkInt} / Release: {release})\nModel: {model}\nProduct: {product}\nDevice: {device}\nBrand:{brand}", + "contact_form_body_android": "Sistema operatiu: Android (SDK Int: {sdkInt} / Versió: {release})\nModel: {model}\nProducte: {product}\nDispositiu: {device}\nMarca:{brand}", "@contact_form_body_android": { "description": "Contact form content for Android devices", "placeholders": { @@ -886,7 +887,7 @@ } } }, - "contact_form_body_ios": "OS: iOS ({version})\nModel: {model}\nLocalized model: {localizedModel}", + "contact_form_body_ios": "Sistema operatiu: iOS ({version})\nModel: {model}\nModel localitzat: {localizedModel}", "@contact_form_body_ios": { "description": "Contact form content for iOS devices", "placeholders": { @@ -904,7 +905,7 @@ } } }, - "contact_form_body": "{osContent}\nApp version:{appVersion}\nApp build number:{appBuildNumber}\nApp package name:{appPackageName}", + "contact_form_body": "{osContent}\nVersió de l'aplicació:{appVersion}\nNúmero de compilació de l'aplicació:{appBuildNumber}\nNom del paquet de l'aplicació:{appPackageName}", "@contact_form_body": { "description": "Contact form content", "placeholders": { @@ -930,11 +931,11 @@ "@authorize": { "description": "Button to accept the request of sending the anonymous analytics or authorize the camera permission" }, - "refuse_button_label": "Refuse", + "refuse_button_label": "Denegar", "@refuse": { "description": "Button to decline the request of sending the anonymous analytics" }, - "ask_me_later_button_label": "Later", + "ask_me_later_button_label": "Més tard", "@ask_me_later": { "description": "Button to ignore the camera permission request" }, @@ -942,7 +943,7 @@ "@are_you_sure": { "description": "Are you sure?" }, - "knowledge_panel_text_source": "Go further on {sourceName}", + "knowledge_panel_text_source": "Aneu més enllà del {sourceName}", "@knowledge_panel_text_source": { "description": "When we show information from for example Wikipedia or health authorities, this is the button label to open the source website", "placeholders": { @@ -955,19 +956,19 @@ "@onboarding_reinventing_text1": { "description": "Onboarding / Reinventing page: text 1/2. If possible, balanced on 3 lines." }, - "onboarding_reinventing_text2": "As we turn 10,\nwe're reinventing it\nfrom the ground up!", + "onboarding_reinventing_text2": "Ja que en fem 10,\nl'estem reinventant\ndes de zero!", "@onboarding_reinventing_text2": { "description": "Onboarding / Reinventing page: text 2/2. If possible, balanced on 3 lines." }, - "onboarding_welcome_loading_dialog_title": "Loading your first example product", + "onboarding_welcome_loading_dialog_title": "S'està carregant el vostre primer exemple de producte", "@onboarding_welcome_loading_dialog_title": { "description": "Title for the onboarding loading dialog" }, - "onboarding_welcome_loading_error": "Seems like there is no example product in your language", + "onboarding_welcome_loading_error": "Sembla que no hi ha cap producte d'exemple en el vostre idioma", "@onboarding_welcome_loading_error": { "description": "Seems like there is no example product in your language" }, - "product_list_your_ranking": "Your ranking", + "product_list_your_ranking": "La teva classificació", "@product_list_your_ranking": { "description": "Your ranking screen title" }, @@ -975,37 +976,37 @@ "@product_list_icon_desc": { "description": "When the history list is empty, icon description (for accessibility) of the message explaining to start scanning" }, - "product_list_empty_title": "Start scanning", + "product_list_empty_title": "Comenceu a escanejar", "@product_list_empty_title": { "description": "When the history list is empty, title of the message explaining to start scanning" }, - "product_list_empty_message": "Scanned products will appear here and you can check detailed information about them", + "product_list_empty_message": "Els productes escanejats apareixeran aquí i podeu consultar informació detallada sobre ells", "@product_list_empty_message": { "description": "When the history list is empty, body of the message explaining to start scanning" }, - "product_list_reloading_in_progress_multiple": "Refreshing {count,plural, =0{product} =1{product} other{products}} in your history", + "product_list_reloading_in_progress_multiple": "Refrescant {count,plural, =0{cap producte} =1{un producte} other{els productes}} en el teu historial", "@product_list_reloading_in_progress_multiple": { "description": "Message to show while loading previous scanned items", "placeholders": { "count": {} } }, - "product_list_reloading_success_multiple": "{count,plural, =0{Product} =1{Product} other{Products}} refresh complete", + "product_list_reloading_success_multiple": "Refrescament acabat {count,plural, =0{pel producte} =1{pel producte} other{pels productes}}", "@product_list_reloading_success_multiple": { "description": "Message to show once previous scanned items are loaded", "placeholders": { "count": {} } }, - "loading_dialog_default_title": "Downloading data", + "loading_dialog_default_title": "Descàrregant les dades", "@loading_dialog_default_title": { "description": "Default loading dialog title" }, - "loading_dialog_default_error_message": "Could not download data", + "loading_dialog_default_error_message": "No s'han pogut baixar les dades", "@loading_dialog_default_error_message": { "description": "Default loading dialog error message" }, - "account_delete": "Delete account", + "account_delete": "Eliminar compte", "@account_delete": { "description": "Delete account button (user profile)" }, @@ -1021,8 +1022,8 @@ "@user_profile_title_guest": { "description": "When the user is not connected" }, - "user_profile_subtitle_guest": "Sign-in or sign-up to join the Open Food Facts community", - "user_profile_title_id_email": "Open Food Facts login: {email}", + "user_profile_subtitle_guest": "Inicieu la sessió o registreu-vos per unir-vos a la comunitat Open Food Facts", + "user_profile_title_id_email": "Inici de sessió Open Food Facts: {email}", "@user_profile_title_id_email": { "description": "User login (when it's an email)", "placeholders": { @@ -1040,11 +1041,11 @@ } } }, - "email_subject_account_deletion": "Delete account", + "email_subject_account_deletion": "Eliminar compte", "@email_subject_account_deletion": { "description": "Email subject for an account deletion" }, - "email_body_account_deletion": "Hi there, please delete my Open Food Facts account: {userId}", + "email_body_account_deletion": "Hola, suprimiu el meu compte d'Open Food Facts: {userId}", "@email_body_account_deletion": { "description": "Email body for an account deletion", "placeholders": { @@ -1054,7 +1055,7 @@ } }, "settings_app_app": "Aplicació", - "settings_app_data": "Privacy & monitoring", + "settings_app_data": "Privacitat i seguiment", "settings_app_camera": "Càmera", "settings_app_products": "Productes", "settings_app_miscellaneous": "Miscel·lània", @@ -1073,7 +1074,7 @@ "@app_haptic_feedback_title": { "description": "Title for the Haptic feedback toggle" }, - "app_haptic_feedback_subtitle": "Vibrations after executing some actions (barcode decoded, product removed…).", + "app_haptic_feedback_subtitle": "Vibracions després d'executar algunes accions (codi de barres descodificat, producte eliminat…).", "@app_haptic_feedback_subtitle": { "description": "SubTitle for the Haptic feedback toggle" }, @@ -1081,7 +1082,7 @@ "@crash_reporting_toggle_title": { "description": "Title for the Crash reporting toggle" }, - "crash_reporting_toggle_subtitle": "When enabled, crash reports are automatically submitted to Open Food Facts' error tracking system, so that bugs can be fixed and thus improve the app.", + "crash_reporting_toggle_subtitle": "Quan està activat, els informes d'error s'envien automàticament al sistema de seguiment d'errors d'Open Food Facts, de manera que es puguin corregir i millorar així l'aplicació.", "@crash_reporting_toggle_subtitle": { "description": "SubTitle for the Crash reporting toggle" }, @@ -1102,7 +1103,7 @@ "description": "When the camera/photo permission failed to be acquired (!= denied)" }, "permission_photo_denied_title": "Permeteu l'ús de la càmera per escanejar codis de barres", - "permission_photo_denied_message": "For an enhanced experience, please allow {appName} to access your camera. You will be able to directly scan barcodes.", + "permission_photo_denied_message": "Per obtenir una experiència millorada, permeteu que {appName} accedeixi a la vostra càmera. Us permetrà escanejar codis de barres directament.", "@permission_photo_denied_message": { "description": "When the camera/photo permission is denied by user", "placeholders": { @@ -1116,14 +1117,14 @@ "description": "When the camera/photo permission is denied by user" }, "permission_photo_denied_dialog_settings_title": "Permís denegat", - "permission_photo_denied_dialog_settings_message": "As you've previously denied the camera permission, you must allow it manually from the Settings.", + "permission_photo_denied_dialog_settings_message": "Com que prèviament heu denegat el permís de la càmera, heu de permetre'l manualment des de la configuració.", "permission_photo_denied_dialog_settings_button_open": "Obre la configuració", "permission_photo_denied_dialog_settings_button_cancel": "Cancel·lar", - "permission_photo_none_found": "No camera detected", + "permission_photo_none_found": "No es detecta cap càmera", "@permission_photo_none_found": { "description": "Message for the user when no camera was detected, replacing the barcode scanner" }, - "permission_photo_denied": "No camera access granted", + "permission_photo_denied": "No s'ha concedit accés a la càmera", "@permission_photo_denied": { "description": "When the camera/photo permission is denied by user" }, @@ -1131,8 +1132,8 @@ "@edit_product_label": { "description": "Edit product button label" }, - "edit_product_form_item_add_action": "Add a new {itemType}", - "description": "Tooltip to show when the user long presses the (+) button", + "edit_product_form_item_add_action": "Afegeix un nou {itemType}", + "description": "Informació sobre eines per mostrar quan l'usuari prem el botó (+) durant una estona", "@edit_product_form_item_add_action": { "placeholders": { "itemType": { @@ -1148,11 +1149,11 @@ "@edit_product_form_item_details_title": { "description": "Product edition - Basic Details - Title" }, - "edit_product_form_item_details_subtitle": "Product name, brand, quantity", + "edit_product_form_item_details_subtitle": "Nom del producte, marca, quantitat", "@edit_product_form_item_details_subtitle": { "description": "Product edition - Basic Details - Subtitle" }, - "edit_product_form_item_other_details_title": "Additional details", + "edit_product_form_item_other_details_title": "Més detalls", "@edit_product_form_item_other_details_title": { "description": "Product edition - Other Details - Title" }, @@ -1164,15 +1165,15 @@ "@edit_product_form_item_photos_title": { "description": "Product edition - Photos - Title" }, - "edit_product_form_item_photos_subtitle": "Add or refresh photos", + "edit_product_form_item_photos_subtitle": "Afegeix o actualitza fotos", "@edit_product_form_item_photos_subtitle": { "description": "Product edition - Photos - SubTitle" }, - "edit_product_form_item_labels_title": "Labels & Certifications", + "edit_product_form_item_labels_title": "Etiquetes i certificacions", "@edit_product_form_item_labels_title": { "description": "Product edition - Labels - Title" }, - "edit_product_form_item_labels_subtitle": "Environmental, Quality labels…", + "edit_product_form_item_labels_subtitle": "Mediambiental, Segells de qualitat…", "@edit_product_form_item_labels_subtitle": { "description": "Product edition - Labels - SubTitle" }, @@ -1216,7 +1217,7 @@ "@edit_product_form_item_origins_explainer_2": { "description": "Product edition - Origins - input explainer, part 2" }, - "edit_product_form_item_countries_title": "Country", + "edit_product_form_item_countries_title": "País", "@edit_product_form_item_countries_title": { "description": "Product edition - Countries - Title" }, @@ -1228,7 +1229,7 @@ "@edit_product_form_item_countries_type": { "description": "Product edition - Countries - input textfield type" }, - "edit_product_form_item_countries_explanations": "Countries where the product is widely available (not including stores specialising in foreign products).", + "edit_product_form_item_countries_explanations": "Països on el producte està àmpliament disponible (sense incloure les botigues especialitzades en productes estrangers).", "@edit_product_form_item_countries_explanations": { "description": "Product edition - Countries - explanations" }, @@ -1244,7 +1245,7 @@ "@edit_product_form_item_emb_codes_type": { "description": "Product edition - Traceability Codes - input textfield type" }, - "edit_product_form_item_emb_codes_explanations": "In Europe, code in an ellipse with the 2 country initials followed by a number and CE.\nExamples: EMB 53062, FR 62.448.034 CE, 84 R 20, 33 RECOLTANT 522", + "edit_product_form_item_emb_codes_explanations": "A Europa, codifiqueu en una el·lipse amb les inicials dels 2 països seguides d'un número i CE.\nPer exemple: EMB 53062, FR 62.448.034 CE, 84 R 20, 33 RECOLTANT 522", "@edit_product_form_item_emb_codes_examples": { "description": "Product edition - EMB Codes - explanations" }, @@ -1272,17 +1273,17 @@ "@edit_product_form_item_categories_explainer_3": { "description": "Product edition - Categories - input explainer, part 3" }, - "edit_product_form_item_exit_confirmation": "Do you want to save your changes before leaving this page?", + "edit_product_form_item_exit_confirmation": "Voleu desar els canvis abans de sortir de la pàgina?", "edit_product_form_item_exit_confirmation_positive_button": "Desa els canvis", "edit_product_form_item_exit_confirmation_negative_button": "Descarta els canvis", - "edit_product_form_item_ingredients_title": "Ingredients & Origins", + "edit_product_form_item_ingredients_title": "Ingredients i orígens", "@edit_product_form_item_ingredients_title": { "description": "Product edition - Ingredients - Title" }, "edit_product_form_item_add_valid_item_tooltip": "Afegeix", - "edit_product_form_item_add_invalid_item_tooltip": "Please enter a text first", - "edit_product_form_item_remove_item_tooltip": "Remove", - "edit_product_form_item_packaging_title": "Recycling instructions photo", + "edit_product_form_item_add_invalid_item_tooltip": "Si us plau, introduïu un text", + "edit_product_form_item_remove_item_tooltip": "Suprimeix", + "edit_product_form_item_packaging_title": "Foto d'instruccions sobre el reciclatge", "@edit_product_form_item_packaging_title": { "description": "Product edition - Packaging - Title" }, @@ -1290,7 +1291,7 @@ "@edit_product_form_item_nutrition_facts_title": { "description": "Product edition - Nutrition facts - Title" }, - "edit_product_form_item_nutrition_facts_subtitle": "Nutrition, alcohol content…", + "edit_product_form_item_nutrition_facts_subtitle": "Nutrició, contingut alcohòlic…", "@edit_product_form_item_nutrition_facts_subtitle": { "description": "Product edition - Nutrition facts - SubTitle" }, @@ -1298,38 +1299,38 @@ "@edit_product_form_save": { "description": "Product edition - Nutrition facts - Save button" }, - "product_field_website_title": "Website", + "product_field_website_title": "Lloc web", "@product_field_website_title": { "description": "Title of a product field: website" }, - "completed_basic_details_btn_text": "Complete basic details", - "not_implemented_snackbar_text": "Not implemented yet", + "completed_basic_details_btn_text": "Detalls bàsics complets", + "not_implemented_snackbar_text": "Encara no s'ha implementat", "category_picker_page_appbar_text": "Categories", "edit_ingredients_extrait_ingredients_btn_text": "Extreu els ingredients", "@edit_ingredients_extrait_ingredients_btn_text": { "description": "Ingredients edition - Extract ingredients" }, - "edit_ingredients_refresh_photo_btn_text": "Refresh photo", + "edit_ingredients_refresh_photo_btn_text": "Actualitza la foto", "@edit_ingredients_refresh_photo_btn_text": { "description": "Ingredients edition - Refresh photo" }, - "edit_packaging_extract_btn_text": "Extract packaging", + "edit_packaging_extract_btn_text": "Extreure l'embalatge", "@edit_packaging_extract_btn_text": { "description": "Packaging edition - OCR-Extract packaging" }, - "edit_packaging_refresh_photo_btn_text": "Refresh photo", + "edit_packaging_refresh_photo_btn_text": "Actualitza la foto", "@edit_packaging_refresh_photo_btn_text": { "description": "Packaging edition - Refresh photo" }, - "edit_ocr_extract_failed": "Failed to detect text in image.", + "edit_ocr_extract_failed": "No ha pogut detectar text a la imatge.", "@edit_ocr_extract_failed": { "description": "OCR extraction - message for failed" }, - "user_list_dialog_new_title": "New list of products", + "user_list_dialog_new_title": "Nova llista de productes", "@user_list_dialog_new_title": { "description": "Title of the 'new user list' dialog" }, - "user_list_dialog_rename_title": "Rename list", + "user_list_dialog_rename_title": "Canvia el nom a la llista", "@user_list_dialog_rename_title": { "description": "Title of the 'rename user list' dialog" }, @@ -1337,7 +1338,7 @@ "@user_list_subtitle_product": { "description": "Subtitle of a paragraph about user lists in a product context" }, - "user_list_add_product": "Add the product to your lists", + "user_list_add_product": "Afegeix el producte a les teves llistes", "@user_list_add_product": { "description": "Label for the dialog to add a product to a list" }, @@ -1345,7 +1346,7 @@ "@user_list_button_new": { "description": "Short label of a 'create a new list' button" }, - "user_list_empty_label": "No list available yet,\nplease start by creating one", + "user_list_empty_label": "Encara no hi ha cap llista disponible,\nsi us plau, comenceu creant-ne una", "@user_list_empty_label": { "description": "Content displayed when there is no list" }, @@ -1357,31 +1358,31 @@ "@added_to_list_msg": { "description": "Message when products have been successfully added to a list" }, - "user_list_popup_clear": "Clear your history", + "user_list_popup_clear": "Neteja l'historial", "@user_list_popup_clear": { "description": "Short label of a 'clear your history list' popup" }, - "user_list_popup_rename": "Rename", + "user_list_popup_rename": "Canviar el nom", "@user_list_popup_rename": { "description": "Short label of a 'rename list' popup" }, - "user_list_name_hint": "My list", + "user_list_name_hint": "La meva llista", "@user_list_name_hint": { "description": "Hint of a user list name text-field in a 'user list' dialog" }, - "user_list_name_error_empty": "Name is mandatory", + "user_list_name_error_empty": "El nom és obligatori", "@user_list_name_error_empty": { "description": "Validation error about the name that cannot be empty" }, - "user_list_name_error_already": "That name is already used", + "user_list_name_error_already": "Aquest nom ja s'està utilitzat", "@user_list_name_error_already": { "description": "Validation error about the name that is already used for another list" }, - "user_list_name_error_same": "That is the same name", + "user_list_name_error_same": "Aquest és el mateix nom", "@user_list_name_error_same": { "description": "Validation error about the renamed name that is the same as the initial list name" }, - "try_again": "Try Again", + "try_again": "Torneu-ho a provar", "@try_again": { "description": "Label for buttons that try to repeat a failed action" }, @@ -1389,7 +1390,7 @@ "@there_was_an_error": { "description": "Label that presents a error" }, - "category_picker_no_category_found_message": "No category found for {items}", + "category_picker_no_category_found_message": "No s'ha trobat cap categoria per a {items}", "@category_picker_no_category_found_message": { "description": "Label when no category is available", "placeholders": { @@ -1398,27 +1399,27 @@ } } }, - "camera_toggle_camera": "Switch between back and front camera", + "camera_toggle_camera": "Canvia entre la càmera posterior i la frontal", "@camera_toggle_camera": { "description": "Explanation for the icon to switch between cameras" }, - "camera_toggle_flash": "Turn ON or OFF the flash of the camera", + "camera_toggle_flash": "Enceneu o apagueu el flaix de la càmera", "@camera_toggle_flash": { "description": "Explanation for the icon to turn on/off the flash" }, - "camera_enable_flash": "Enable flash", + "camera_enable_flash": "Activa el flaix", "@camera_enable_flash": { "description": "Enable flash (tooltip)" }, - "camera_disable_flash": "Disable flash", + "camera_disable_flash": "Desactiva el flaix", "@camera_disable_flash": { "description": "Disable flash (tooltip)" }, - "camera_flash_error_dialog_title": "An error occurred!", + "camera_flash_error_dialog_title": "S'ha produït un error!", "@camera_flash_error_dialog_title": { "description": "Title of the dialog explaining that an error happened while enabling/disabling the flash of the camera" }, - "camera_flash_error_dialog_message": "An error occurred while changing the state of your flash. Please ensure your smartphone has not the torch already enabled.", + "camera_flash_error_dialog_message": "S'ha produït un error en canviar l'estat del flaix. Assegureu-vos que el vostre telèfon no tingui la llanterna activada.", "@camera_flash_error_dialog_message": { "description": "Content of the dialog explaining that an error happened while enabling/disabling the flash of the camera" }, @@ -1426,27 +1427,27 @@ "@category_picker_no_category_found_button": { "description": "Button label when no category is available" }, - "dev_preferences_screen_title": "DEV Mode", + "dev_preferences_screen_title": "Mode Desenvolupador", "@dev_preferences_screen_title": { "description": "User dev preferences - Title" }, - "dev_preferences_reset_onboarding_title": "Restart onboarding", + "dev_preferences_reset_onboarding_title": "Reinicieu la incorporació", "@dev_preferences_reset_onboarding_title": { "description": "User dev preferences - Reset onboarding - Title" }, - "dev_preferences_reset_onboarding_subtitle": "You then have to restart the App to see it again.", + "dev_preferences_reset_onboarding_subtitle": "Llavors heu de reiniciar l'aplicació per tornar-ho a veure.", "@dev_preferences_reset_onboarding_subtitle": { "description": "User dev preferences - Reset onboarding - Subtitle" }, - "dev_preferences_environment_switch_title": "Switch between openfoodfacts.org (PROD) and test env", + "dev_preferences_environment_switch_title": "Canvia entre openfoodfacts.org (PROD) i prova env", "@dev_preferences_environment_switch_title": { "description": "User dev preferences - Environment (prod/test) switcher - Title" }, - "dev_preferences_test_environment_title": "Test environment parameters", + "dev_preferences_test_environment_title": "Paràmetres de l'entorn de prova", "@dev_preferences_test_environment_title": { "description": "User dev preferences - Info about test environment - Title" }, - "dev_preferences_test_environment_subtitle": "Base URL for current test env: {url}", + "dev_preferences_test_environment_subtitle": "URL base per a l'entorn de prova actual: {url}", "@dev_preferences_test_environment_subtitle": { "description": "User dev preferences - Info about test environment - Value", "placeholders": { @@ -1455,43 +1456,43 @@ } } }, - "dev_preferences_test_environment_dialog_title": "Test environment host", + "dev_preferences_test_environment_dialog_title": "Amfitrió de l'entorn de prova", "@dev_preferences_test_environment_dialog_title": { "description": "User dev preferences - Info about test environment - Dialog title" }, - "dev_preferences_ml_kit_title": "Use ML Kit", + "dev_preferences_ml_kit_title": "Utilitzeu el kit ML", "@dev_preferences_ml_kit_title": { "description": "User dev preferences - Enable ML Kit - Title" }, - "dev_preferences_ml_kit_subtitle": "then you have to restart this app", + "dev_preferences_ml_kit_subtitle": "aleshores heu de reiniciar aquesta aplicació", "@dev_preferences_ml_kit_subtitle": { "description": "User dev preferences - Enable ML Kit - Subtitle" }, - "dev_preferences_product_additional_features_title": "Additional button on product page", + "dev_preferences_product_additional_features_title": "Botó addicional a la pàgina del producte", "@dev_preferences_product_additional_features_title": { "description": "User dev preferences - Additional buttons on product page - Title" }, - "dev_preferences_edit_ingredients_title": "Edit ingredients via a knowledge panel button", + "dev_preferences_edit_ingredients_title": "Editeu els ingredients mitjançant un botó del tauler de coneixement", "@dev_preferences_edit_ingredients_title": { "description": "User dev preferences - Additional buttons on product page - Subtitle" }, - "dev_preferences_export_history_title": "Export History", + "dev_preferences_export_history_title": "Exporta l'historial", "@dev_preferences_export_history_title": { "description": "User dev preferences - Export history - Title" }, - "dev_preferences_export_history_progress_error": "exception", + "dev_preferences_export_history_progress_error": "excepció", "@dev_preferences_export_history_progress_error": { "description": "User dev preferences - Export history - Item - Error" }, - "dev_preferences_export_history_progress_found": "product found", + "dev_preferences_export_history_progress_found": "producte trobat", "@dev_preferences_export_history_progress_found": { "description": "User dev preferences - Export history - Item - Found" }, - "dev_preferences_export_history_progress_not_found": "product NOT found", + "dev_preferences_export_history_progress_not_found": "producte NO trobat", "@dev_preferences_export_history_progress_not_found": { "description": "User dev preferences - Export history - Item - Not found" }, - "dev_preferences_export_history_dialog_title": "Export history", + "dev_preferences_export_history_dialog_title": "Exportació de l'historial", "@dev_preferences_export_history_dialog_title": { "description": "User dev preferences - Export history - Dialog title" }, @@ -1503,8 +1504,8 @@ "@dev_preferences_button_negative": { "description": "User dev preferences - Negative button label" }, - "dev_preferences_migration_title": "Data migration from V1", - "dev_preferences_migration_subtitle": "Status: {status}", + "dev_preferences_migration_title": "Migració de dades des de la V1", + "dev_preferences_migration_subtitle": "Estat: {status}", "@dev_preferences_migration_subtitle": { "placeholders": { "status": { @@ -1512,29 +1513,29 @@ } } }, - "dev_preferences_migration_status_already_done": "success or fresh install", - "dev_preferences_migration_status_success": "success", + "dev_preferences_migration_status_already_done": "èxit o instal·lació nova", + "dev_preferences_migration_status_success": "èxit", "dev_preferences_migration_status_error": "error", - "dev_preferences_migration_status_in_progress": "in progress", - "dev_preferences_migration_status_required": "required (click to start)", - "dev_preferences_migration_status_not_started": "unknown", - "dev_preferences_import_history_title": "Import History", + "dev_preferences_migration_status_in_progress": "en curs", + "dev_preferences_migration_status_required": "obligatori (feu clic per començar)", + "dev_preferences_migration_status_not_started": "desconegut", + "dev_preferences_import_history_title": "Historial d'importacions", "@dev_preferences_import_history_title": { "description": "User dev preferences - Import history - Title" }, - "dev_preferences_import_history_subtitle": "Will clear history and put 3 products in there", + "dev_preferences_import_history_subtitle": "Esborrarà l'historial i hi posarà 3 productes", "@dev_preferences_import_history_subtitle": { "description": "User dev preferences - Import history - Subtitle" }, - "dev_preferences_import_history_result_success": "Done", + "dev_preferences_import_history_result_success": "Fet", "@dev_preferences_import_history_result_success": { "description": "User dev preferences - Import history - Result successful" }, - "dev_mode_scan_mode_title": "Scan Mode", + "dev_mode_scan_mode_title": "Mode d'escaneig", "@dev_mode_scan_mode_title": { "description": "User dev preferences - Scan mode - Title" }, - "dev_mode_scan_mode_subtitle": "Current scan mode is: \"{mode}\"", + "dev_mode_scan_mode_subtitle": "El mode d'exploració actual és: \"{mode}\"", "@dev_mode_scan_mode_subtitle": { "description": "User dev preferences - Scan mode - Subtitle", "placeholders": { @@ -1543,31 +1544,31 @@ } } }, - "dev_mode_scan_mode_dialog_title": "Scan Mode", + "dev_mode_scan_mode_dialog_title": "Mode d'escaneig", "@dev_mode_scan_mode_dialog_title": { "description": "User dev preferences - Scan mode - Dialog Title" }, - "dev_mode_hide_ecoscore_title": "Exclude Eco-Score", + "dev_mode_hide_ecoscore_title": "Exclou l'Eco-Score", "@dev_mode_hide_ecoscore_title": { "description": "User dev preferences - Disable Ecoscore - Title" }, - "dev_mode_scan_camera_only": "Only camera stream, no scanning", + "dev_mode_scan_camera_only": "Només flux de càmera, sense escaneig", "@dev_mode_scan_camera_only": { "description": "Scan mode - Camera only" }, - "dev_mode_scan_preprocess_full_image": "Camera stream and full image preprocessing, no scanning", + "dev_mode_scan_preprocess_full_image": "Flux de càmera i preprocessament complet d'imatges, sense escaneig", "@dev_mode_scan_preprocess_full_image": { "description": "Scan mode - Preprocess full image" }, - "dev_mode_scan_preprocess_half_image": "Camera stream and half image preprocessing, no scanning", + "dev_mode_scan_preprocess_half_image": "Flux de càmera i preprocessament de mitja imatge, sense escaneig", "@dev_mode_scan_preprocess_half_image": { "description": "Scan mode - Preprocess half image" }, - "dev_mode_scan_scan_full_image": "Full image scanning", + "dev_mode_scan_scan_full_image": "Escaneig complet d'imatge", "@dev_mode_scan_scan_full_image": { "description": "Scan mode - Scan full image" }, - "dev_mode_scan_scan_half_image": "Half image scanning", + "dev_mode_scan_scan_half_image": "Escaneig de mitja imatge", "@dev_mode_scan_scan_half_image": { "description": "Scan mode - Scan half image" }, @@ -1580,7 +1581,7 @@ } } }, - "product_search_button_download_more": "Download {count} more products\nAlready downloaded {downloaded} out of {totalSize}.", + "product_search_button_download_more": "Descarrega {count} productes més\nJa s'han descarregat {downloaded} de {totalSize}.", "@product_search_button_download_more": { "description": "Product search list - Button to download more results", "placeholders": { @@ -1607,19 +1608,19 @@ "@user_search_photographer_title": { "description": "User search (photographer): list tile title" }, - "user_search_to_be_completed_title": "My to-be-completed products", + "user_search_to_be_completed_title": "Els meus productes pendents de completar", "@user_search_to_be_completed_title": { "description": "User search (to be completed): list tile title" }, - "all_search_to_be_completed_title": "All to-be-completed products", + "all_search_to_be_completed_title": "Tots els productes per completar", "@all_search_to_be_completed_title": { "description": "All products to be completed: list tile title" }, - "edit_product_action_retake_picture": "Retake photo", + "edit_product_action_retake_picture": "Torna a fer la foto", "@edit_product_action_retake_picture": { "description": "Product edition - FAB actions - retake a picture" }, - "edit_product_action_take_picture": "Take photo", + "edit_product_action_take_picture": "Fes una foto", "@edit_product_action_take_picture": { "description": "Product edition - FAB actions - take a picture" }, @@ -1627,7 +1628,7 @@ "@edit_product_action_confirm": { "description": "Product edition - FAB actions - confirm" }, - "signup_page_terms_of_use_line1": "I agree to the Open Food Facts ", + "signup_page_terms_of_use_line1": "Estic d'acord amb Open Food Facts ", "@signup_page_terms_of_use_line1": { "description": "User consent for terms of use (line 1)" }, @@ -1635,11 +1636,11 @@ "@signup_page_terms_of_use_line2": { "description": "User consent for terms of use (line 2)" }, - "analytics_consent_image_semantic_label": "Analytics icon", + "analytics_consent_image_semantic_label": "Icona d'Analítiques", "@analytics_consent_image_semantic_label": { "description": "Consent Analytics icon semantics label" }, - "knowledge_panel_page_loading_error": "Fatal Error: {error}", + "knowledge_panel_page_loading_error": "Error fatal: {error}", "@knowledge_panel_page_loading_error": { "description": "Knowledge panel page template - Error while loading future", "placeholders": { @@ -1648,7 +1649,7 @@ } } }, - "preferences_page_loading_error": "Fatal Error: {error}", + "preferences_page_loading_error": "Error fatal: {error}", "@preferences_page_loading_error": { "description": "Preferences page - Error while loading future", "placeholders": { @@ -1657,7 +1658,7 @@ } } }, - "summary_card_button_add_basic_details": "Complete basic details", + "summary_card_button_add_basic_details": "Detalls bàsics complets", "@summary_card_button_add_basic_details": { "description": "Summary card - Button to add details about the product" }, @@ -1665,31 +1666,31 @@ "@edit_photo_button_label": { "description": "Edit photo button label" }, - "edit_photo_unselect_button_label": "Unselect photo", + "edit_photo_unselect_button_label": "Desselecciona la foto", "@edit_photo_unselect_button_label": { "description": "Edit 'unselect photo' button label" }, - "edit_photo_select_existing_button_label": "Select an existing image", + "edit_photo_select_existing_button_label": "Seleccionar una imatge existent", "@edit_photo_select_existing_button_label": { "description": "Edit 'select existing image' button label" }, - "edit_photo_select_existing_all_label": "Existing images", + "edit_photo_select_existing_all_label": "Imatges existents", "@edit_photo_select_existing_all_label": { "description": "Page title" }, - "edit_photo_select_existing_download_label": "Retrieving existing images…", + "edit_photo_select_existing_download_label": "Recuperant imatges existents…", "@edit_photo_select_existing_download_label": { "description": "Dialog label" }, - "edit_photo_select_existing_downloaded_none": "There are no images previously uploaded related to this product.", + "edit_photo_select_existing_downloaded_none": "No hi ha imatges carregades anteriorment relacionades amb aquest producte.", "@edit_photo_select_existing_downloaded_none": { "description": "Error message" }, - "edit_photo_language_not_this_one": "No image in that language yet", + "edit_photo_language_not_this_one": "Encara no hi ha imatge en aquest idioma", "@edit_photo_language_not_this_one": { "description": "Warning message: for this product and this field, there are 'translated' images, but not in that language" }, - "edit_photo_language_none": "No image yet", + "edit_photo_language_none": "Encara no hi ha imatge", "@edit_photo_language_none": { "description": "Warning message: for this product and this field, there are no images at all, in any language" }, @@ -1697,12 +1698,12 @@ "@category_picker_screen_title": { "description": "Categories picker screen title" }, - "basic_details": "Basic Details", - "product_name": "Product Name", - "add_basic_details_product_name_error": "Please enter the product name", - "brand_name": "Brand name", - "add_basic_details_brand_name_error": "Please enter the brand name", - "quantity": "Quantity and weight", + "basic_details": "Detalls bàsics", + "product_name": "Nom del producte", + "add_basic_details_product_name_error": "Si us plau, introduïu el nom del producte", + "brand_name": "Nom de la marca", + "add_basic_details_brand_name_error": "Si us plau, introduïu el nom de la marca", + "quantity": "Quantitat i pes", "barcode": "Codi de barres", "barcode_barcode": "Codi de barres: {barcode}", "@barcode_barcode": { @@ -1713,20 +1714,20 @@ } } }, - "barcode_invalid_error": "Invalid barcode", - "basic_details_add_success": "Basic details added successfully", - "basic_details_add_error": "Unable to add basic details. Please try again after some time", + "barcode_invalid_error": "Codi de barres invàlid", + "basic_details_add_success": "Els detalls bàsics s'han afegit correctament", + "basic_details_add_error": "No es poden afegir detalls bàsics. Si us plau, torna-ho a provar d'aquí a un temps", "@basic_details_add_error": { "description": "Error message when error occurs while submitting basic details" }, - "confirm_clear": "You're about to clear your entire history: are you sure you want to continue?", + "confirm_clear": "Esteu a punt d'esborrar tot el vostre historial: esteu segurs que voleu continuar?", "@confirm_clear": { "description": "Asking about whether to clear the history list or not" }, - "alert_clear_selected_user_list": "You're about to clear selected items in your history", - "confirm_clear_selected_user_list": "Are you sure you want to continue?", - "alert_select_items_to_clear": "Please select one or more items to clear", - "confirm_clear_user_list": "You're about to clear this list ({name}): are you sure you want to continue?", + "alert_clear_selected_user_list": "Esteu a punt d'esborrar els elements seleccionats del vostre historial", + "confirm_clear_selected_user_list": "Estàs segur que vols continuar?", + "alert_select_items_to_clear": "Seleccioneu un o més elements per esborrar", + "confirm_clear_user_list": "Esteu a punt d'esborrar aquesta llista ({name}): estàs segur que vols continuar?", "@confirm_clear_user_list": { "description": "Asking about whether to clear the list or not", "placeholders": { @@ -1735,7 +1736,7 @@ } } }, - "confirm_delete_user_list": "You're about to delete this list ({name}): are you sure you want to continue?", + "confirm_delete_user_list": "Esteu a punt d'eliminar aquesta llista ({name}): segur que voleu continuar?", "@confirm_delete_user_list": { "description": "Asking about whether to delete the list or not", "placeholders": { @@ -1744,7 +1745,7 @@ } } }, - "importance_label": "{name} importance: {id}", + "importance_label": "{name} importància: {id}", "@importance_label": { "description": "Used when user selects a food preference. example: Vegan importance; mandatory", "placeholders": { @@ -1764,7 +1765,7 @@ "@user_list_all_empty": { "description": "Small message when there are no user lists" }, - "user_list_length": "{count,plural, =0{Empty list} =1{One product} other{{count} products}}", + "user_list_length": "{count,plural, =0{Llista buida} =1{Un producte} other{{count} productes}}", "@user_list_length": { "description": "Length of a user product list", "placeholders": { @@ -1775,7 +1776,7 @@ "@add_list_label": { "description": "Label for the add list button" }, - "open_food_preferences_tooltip": "Edit your food preferences", + "open_food_preferences_tooltip": "Editeu les vostres preferències alimentàries", "@open_food_preferences_tooltip": { "description": "Tooltip (message displayed on long press) to open the user food preferences" }, @@ -1783,19 +1784,19 @@ "@add_photo_button_label": { "description": "Label for the add photo button" }, - "add_packaging_photo_button_label": "Take photos of any packaging/recycling information", + "add_packaging_photo_button_label": "Feu fotos de qualsevol informació d'embalatge/reciclatge", "@add_packaging_photo_button_label": { "description": "Label for the add PACKAGING photo button" }, - "choose_image_source_title": "Choose image source", + "choose_image_source_title": "Trieu la font de la imatge", "@choose_image_source_title": { "description": "Title for the image source chooser" }, - "choose_image_source_body": "Please choose a image source", + "choose_image_source_body": "Si us plau, trieu una font d'imatge", "@choose_image_source_body": { "description": "Body for the image source chooser" }, - "gallery_source_label": "Gallery", + "gallery_source_label": "Galeria", "@gallery_source_label": { "description": "Label for the gallery image source" }, @@ -1803,7 +1804,7 @@ "@share": { "description": "Button label for sharing something on another app. For example sharing the link to a product via Email" }, - "share_product_text": "Have a look at this product on Open Food Facts: {url}", + "share_product_text": "Fes una ullada a aquest producte a Open Food Facts: {url}", "@share_product_text": { "description": "The content which is send, when sharing a product", "placeholders": { @@ -1812,7 +1813,7 @@ } } }, - "share_product_list_text": "Have a look at my list of products on Open Food Facts: {url}", + "share_product_list_text": "Fes una ullada a la meva llista de productes a Open Food Facts: {url}", "@share_product_list_text": { "description": "The content which is send, when sharing a product list", "placeholders": { @@ -1821,45 +1822,45 @@ } } }, - "capture": "Capture New", + "capture": "Nova captura", "@capture": { "description": "Button label for taking a photo" }, - "choose_from_gallery": "Choose from gallery", + "choose_from_gallery": "Triar de la galeria", "@choose_from_gallery": { "description": "Button label for choosing a photo from gallery" }, - "image_upload_queued": "The image will be uploaded in the background as soon as possible.", + "image_upload_queued": "La imatge es penjarà en segon pla tan aviat com sigui possible.", "@image_upload_queued": { "description": "Message when a photo is queued for upload" }, - "background_task_title_full_refresh": "Starting the refresh of all the products locally stored", + "background_task_title_full_refresh": "Començant l'actualització de tots els productes emmagatzemats localment", "@background_task_title_full_refresh": { "description": "Snackbar message when a full refresh is started" }, - "background_task_title_top_n": "Starting the download of the most popular products", + "background_task_title_top_n": "Començant la descàrrega dels productes més populars", "@background_task_title_top_n": { "description": "Snackbar message when a download of the most popular products is started" }, - "expand_nutrition_facts": "Expand nutrition facts table", + "expand_nutrition_facts": "Amplieu la taula de dades nutricionals", "@expand_nutrition_facts": { "description": "Label for expanding nutrition facts table in application setting" }, - "expand_nutrition_facts_body": "Keep the nutrition facts table expanded", - "expand_ingredients": "Expand ingredients", + "expand_nutrition_facts_body": "Manteniu la taula de dades nutricionals ampliada", + "expand_ingredients": "Ampliar els ingredients", "@expand_ingredients": { "description": "Label for expanding nutrition facts table in application setting" }, - "expand_ingredients_body": "Keep the ingredients panel expanded", + "expand_ingredients_body": "Manteniu el panell d'ingredients expandit", "no_internet_connection": "No hi ha connexió a Internet", "@no_internet_connection": { "description": "Message when there is no internet connection" }, - "world_results_label": "Entire world", + "world_results_label": "Món sencer", "@world_results_label": { "description": "Label describing the current source of the results: the entire world. Keep it short" }, - "world_results_action": "Extend your search to the world", + "world_results_action": "Amplieu la vostra cerca al món", "@world_results_action": { "description": "Label for the action button that displays the results from the entire world" }, @@ -1877,34 +1878,34 @@ } } }, - "clipboard_barcode_copied": "Barcode {barcode} copied to the clipboard!", + "clipboard_barcode_copied": "Codi de barres {barcode} copiat al porta-retalls!", "choose_app_language": "Trieu la llengua de l'aplicació", "@choose_app_language": { "description": "Choose Application Language" }, - "help_with_openfoodfacts": "Help with OpenFoodFacts", + "help_with_openfoodfacts": "Ajuda a OpenFoodFacts", "@help_with_openfoodfacts": { "description": "Label for the email title" }, - "product_task_background_schedule": "The product will be updated in the background as soon as possible.", + "product_task_background_schedule": "El producte s'actualitzarà en segon pla tan aviat com sigui possible.", "@product_task_background_schedule": { "description": "Message when a product is scheduled for background update" }, - "no_email_client_available_dialog_title": "No email apps!", + "no_email_client_available_dialog_title": "No hi ha aplicacions de correu electrònic!", "@no_email_client_available_dialog_title": { "description": "Title for the dialog when no email client is installed on the device" }, - "no_email_client_available_dialog_content": "Please send us manually an email to contact@openfoodfacts.org", + "no_email_client_available_dialog_content": "Envieu-nos manualment un correu electrònic a contact@openfoodfacts.org", "@no_email_client_available_dialog_content": { "description": "Content for the dialog when no email client is installed on the device" }, "all_images": "Totes les imatges", - "selected_images": "Selected Images", - "product_card_remove_product_tooltip": "Remove product", + "selected_images": "Imatges seleccionades", + "product_card_remove_product_tooltip": "Eliminar el producte", "@product_card_remove_product_tooltip": { "description": "Tooltip (message visible with a long-press) on a product item in the carousel" }, - "scan_announce_new_barcode": "New barcode scanned: {barcode}", + "scan_announce_new_barcode": "Nou codi de barres escanejat: {barcode}", "@scan_announce_new_barcode": { "description": "Text to pronounce by the Accessibility tool when a new barcode is decoded", "placeholders": { @@ -1914,15 +1915,15 @@ } } }, - "scan_header_clear_button_tooltip": "Remove all products from the carousel", + "scan_header_clear_button_tooltip": "Traieu tots els productes del carrusel", "@scan_header_clear_button_tooltip": { "description": "Tooltip (message visible with a long-press) on the Clear button on top of the scanner" }, - "scan_header_compare_button_invalid_state_tooltip": "Please scan at least two products to compare them", + "scan_header_compare_button_invalid_state_tooltip": "Si us plau, escaneja almenys dos productes per comparar-los", "@scan_header_compare_button_invalid_state_tooltip": { "description": "Tooltip (message visible with a long-press) on the Compare button on top of the scanner, when there is just one product scanned" }, - "scan_header_compare_button_valid_state_tooltip": "Click to compare the products you have scanned", + "scan_header_compare_button_valid_state_tooltip": "Feu clic per comparar els productes que heu escanejat", "@scan_header_compare_button_valid_state_tooltip": { "description": "Tooltip (message visible with a long-press) on the Compare button on top of the scanner, when there is at least two prodiucts" }, @@ -1930,7 +1931,7 @@ "@portion_calculator_description": { "description": "Sort of title that describes the portion calculator." }, - "portion_calculator_result_title": "Nutrition facts for {grams} g (or ml)", + "portion_calculator_result_title": "Dades nutricionals per a {grams} g (o ml)", "@portion_calculator_result_title": { "description": "Title of the results of the portion calculator.", "placeholders": { @@ -1939,15 +1940,15 @@ } } }, - "offline_data": "Offline Data", + "offline_data": "Dades fora de línia", "@offline_data": { "description": "App bar title for the offline data page" }, - "ocr_image_upload_instruction": "Upload an image to automatically extract the information it contains.", + "ocr_image_upload_instruction": "Carregueu una imatge per extreure automàticament la informació que conté.", "@ocr_image_upload_instruction": { "description": "Message shown when there is no image on the OCR extraction page for ingredients or recycling instructions" }, - "upload_image": "Upload Photo", + "upload_image": "Penjar una foto", "@upload_image": { "description": "Message shown on asking to upload image" }, @@ -1959,54 +1960,54 @@ "@word_separator": { "description": "Word separator string. In English, this is a comma followed by a space: ', '" }, - "image_download_error": "Failed to download image", + "image_download_error": "No s'ha pogut baixar la imatge", "@image_download_error": { "description": "Error message, when image download fails" }, - "image_edit_url_error": "Failed to edit image because the image URL was not set.", + "image_edit_url_error": "No s'ha pogut editar la imatge perquè l'URL de la imatge no s'ha definit.", "@image_edit_url_error": { "description": "Error message, when editing image fails, due to missing url." }, - "user_picture_source_remember": "Remember my choice", + "user_picture_source_remember": "Recorda la meva selecció", "@user_picture_source_remember": { "description": "Checkbox label when select a picture source" }, - "user_picture_source_select": "Select each time", + "user_picture_source_select": "Seleccioneu cada cop", "@user_picture_source_select": { "description": "Choice of selecting the picture source each time" }, - "robotoff_continue": "Continue", + "robotoff_continue": "Continuar", "@robotoff_continue": { "description": "Shown when robotoff question are all answered and user wants to continue answering" }, - "robotoff_next_n_questions": "Next {count,plural, =1{question} other{{count} questions}}", + "robotoff_next_n_questions": "Pròxim{count,plural, =1{a pregunta} other{es {count} preguntes}}", "@robotoff_next_n_questions": { "description": "Shown when robotoff question are all answered and user wants to continue answering", "placeholders": { "count": {} } }, - "show_password": "Show Password", + "show_password": "Mostra la contrasenya", "@show_password": { "description": "Show hidden password in password field" }, "app_rating_dialog_title": "Perfecte! Feu que d'altres coneguin la vostra opinió d'aquesta aplicació!", - "app_rating_dialog_positive_action": "Rate the app", - "app_rating_dialog_negative_action": "Later", - "app_rating_dialog_title_enjoying_app": "Are you enjoying this app?", - "app_rating_dialog_title_enjoying_positive_actions": "Yeah!", - "not_really": "Not really", - "app_rating_dialog_title_not_enjoying_app": "We are so sorry to hear that! Could you tell us what happened?", - "edit_packagings_title": "Packaging components", + "app_rating_dialog_positive_action": "Valora l'aplicació", + "app_rating_dialog_negative_action": "Més tard", + "app_rating_dialog_title_enjoying_app": "Estàs gaudint d'aquesta aplicació?", + "app_rating_dialog_title_enjoying_positive_actions": "Sí!", + "not_really": "No ben bé", + "app_rating_dialog_title_not_enjoying_app": "Ens sap molt greu escoltar-ho! Ens podries explicar què ha passat?", + "edit_packagings_title": "Components d'embalatge", "@edit_packagings_title": { "description": "Title of the structured packagings page" }, - "edit_packagings_element_add": "Add a packaging component", + "edit_packagings_element_add": "Afegiu un component d'embalatge", "@edit_packagings_element_add": { "description": "Button label" }, - "edit_packagings_completed": "The packaging is complete", - "edit_packagings_element_title": "Packaging component #{index}", + "edit_packagings_completed": "L'embalatge està complet", + "edit_packagings_element_title": "Component d'embalatge #{index}", "@edit_packagings_element_title": { "description": "Element title. Please do not change the index placeholder", "placeholders": { @@ -2019,15 +2020,15 @@ "@edit_packagings_element_field_units": { "description": "Field label" }, - "edit_packagings_element_hint_units": "Enter the number of packaging units of the same shape and material contained in the product.", + "edit_packagings_element_hint_units": "Introduïu el nombre d'unitats d'embalatge de la mateixa forma i material que conté el producte.", "@edit_packagings_element_hint_units": { "description": "Field verbose hint, more like an info than a text field hint" }, - "edit_packagings_element_field_shape": "Shape", + "edit_packagings_element_field_shape": "Forma", "@edit_packagings_element_field_shape": { "description": "Field label" }, - "edit_packagings_element_hint_shape": "Enter the shape name listed in the recycling instructions if they are available, or select a shape.", + "edit_packagings_element_hint_shape": "Introduïu el nom de la forma que apareix a les instruccions de reciclatge si està disponible o seleccioneu una forma.", "@edit_packagings_element_hint_shape": { "description": "Field verbose hint, more like an info than a text field hint" }, @@ -2039,7 +2040,7 @@ "@edit_packagings_element_field_material": { "description": "Field label" }, - "edit_packagings_element_hint_material": "Enter the specific material if it can be determined (a material code inside a triangle can often be found on packaging parts), or a generic material (for instance plastic or metal) if you are unsure.", + "edit_packagings_element_hint_material": "Introduïu el material específic si es pot determinar (hi sol haver un codi de material dins d'un triangle a les peces d'embalatge) o un material genèric (per exemple, plàstic o metall) si no n'esteu segurs.", "@edit_packagings_element_hint_material": { "description": "Field verbose hint, more like an info than a text field hint" }, @@ -2047,56 +2048,56 @@ "@edit_packagings_element_example_material": { "description": "Text field hint" }, - "edit_packagings_element_field_recycling": "Recycling instruction", + "edit_packagings_element_field_recycling": "Instrucció de reciclatge", "@edit_packagings_element_field_recycling": { "description": "Field label" }, - "edit_packagings_element_hint_recycling": "Enter recycling instructions only if they are listed on the product.", + "edit_packagings_element_hint_recycling": "Introduïu les instruccions de reciclatge només si apareixen al producte.", "@edit_packagings_element_hint_recycling": { "description": "Field verbose hint, more like an info than a text field hint" }, - "edit_packagings_element_example_recycling": "Recycle", + "edit_packagings_element_example_recycling": "Reciclar", "@edit_packagings_element_example_recycling": { "description": "Text field hint" }, - "edit_packagings_element_field_quantity": "Net quantity of product per unit", + "edit_packagings_element_field_quantity": "Quantitat neta de producte per unitat", "@edit_packagings_element_field_quantity": { "description": "Field label" }, - "edit_packagings_element_hint_quantity": "Enter the net weight or net volume and indicate the unit (for example g or ml).", + "edit_packagings_element_hint_quantity": "Introduïu el pes net o el volum net i indiqueu la unitat (per exemple, g o ml).", "@edit_packagings_element_hint_quantity": { "description": "Field verbose hint, more like an info than a text field hint" }, - "edit_packagings_element_field_weight": "Weight of one empty unit (g)", + "edit_packagings_element_field_weight": "Pes d'una unitat buida (g)", "@edit_packagings_element_field_weight": { "description": "Field label" }, - "edit_packagings_element_hint_weight": "Remove any remaining food and wash and dry the packaging part before weighing. If possible, use a scale with 0.1g or 0.01g precision.", + "edit_packagings_element_hint_weight": "Traieu els aliments restants i renteu i assequeu la part de l'embalatge abans de pesar. Si és possible, utilitzeu una escala amb una precisió de 0,1 g o 0,01 g.", "@edit_packagings_element_hint_weight": { "description": "Field verbose hint, more like an info than a text field hint" }, - "background_task_title": "Pending contributions", - "background_task_subtitle": "Your contributions are automatically saved to our server, but not always in real-time.", - "background_task_list_empty": "No Pending Background Tasks", - "background_task_error_server_time_out": "Server timeout", - "background_task_error_no_internet": "Internet connection error. Try later.", - "background_task_operation_unknown": "unknown operation type", - "background_task_operation_details": "detailed changes", - "background_task_operation_image": "photo upload", - "background_task_operation_refresh": "refresh delayed after photo upload", - "background_task_run_started": "started", - "background_task_run_not_started": "not started yet", - "background_task_run_to_be_deleted": "to be deleted", - "background_task_question_stop": "Do you want to stop that task ASAP?", - "feed_back": "Feedback", + "background_task_title": "Contribucions pendents", + "background_task_subtitle": "Les teves contribucions es guarden automàticament al nostre servidor, però no sempre en temps real.", + "background_task_list_empty": "No hi ha tasques de fons pendents", + "background_task_error_server_time_out": "Temps d'espera del servidor", + "background_task_error_no_internet": "Error de connexió a Internet. Prova-ho més tard.", + "background_task_operation_unknown": "tipus d'operació desconegut", + "background_task_operation_details": "canvis detallats", + "background_task_operation_image": "càrrega de fotos", + "background_task_operation_refresh": "actualització retardada després de pujar la foto", + "background_task_run_started": "iniciada", + "background_task_run_not_started": "sense iniciar", + "background_task_run_to_be_deleted": "per ser suprimit", + "background_task_question_stop": "Vols aturar aquesta tasca el més aviat possible?", + "feed_back": "Suggeriments", "undo": "Desfés", - "copy_email_to_clip_board": "Copy email to clipboard", + "copy_email_to_clip_board": "Copiar correu electrònic al porta-retalls", "@copy_email_to_clip_board": { "description": "Button: Copy the email adress to the clipboard. Shown when an automatic opening of an email application is not possible" }, - "please_send_us_an_email_to": "Please send us manually an email to", - "email_copied_to_clip_board": "Email copied to clipboard!", - "select_accent_color": "Select Accent Color", + "please_send_us_an_email_to": "Envieu-nos manualment un correu electrònic a", + "email_copied_to_clip_board": "S'ha copiat el correu electrònic al porta-retalls!", + "select_accent_color": "Seleccioneu Color d'accent", "@select_accent_color": { "description": "Accent Color for the application in AMOLED mode." }, @@ -2108,15 +2109,15 @@ "@color_blue": { "description": "Color Blue" }, - "color_cyan": "Cyan", + "color_cyan": "Cian", "@color_cyan": { "description": "Color Cyan" }, - "color_green": "Green", + "color_green": "Verd", "@color_green": { "description": "Color Green" }, - "color_light_brown": "Default", + "color_light_brown": "Per defecte", "@color_light_brown": { "description": "Color Light Brown, Default Open Food Facts Color" }, @@ -2136,11 +2137,11 @@ "@color_red": { "description": "Color Red" }, - "color_rust": "Rust", + "color_rust": "Rovell", "@color_rust": { "description": "Color Rust" }, - "color_teal": "Teal", + "color_teal": "Turquesa fosc", "@color_teal": { "description": "Color Teal" }, @@ -2160,11 +2161,11 @@ "@contrast_low": { "description": "Low Contrast Text Color" }, - "product_loader_not_found_title": "Product not found!", + "product_loader_not_found_title": "Producte no trobat!", "@product_loader_not_found_title": { "description": "When fetching a product opened via a link and it doesn't exist" }, - "product_loader_not_found_message": "A product with the following barcode doesn't exist in our database: {barcode}", + "product_loader_not_found_message": "Un producte amb el codi de barres següent no existeix a la nostra base de dades: {barcode}", "@product_loader_not_found_message": { "description": "When fetching a product opened via a link, it doesn't exist", "placeholders": { @@ -2173,31 +2174,31 @@ } } }, - "product_loader_network_error_title": "No internet connection!", + "product_loader_network_error_title": "No hi ha conexió a internet!", "@product_loader_network_error_title": { "description": "When fetching a product opened via a link and there is no connection" }, - "product_loader_network_error_message": "Please check that your smartphone is on a WiFi network or has mobile data enabled", + "product_loader_network_error_message": "Si us plau, comproveu que el vostre telèfon estigui connectat a una xarxa WiFi o que tingui activades les dades mòbils", "@product_loader_network_error_message": { "description": "When fetching a product opened via a link and there is no connection" }, - "page_not_found_title": "Page not found!", + "page_not_found_title": "No s'ha pogut trobar la pàgina!", "@page_not_found_title": { "description": "Title for a page not found (when an URL is not recognized)" }, - "page_not_found_button": "Go back to the homepage", + "page_not_found_button": "Torna a la pàgina d'inici", "@page_not_found_button": { "description": "Button to go back to the homepage" }, - "download_data": "Download data", + "download_data": "Descarregar dades", "@download_data": { "description": "App bar title for the download data page" }, - "download_top_products": "Download the top 1000 products in your country for instant scanning", + "download_top_products": "Baixeu els 1000 millors productes del vostre país per a l'escaneig instantani", "@download_top_products": { "description": "Download the top 1000 products in your country for instant scanning" }, - "download_top_n_products": "Download the top {count,plural, other{{count} products}} in your country for instant scanning", + "download_top_n_products": "Descarrega el{count,plural, other{/s {count} producte/s més popular/s}} al teu país per a l'escaneig instantani", "@download_top_n_products": { "placeholders": { "count": { @@ -2205,11 +2206,11 @@ } } }, - "download_in_progress": "Downloading data\nThis may take a while", + "download_in_progress": "Baixant dades\nAixò pot trigar una estona", "@download_in_progress": { "description": "Download in progress" }, - "downloaded_products": "{num} products added", + "downloaded_products": "{num} productes afegits", "@downloaded_products": { "description": "text to show when products added", "placeholders": { @@ -2218,23 +2219,23 @@ } } }, - "update_offline_data": "Update offline product data", + "update_offline_data": "Actualitza les dades del producte fora de línia", "@update_offline_data": { "description": "List tile title for the update offline data page" }, - "update_local_database_sub": "Update the local product database with the latest data from Open Food Facts", + "update_local_database_sub": "Actualitzeu la base de dades de productes locals amb les dades més recents d'Open Food Facts", "@update_local_database_sub": { "description": "Update the local product database with the latest data from server" }, - "clear_local_database": "Clear offline product data", + "clear_local_database": "Esborra les dades del producte fora de línia", "@clear_local_database": { "description": "List tile title for the clear local database page" }, - "clear_local_database_sub": "Clear all local product data from your app to free up space", + "clear_local_database_sub": "Esborra totes les dades de productes locals de la teva aplicació per alliberar espai", "@clear_local_database_sub": { "description": "Clear all local product data from your app to free up space" }, - "deleted_products": "{num} products deleted", + "deleted_products": "{num} productes suprimits", "@deleted_products": { "description": "text to show when products are deleted from local databse", "placeholders": { @@ -2247,19 +2248,19 @@ "@loading": { "description": "Loading…" }, - "know_more": "Know More", + "know_more": "Més informació", "@know_more": { "description": "Know More" }, - "offline_data_desc": "Click to know more about offline data", + "offline_data_desc": "Feu clic per obtenir més informació sobre les dades fora de línia", "@offline_data_desc": { "description": "Click to know more about offline data" }, - "offline_product_data_title": "Offline product data", + "offline_product_data_title": "Dades de producte fora de línia", "@offline_product_data_title": { "description": "Offline Product Data" }, - "available_for_download": "{num} products available for immediate scaning", + "available_for_download": "{num} productes disponibles per a l'exploració immediata", "@available_for_download": { "description": "text to show details of products available for download", "placeholders": { diff --git a/packages/smooth_app/lib/l10n/app_ce.arb b/packages/smooth_app/lib/l10n/app_ce.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_ce.arb +++ b/packages/smooth_app/lib/l10n/app_ce.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_co.arb b/packages/smooth_app/lib/l10n/app_co.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_co.arb +++ b/packages/smooth_app/lib/l10n/app_co.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_cs.arb b/packages/smooth_app/lib/l10n/app_cs.arb index f211656f015..ad2ff52e4e2 100644 --- a/packages/smooth_app/lib/l10n/app_cs.arb +++ b/packages/smooth_app/lib/l10n/app_cs.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Klepnutím zobrazíte více informací…", + "tap_for_more": "Klepnutím zobrazíte více informací…", "@Product": {}, "product": "Produkt", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Neznámá úroveň průmyslového zpracování potraviny", "new_product_title_pictures": "Pojďme udělat pár fotek!", "new_product_title_misc": "A pár základních údajů…", + "hey_incomplete_product_message": "Klepnutím odpovězte na 3 otázky HNED k výpočtu Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Fotografie nutričních údajů byla nahrána", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Informační fotografie o recyklaci", @@ -764,35 +765,35 @@ "percent": {} } }, - "plural_ago_days": "{count,plural, one {} few {před {count} dny} many {před {count} dny}=1{před jedním dnem} other{před {count} dny}}", + "plural_ago_days": "{count,plural, =1{před {count} dny} other{před {count} dny}}", "@plural_ago_days": { "description": "Cached results from: x days ago", "placeholders": { "count": {} } }, - "plural_ago_hours": "{count,plural, one {} few {před {count} hodinami} many {před {count} hodinami}=1{před hodinou} other{před {count} hodinami}}", + "plural_ago_hours": "{count,plural, =1{před {count} hodinami} many{před {count} hodinami} other{před {count} hodinami}}", "@plural_ago_hours": { "description": "Cached results from: x hours ago", "placeholders": { "count": {} } }, - "plural_ago_minutes": "{count,plural, one {} few {před {count} minutami} many {před {count} minutami}=0{před méně než minutou} =1{před minutou} other{před {count} minutami}}", + "plural_ago_minutes": "{count,plural, =0{před méně než minutou} =1{před minutou} many{před {count} minutami} other{před {count} minutami}}", "@plural_ago_minutes": { "description": "Cached results from: x minutes ago", "placeholders": { "count": {} } }, - "plural_ago_months": "{count,plural, one {} few {před {count} měsíci} many {před {count} měsíci}=1{před měsícem} other{před {count} měsíci}}", + "plural_ago_months": "{count,plural, =1{před měsícem} other{před {count} měsíci}}", "@plural_ago_months": { "description": "Cached results from: x months ago", "placeholders": { "count": {} } }, - "plural_ago_weeks": "před {count,plural, one {} few {{count} týdny} many {před {count} týdny}=1{týdnem} other{{count} týdny}}", + "plural_ago_weeks": "před {count,plural, =1{týdnem} other{{count} týdny}}", "@plural_ago_weeks": { "description": "Cached results from: x weeks ago", "placeholders": { @@ -1764,7 +1765,7 @@ "@user_list_all_empty": { "description": "Small message when there are no user lists" }, - "user_list_length": "{count,plural, few {{count} produkty} many {{count} produktů}=0{Prázdný seznam} =1{1 produkt} other{{count} produktů}}", + "user_list_length": "{count,plural, =0{Prázdný seznam} =1{1 produkt} other{{count} produktů}}", "@user_list_length": { "description": "Length of a user product list", "placeholders": { @@ -1979,7 +1980,7 @@ "@robotoff_continue": { "description": "Shown when robotoff question are all answered and user wants to continue answering" }, - "robotoff_next_n_questions": "Další {count,plural, one {} few {{count} otázky} many {{count} otázek}=1{otázka} other{{count} otázek}}", + "robotoff_next_n_questions": "Další {count,plural, =1{otázka} other{{count} otázek}}", "@robotoff_next_n_questions": { "description": "Shown when robotoff question are all answered and user wants to continue answering", "placeholders": { diff --git a/packages/smooth_app/lib/l10n/app_cv.arb b/packages/smooth_app/lib/l10n/app_cv.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_cv.arb +++ b/packages/smooth_app/lib/l10n/app_cv.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_cy.arb b/packages/smooth_app/lib/l10n/app_cy.arb index a9efc128a6f..cf855d81b84 100644 --- a/packages/smooth_app/lib/l10n/app_cy.arb +++ b/packages/smooth_app/lib/l10n/app_cy.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Gynnyrch", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_da.arb b/packages/smooth_app/lib/l10n/app_da.arb index 88a78a509b4..a1f782a7242 100644 --- a/packages/smooth_app/lib/l10n/app_da.arb +++ b/packages/smooth_app/lib/l10n/app_da.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Produkt", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Fødevareforarbejdningsniveau ukendt", "new_product_title_pictures": "Lad os tage nogle fotos!", "new_product_title_misc": "Og nogle basisdata…", + "hey_incomplete_product_message": "Tryk for at besvare 3 spørgsmål NU for at beregne Nutri-Score, Øko-Score samt Ultrabehandling (NOVA)!", "nutritional_facts_photo_uploaded": "Næringsfaktafoto uploadet", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Genbrugsoplysningsfoto", @@ -1904,7 +1905,7 @@ "@product_card_remove_product_tooltip": { "description": "Tooltip (message visible with a long-press) on a product item in the carousel" }, - "scan_announce_new_barcode": "New barcode scanned: {barcode}", + "scan_announce_new_barcode": "Ny stregkode skannet: {barcode}", "@scan_announce_new_barcode": { "description": "Text to pronounce by the Accessibility tool when a new barcode is decoded", "placeholders": { @@ -2078,7 +2079,7 @@ "background_task_title": "Afventende bidrag", "background_task_subtitle": "Bidragene auto-gemmes på vores server (dog ikke altid i realtid).", "background_task_list_empty": "Ingen afventende baggrundsopgaver", - "background_task_error_server_time_out": "Server timeout", + "background_task_error_server_time_out": "Server-timeout", "background_task_error_no_internet": "Internetforbindelsesfejl. Forsøg senere.", "background_task_operation_unknown": "ukendt operationstype", "background_task_operation_details": "detaljerede ændringer", diff --git a/packages/smooth_app/lib/l10n/app_de.arb b/packages/smooth_app/lib/l10n/app_de.arb index f94bde2777a..ddad5cc40c2 100644 --- a/packages/smooth_app/lib/l10n/app_de.arb +++ b/packages/smooth_app/lib/l10n/app_de.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Antippen, um mehr Infos anzuzeigen …", + "tap_for_more": "Antippen, um mehr Infos anzuzeigen …", "@Product": {}, "product": "Produkt", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Grad der Lebensmittelverarbeitung unbekannt", "new_product_title_pictures": "Lassen Sie uns ein paar Fotos aufnehmen!", "new_product_title_misc": "Und einige grundlegende Daten …", + "hey_incomplete_product_message": "Tippen Sie JETZT auf die Antwort auf 3 Fragen, um Nutri-Score, Eco-Score und Ultra-Processing (NOVA) zu berechnen!", "nutritional_facts_photo_uploaded": "Foto mit Nährwertangaben hochgeladen", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Foto mit Informationen zum Recycling", diff --git a/packages/smooth_app/lib/l10n/app_el.arb b/packages/smooth_app/lib/l10n/app_el.arb index a2de5b99f05..7e26b5aaa20 100644 --- a/packages/smooth_app/lib/l10n/app_el.arb +++ b/packages/smooth_app/lib/l10n/app_el.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Προϊόν", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Η φωτογραφία διατροφικών στοιχείων μεταφορτώθηκε", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Φωτογραφία πληροφοριών ανακύκλωσης", @@ -1365,7 +1366,7 @@ "@user_list_popup_rename": { "description": "Short label of a 'rename list' popup" }, - "user_list_name_hint": "Η λίστα μου", + "user_list_name_hint": "My list", "@user_list_name_hint": { "description": "Hint of a user list name text-field in a 'user list' dialog" }, diff --git a/packages/smooth_app/lib/l10n/app_en.arb b/packages/smooth_app/lib/l10n/app_en.arb index 18c3d011ae8..d557b811703 100644 --- a/packages/smooth_app/lib/l10n/app_en.arb +++ b/packages/smooth_app/lib/l10n/app_en.arb @@ -424,6 +424,10 @@ "@history_navbar_label": { "description": "BottomNavigationBarLabel: For the history and compare mode" }, + "list_navbar_label": "Lists", + "@list_navbar_label": { + "description": "BottomNavigationBarLabel: For the lists" + }, "category": "Filter by category", "@category": { "description": "From a product list, there's a category filter: this is its title" @@ -449,7 +453,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -534,6 +538,14 @@ "@crop_page_action_local": { "description": "Action being performed on the crop page" }, + "crop_page_action_local_failed_title": "Oops… there's something with your photo!", + "@crop_page_action_local_title": { + "description": "The save of the picture locally failed - error dialog message" + }, + "crop_page_action_local_failed_message": "We are unable to process the image locally, before sending it to our server. Please try again later or contact-us if the issue persists.", + "@crop_page_action_local_message": { + "description": "The save of the picture locally failed - error dialog message" + }, "crop_page_too_small_image_title": "The image is too small!", "@crop_page_too_small_image_title": { "description": "Title of a dialog warning the user that the image is too small for upload" @@ -744,6 +756,10 @@ "@product_removed_history": { "description": "Product got removed from history" }, + "product_removed_list": "Product removed from list", + "@product_removed_list": { + "description": "Product got removed from list" + }, "product_could_not_remove": "Could not remove product", "@product_could_not_remove": { "description": "Could not remove product from a list" @@ -1276,9 +1292,9 @@ "edit_product_form_item_exit_confirmation": "Do you want to save your changes before leaving this page?", "edit_product_form_item_exit_confirmation_positive_button": "Save changes", "edit_product_form_item_exit_confirmation_negative_button": "Discard changes", - "edit_product_form_item_ingredients_title": "Ingredients & Origins", + "edit_product_form_item_ingredients_title": "Ingredients", "@edit_product_form_item_ingredients_title": { - "description": "Product edition - Ingredients - Title" + "description": "Product edition - Ingredients - Title (note: this section was previously called Ingredients & Origins)" }, "edit_product_form_item_add_valid_item_tooltip": "Add", "edit_product_form_item_add_invalid_item_tooltip": "Please enter a text first", @@ -1346,7 +1362,7 @@ "@user_list_button_new": { "description": "Short label of a 'create a new list' button" }, - "user_list_empty_label": "No list available yet,\nplease start by creating one", + "user_list_empty_label": "No list available yet, please start by creating one", "@user_list_empty_label": { "description": "Content displayed when there is no list" }, @@ -1572,6 +1588,10 @@ "@dev_mode_scan_scan_half_image": { "description": "Scan mode - Scan half image" }, + "search_history_item_edit_tooltip": "Reuse and edit this search", + "@search_history_item_edit_tooltip": { + "description": "A tooltip to explain the Pen button near a search term -> it allows to reuse the item" + }, "product_search_no_more_results": "You've downloaded all the {totalSize} products.", "@product_search_no_more_results": { "description": "Product search list - No more results available", @@ -1720,6 +1740,10 @@ "@basic_details_add_error": { "description": "Error message when error occurs while submitting basic details" }, + "clear_search": "Clear your search", + "@confirm_clearclear_search": { + "description": "Tooltip to explain that the X button clears the content of the search" + }, "confirm_clear": "You're about to clear your entire history: are you sure you want to continue?", "@confirm_clear": { "description": "Asking about whether to clear the history list or not" @@ -1765,6 +1789,10 @@ "@user_list_all_empty": { "description": "Small message when there are no user lists" }, + "product_list_select": "Select a list", + "@product_list_select": { + "description": "Top title for the selection of a list" + }, "user_list_length": "{count,plural, =0{Empty list} =1{One product} other{{count} products}}", "@user_list_length": { "description": "Length of a user product list", @@ -1927,10 +1955,30 @@ "@scan_header_compare_button_valid_state_tooltip": { "description": "Tooltip (message visible with a long-press) on the Compare button on top of the scanner, when there is at least two prodiucts" }, - "portion_calculator_description": "Calculate nutrition facts for a specific quantity", + "portion_calculator_description": "Calculate nutrition facts for a specific quantity:", "@portion_calculator_description": { "description": "Sort of title that describes the portion calculator." }, + "portion_calculator_hint": "Quantity in", + "@portion_calculator_hint": { + "description": "Hint to show when a quantity is empty in the portion calculator." + }, + "portion_calculator_accessibility": "Input a quantity to calculate nutrition facts", + "@portion_calculator_accessibility": { + "description": "Hint for the acessibility to explain to enter a quantity." + }, + "portion_calculator_error": "Please enter a quantity between {min} and {max} g", + "@portion_calculator_error": { + "description": "Error message to explain that the quantity is invalid.", + "placeholders": { + "min": { + "type": "int" + }, + "max": { + "type": "int" + } + } + }, "portion_calculator_result_title": "Nutrition facts for {grams} g (or ml)", "@portion_calculator_result_title": { "description": "Title of the results of the portion calculator.", @@ -2268,5 +2316,9 @@ "type": "int" } } + }, + "country_selector_title": "Select your country:", + "@country_selector_title": { + "description": "Label written as the title of the dialog to select the user country" } -} \ No newline at end of file +} diff --git a/packages/smooth_app/lib/l10n/app_eo.arb b/packages/smooth_app/lib/l10n/app_eo.arb index 9075b5354e3..f4ef6d2b9f8 100644 --- a/packages/smooth_app/lib/l10n/app_eo.arb +++ b/packages/smooth_app/lib/l10n/app_eo.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Produkto", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_es.arb b/packages/smooth_app/lib/l10n/app_es.arb index 9cbd268bd97..fb5432b3be9 100644 --- a/packages/smooth_app/lib/l10n/app_es.arb +++ b/packages/smooth_app/lib/l10n/app_es.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Pulsa para ver más información…", + "tap_for_more": "Pulsa para ver más información…", "@Product": {}, "product": "Producto", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Nivel de procesamiento de alimentos desconocido", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Se ha subido la foto con información nutricional", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Foto de información de reciclaje", diff --git a/packages/smooth_app/lib/l10n/app_et.arb b/packages/smooth_app/lib/l10n/app_et.arb index f496cccb853..14269e50056 100644 --- a/packages/smooth_app/lib/l10n/app_et.arb +++ b/packages/smooth_app/lib/l10n/app_et.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Toode", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_eu.arb b/packages/smooth_app/lib/l10n/app_eu.arb index d7199b7b138..fc7fe09c80f 100644 --- a/packages/smooth_app/lib/l10n/app_eu.arb +++ b/packages/smooth_app/lib/l10n/app_eu.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Sakatu informazio gehiago ikusteko…", + "tap_for_more": "Sakatu informazio gehiago ikusteko…", "@Product": {}, "product": "Produktua", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Sakatu 3 galderei erantzuteko ORAIN eta Nutri-Score, Eco-Score eta Ultra-prozesatuak (NOVA) kalkulatzeko!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", @@ -1365,7 +1366,7 @@ "@user_list_popup_rename": { "description": "Short label of a 'rename list' popup" }, - "user_list_name_hint": "My list", + "user_list_name_hint": "Nire zerrenda", "@user_list_name_hint": { "description": "Hint of a user list name text-field in a 'user list' dialog" }, diff --git a/packages/smooth_app/lib/l10n/app_fa.arb b/packages/smooth_app/lib/l10n/app_fa.arb index 476e9d9d4cc..854bbf14f53 100644 --- a/packages/smooth_app/lib/l10n/app_fa.arb +++ b/packages/smooth_app/lib/l10n/app_fa.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "محصول", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_fi.arb b/packages/smooth_app/lib/l10n/app_fi.arb index 76f5b50ac4f..ef666d5dc80 100644 --- a/packages/smooth_app/lib/l10n/app_fi.arb +++ b/packages/smooth_app/lib/l10n/app_fi.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Lisätietoja…", + "tap_for_more": "Lisätietoja…", "@Product": {}, "product": "Tuote", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Ruoan jalostusaste tuntematon", "new_product_title_pictures": "Otetaan muutamia kuvia!", "new_product_title_misc": "Ja joitakin perustietoja…", + "hey_incomplete_product_message": "Napauta vastataksesi kolmeen kysymykseen NYT laskeaksesi Nutri-Scoren, Eco-Scoren ja Ultrajalostuksen (NOVA)!", "nutritional_facts_photo_uploaded": "Ravintosisältökuva ladattu", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Kierrätyskuva", diff --git a/packages/smooth_app/lib/l10n/app_fil.arb b/packages/smooth_app/lib/l10n/app_fil.arb index a202fa53b6c..a3db2e6d3bd 100644 --- a/packages/smooth_app/lib/l10n/app_fil.arb +++ b/packages/smooth_app/lib/l10n/app_fil.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Produkto", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_fo.arb b/packages/smooth_app/lib/l10n/app_fo.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_fo.arb +++ b/packages/smooth_app/lib/l10n/app_fo.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_fr.arb b/packages/smooth_app/lib/l10n/app_fr.arb index e78434530ba..e26d5de4184 100644 --- a/packages/smooth_app/lib/l10n/app_fr.arb +++ b/packages/smooth_app/lib/l10n/app_fr.arb @@ -209,7 +209,7 @@ "@sign_up_page_action_doing_it": { "description": "Progress indicator dialog during the actual signing up process" }, - "sign_up_page_action_ok": "Toutes nos félicitations! Votre compte vient d'être créé.", + "sign_up_page_action_ok": "Toutes nos félicitations ! Votre compte vient d'être créé.", "sign_up_page_display_name_hint": "Nom", "sign_up_page_display_name_error_empty": "Veuillez saisir le nom d'affichage que vous souhaitez utiliser", "sign_up_page_email_hint": "Courriel", @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Appuyez pour plus d'informations...", + "tap_for_more": "Appuyez pour plus d'informations...", "@Product": {}, "product": "Produit", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Degré de transformation des aliments inconnu", "new_product_title_pictures": "Prenons quelques photos !", "new_product_title_misc": "Et quelques données de base…", + "hey_incomplete_product_message": "Appuyez pour répondre MAINTENANT à 3 questions pour calculer le Nutri-Score, l'Eco-Score et le niveau d'ultra-transformation NOVA.", "nutritional_facts_photo_uploaded": "Photo des informations nutritionnelles téléchargée", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Photo des informations de recyclage", @@ -1345,7 +1346,7 @@ "@user_list_button_new": { "description": "Short label of a 'create a new list' button" }, - "user_list_empty_label": "Aucune liste disponible pour le moment,\nveuillez commencer par en créer une", + "user_list_empty_label": "Aucune liste disponible pour le moment, veuillez commencer par en créer une", "@user_list_empty_label": { "description": "Content displayed when there is no list" }, diff --git a/packages/smooth_app/lib/l10n/app_ga.arb b/packages/smooth_app/lib/l10n/app_ga.arb index 4b8a4f4d722..77070967d7d 100644 --- a/packages/smooth_app/lib/l10n/app_ga.arb +++ b/packages/smooth_app/lib/l10n/app_ga.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Táirge", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_gd.arb b/packages/smooth_app/lib/l10n/app_gd.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_gd.arb +++ b/packages/smooth_app/lib/l10n/app_gd.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_gl.arb b/packages/smooth_app/lib/l10n/app_gl.arb index 0f07a29cd99..e63fda8e43c 100644 --- a/packages/smooth_app/lib/l10n/app_gl.arb +++ b/packages/smooth_app/lib/l10n/app_gl.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Produto", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_gu.arb b/packages/smooth_app/lib/l10n/app_gu.arb index 1c0fd7ca9da..1a54dc20fa9 100644 --- a/packages/smooth_app/lib/l10n/app_gu.arb +++ b/packages/smooth_app/lib/l10n/app_gu.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_ha.arb b/packages/smooth_app/lib/l10n/app_ha.arb index 97a15adb5b6..6befc94c55a 100644 --- a/packages/smooth_app/lib/l10n/app_ha.arb +++ b/packages/smooth_app/lib/l10n/app_ha.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_he.arb b/packages/smooth_app/lib/l10n/app_he.arb index d3c68c1b7ce..9374b90f415 100644 --- a/packages/smooth_app/lib/l10n/app_he.arb +++ b/packages/smooth_app/lib/l10n/app_he.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "נגיעה לצפייה פרטים נוספים…", + "tap_for_more": "נגיעה לצפייה פרטים נוספים…", "@Product": {}, "product": "מוצר", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "רמת עיבוד המזון אינה ידועה", "new_product_title_pictures": "בואו נצלם קצת!", "new_product_title_misc": "וכמה נתונים בסיסיים…", + "hey_incomplete_product_message": "נא לגעת כאן כדי לענות על 3 שאלות כעת ולחשב את ה־Nutri-Score,‏ Eco-Score ואולטרא עיבוד (NOVA) כעת!", "nutritional_facts_photo_uploaded": "תמונת הפירוט התזונתי נשלחה", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "תמונת פרטי המיחזור", diff --git a/packages/smooth_app/lib/l10n/app_hi.arb b/packages/smooth_app/lib/l10n/app_hi.arb index 5cd2c7d4bb6..c450b108a99 100644 --- a/packages/smooth_app/lib/l10n/app_hi.arb +++ b/packages/smooth_app/lib/l10n/app_hi.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_hr.arb b/packages/smooth_app/lib/l10n/app_hr.arb index 6ef4adaeb03..66e03614a19 100644 --- a/packages/smooth_app/lib/l10n/app_hr.arb +++ b/packages/smooth_app/lib/l10n/app_hr.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Proizvod", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_ht.arb b/packages/smooth_app/lib/l10n/app_ht.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_ht.arb +++ b/packages/smooth_app/lib/l10n/app_ht.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_hu.arb b/packages/smooth_app/lib/l10n/app_hu.arb index 5de4f064100..24affa54a68 100644 --- a/packages/smooth_app/lib/l10n/app_hu.arb +++ b/packages/smooth_app/lib/l10n/app_hu.arb @@ -258,7 +258,7 @@ "sign_up_page_producer_error_empty": "Kérjük, adjon meg egy termelő vagy egy márkanevet", "sign_up_page_subscribe_checkbox": "Szeretnék feliratkozni az Open Food Facts hírlevélre (bármikor leiratkozhat róla)", "sign_up_page_user_name_already_used": "A felhasználónév már létezik, kérjük válassz másik felhasználónevet.", - "sign_up_page_email_already_exists": "already exists, login to the account or try with another email.", + "sign_up_page_email_already_exists": "már létezik, jelentkezzen be a fiókba, vagy próbálja meg másik e-mail címmel.", "sign_up_page_provide_valid_email": "Kérem adjon meg valós email címet.", "@Settings": {}, "settingsTitle": "Beállítások", @@ -290,11 +290,11 @@ "@support": { "description": "Button label: Opens a pop up window where all ways to get support are shown" }, - "support_join_slack": "Ask for help in our Slack channel", + "support_join_slack": "Kérjen segítséget Slack csatornánkon.", "support_via_forum": "Kérj segítséget a fórumonkon", "support_via_email": "Küldj nekünk emailt", "support_via_email_include_logs_dialog_title": "Elküldi az alkalmazásnaplókat?", - "support_via_email_include_logs_dialog_body": "Do you wish to include application logs in attachment to your email?", + "support_via_email_include_logs_dialog_body": "Szeretné csatolni a jelentkezési naplókat az e-mail mellékleteként?", "termsOfUse": "Felhasználási feltételek (ToU) (angolul)", "@termsOfUse": {}, "about_this_app": "Az alkalmazásról", @@ -340,9 +340,9 @@ "@contribute_translate_text": {}, "contribute_translate_text_2": "A fordítás a projekt egyik legfontosabb feladatai közé tartozik", "@contribute_translate_text_2": {}, - "contribute_share_header": "Share Open Food Facts with your friends", + "contribute_share_header": "Ossza meg barátaival az Open Food Facts-t.", "@contribute_share_header": {}, - "contribute_share_content": "I wanted to let you know about the app I've been using, Open Food Facts, which allows you to get the health and environmental impacts of your food, in a personalized way. It works by scanning the barcodes on the packaging. Finally it's free, does not require registration, and you can even help increase the number of products decyphered. Here's the link to get it for your phone: https://openfoodfacts.app", + "contribute_share_content": "Szerettem volna, ha tudsz az általam használt Open Food Facts nevű alkalmazásról, amely lehetővé teszi, hogy személyre szabott módon megismerd az ételek egészségügyi és környezeti hatásait. A csomagoláson található vonalkódok beolvasásával működik. Végre ingyenes, nem igényel regisztrációt, és még segíthetsz is növelni a megfejtett termékek számát. Itt a link, hogy beszerezd a telefonodra: https://openfoodfacts.app", "@contribute_share_content": { "description": "Content that will be shared, don't forget to include the URL" }, @@ -350,11 +350,11 @@ "@tap_to_answer": { "description": "Button label 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." }, - "tap_to_answer_hint": "Tap here to answer questions about this product", + "tap_to_answer_hint": "Koppintson ide a termékkel kapcsolatos kérdések megválaszolásához", "@tap_to_answer_hint": { "description": "Hint for accessibility readers to answer Robotoff questions." }, - "robotoff_questions_loading_hint": "Please wait while questions about this product are loaded", + "robotoff_questions_loading_hint": "Kérjük, várjon, amíg a termékkel kapcsolatos kérdések betöltődnek", "@robotoff_questions_loading_hint": { "description": "Hint for accessibility readers while Robotoff questions are loaded" }, @@ -362,21 +362,21 @@ "@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": "Segítsen az élelmiszerek átláthatóságának javításában és kapjon jutalmat", "@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." }, - "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", - "question_no_button_accessibility_value": "Answer with no", - "question_skip_button_accessibility_value": "Skip this question", - "tap_to_edit_search": "Tap to edit search", + "question_sign_in_text": "Jelentkezzen be az Open Food Facts fiókjába, hogy jóváírást kapjon hozzájárulásaiért", + "question_yes_button_accessibility_value": "Válaszoljon igennel", + "question_no_button_accessibility_value": "Válaszoljon nemmel", + "question_skip_button_accessibility_value": "Ezt a kérdést kihagyhatja", + "tap_to_edit_search": "Koppintson a keresés szerkesztéséhez", "@Personal preferences": {}, "myPreferences": "Saját beállítások", "@myPreferences": { "description": "Page title: Page where the ranking preferences can be changed" }, - "account_create_message": "Create your account and join the Open Food Facts community to help build food knowledge all over the world!", + "account_create_message": "Hozzon létre fiókot, és csatlakozzon az Open Food Facts közösséghez, hogy segítsen az élelmiszerekkel kapcsolatos ismeretek bővítésében az egész világon!", "@account_create_message": { "description": "The Message to be displayed if the user does not have an account and wants to contribute" }, @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Termék", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Az élelmiszer-feldolgozottság szintje ismeretlen", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_hy.arb b/packages/smooth_app/lib/l10n/app_hy.arb index 5759693b9af..443aa185c90 100644 --- a/packages/smooth_app/lib/l10n/app_hy.arb +++ b/packages/smooth_app/lib/l10n/app_hy.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_id.arb b/packages/smooth_app/lib/l10n/app_id.arb index 4404b2ccbc2..9fa6d421d1d 100644 --- a/packages/smooth_app/lib/l10n/app_id.arb +++ b/packages/smooth_app/lib/l10n/app_id.arb @@ -27,7 +27,7 @@ "go_back_to_top": "Kembali ke atas", "save": "menyimpan ", "save_confirmation": "Apa Anda yakin ingin menyimpan?", - "skip": "Skip", + "skip": "Lewati", "cancel": "Batalkan", "@cancel": {}, "ignore": "Abaikan", @@ -40,7 +40,7 @@ "@no": {}, "stop": "Berhenti", "@stop": {}, - "finish": "Finish", + "finish": "Selesai", "@finish": {}, "calculate": "Hitung", "@calculate": { @@ -286,7 +286,7 @@ "description": "Button label: Opens a pop up window where all contributors of this app are shown" }, "contributors": "Kontributor", - "support": "Support", + "support": "Dukungan", "@support": { "description": "Button label: Opens a pop up window where all ways to get support are shown" }, @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Ketuk untuk lihat info selengkapnya…", + "tap_for_more": "Ketuk untuk lihat info selengkapnya…", "@Product": {}, "product": "Produk", "@product": {}, @@ -534,11 +534,11 @@ "@crop_page_action_local": { "description": "Action being performed on the crop page" }, - "crop_page_too_small_image_title": "The image is too small!", + "crop_page_too_small_image_title": "Gambar terlalu kecil!", "@crop_page_too_small_image_title": { "description": "Title of a dialog warning the user that the image is too small for upload" }, - "crop_page_too_small_image_message": "The minimum size in pixels for picture upload is {expectedMinWidth}x{expectedMinHeight}. The current picture is {actualWidth}x{actualHeight}.", + "crop_page_too_small_image_message": "Ukuran minimum piksel untuk unggahan gambar adalah {expectedMinWidth}x{expectedMinHeight}. Gambar ini {actualHeight}×{actualWidth}.", "@crop_page_too_small_image_message": { "description": "Message of a dialog warning the user that the image is too small for upload", "placeholders": { @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Tingkat pemrosesan makanan tidak diketahui", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Ketuk untuk menjawab 3 pertanyaan SEKARANG untuk menghitung Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Foto informasi nilai gizi telah diunggah", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Foto informasi daur ulang", @@ -1365,7 +1366,7 @@ "@user_list_popup_rename": { "description": "Short label of a 'rename list' popup" }, - "user_list_name_hint": "My list", + "user_list_name_hint": "Daftar saya", "@user_list_name_hint": { "description": "Hint of a user list name text-field in a 'user list' dialog" }, @@ -1904,7 +1905,7 @@ "@product_card_remove_product_tooltip": { "description": "Tooltip (message visible with a long-press) on a product item in the carousel" }, - "scan_announce_new_barcode": "New barcode scanned: {barcode}", + "scan_announce_new_barcode": "Kode batang baru dipindai: {barcode}", "@scan_announce_new_barcode": { "description": "Text to pronounce by the Accessibility tool when a new barcode is decoded", "placeholders": { @@ -2078,7 +2079,7 @@ "background_task_title": "Pending contributions", "background_task_subtitle": "Your contributions are automatically saved to our server, but not always in real-time.", "background_task_list_empty": "No Pending Background Tasks", - "background_task_error_server_time_out": "Server timeout", + "background_task_error_server_time_out": "Batas waktu server", "background_task_error_no_internet": "Internet connection error. Try later.", "background_task_operation_unknown": "unknown operation type", "background_task_operation_details": "detailed changes", diff --git a/packages/smooth_app/lib/l10n/app_ii.arb b/packages/smooth_app/lib/l10n/app_ii.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_ii.arb +++ b/packages/smooth_app/lib/l10n/app_ii.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_is.arb b/packages/smooth_app/lib/l10n/app_is.arb index 5d2c5b665e9..18bc59d83b2 100644 --- a/packages/smooth_app/lib/l10n/app_is.arb +++ b/packages/smooth_app/lib/l10n/app_is.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_it.arb b/packages/smooth_app/lib/l10n/app_it.arb index 3f719b6980b..4fa9732d464 100644 --- a/packages/smooth_app/lib/l10n/app_it.arb +++ b/packages/smooth_app/lib/l10n/app_it.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tocca per visualizzare altre info…", + "tap_for_more": "Tocca per visualizzare altre info…", "@Product": {}, "product": "Prodotto", "@product": {}, @@ -534,11 +534,11 @@ "@crop_page_action_local": { "description": "Action being performed on the crop page" }, - "crop_page_too_small_image_title": "The image is too small!", + "crop_page_too_small_image_title": "L'immagine è troppo piccola!", "@crop_page_too_small_image_title": { "description": "Title of a dialog warning the user that the image is too small for upload" }, - "crop_page_too_small_image_message": "The minimum size in pixels for picture upload is {expectedMinWidth}x{expectedMinHeight}. The current picture is {actualWidth}x{actualHeight}.", + "crop_page_too_small_image_message": "La dimensione minima in pixel per il caricamento dell'immagine è {expectedMinWidth}x{expectedMinHeight}. L'immagine corrente è {actualWidth}x{actualHeight}.", "@crop_page_too_small_image_message": { "description": "Message of a dialog warning the user that the image is too small for upload", "placeholders": { @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Livello di trasformazione alimentare sconosciuto", "new_product_title_pictures": "Scattiamo delle foto!", "new_product_title_misc": "E dei dati essenziali…", + "hey_incomplete_product_message": "Tocca ORA per rispondere a 3 domande e calcolare il Nutri-Score, l'Eco-Score e l'Ultra-trasformazione (NOVA)!", "nutritional_facts_photo_uploaded": "Foto dei valori nutrizionali caricata", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Foto delle istruzioni di riciclaggio", @@ -1904,7 +1905,7 @@ "@product_card_remove_product_tooltip": { "description": "Tooltip (message visible with a long-press) on a product item in the carousel" }, - "scan_announce_new_barcode": "New barcode scanned: {barcode}", + "scan_announce_new_barcode": "Nuovo codice a barre scansionato: {barcode}", "@scan_announce_new_barcode": { "description": "Text to pronounce by the Accessibility tool when a new barcode is decoded", "placeholders": { @@ -2078,7 +2079,7 @@ "background_task_title": "Contributi in sospeso", "background_task_subtitle": "I tuoi contributi sono salvati automaticamente sul nostro server, ma non sono sempre in tempo reale.", "background_task_list_empty": "Nessuna Attività in Background in Sospeso", - "background_task_error_server_time_out": "Server timeout", + "background_task_error_server_time_out": "Timeout del server", "background_task_error_no_internet": "Errore di connessione a Internet. Riprova più tardi.", "background_task_operation_unknown": "tipo di operazione sconosciuto", "background_task_operation_details": "modifiche dettagliate", diff --git a/packages/smooth_app/lib/l10n/app_iu.arb b/packages/smooth_app/lib/l10n/app_iu.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_iu.arb +++ b/packages/smooth_app/lib/l10n/app_iu.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_ja.arb b/packages/smooth_app/lib/l10n/app_ja.arb index 339449564e4..081378a0b0c 100644 --- a/packages/smooth_app/lib/l10n/app_ja.arb +++ b/packages/smooth_app/lib/l10n/app_ja.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "詳細情報を表示するにはタップしてください…", + "tap_for_more": "詳細情報を表示するにはタップしてください…", "@Product": {}, "product": "製品", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "食品加工レベル不明", "new_product_title_pictures": "写真を撮りましょう!", "new_product_title_misc": "そしていくつかの基本データ…", + "hey_incomplete_product_message": "今すぐタップして 3 つの質問に答えて、ニュートリ スコア、エコ スコア、およびウルトラ プロセッシング (NOVA) を計算してください。", "nutritional_facts_photo_uploaded": "栄養成分表の画像がアップロードされました", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "リサイクル情報の画像", diff --git a/packages/smooth_app/lib/l10n/app_jv.arb b/packages/smooth_app/lib/l10n/app_jv.arb index b90bc0d66f1..071c838f7ea 100644 --- a/packages/smooth_app/lib/l10n/app_jv.arb +++ b/packages/smooth_app/lib/l10n/app_jv.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_ka.arb b/packages/smooth_app/lib/l10n/app_ka.arb index dc32b55d3e8..b18f77400b4 100644 --- a/packages/smooth_app/lib/l10n/app_ka.arb +++ b/packages/smooth_app/lib/l10n/app_ka.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_kk.arb b/packages/smooth_app/lib/l10n/app_kk.arb index bf97e835499..3e56f7a5afa 100644 --- a/packages/smooth_app/lib/l10n/app_kk.arb +++ b/packages/smooth_app/lib/l10n/app_kk.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Өнім", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_km.arb b/packages/smooth_app/lib/l10n/app_km.arb index d5d6b87d75f..7a9748a8e7c 100644 --- a/packages/smooth_app/lib/l10n/app_km.arb +++ b/packages/smooth_app/lib/l10n/app_km.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_kn.arb b/packages/smooth_app/lib/l10n/app_kn.arb index 2a469e4d909..a2aa4b5dc0d 100644 --- a/packages/smooth_app/lib/l10n/app_kn.arb +++ b/packages/smooth_app/lib/l10n/app_kn.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_ko.arb b/packages/smooth_app/lib/l10n/app_ko.arb index c912f92da69..7cf553e6eed 100644 --- a/packages/smooth_app/lib/l10n/app_ko.arb +++ b/packages/smooth_app/lib/l10n/app_ko.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "생성물", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "식품 가공 수준을 알 수 없음", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "영양 성분 사진을 올렸습니다", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "재활용 정보 사진", @@ -1365,7 +1366,7 @@ "@user_list_popup_rename": { "description": "Short label of a 'rename list' popup" }, - "user_list_name_hint": "내 목록", + "user_list_name_hint": "My list", "@user_list_name_hint": { "description": "Hint of a user list name text-field in a 'user list' dialog" }, diff --git a/packages/smooth_app/lib/l10n/app_ku.arb b/packages/smooth_app/lib/l10n/app_ku.arb index e37ab14ed01..c1443ec6ccb 100644 --- a/packages/smooth_app/lib/l10n/app_ku.arb +++ b/packages/smooth_app/lib/l10n/app_ku.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Berhem", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_kw.arb b/packages/smooth_app/lib/l10n/app_kw.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_kw.arb +++ b/packages/smooth_app/lib/l10n/app_kw.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_ky.arb b/packages/smooth_app/lib/l10n/app_ky.arb index f02292e6044..e37e5bbe8bd 100644 --- a/packages/smooth_app/lib/l10n/app_ky.arb +++ b/packages/smooth_app/lib/l10n/app_ky.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_la.arb b/packages/smooth_app/lib/l10n/app_la.arb index 78af0a7b2e7..8c000c3e66e 100644 --- a/packages/smooth_app/lib/l10n/app_la.arb +++ b/packages/smooth_app/lib/l10n/app_la.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_lb.arb b/packages/smooth_app/lib/l10n/app_lb.arb index 10b6f4c6062..e321eb28e28 100644 --- a/packages/smooth_app/lib/l10n/app_lb.arb +++ b/packages/smooth_app/lib/l10n/app_lb.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Produkt", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_lo.arb b/packages/smooth_app/lib/l10n/app_lo.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_lo.arb +++ b/packages/smooth_app/lib/l10n/app_lo.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_lt.arb b/packages/smooth_app/lib/l10n/app_lt.arb index 8281272b949..28e728b0862 100644 --- a/packages/smooth_app/lib/l10n/app_lt.arb +++ b/packages/smooth_app/lib/l10n/app_lt.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Bakstelėkite, kad pamatytumėte daugiau informacijos…", + "tap_for_more": "Bakstelėkite, kad pamatytumėte daugiau informacijos…", "@Product": {}, "product": "Produktas", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Maisto perdirbimo lygis nežinomas", "new_product_title_pictures": "Nufotografuokime kelias nuotraukas!", "new_product_title_misc": "Ir kai kurie pagrindiniai duomenys…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Įkelta Maistingumo lentelės nuotrauka", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Perdirbimo informacijos nuotrauka", @@ -1365,7 +1366,7 @@ "@user_list_popup_rename": { "description": "Short label of a 'rename list' popup" }, - "user_list_name_hint": "Mano sąrašas", + "user_list_name_hint": "My list", "@user_list_name_hint": { "description": "Hint of a user list name text-field in a 'user list' dialog" }, @@ -1398,11 +1399,11 @@ } } }, - "camera_toggle_camera": "Switch between back and front camera", + "camera_toggle_camera": "Perjungti galinę kamerą į priekinę ir atvirkščiai", "@camera_toggle_camera": { "description": "Explanation for the icon to switch between cameras" }, - "camera_toggle_flash": "Turn ON or OFF the flash of the camera", + "camera_toggle_flash": "Įjungti arba išjungti kameros blykstę", "@camera_toggle_flash": { "description": "Explanation for the icon to turn on/off the flash" }, @@ -1723,9 +1724,9 @@ "@confirm_clear": { "description": "Asking about whether to clear the history list or not" }, - "alert_clear_selected_user_list": "You're about to clear selected items in your history", - "confirm_clear_selected_user_list": "Are you sure you want to continue?", - "alert_select_items_to_clear": "Please select one or more items to clear", + "alert_clear_selected_user_list": "Ketinate išvalyti pasirinktus elementus savo istorijoje", + "confirm_clear_selected_user_list": "Ar tikrai norite tęsti?", + "alert_select_items_to_clear": "Pasirinkite vieną ar kelis elementus išvalyti", "confirm_clear_user_list": "Ketinate išvalyti šį sąrašą ({name}): ar tikrai norite tęsti?", "@confirm_clear_user_list": { "description": "Asking about whether to clear the list or not", diff --git a/packages/smooth_app/lib/l10n/app_lv.arb b/packages/smooth_app/lib/l10n/app_lv.arb index 37de8a1ec63..a24c0ba9c15 100644 --- a/packages/smooth_app/lib/l10n/app_lv.arb +++ b/packages/smooth_app/lib/l10n/app_lv.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Produkts", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_mg.arb b/packages/smooth_app/lib/l10n/app_mg.arb index ea6508f0a8d..0ce3f87b7b2 100644 --- a/packages/smooth_app/lib/l10n/app_mg.arb +++ b/packages/smooth_app/lib/l10n/app_mg.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_mi.arb b/packages/smooth_app/lib/l10n/app_mi.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_mi.arb +++ b/packages/smooth_app/lib/l10n/app_mi.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_ml.arb b/packages/smooth_app/lib/l10n/app_ml.arb index 375f056ea7b..f641b6a7c79 100644 --- a/packages/smooth_app/lib/l10n/app_ml.arb +++ b/packages/smooth_app/lib/l10n/app_ml.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_mn.arb b/packages/smooth_app/lib/l10n/app_mn.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_mn.arb +++ b/packages/smooth_app/lib/l10n/app_mn.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_mr.arb b/packages/smooth_app/lib/l10n/app_mr.arb index 97414839627..6db5ef73b98 100644 --- a/packages/smooth_app/lib/l10n/app_mr.arb +++ b/packages/smooth_app/lib/l10n/app_mr.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "उत्पादन", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_ms.arb b/packages/smooth_app/lib/l10n/app_ms.arb index 65766dd7512..f343ffbc58a 100644 --- a/packages/smooth_app/lib/l10n/app_ms.arb +++ b/packages/smooth_app/lib/l10n/app_ms.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Produk", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Foto fakta nutrisi dimuat naik", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_mt.arb b/packages/smooth_app/lib/l10n/app_mt.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_mt.arb +++ b/packages/smooth_app/lib/l10n/app_mt.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_my.arb b/packages/smooth_app/lib/l10n/app_my.arb index a75b1faca8a..06199aa5e29 100644 --- a/packages/smooth_app/lib/l10n/app_my.arb +++ b/packages/smooth_app/lib/l10n/app_my.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_nb.arb b/packages/smooth_app/lib/l10n/app_nb.arb index 2e0668fe6f7..ea1bf99f674 100644 --- a/packages/smooth_app/lib/l10n/app_nb.arb +++ b/packages/smooth_app/lib/l10n/app_nb.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Produkt", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", @@ -1365,7 +1366,7 @@ "@user_list_popup_rename": { "description": "Short label of a 'rename list' popup" }, - "user_list_name_hint": "Min liste", + "user_list_name_hint": "My list", "@user_list_name_hint": { "description": "Hint of a user list name text-field in a 'user list' dialog" }, diff --git a/packages/smooth_app/lib/l10n/app_ne.arb b/packages/smooth_app/lib/l10n/app_ne.arb index 5b36ceee1fa..4a5a0928649 100644 --- a/packages/smooth_app/lib/l10n/app_ne.arb +++ b/packages/smooth_app/lib/l10n/app_ne.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "उत्पादन", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_nl.arb b/packages/smooth_app/lib/l10n/app_nl.arb index 1e2e0b2a1fb..92e7f757ac7 100644 --- a/packages/smooth_app/lib/l10n/app_nl.arb +++ b/packages/smooth_app/lib/l10n/app_nl.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tik om meer info te zien…", + "tap_for_more": "Tik om meer info te zien…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Voedselverwerkingsniveau onbekend", "new_product_title_pictures": "Laten we wat foto's maken!", "new_product_title_misc": "En enkele basisgegevens…", + "hey_incomplete_product_message": "Tik om 3 vragen NU te beantwoorden om de Nutri-Score, de Eco-Score & de Ultra-verwerking (NOVA) te berekenen!", "nutritional_facts_photo_uploaded": "Foto van voedingswaarden geüpload", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Foto van recyclen", @@ -1979,7 +1980,7 @@ "@robotoff_continue": { "description": "Shown when robotoff question are all answered and user wants to continue answering" }, - "robotoff_next_n_questions": "Volgende {count,plural, one {}=1{vraag} other{{count} vragen}}", + "robotoff_next_n_questions": "Volgende {count,plural, =1{vraag} other{{count} vragen}}", "@robotoff_next_n_questions": { "description": "Shown when robotoff question are all answered and user wants to continue answering", "placeholders": { diff --git a/packages/smooth_app/lib/l10n/app_nn.arb b/packages/smooth_app/lib/l10n/app_nn.arb index 950ae26e0cd..6d56596c024 100644 --- a/packages/smooth_app/lib/l10n/app_nn.arb +++ b/packages/smooth_app/lib/l10n/app_nn.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Produkt", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_no.arb b/packages/smooth_app/lib/l10n/app_no.arb index 950ae26e0cd..6d56596c024 100644 --- a/packages/smooth_app/lib/l10n/app_no.arb +++ b/packages/smooth_app/lib/l10n/app_no.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Produkt", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_nr.arb b/packages/smooth_app/lib/l10n/app_nr.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_nr.arb +++ b/packages/smooth_app/lib/l10n/app_nr.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_oc.arb b/packages/smooth_app/lib/l10n/app_oc.arb index 5466bdd8b6c..8ad648834c5 100644 --- a/packages/smooth_app/lib/l10n/app_oc.arb +++ b/packages/smooth_app/lib/l10n/app_oc.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Produit", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_or.arb b/packages/smooth_app/lib/l10n/app_or.arb index 0521cbc2582..c8b18860248 100644 --- a/packages/smooth_app/lib/l10n/app_or.arb +++ b/packages/smooth_app/lib/l10n/app_or.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_pa.arb b/packages/smooth_app/lib/l10n/app_pa.arb index b8d9686c2dd..b2b1d9d4468 100644 --- a/packages/smooth_app/lib/l10n/app_pa.arb +++ b/packages/smooth_app/lib/l10n/app_pa.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_pl.arb b/packages/smooth_app/lib/l10n/app_pl.arb index 22d1d8fa8ce..ace9fdf2451 100644 --- a/packages/smooth_app/lib/l10n/app_pl.arb +++ b/packages/smooth_app/lib/l10n/app_pl.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Kliknij, aby wyświetlić więcej informacji…", + "tap_for_more": "Kliknij, aby wyświetlić więcej informacji…", "@Product": {}, "product": "Produkt", "@product": {}, @@ -534,11 +534,11 @@ "@crop_page_action_local": { "description": "Action being performed on the crop page" }, - "crop_page_too_small_image_title": "The image is too small!", + "crop_page_too_small_image_title": "Obraz jest za mały!", "@crop_page_too_small_image_title": { "description": "Title of a dialog warning the user that the image is too small for upload" }, - "crop_page_too_small_image_message": "The minimum size in pixels for picture upload is {expectedMinWidth}x{expectedMinHeight}. The current picture is {actualWidth}x{actualHeight}.", + "crop_page_too_small_image_message": "Minimalny rozmiar obrazu w pikselach wynosi: {expectedMinWidth}x{expectedMinHeight}. Obecne wymiary obrazu wynoszą: {actualWidth}x{actualHeight}.", "@crop_page_too_small_image_message": { "description": "Message of a dialog warning the user that the image is too small for upload", "placeholders": { @@ -581,12 +581,13 @@ "new_product_subtitle_nutriscore": "Otrzymaj go, wypełniając kategorię żywności oraz wartości odżywcze", "new_product_title_ecoscore": "Obliczanie Eco-Score", "new_product_subtitle_ecoscore": "Otrzymaj go, wypełniając przynajmniej jedną kategorię", - "new_product_additional_ecoscore": "Make Eco-Score computation more precise with origins, packaging & more", - "new_product_title_nova": "Compute the food processing level (NOVA)", + "new_product_additional_ecoscore": "Spraw, by Eko-Wynik był bardziej precyzyjny dzięki pochodzeniu, pakowaniu & więcej", + "new_product_title_nova": "Oblicz stopień przetworzenia jedzenia (NOVA)", "new_product_subtitle_nova": "Otrzymaj go, wypełniając kategorię żywności oraz składniki", "new_product_desc_nova_unknown": "Poziom przetwarzania żywności nieznany", "new_product_title_pictures": "Zróbmy kilka zdjęć!", "new_product_title_misc": "I trochę podstawowych danych…", + "hey_incomplete_product_message": "Dotknij, aby odpowiedzieć na 3 pytania TERAZ, aby obliczyć wskaźnik odżywczości, Eko-wynik & Ultra-przetwarzanie (NOVA)!", "nutritional_facts_photo_uploaded": "Przesłano zdjęcie z listą wartości odżywczych", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Zdjęcie informacji dotyczących Recyklingu", @@ -1398,11 +1399,11 @@ } } }, - "camera_toggle_camera": "Switch between back and front camera", + "camera_toggle_camera": "Przerzuć między tylną a przednią kamerą", "@camera_toggle_camera": { "description": "Explanation for the icon to switch between cameras" }, - "camera_toggle_flash": "Turn ON or OFF the flash of the camera", + "camera_toggle_flash": "Włącz lub wyłącz lampę w kamerze", "@camera_toggle_flash": { "description": "Explanation for the icon to turn on/off the flash" }, @@ -1723,9 +1724,9 @@ "@confirm_clear": { "description": "Asking about whether to clear the history list or not" }, - "alert_clear_selected_user_list": "You're about to clear selected items in your history", - "confirm_clear_selected_user_list": "Are you sure you want to continue?", - "alert_select_items_to_clear": "Please select one or more items to clear", + "alert_clear_selected_user_list": "Za moment wyczyścisz wybrane przedmioty w twojej historii", + "confirm_clear_selected_user_list": "Na pewno chcesz kontynuować?", + "alert_select_items_to_clear": "Proszę wybrać jeden lub więcej przedmiotów do wyczyszczenia", "confirm_clear_user_list": "Zamierzasz wyczyścić tę listę ({name}): czy na pewno chcesz kontynuować?", "@confirm_clear_user_list": { "description": "Asking about whether to clear the list or not", @@ -1833,11 +1834,11 @@ "@image_upload_queued": { "description": "Message when a photo is queued for upload" }, - "background_task_title_full_refresh": "Starting the refresh of all the products locally stored", + "background_task_title_full_refresh": "Rozpoczynanie odświeżania wszystkich lokalnie przechowywanych produktów", "@background_task_title_full_refresh": { "description": "Snackbar message when a full refresh is started" }, - "background_task_title_top_n": "Starting the download of the most popular products", + "background_task_title_top_n": "Rozpoczynanie pobierania najbardziej popularnych produktów", "@background_task_title_top_n": { "description": "Snackbar message when a download of the most popular products is started" }, @@ -1904,7 +1905,7 @@ "@product_card_remove_product_tooltip": { "description": "Tooltip (message visible with a long-press) on a product item in the carousel" }, - "scan_announce_new_barcode": "New barcode scanned: {barcode}", + "scan_announce_new_barcode": "Nowy kod kreskowy: {barcode}", "@scan_announce_new_barcode": { "description": "Text to pronounce by the Accessibility tool when a new barcode is decoded", "placeholders": { @@ -1979,7 +1980,7 @@ "@robotoff_continue": { "description": "Shown when robotoff question are all answered and user wants to continue answering" }, - "robotoff_next_n_questions": "Next {count,plural, =1{question} other{{count} questions}}", + "robotoff_next_n_questions": "Następne {count,plural, =1{pytanie}other{{count} pytania}}", "@robotoff_next_n_questions": { "description": "Shown when robotoff question are all answered and user wants to continue answering", "placeholders": { @@ -2078,7 +2079,7 @@ "background_task_title": "Oczekujące wpisy", "background_task_subtitle": "Twój wkład zapisywany jest automatycznie, jednak wprowadzone zmiany nie będą widoczne natychmiast.", "background_task_list_empty": "Brak oczekujących zadań w tle", - "background_task_error_server_time_out": "Server timeout", + "background_task_error_server_time_out": "Przerwa serwera", "background_task_error_no_internet": "Błąd połączenia internetowego. Spróbuj ponownie później.", "background_task_operation_unknown": "Nieznany typ żądania", "background_task_operation_details": "szczegółowe zmiany", @@ -2197,7 +2198,7 @@ "@download_top_products": { "description": "Download the top 1000 products in your country for instant scanning" }, - "download_top_n_products": "Download the top {count,plural, other{{count} products}} in your country for instant scanning", + "download_top_n_products": "Pobierz topowe {count,plural, one {} few {{count} produkty} many {{count} produkty}other{{count} produkty}} w twoim kraju dla natychmiastowego skanowania", "@download_top_n_products": { "placeholders": { "count": { diff --git a/packages/smooth_app/lib/l10n/app_pt.arb b/packages/smooth_app/lib/l10n/app_pt.arb index b0aa760d41c..f89e3d50b53 100644 --- a/packages/smooth_app/lib/l10n/app_pt.arb +++ b/packages/smooth_app/lib/l10n/app_pt.arb @@ -129,7 +129,7 @@ "@offUtility": { "description": "Description of what a user can use Open Food Facts for." }, - "productDataUtility": "Veja os dados de alimentos relevantes às suas preferências.", + "productDataUtility": "Veja as informações nutricionais relevantes às suas preferências.", "@productDataUtility": { "description": "Description of what a user can use the product data for." }, @@ -350,11 +350,11 @@ "@tap_to_answer": { "description": "Button label 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." }, - "tap_to_answer_hint": "Tap here to answer questions about this product", + "tap_to_answer_hint": "Toque aqui para responder a perguntas sobre este produto", "@tap_to_answer_hint": { "description": "Hint for accessibility readers to answer Robotoff questions." }, - "robotoff_questions_loading_hint": "Please wait while questions about this product are loaded", + "robotoff_questions_loading_hint": "Por favor, aguarde enquanto as perguntas sobre este produto estão sendo carregadas", "@robotoff_questions_loading_hint": { "description": "Hint for accessibility readers while Robotoff questions are loaded" }, @@ -367,9 +367,9 @@ "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." }, "question_sign_in_text": "Entre na sua conta Open Food Facts para ganhar crédito pelas suas contribuições", - "question_yes_button_accessibility_value": "Answer with yes", - "question_no_button_accessibility_value": "Answer with no", - "question_skip_button_accessibility_value": "Skip this question", + "question_yes_button_accessibility_value": "Responda com sim", + "question_no_button_accessibility_value": "Responda com não", + "question_skip_button_accessibility_value": "Pular essa pergunta", "tap_to_edit_search": "Toque para editar pesquisa", "@Personal preferences": {}, "myPreferences": "Minhas preferências", @@ -437,11 +437,11 @@ "@filter": { "description": "A button that opens a menu where you can filter within categories. Juices => Apple juices/Orange juices" }, - "scan": "Scan session", + "scan": "Escanear sessão", "@scan": { "description": "Page title: List type: Products in the scan session" }, - "scan_history": "Scan history", + "scan_history": "Histórico de escaneamento", "@scan_history": { "description": "Page title: List type: Products in the whole scan history" }, @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Clique para ver mais informações…", + "tap_for_more": "Clique para ver mais informações…", "@Product": {}, "product": "Produto", "@product": {}, @@ -502,7 +502,7 @@ "@add_product_information_button_label": {}, "new_product": "Produto Novo", "@new_product": {}, - "new_product_dialog_title": "You have just found a new product!", + "new_product_dialog_title": "Você acabou de encontrar um novo produto!", "@new_product_dialog_title": { "description": "Please keep it short, like 50 characters. Title of the dialog when the user searched for an unknown barcode." }, @@ -510,7 +510,7 @@ "@new_product_leave_message": { "description": "Alert dialog message when a user landed on the 'add new product' page, didn't input anything and tried to leave the page." }, - "new_product_dialog_description": "Please take photos of the packaging to add this product to our common database", + "new_product_dialog_description": "Por favor, fotografe a embalagem para adicionar esse produto à nossa base de dados comum", "@new_product_dialog_description": { "description": "Please keep it short, like less than 100 characters. Explanatory text of the dialog when the user searched for an unknown barcode." }, @@ -522,15 +522,15 @@ }, "confirm_button_label": "Confirmar", "send_image_button_label": "Enviar imagem", - "crop_page_action_saving": "Saving the image…", + "crop_page_action_saving": "Salvando a imagem…", "@crop_page_action_saving": { "description": "Action being performed on the crop page" }, - "crop_page_action_cropping": "Cropping the image…", + "crop_page_action_cropping": "Recortando a imagem…", "@crop_page_action_cropping": { "description": "Action being performed on the crop page" }, - "crop_page_action_local": "Saving a local version…", + "crop_page_action_local": "Salvando versão local…", "@crop_page_action_local": { "description": "Action being performed on the crop page" }, @@ -556,7 +556,7 @@ } } }, - "crop_page_action_server": "Preparing a call to the server…", + "crop_page_action_server": "Preparando uma chamada para o servidor…", "@crop_page_action_server": { "description": "Action being performed on the crop page" }, @@ -576,17 +576,18 @@ "@nutritional_facts_photo_button_label": {}, "nutritional_facts_input_button_label": "Preencher informações nutricionais", "nutritional_facts_added": "Informações nutricionais adicionadas", - "categories_added": "Categories added", - "new_product_title_nutriscore": "Compute the Nutri-Score", - "new_product_subtitle_nutriscore": "Get it by filling the food category and nutritional values", - "new_product_title_ecoscore": "Compute the Eco-Score", - "new_product_subtitle_ecoscore": "Get it by filling at least a category", + "categories_added": "Categorias adicionadas", + "new_product_title_nutriscore": "Calcular o Nutri-Score", + "new_product_subtitle_nutriscore": "Obtenha preenchendo a categoria de alimentos e valores nutricionais", + "new_product_title_ecoscore": "Calcular o Eco-Score", + "new_product_subtitle_ecoscore": "Obtenha preenchendo pelo menos uma categoria", "new_product_additional_ecoscore": "Make Eco-Score computation more precise with origins, packaging & more", "new_product_title_nova": "Compute o nível de processamento alimentar (NOVA)", "new_product_subtitle_nova": "Ganhe em preencher a categoria e ingredientes alimentares", "new_product_desc_nova_unknown": "Nível desconhecido de processamento do alimento", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Toque para responder a 3 perguntas AGORA para avaliar o Nutri-Score, o Eco-Score e o Ultra-processamento (NOVA)!", "nutritional_facts_photo_uploaded": "Foto das informações nutricionais enviada", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Foto de informações sobre reciclagem", diff --git a/packages/smooth_app/lib/l10n/app_qu.arb b/packages/smooth_app/lib/l10n/app_qu.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_qu.arb +++ b/packages/smooth_app/lib/l10n/app_qu.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_rm.arb b/packages/smooth_app/lib/l10n/app_rm.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_rm.arb +++ b/packages/smooth_app/lib/l10n/app_rm.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_ro.arb b/packages/smooth_app/lib/l10n/app_ro.arb index 0c6d9ba8155..dfc01f456f1 100644 --- a/packages/smooth_app/lib/l10n/app_ro.arb +++ b/packages/smooth_app/lib/l10n/app_ro.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Atingeți pentru a vedea mai multe informații…", + "tap_for_more": "Atingeți pentru a vedea mai multe informații…", "@Product": {}, "product": "Produs", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Nivel de procesare a alimentelor necunoscut", "new_product_title_pictures": "Hai să facem niște poze!", "new_product_title_misc": "Și câteva date de bază…", + "hey_incomplete_product_message": "Atingeți pentru a răspunde la 3 întrebări ACUM pentru a calcula Nutri-Score, Eco-Score și Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Fotografie cu informații nutriționale încărcate", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Fotografia cu informații despre reciclare", @@ -1979,7 +1980,7 @@ "@robotoff_continue": { "description": "Shown when robotoff question are all answered and user wants to continue answering" }, - "robotoff_next_n_questions": "Următorul {count,plural, one {} few {{count} întrebări}=1{intrebare} other{{count} întrebări}}", + "robotoff_next_n_questions": "Următorul {count,plural, =1{intrebare} other{{count} întrebări}}", "@robotoff_next_n_questions": { "description": "Shown when robotoff question are all answered and user wants to continue answering", "placeholders": { diff --git a/packages/smooth_app/lib/l10n/app_ru.arb b/packages/smooth_app/lib/l10n/app_ru.arb index 1e803ec9979..90838772dd7 100644 --- a/packages/smooth_app/lib/l10n/app_ru.arb +++ b/packages/smooth_app/lib/l10n/app_ru.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Продукт", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Степень обработки продуктов неизвестна", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Фото информации о пищевой ценности загружено", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Информационное фото по переработке", @@ -1365,7 +1366,7 @@ "@user_list_popup_rename": { "description": "Short label of a 'rename list' popup" }, - "user_list_name_hint": "Мой список", + "user_list_name_hint": "My list", "@user_list_name_hint": { "description": "Hint of a user list name text-field in a 'user list' dialog" }, diff --git a/packages/smooth_app/lib/l10n/app_sa.arb b/packages/smooth_app/lib/l10n/app_sa.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_sa.arb +++ b/packages/smooth_app/lib/l10n/app_sa.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_sc.arb b/packages/smooth_app/lib/l10n/app_sc.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_sc.arb +++ b/packages/smooth_app/lib/l10n/app_sc.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_sd.arb b/packages/smooth_app/lib/l10n/app_sd.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_sd.arb +++ b/packages/smooth_app/lib/l10n/app_sd.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_sg.arb b/packages/smooth_app/lib/l10n/app_sg.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_sg.arb +++ b/packages/smooth_app/lib/l10n/app_sg.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_si.arb b/packages/smooth_app/lib/l10n/app_si.arb index 5748f7c3357..e2923aa377f 100644 --- a/packages/smooth_app/lib/l10n/app_si.arb +++ b/packages/smooth_app/lib/l10n/app_si.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_sk.arb b/packages/smooth_app/lib/l10n/app_sk.arb index 856740ddcec..c07d8df03ed 100644 --- a/packages/smooth_app/lib/l10n/app_sk.arb +++ b/packages/smooth_app/lib/l10n/app_sk.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_sl.arb b/packages/smooth_app/lib/l10n/app_sl.arb index 3824f87f6ef..183db9ebdff 100644 --- a/packages/smooth_app/lib/l10n/app_sl.arb +++ b/packages/smooth_app/lib/l10n/app_sl.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Izdelek", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Neznana stopnja predelave hrane", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Slika hranilnih vrednosti je naložena", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Slika z informacijami o recikliranju", @@ -1365,7 +1366,7 @@ "@user_list_popup_rename": { "description": "Short label of a 'rename list' popup" }, - "user_list_name_hint": "Moj seznam", + "user_list_name_hint": "My list", "@user_list_name_hint": { "description": "Hint of a user list name text-field in a 'user list' dialog" }, diff --git a/packages/smooth_app/lib/l10n/app_sn.arb b/packages/smooth_app/lib/l10n/app_sn.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_sn.arb +++ b/packages/smooth_app/lib/l10n/app_sn.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_so.arb b/packages/smooth_app/lib/l10n/app_so.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_so.arb +++ b/packages/smooth_app/lib/l10n/app_so.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_sq.arb b/packages/smooth_app/lib/l10n/app_sq.arb index d217c13e88b..609aea31a89 100644 --- a/packages/smooth_app/lib/l10n/app_sq.arb +++ b/packages/smooth_app/lib/l10n/app_sq.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Foto e informacionit te riciklimit.", diff --git a/packages/smooth_app/lib/l10n/app_sr.arb b/packages/smooth_app/lib/l10n/app_sr.arb index 2c66709be7e..f3a9f7b77aa 100644 --- a/packages/smooth_app/lib/l10n/app_sr.arb +++ b/packages/smooth_app/lib/l10n/app_sr.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Proizvod", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_ss.arb b/packages/smooth_app/lib/l10n/app_ss.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_ss.arb +++ b/packages/smooth_app/lib/l10n/app_ss.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_st.arb b/packages/smooth_app/lib/l10n/app_st.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_st.arb +++ b/packages/smooth_app/lib/l10n/app_st.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_sv.arb b/packages/smooth_app/lib/l10n/app_sv.arb index 0f0744a365c..a62376405f8 100644 --- a/packages/smooth_app/lib/l10n/app_sv.arb +++ b/packages/smooth_app/lib/l10n/app_sv.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Produkt", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Foto på näringsfakta uppladdat", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Foto på återvinningsinformation", diff --git a/packages/smooth_app/lib/l10n/app_sw.arb b/packages/smooth_app/lib/l10n/app_sw.arb index 2a190f8f80f..caf7fdeb61d 100644 --- a/packages/smooth_app/lib/l10n/app_sw.arb +++ b/packages/smooth_app/lib/l10n/app_sw.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_ta.arb b/packages/smooth_app/lib/l10n/app_ta.arb index c1aa01e1831..ea37c583911 100644 --- a/packages/smooth_app/lib/l10n/app_ta.arb +++ b/packages/smooth_app/lib/l10n/app_ta.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_te.arb b/packages/smooth_app/lib/l10n/app_te.arb index 27753201efc..3a9f3985453 100644 --- a/packages/smooth_app/lib/l10n/app_te.arb +++ b/packages/smooth_app/lib/l10n/app_te.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_tg.arb b/packages/smooth_app/lib/l10n/app_tg.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_tg.arb +++ b/packages/smooth_app/lib/l10n/app_tg.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_th.arb b/packages/smooth_app/lib/l10n/app_th.arb index 03492ee0a92..f7eba16908b 100644 --- a/packages/smooth_app/lib/l10n/app_th.arb +++ b/packages/smooth_app/lib/l10n/app_th.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "สินค้า", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "รูปภาพโภชนาการอาหารอัพโหลดแล้ว", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "ข้อมูลการรีไซเคิล", diff --git a/packages/smooth_app/lib/l10n/app_ti.arb b/packages/smooth_app/lib/l10n/app_ti.arb index 98ad6046ed5..b1fbecb7743 100644 --- a/packages/smooth_app/lib/l10n/app_ti.arb +++ b/packages/smooth_app/lib/l10n/app_ti.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_tl.arb b/packages/smooth_app/lib/l10n/app_tl.arb index b70e6c740cd..7ad6d27cbeb 100644 --- a/packages/smooth_app/lib/l10n/app_tl.arb +++ b/packages/smooth_app/lib/l10n/app_tl.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Produkto", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_tn.arb b/packages/smooth_app/lib/l10n/app_tn.arb index 37ba601ba46..32e067fa9b8 100644 --- a/packages/smooth_app/lib/l10n/app_tn.arb +++ b/packages/smooth_app/lib/l10n/app_tn.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_tr.arb b/packages/smooth_app/lib/l10n/app_tr.arb index 3fa1dda8486..1c7c3d5f7bb 100644 --- a/packages/smooth_app/lib/l10n/app_tr.arb +++ b/packages/smooth_app/lib/l10n/app_tr.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Daha fazla bilgi görmek için dokunun…", + "tap_for_more": "Daha fazla bilgi görmek için dokunun…", "@Product": {}, "product": "Ürün", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Gıda işleme seviyesi bilinmemektedir", "new_product_title_pictures": "Birkaç fotoğraf çekelim!", "new_product_title_misc": "Ve bazı temel veriler…", + "hey_incomplete_product_message": "Nutri-Score, Eco-Score ve Ultra-processing'i (NOVA) hesaplamada ŞİMDİ 3 soruyu yanıtlamak için dokunun!", "nutritional_facts_photo_uploaded": "\"Besin değerleri\" fotoğrafı yüklendi", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Geri dönüşüm bilgisi fotoğrafı", diff --git a/packages/smooth_app/lib/l10n/app_ts.arb b/packages/smooth_app/lib/l10n/app_ts.arb index 37ba601ba46..32e067fa9b8 100644 --- a/packages/smooth_app/lib/l10n/app_ts.arb +++ b/packages/smooth_app/lib/l10n/app_ts.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_tt.arb b/packages/smooth_app/lib/l10n/app_tt.arb index 7ae680c1561..e9528900b07 100644 --- a/packages/smooth_app/lib/l10n/app_tt.arb +++ b/packages/smooth_app/lib/l10n/app_tt.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_tw.arb b/packages/smooth_app/lib/l10n/app_tw.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_tw.arb +++ b/packages/smooth_app/lib/l10n/app_tw.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_ty.arb b/packages/smooth_app/lib/l10n/app_ty.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_ty.arb +++ b/packages/smooth_app/lib/l10n/app_ty.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_ug.arb b/packages/smooth_app/lib/l10n/app_ug.arb index 3bc3f428953..89682ecdb4e 100644 --- a/packages/smooth_app/lib/l10n/app_ug.arb +++ b/packages/smooth_app/lib/l10n/app_ug.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_uk.arb b/packages/smooth_app/lib/l10n/app_uk.arb index 65b906555a2..02b9a235ef1 100644 --- a/packages/smooth_app/lib/l10n/app_uk.arb +++ b/packages/smooth_app/lib/l10n/app_uk.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Натисніть, щоб побачити більше…", + "tap_for_more": "Натисніть, щоб побачити більше…", "@Product": {}, "product": "Продукт", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Рівень обробки їжі є невідомим", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Натисніть, щоб відповісти на 3 запитання ЗАРАЗ для обчислення Nutri-Score, Eco-Score та ультра-обробки (ЗАРАЗ)!", "nutritional_facts_photo_uploaded": "Завантаження фотографій харчових фактів", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Фотографія з інформацією щодо перероблювання", diff --git a/packages/smooth_app/lib/l10n/app_ur.arb b/packages/smooth_app/lib/l10n/app_ur.arb index a5dfd8d2452..308a4c3294f 100644 --- a/packages/smooth_app/lib/l10n/app_ur.arb +++ b/packages/smooth_app/lib/l10n/app_ur.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_uz.arb b/packages/smooth_app/lib/l10n/app_uz.arb index 88210605ab3..82d6e3521c9 100644 --- a/packages/smooth_app/lib/l10n/app_uz.arb +++ b/packages/smooth_app/lib/l10n/app_uz.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_ve.arb b/packages/smooth_app/lib/l10n/app_ve.arb index 37ba601ba46..32e067fa9b8 100644 --- a/packages/smooth_app/lib/l10n/app_ve.arb +++ b/packages/smooth_app/lib/l10n/app_ve.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_vi.arb b/packages/smooth_app/lib/l10n/app_vi.arb index 8fde93be292..cd9bc4cf160 100644 --- a/packages/smooth_app/lib/l10n/app_vi.arb +++ b/packages/smooth_app/lib/l10n/app_vi.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Sản phẩm", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Mức độ chế biến thực phẩm không xác định", "new_product_title_pictures": "Hãy chụp một vài bức ảnh!", "new_product_title_misc": "Và một số dữ liệu cơ bản…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Ảnh thông tin dinh dưỡng đã được tải lên", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Ảnh thông tin tái chế", @@ -1365,7 +1366,7 @@ "@user_list_popup_rename": { "description": "Short label of a 'rename list' popup" }, - "user_list_name_hint": "Danh sách của tôi", + "user_list_name_hint": "My list", "@user_list_name_hint": { "description": "Hint of a user list name text-field in a 'user list' dialog" }, diff --git a/packages/smooth_app/lib/l10n/app_wa.arb b/packages/smooth_app/lib/l10n/app_wa.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_wa.arb +++ b/packages/smooth_app/lib/l10n/app_wa.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_wo.arb b/packages/smooth_app/lib/l10n/app_wo.arb index 37ba601ba46..32e067fa9b8 100644 --- a/packages/smooth_app/lib/l10n/app_wo.arb +++ b/packages/smooth_app/lib/l10n/app_wo.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_xh.arb b/packages/smooth_app/lib/l10n/app_xh.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_xh.arb +++ b/packages/smooth_app/lib/l10n/app_xh.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_yi.arb b/packages/smooth_app/lib/l10n/app_yi.arb index 7998c99244e..6beaff2f392 100644 --- a/packages/smooth_app/lib/l10n/app_yi.arb +++ b/packages/smooth_app/lib/l10n/app_yi.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_yo.arb b/packages/smooth_app/lib/l10n/app_yo.arb index 3d3aab25433..297850dd470 100644 --- a/packages/smooth_app/lib/l10n/app_yo.arb +++ b/packages/smooth_app/lib/l10n/app_yo.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_zh.arb b/packages/smooth_app/lib/l10n/app_zh.arb index 296e03a0917..fbc8f397b93 100644 --- a/packages/smooth_app/lib/l10n/app_zh.arb +++ b/packages/smooth_app/lib/l10n/app_zh.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "產品", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/l10n/app_zu.arb b/packages/smooth_app/lib/l10n/app_zu.arb index 3ac260adef0..c9e2aedf7d1 100644 --- a/packages/smooth_app/lib/l10n/app_zu.arb +++ b/packages/smooth_app/lib/l10n/app_zu.arb @@ -449,7 +449,7 @@ "@search": { "description": "Hint text of a search text input field" }, - "tab_for_more": "Tap to see more info…", + "tap_for_more": "Tap to see more info…", "@Product": {}, "product": "Product", "@product": {}, @@ -587,6 +587,7 @@ "new_product_desc_nova_unknown": "Food processing level unknown", "new_product_title_pictures": "Let's take some pictures!", "new_product_title_misc": "And some basic data…", + "hey_incomplete_product_message": "Tap to answer 3 questions NOW to compute Nutri-Score, Eco-Score & Ultra-processing (NOVA)!", "nutritional_facts_photo_uploaded": "Nutrition facts photo uploaded", "@nutritional_facts_photo_uploaded": {}, "recycling_photo_button_label": "Recycling information photo", diff --git a/packages/smooth_app/lib/pages/all_product_list_page.dart b/packages/smooth_app/lib/pages/all_product_list_page.dart new file mode 100644 index 00000000000..a4e0924d31f --- /dev/null +++ b/packages/smooth_app/lib/pages/all_product_list_page.dart @@ -0,0 +1,77 @@ +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/design_constants.dart'; +import 'package:smooth_app/pages/preferences/user_preferences_list_tile.dart'; +import 'package:smooth_app/pages/product/common/product_query_page_helper.dart'; +import 'package:smooth_app/pages/product_list_user_dialog_helper.dart'; +import 'package:smooth_app/widgets/smooth_app_bar.dart'; +import 'package:smooth_app/widgets/smooth_scaffold.dart'; + +/// Page that lists all product lists. +class AllProductListPage extends StatelessWidget { + const AllProductListPage(); + + @override + Widget build(BuildContext context) { + final LocalDatabase localDatabase = context.watch(); + final DaoProductList daoProductList = DaoProductList(localDatabase); + final List productLists = [ + ProductList.scanSession(), + ProductList.scanHistory(), + ProductList.history(), + ]; + final List userLists = daoProductList.getUserLists(); + for (final String userList in userLists) { + productLists.add(ProductList.user(userList)); + } + final AppLocalizations appLocalizations = AppLocalizations.of(context); + return SmoothScaffold( + appBar: SmoothAppBar(title: Text(appLocalizations.product_list_select)), + body: ListView.builder( + itemCount: productLists.length, + itemBuilder: (final BuildContext context, final int index) { + final ProductList productList = productLists[index]; + return UserPreferencesListTile( + title: Text( + ProductQueryPageHelper.getProductListLabel( + productList, + appLocalizations, + ), + ), + subtitle: FutureBuilder( + future: daoProductList.getLength(productList), + builder: ( + final BuildContext context, + final AsyncSnapshot snapshot, + ) { + if (snapshot.data != null) { + return Text( + appLocalizations.user_list_length(snapshot.data!), + ); + } + return EMPTY_WIDGET; + }, + ), + onTap: () => Navigator.of(context).pop(productList), + onLongPress: () async => ProductListUserDialogHelper(daoProductList) + .showDeleteUserListDialog(context, productList), + ); + }, + ), + floatingActionButton: FloatingActionButton.extended( + onPressed: () async => ProductListUserDialogHelper(daoProductList) + .showCreateUserListDialog(context), + label: Row( + children: [ + const Icon(Icons.add), + Text(appLocalizations.add_list_label), + ], + ), + ), + ); + } +} diff --git a/packages/smooth_app/lib/pages/all_user_product_list_page.dart b/packages/smooth_app/lib/pages/all_user_product_list_page.dart deleted file mode 100644 index 03a4a4e8222..00000000000 --- a/packages/smooth_app/lib/pages/all_user_product_list_page.dart +++ /dev/null @@ -1,143 +0,0 @@ -import 'package:auto_size_text/auto_size_text.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:flutter_svg/flutter_svg.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/design_constants.dart'; -import 'package:smooth_app/helpers/app_helper.dart'; -import 'package:smooth_app/pages/preferences/user_preferences_list_tile.dart'; -import 'package:smooth_app/pages/product/common/product_list_page.dart'; -import 'package:smooth_app/pages/product_list_user_dialog_helper.dart'; -import 'package:smooth_app/themes/constant_icons.dart'; -import 'package:smooth_app/widgets/smooth_app_bar.dart'; -import 'package:smooth_app/widgets/smooth_scaffold.dart'; - -/// Page that lists all user product lists. -class AllUserProductList extends StatelessWidget { - const AllUserProductList(); - - @override - Widget build(BuildContext context) { - final LocalDatabase localDatabase = context.watch(); - final DaoProductList daoProductList = DaoProductList(localDatabase); - return FutureBuilder>( - future: daoProductList.getUserLists(), - builder: ( - final BuildContext context, - final AsyncSnapshot> snapshot, - ) { - if (snapshot.data != null) { - return _AllUserProductListLoaded(snapshot.data!); - } - return const Center(child: CircularProgressIndicator.adaptive()); - }, - ); - } -} - -/// Page that lists all user product lists, with already loaded data. -class _AllUserProductListLoaded extends StatefulWidget { - const _AllUserProductListLoaded(this.userLists); - - final List userLists; - - @override - State<_AllUserProductListLoaded> createState() => - _AllUserProductListLoadedState(); -} - -class _AllUserProductListLoadedState extends State<_AllUserProductListLoaded> { - @override - Widget build(BuildContext context) { - final LocalDatabase localDatabase = context.watch(); - final DaoProductList daoProductList = DaoProductList(localDatabase); - final AppLocalizations appLocalizations = AppLocalizations.of(context); - final ThemeData themeData = Theme.of(context); - final List userLists = widget.userLists; - return SmoothScaffold( - appBar: SmoothAppBar(title: Text(appLocalizations.user_list_all_title)), - body: userLists.isEmpty - ? Center( - child: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SvgPicture.asset( - 'assets/misc/empty-list.svg', - height: MediaQuery.of(context).size.height * .4, - package: AppHelper.APP_PACKAGE, - ), - Padding( - padding: const EdgeInsets.all(SMALL_SPACE), - child: AutoSizeText( - appLocalizations.user_list_all_empty, - style: themeData.textTheme.displayLarge, - textAlign: TextAlign.center, - maxLines: 1, - ), - ), - ], - ), - ), - ) - : ListView.builder( - itemCount: userLists.length, - itemBuilder: (final BuildContext context, final int index) { - final String userList = userLists[index]; - final ProductList productList = ProductList.user(userList); - return UserPreferencesListTile( - title: Text(userList), - subtitle: FutureBuilder( - future: daoProductList.getLength(productList), - builder: ( - final BuildContext context, - final AsyncSnapshot snapshot, - ) { - if (snapshot.data != null) { - return Text( - appLocalizations.user_list_length(snapshot.data!), - ); - } - return EMPTY_WIDGET; - }, - ), - trailing: Icon(ConstantIcons.instance.getForwardIcon()), - onTap: () async { - await daoProductList.get(productList); - if (!mounted) { - return; - } - await Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => - ProductListPage(productList), - ), - ); - setState(() {}); - }, - onLongPress: () async => - ProductListUserDialogHelper(daoProductList) - .showDeleteUserListDialog( - context, - ProductList.user(userList), - ), - ); - }, - ), - floatingActionButton: FloatingActionButton.extended( - onPressed: () async => ProductListUserDialogHelper(daoProductList) - .showCreateUserListDialog(context), - label: Row( - children: [ - const Icon(Icons.add), - Text(appLocalizations.add_list_label), - ], - ), - ), - ); - } -} diff --git a/packages/smooth_app/lib/pages/crop_page.dart b/packages/smooth_app/lib/pages/crop_page.dart index 4e4c7d0342b..599c9636f42 100644 --- a/packages/smooth_app/lib/pages/crop_page.dart +++ b/packages/smooth_app/lib/pages/crop_page.dart @@ -17,6 +17,7 @@ import 'package:smooth_app/database/local_database.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/generic_lib/dialogs/smooth_alert_dialog.dart'; import 'package:smooth_app/generic_lib/loading_dialog.dart'; +import 'package:smooth_app/helpers/analytics_helper.dart'; import 'package:smooth_app/helpers/database_helper.dart'; import 'package:smooth_app/helpers/image_compute_container.dart'; import 'package:smooth_app/helpers/image_field_extension.dart'; @@ -239,7 +240,15 @@ class _CropPageState extends State { maxSize: _screenSize.longestSide, ); setState(() => _progress = appLocalizations.crop_page_action_local); - await saveBmp(file: result, source: cropped); + + try { + await saveBmp(file: result, source: cropped) + .timeout(const Duration(seconds: 10)); + } catch (e, trace) { + AnalyticsHelper.sendException(e, stackTrace: trace); + rethrow; + } + return result; } @@ -387,6 +396,7 @@ class _CropPageState extends State { return true; } } catch (e) { + _showErrorDialog(); return false; } finally { _progress = null; @@ -493,6 +503,20 @@ class _CropPageState extends State { return false; } } + + Future _showErrorDialog() { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + + return showDialog( + context: context, + builder: (BuildContext context) { + return SmoothSimpleErrorAlertDialog( + title: appLocalizations.crop_page_action_local_failed_title, + message: appLocalizations.crop_page_action_local_failed_message, + ); + }, + ); + } } /// Standard icon button for this page. diff --git a/packages/smooth_app/lib/pages/image_crop_page.dart b/packages/smooth_app/lib/pages/image_crop_page.dart index 1ac69d64e53..495f318f9f2 100644 --- a/packages/smooth_app/lib/pages/image_crop_page.dart +++ b/packages/smooth_app/lib/pages/image_crop_page.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:http/http.dart' as http; import 'package:image_picker/image_picker.dart'; @@ -10,8 +11,8 @@ import 'package:provider/provider.dart'; import 'package:smooth_app/data_models/user_preferences.dart'; import 'package:smooth_app/database/dao_int.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; -import 'package:smooth_app/generic_lib/dialogs/smooth_alert_dialog.dart'; import 'package:smooth_app/generic_lib/loading_dialog.dart'; +import 'package:smooth_app/generic_lib/widgets/smooth_bottom_sheet.dart'; import 'package:smooth_app/helpers/camera_helper.dart'; import 'package:smooth_app/helpers/database_helper.dart'; import 'package:smooth_app/pages/crop_page.dart'; @@ -41,49 +42,169 @@ Future _getUserPictureSource( if (source != UserPictureSource.SELECT) { return source; } - final AppLocalizations appLocalizations = AppLocalizations.of(context); - bool? remember = false; - return showDialog( - context: context, - builder: (BuildContext context) => StatefulBuilder( - builder: ( - final BuildContext context, - final void Function(VoidCallback fn) setState, - ) => - SmoothAlertDialog( - title: appLocalizations.choose_image_source_title, - actionsAxis: Axis.vertical, - body: CheckboxListTile( - activeColor: FAIR_GREY_COLOR, - value: remember, - onChanged: (final bool? value) => setState( - () => remember = value, + + return showSmoothModalSheet( + context: context, + builder: (BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + + return SmoothModalSheet( + title: appLocalizations.choose_image_source_title, + closeButton: true, + closeButtonSemanticsOrder: 5.0, + body: const _ImageSourcePicker(), + bodyPadding: const EdgeInsetsDirectional.only( + start: 10.0, + end: MEDIUM_SPACE, + top: LARGE_SPACE, + bottom: MEDIUM_SPACE, + ), + ); + }); +} + +class _ImageSourcePicker extends StatefulWidget { + const _ImageSourcePicker(); + + @override + State<_ImageSourcePicker> createState() => _ImageSourcePickerState(); +} + +class _ImageSourcePickerState extends State<_ImageSourcePicker> { + bool rememberChoice = false; + + @override + Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + final Color primaryColor = Theme.of(context).primaryColor; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: Row( + children: [ + Expanded( + flex: 5, + child: _ImageSourceButton( + semanticsOrder: 2.0, + onPressed: () => _selectSource(UserPictureSource.CAMERA), + label: Text( + appLocalizations.settings_app_camera, + textAlign: TextAlign.center, + ), + icon: const Icon(Icons.camera_alt, size: 30.0), + ), + ), + const Spacer(), + Expanded( + flex: 5, + child: _ImageSourceButton( + onPressed: () => _selectSource(UserPictureSource.GALLERY), + semanticsOrder: 3.0, + label: Text( + appLocalizations.gallery_source_label, + textAlign: TextAlign.center, + ), + icon: const Icon(Icons.image, size: 30.0), + ), + ), + ], + ), ), - title: Text(appLocalizations.user_picture_source_remember), ), - positiveAction: SmoothActionButton( - text: appLocalizations.settings_app_camera, - onPressed: () { - const UserPictureSource result = UserPictureSource.CAMERA; - if (remember == true) { - userPreferences.setUserPictureSource(result); - } - Navigator.pop(context, result); - }, + const SizedBox(height: VERY_LARGE_SPACE), + Semantics( + sortKey: const OrdinalSortKey(4.0), + value: appLocalizations.user_picture_source_remember, + checked: rememberChoice, + excludeSemantics: true, + child: InkWell( + onTap: () => setState(() => rememberChoice = !rememberChoice), + borderRadius: ANGULAR_BORDER_RADIUS, + splashColor: primaryColor.withOpacity(0.2), + child: Row( + children: [ + IgnorePointer( + child: Checkbox.adaptive( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(6.0)), + ), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + value: rememberChoice, + onChanged: (final bool? value) => setState( + () => rememberChoice = value ?? false, + ), + ), + ), + Expanded( + child: Text(appLocalizations.user_picture_source_remember), + ) + ], + ), + ), ), - negativeAction: SmoothActionButton( - text: appLocalizations.gallery_source_label, - onPressed: () { - const UserPictureSource result = UserPictureSource.GALLERY; - if (remember == true) { - userPreferences.setUserPictureSource(result); - } - Navigator.pop(context, result); - }, + ], + ); + } + + void _selectSource(UserPictureSource source) { + if (rememberChoice == true) { + context.read().setUserPictureSource(source); + } + Navigator.pop(context, source); + } +} + +class _ImageSourceButton extends StatelessWidget { + const _ImageSourceButton({ + required this.icon, + required this.label, + required this.onPressed, + this.semanticsOrder, + }); + + final Icon icon; + final Widget label; + final VoidCallback onPressed; + final double? semanticsOrder; + + @override + Widget build(BuildContext context) { + final Color primaryColor = Theme.of(context).primaryColor; + + return Semantics( + sortKey: semanticsOrder != null ? OrdinalSortKey(semanticsOrder!) : null, + child: OutlinedButton( + onPressed: onPressed, + style: ButtonStyle( + side: MaterialStatePropertyAll( + BorderSide(color: primaryColor), + ), + padding: const MaterialStatePropertyAll( + EdgeInsets.symmetric(vertical: LARGE_SPACE), + ), + shape: MaterialStatePropertyAll( + RoundedRectangleBorder( + borderRadius: ROUNDED_BORDER_RADIUS, + side: BorderSide(color: primaryColor), + ), + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + icon, + const SizedBox(height: SMALL_SPACE), + label, + ], ), ), - ), - ); + ); + } } /// Lets the user pick a picture, crop it, and save it. diff --git a/packages/smooth_app/lib/pages/navigator/app_navigator.dart b/packages/smooth_app/lib/pages/navigator/app_navigator.dart index 6e8aa4cabb5..c330a4fd9c7 100644 --- a/packages/smooth_app/lib/pages/navigator/app_navigator.dart +++ b/packages/smooth_app/lib/pages/navigator/app_navigator.dart @@ -35,10 +35,7 @@ class AppNavigator extends InheritedWidget { AppNavigator({ Key? key, required Widget child, - List? observers, - }) : _router = _SmoothGoRouter( - observers: observers, - ), + }) : _router = _SmoothGoRouter(), super(key: key, child: child); // GoRouter is never accessible directly @@ -89,7 +86,11 @@ class AppNavigator extends InheritedWidget { /// One drawback of the implementation is that we never know the base URL of the /// deep link (eg: es.openfoodfacts.org) class _SmoothGoRouter { - _SmoothGoRouter({ + factory _SmoothGoRouter() { + return _singleton; + } + + _SmoothGoRouter._internal({ List? observers, }) { router = GoRouter( @@ -213,6 +214,8 @@ class _SmoothGoRouter { } else { return _openExternalLink(path); } + } else if (path == _ExternalRoutes.MOBILE_APP_DOWNLOAD.path) { + return AppRoutes.HOME; } else if (path != _InternalAppRoutes.HOME_PAGE.path) { return _openExternalLink(path); } @@ -237,6 +240,7 @@ class _SmoothGoRouter { ); } + static final _SmoothGoRouter _singleton = _SmoothGoRouter._internal(); late GoRouter router; // Indicates whether [_initAppLanguage] was already called @@ -319,11 +323,14 @@ enum _InternalAppRoutes { const _InternalAppRoutes(this.path); final String path; +} - @override - String toString() { - return path; - } +enum _ExternalRoutes { + MOBILE_APP_DOWNLOAD('/open-food-facts-mobile-app'); + + const _ExternalRoutes(this.path); + + final String path; } /// A list of internal routes to use with [AppNavigator] @@ -341,21 +348,21 @@ class AppRoutes { bool useHeroAnimation = true, String? heroTag = '', }) => - '/${_InternalAppRoutes.PRODUCT_DETAILS_PAGE}/$barcode' + '/${_InternalAppRoutes.PRODUCT_DETAILS_PAGE.path}/$barcode' '?heroAnimation=$useHeroAnimation' '&heroTag=$heroTag'; // Product loader (= when a product is not in the database) - typical use case: deep links static String PRODUCT_LOADER(String barcode) => - '/${_InternalAppRoutes.PRODUCT_LOADER_PAGE}/$barcode'; + '/${_InternalAppRoutes.PRODUCT_LOADER_PAGE.path}/$barcode'; // Product creator or "add product" feature static String PRODUCT_CREATOR(String barcode) => - '/${_InternalAppRoutes.PRODUCT_CREATOR_PAGE}/$barcode'; + '/${_InternalAppRoutes.PRODUCT_CREATOR_PAGE.path}/$barcode'; // App preferences static String PREFERENCES(PreferencePageType type) => - '/${_InternalAppRoutes.PREFERENCES_PAGE}/${type.name}'; + '/${_InternalAppRoutes.PREFERENCES_PAGE.path}/${type.name}'; // Search view static String get SEARCH => '/${_InternalAppRoutes.SEARCH_PAGE.path}'; diff --git a/packages/smooth_app/lib/pages/onboarding/consent_analytics_page.dart b/packages/smooth_app/lib/pages/onboarding/consent_analytics_page.dart index b0af37a1a99..f82af0c5ade 100644 --- a/packages/smooth_app/lib/pages/onboarding/consent_analytics_page.dart +++ b/packages/smooth_app/lib/pages/onboarding/consent_analytics_page.dart @@ -72,14 +72,14 @@ class ConsentAnalyticsPage extends StatelessWidget { ), ), OnboardingBottomBar( - leftButton: _buildButton( + startButton: _buildButton( context, appLocalizations.refuse_button_label, false, const Color(0xFFA08D84), Colors.white, ), - rightButton: _buildButton( + endButton: _buildButton( context, appLocalizations.authorize_button_label, true, diff --git a/packages/smooth_app/lib/pages/onboarding/country_selector.dart b/packages/smooth_app/lib/pages/onboarding/country_selector.dart index 54befe624f5..32855cacea2 100644 --- a/packages/smooth_app/lib/pages/onboarding/country_selector.dart +++ b/packages/smooth_app/lib/pages/onboarding/country_selector.dart @@ -8,6 +8,7 @@ import 'package:smooth_app/data_models/user_preferences.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/generic_lib/dialogs/smooth_alert_dialog.dart'; import 'package:smooth_app/generic_lib/widgets/smooth_text_form_field.dart'; +import 'package:smooth_app/helpers/keyboard_helper.dart'; import 'package:smooth_app/query/product_query.dart'; /// A selector for selecting user's country. @@ -23,6 +24,7 @@ class CountrySelector extends StatefulWidget { } class _CountrySelectorState extends State { + final TextEditingController _countryController = TextEditingController(); late List _countryList; late Future _initFuture; @@ -55,7 +57,6 @@ class _CountrySelectorState extends State { @override Widget build(BuildContext context) { final AppLocalizations appLocalizations = AppLocalizations.of(context); - final TextEditingController countryController = TextEditingController(); return FutureBuilder( future: _initFuture, builder: (BuildContext context, AsyncSnapshot snapshot) { @@ -80,71 +81,111 @@ class _CountrySelectorState extends State { return StatefulBuilder( builder: (BuildContext context, void Function(VoidCallback fn) setState) { + const double horizontalPadding = 16.0 + SMALL_SPACE; + return SmoothAlertDialog( + contentPadding: const EdgeInsetsDirectional.symmetric( + horizontal: 0.0, + vertical: SMALL_SPACE, + ), body: SizedBox( - height: MediaQuery.of(context).size.height / 2, + height: MediaQuery.of(context).size.height / + (context.keyboardVisible ? 1.0 : 1.5), width: MediaQuery.of(context).size.width, child: Column( children: [ - SmoothTextFormField( - type: TextFieldTypes.PLAIN_TEXT, - prefixIcon: const Icon(Icons.search), - controller: countryController, - onChanged: (String? query) { - setState( - () { - filteredList = _countryList - .where( - (Country item) => - item.name.toLowerCase().contains( - query!.toLowerCase(), - ) || - item.countryCode - .toLowerCase() - .contains( - query.toLowerCase(), - ), - ) - .toList(growable: false); - }, - ); - }, - hintText: appLocalizations.search, + Container( + alignment: AlignmentDirectional.centerStart, + padding: const EdgeInsetsDirectional.only( + start: horizontalPadding - 1.0, + end: horizontalPadding, + top: SMALL_SPACE, + ), + child: Text( + appLocalizations.country_selector_title, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20.0, + ), + ), ), - Expanded( - child: ListView.builder( - itemBuilder: (BuildContext context, int index) { - final Country country = filteredList[index]; - final bool selected = - country == selectedCountry; - return ListTile( - dense: true, - trailing: selected - ? const Icon(Icons.check) - : null, - title: Text( - country.name, - softWrap: false, - overflow: TextOverflow.fade, - style: selected - ? const TextStyle( - fontWeight: FontWeight.bold, - ) - : null, - ), - onTap: () => - Navigator.of(context).pop(country), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: SMALL_SPACE, + vertical: MEDIUM_SPACE, + ), + child: SmoothTextFormField( + type: TextFieldTypes.PLAIN_TEXT, + prefixIcon: const Icon(Icons.search), + controller: _countryController, + onChanged: (String? query) { + setState( + () { + filteredList = _countryList + .where( + (Country item) => + item.name + .toLowerCase() + .contains( + query!.toLowerCase(), + ) || + item.countryCode + .toLowerCase() + .contains( + query.toLowerCase(), + ), + ) + .toList(growable: false); + }, ); }, - itemCount: filteredList.length, - shrinkWrap: true, + hintText: appLocalizations.search, + ), + ), + Expanded( + child: Scrollbar( + child: ListView.separated( + itemBuilder: + (BuildContext context, int index) { + final Country country = filteredList[index]; + final bool selected = + country == selectedCountry; + return ListTile( + dense: true, + contentPadding: + const EdgeInsets.symmetric( + horizontal: horizontalPadding, + ), + trailing: selected + ? const Icon(Icons.check) + : null, + title: _FilterableText( + text: country.name, + filter: _countryController.text, + selected: selected, + ), + onTap: () { + Navigator.of(context).pop(country); + _countryController.clear(); + }, + ); + }, + separatorBuilder: (_, __) => const Divider( + height: 1.0, + ), + itemCount: filteredList.length, + shrinkWrap: true, + ), ), ) ], ), ), positiveAction: SmoothActionButton( - onPressed: () => Navigator.pop(context), + onPressed: () { + Navigator.pop(context); + _countryController.clear(); + }, text: appLocalizations.cancel, ), ); @@ -163,6 +204,9 @@ class _CountrySelectorState extends State { decoration: const BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(10)), ), + padding: const EdgeInsets.symmetric( + vertical: SMALL_SPACE, + ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, @@ -275,4 +319,87 @@ class _CountrySelectorState extends State { }, ); } + + @override + void dispose() { + _countryController.dispose(); + super.dispose(); + } +} + +class _FilterableText extends StatelessWidget { + const _FilterableText({ + required this.text, + required this.filter, + required this.selected, + }); + + final String text; + final String filter; + final bool selected; + + @override + Widget build(BuildContext context) { + final List<(String, TextStyle?)> parts = _getParts( + defaultStyle: TextStyle(fontWeight: selected ? FontWeight.bold : null), + highlightedStyle: TextStyle( + fontWeight: selected ? FontWeight.bold : null, + backgroundColor: Theme.of(context).primaryColor.withOpacity(0.2), + ), + ); + + final TextStyle defaultTextStyle = DefaultTextStyle.of(context).style; + + return Text.rich( + TextSpan( + children: parts.map(((String, TextStyle?) part) { + return TextSpan( + text: part.$1, + style: defaultTextStyle.merge(part.$2), + ); + }).toList(growable: false), + ), + softWrap: false, + overflow: TextOverflow.fade, + ); + } + + /// Returns a List containing parts of the text with the right style + /// according to the [filter] + List<(String, TextStyle?)> _getParts({ + required TextStyle? defaultStyle, + required TextStyle? highlightedStyle, + }) { + final Iterable highlightedParts = + RegExp(filter.toLowerCase()).allMatches( + text.toLowerCase(), + ); + + final List<(String, TextStyle?)> parts = <(String, TextStyle?)>[]; + + if (highlightedParts.isEmpty) { + parts.add((text, defaultStyle)); + } else { + parts + .add((text.substring(0, highlightedParts.first.start), defaultStyle)); + for (int i = 0; i != highlightedParts.length; i++) { + final RegExpMatch subPart = highlightedParts.elementAt(i); + + parts.add( + (text.substring(subPart.start, subPart.end), highlightedStyle), + ); + + if (i < highlightedParts.length - 1) { + parts.add(( + text.substring( + subPart.end, highlightedParts.elementAt(i + 1).start), + defaultStyle + )); + } else if (subPart.end < text.length) { + parts.add((text.substring(subPart.end, text.length), defaultStyle)); + } + } + } + return parts; + } } diff --git a/packages/smooth_app/lib/pages/onboarding/next_button.dart b/packages/smooth_app/lib/pages/onboarding/next_button.dart index d0ba7bdbc86..2f1dce639ad 100644 --- a/packages/smooth_app/lib/pages/onboarding/next_button.dart +++ b/packages/smooth_app/lib/pages/onboarding/next_button.dart @@ -36,7 +36,7 @@ class NextButton extends StatelessWidget { OnboardingFlowNavigator(userPreferences); final OnboardingPage previousPage = currentPage.getPrevPage(); return OnboardingBottomBar( - leftButton: previousPage.isOnboardingNotStarted() + startButton: previousPage.isOnboardingNotStarted() ? null : OnboardingBottomIcon( onPressed: () async => navigator.navigateToPage( @@ -46,11 +46,11 @@ class NextButton extends StatelessWidget { backgroundColor: Colors.white, foregroundColor: Colors.black, icon: ConstantIcons.instance.getBackIcon(), - iconPadding: Platform.isIOS - ? const EdgeInsetsDirectional.only(end: 2.5) + iconPadding: Platform.isIOS || Platform.isMacOS + ? const EdgeInsetsDirectional.only(end: 2.0) : EdgeInsets.zero, ), - rightButton: OnboardingBottomButton( + endButton: OnboardingBottomButton( onPressed: () async { await OnboardingLoader(localDatabase) .runAtNextTime(currentPage, context); diff --git a/packages/smooth_app/lib/pages/onboarding/onboarding_bottom_bar.dart b/packages/smooth_app/lib/pages/onboarding/onboarding_bottom_bar.dart index 1a89d947e3b..ea8042c1934 100644 --- a/packages/smooth_app/lib/pages/onboarding/onboarding_bottom_bar.dart +++ b/packages/smooth_app/lib/pages/onboarding/onboarding_bottom_bar.dart @@ -4,13 +4,13 @@ import 'package:smooth_app/generic_lib/design_constants.dart'; /// Bottom Bar during onboarding. Typical use case: previous/next buttons. class OnboardingBottomBar extends StatelessWidget { const OnboardingBottomBar({ - required this.rightButton, + required this.endButton, required this.backgroundColor, - this.leftButton, + this.startButton, }); - final Widget rightButton; - final Widget? leftButton; + final Widget endButton; + final Widget? startButton; /// Color of the background where we put the buttons. /// @@ -22,7 +22,7 @@ class OnboardingBottomBar extends StatelessWidget { final Size screenSize = MediaQuery.of(context).size; // Side padding is 8% of total width. final double sidePadding = screenSize.width * .08; - final bool hasPrevious = leftButton != null; + final bool hasPrevious = startButton != null; return Column( children: [ Container( @@ -43,8 +43,8 @@ class OnboardingBottomBar extends StatelessWidget { : MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.center, children: [ - if (leftButton != null) leftButton!, - rightButton, + if (startButton != null) startButton!, + endButton, ], ), ), @@ -79,6 +79,10 @@ class OnboardingBottomButton extends StatelessWidget { onPressed: onPressed, style: ButtonStyle( backgroundColor: MaterialStateProperty.all(backgroundColor), + overlayColor: backgroundColor == Colors.white + ? MaterialStateProperty.all( + Theme.of(context).splashColor) + : null, shape: MaterialStateProperty.all( const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(40))), diff --git a/packages/smooth_app/lib/pages/onboarding/permissions_page.dart b/packages/smooth_app/lib/pages/onboarding/permissions_page.dart index a8972e9bdc2..54a72347fd5 100644 --- a/packages/smooth_app/lib/pages/onboarding/permissions_page.dart +++ b/packages/smooth_app/lib/pages/onboarding/permissions_page.dart @@ -12,6 +12,7 @@ import 'package:smooth_app/helpers/permission_helper.dart'; import 'package:smooth_app/helpers/provider_helper.dart'; import 'package:smooth_app/pages/onboarding/onboarding_bottom_bar.dart'; import 'package:smooth_app/pages/onboarding/onboarding_flow_navigator.dart'; +import 'package:smooth_app/widgets/smooth_text.dart'; class PermissionsPage extends StatefulWidget { const PermissionsPage( @@ -80,22 +81,24 @@ class _PermissionsPageState extends State { appLocalizations.permissions_page_body1, maxLines: 2, textAlign: TextAlign.center, + style: WellSpacedTextHelper.TEXT_STYLE_WITH_WELL_SPACED, ), const SizedBox(height: MEDIUM_SPACE), AutoSizeText( appLocalizations.permissions_page_body2, maxLines: 3, textAlign: TextAlign.center, + style: WellSpacedTextHelper.TEXT_STYLE_WITH_WELL_SPACED, ), ], ), ), )), OnboardingBottomBar( - leftButton: _IgnoreButton( + startButton: _IgnoreButton( onPermissionIgnored: () => _moveToNextScreen(context), ), - rightButton: _AskPermissionButton( + endButton: _AskPermissionButton( onPermissionIgnored: () => _moveToNextScreen(context), ), backgroundColor: widget.backgroundColor, diff --git a/packages/smooth_app/lib/pages/onboarding/reinvention_page.dart b/packages/smooth_app/lib/pages/onboarding/reinvention_page.dart index ebd302b0c95..f20492aaa31 100644 --- a/packages/smooth_app/lib/pages/onboarding/reinvention_page.dart +++ b/packages/smooth_app/lib/pages/onboarding/reinvention_page.dart @@ -1,14 +1,17 @@ +import 'dart:async'; import 'dart:io'; import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:rive/rive.dart'; +import 'package:sensors_plus/sensors_plus.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; +import 'package:smooth_app/generic_lib/duration_constants.dart'; import 'package:smooth_app/helpers/app_helper.dart'; import 'package:smooth_app/pages/onboarding/next_button.dart'; import 'package:smooth_app/pages/onboarding/onboarding_flow_navigator.dart'; -import 'package:smooth_app/widgets/smooth_scaffold.dart'; /// Onboarding page: "reinvention" class ReinventionPage extends StatelessWidget { @@ -25,83 +28,171 @@ class ReinventionPage extends StatelessWidget { .displayMedium! .copyWith(fontSize: muchTooBigFontSize); final Size screenSize = MediaQuery.of(context).size; + final double animHeight = 352.0 * screenSize.width / 375.0; - return SmoothScaffold( - backgroundColor: backgroundColor, - brightness: Brightness.dark, - body: Stack( - children: [ - SafeArea( - bottom: false, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Flexible( - flex: 30, - child: Padding( - padding: const EdgeInsets.all(SMALL_SPACE), - child: Center( - child: AutoSizeText( - appLocalizations.onboarding_reinventing_text1, - style: headlineStyle, - maxLines: 3, - textAlign: TextAlign.center, + return SafeArea( + bottom: false, + child: Container( + color: backgroundColor, + child: Stack( + children: [ + Positioned( + left: 0.0, + right: 0.0, + bottom: 0.0, + top: screenSize.height * 0.75, + child: Background( + screenWidth: screenSize.width, + ), + ), + Positioned( + left: 0.0, + right: 0.0, + bottom: 0.0, + child: RepaintBoundary( + child: SizedBox( + width: screenSize.width, + height: animHeight, + child: const RiveAnimation.asset( + 'assets/onboarding/onboarding.riv', + artboard: 'Reinvention', + animations: ['Loop'], + alignment: Alignment.bottomCenter, + ), + ), + ), + ), + Positioned( + top: 0.0, + left: 0.0, + right: 0.0, + bottom: animHeight - 20.0, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Flexible( + flex: 30, + child: Padding( + padding: const EdgeInsets.all(SMALL_SPACE), + child: Center( + child: AutoSizeText( + appLocalizations.onboarding_reinventing_text1, + style: headlineStyle, + maxLines: 3, + textAlign: TextAlign.center, + ), ), ), ), - ), - Flexible( - flex: 15, - child: SvgPicture.asset( - 'assets/onboarding/birthday-cake.svg', - package: AppHelper.APP_PACKAGE, + Flexible( + flex: 15, + child: SvgPicture.asset( + 'assets/onboarding/birthday-cake.svg', + package: AppHelper.APP_PACKAGE, + ), ), - ), - Flexible( - flex: 30, - child: Padding( - padding: const EdgeInsets.all(SMALL_SPACE), - child: Center( - child: AutoSizeText( - appLocalizations.onboarding_reinventing_text2, - style: headlineStyle, - maxLines: 3, - textAlign: TextAlign.center, + Flexible( + flex: 30, + child: Padding( + padding: const EdgeInsets.all(SMALL_SPACE), + child: Center( + child: AutoSizeText( + appLocalizations.onboarding_reinventing_text2, + style: headlineStyle, + maxLines: 3, + textAlign: TextAlign.center, + ), ), ), ), - ), - Flexible( - flex: 25, - child: SvgPicture.asset( - 'assets/onboarding/title.svg', - package: AppHelper.APP_PACKAGE, + Flexible( + flex: 25, + child: SvgPicture.asset( + 'assets/onboarding/title.svg', + package: AppHelper.APP_PACKAGE, + ), ), + ], + ), + ), + Positioned( + bottom: 0, + child: SafeArea( + bottom: !Platform.isIOS, + child: const NextButton( + OnboardingPage.REINVENTION, + backgroundColor: null, + nextKey: Key('nextAfterReinvention'), ), - SvgPicture.asset( - // supposed to be a square or something like that - // at least not too tall - 'assets/onboarding/reinvention.svg', - width: screenSize.width, - package: AppHelper.APP_PACKAGE, - ), - ], + ), ), - ), - Positioned( - bottom: 0, - child: SafeArea( - bottom: !Platform.isIOS, - child: const NextButton( - OnboardingPage.REINVENTION, - backgroundColor: null, - nextKey: Key('nextAfterReinvention'), + ], + ), + ), + ); + } +} + +class Background extends StatefulWidget { + const Background({required this.screenWidth}); + + final double screenWidth; + + @override + State createState() => _BackgroundState(); +} + +class _BackgroundState extends State { + StreamSubscription? _subscription; + double parallax = 0.0; + + @override + void initState() { + super.initState(); + + if (Platform.isAndroid || Platform.isIOS) { + _subscription = gyroscopeEvents.listen((GyroscopeEvent event) { + setState(() => parallax = event.y); + }); + } + } + + @override + Widget build(BuildContext context) { + return SizedBox.expand( + child: RepaintBoundary( + child: Stack( + children: [ + AnimatedPositioned( + bottom: 0.0, + right: (-15.0 * parallax).clamp(-30.0, 0.0), + width: widget.screenWidth * 0.808, + duration: SmoothAnimationsDuration.short, + child: SvgPicture.asset( + 'assets/onboarding/hill_end.svg', + fit: BoxFit.fill, ), ), - ), - ], + AnimatedPositioned( + bottom: 0.0, + left: (-10.0 * parallax).clamp(-20.0, 0.0), + width: widget.screenWidth * 0.855, + duration: SmoothAnimationsDuration.short, + child: SvgPicture.asset( + 'assets/onboarding/hill_start.svg', + fit: BoxFit.fill, + ), + ) + ], + ), ), ); } + + @override + void dispose() { + _subscription?.cancel(); + super.dispose(); + } } diff --git a/packages/smooth_app/lib/pages/onboarding/welcome_page.dart b/packages/smooth_app/lib/pages/onboarding/welcome_page.dart index e7bb6171f36..7850ea51aad 100644 --- a/packages/smooth_app/lib/pages/onboarding/welcome_page.dart +++ b/packages/smooth_app/lib/pages/onboarding/welcome_page.dart @@ -96,7 +96,6 @@ class WelcomePage extends StatelessWidget { ), Padding( padding: const EdgeInsetsDirectional.only( - start: SMALL_SPACE, bottom: VERY_SMALL_SPACE, ), child: Text( diff --git a/packages/smooth_app/lib/pages/page_manager.dart b/packages/smooth_app/lib/pages/page_manager.dart index 59217c49720..f3433647ccc 100644 --- a/packages/smooth_app/lib/pages/page_manager.dart +++ b/packages/smooth_app/lib/pages/page_manager.dart @@ -9,7 +9,7 @@ import 'package:smooth_app/widgets/tab_navigator.dart'; enum BottomNavigationTab { Profile, Scan, - History, + List, } /// Here the different tabs in the bottom navigation bar are taken care of, @@ -27,14 +27,14 @@ class PageManagerState extends State { static const List _pageKeys = [ BottomNavigationTab.Profile, BottomNavigationTab.Scan, - BottomNavigationTab.History, + BottomNavigationTab.List, ]; final Map> _navigatorKeys = >{ BottomNavigationTab.Profile: GlobalKey(), BottomNavigationTab.Scan: GlobalKey(), - BottomNavigationTab.History: GlobalKey(), + BottomNavigationTab.List: GlobalKey(), }; BottomNavigationTab _currentPage = BottomNavigationTab.Scan; @@ -76,7 +76,7 @@ class PageManagerState extends State { final List tabs = [ _buildOffstageNavigator(BottomNavigationTab.Profile), _buildOffstageNavigator(BottomNavigationTab.Scan), - _buildOffstageNavigator(BottomNavigationTab.History), + _buildOffstageNavigator(BottomNavigationTab.List), ]; final UserPreferences userPreferences = context.watch(); @@ -111,8 +111,8 @@ class PageManagerState extends State { label: appLocalizations.scan_navbar_label, ), BottomNavigationBarItem( - icon: const Icon(Icons.history), - label: appLocalizations.history_navbar_label, + icon: const Icon(Icons.list), + label: appLocalizations.list_navbar_label, ), ], ); diff --git a/packages/smooth_app/lib/pages/personalized_ranking_page.dart b/packages/smooth_app/lib/pages/personalized_ranking_page.dart index 78d7b070e9c..df32ddc07a9 100644 --- a/packages/smooth_app/lib/pages/personalized_ranking_page.dart +++ b/packages/smooth_app/lib/pages/personalized_ranking_page.dart @@ -219,10 +219,10 @@ class _PersonalizedRankingPageState extends State Dismissible( direction: DismissDirection.endToStart, background: Container( - alignment: Alignment.centerRight, - margin: const EdgeInsets.symmetric(vertical: 14), + alignment: AlignmentDirectional.centerEnd, + margin: const EdgeInsets.symmetric(vertical: 14.0), color: RED_COLOR, - padding: const EdgeInsetsDirectional.only(end: 30), + padding: const EdgeInsetsDirectional.only(end: 30.0), child: const Icon( Icons.delete, color: Colors.white, diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_account.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_account.dart index 26b0e465f1b..e3d7378b925 100644 --- a/packages/smooth_app/lib/pages/preferences/user_preferences_account.dart +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_account.dart @@ -2,10 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:provider/provider.dart'; -import 'package:smooth_app/data_models/product_list.dart'; import 'package:smooth_app/data_models/user_management_provider.dart'; import 'package:smooth_app/data_models/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/buttons/smooth_simple_button.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; @@ -16,7 +14,6 @@ import 'package:smooth_app/helpers/user_management_helper.dart'; import 'package:smooth_app/pages/preferences/abstract_user_preferences.dart'; import 'package:smooth_app/pages/preferences/user_preferences_list_tile.dart'; import 'package:smooth_app/pages/preferences/user_preferences_page.dart'; -import 'package:smooth_app/pages/product/common/product_list_page.dart'; import 'package:smooth_app/pages/product/common/product_query_page_helper.dart'; import 'package:smooth_app/pages/user_management/login_page.dart'; import 'package:smooth_app/query/paged_product_query.dart'; @@ -289,12 +286,6 @@ class _UserPreferencesPageState extends State { context: context, localDatabase: localDatabase, ), - _buildProductLocalTile( - productList: ProductList.scanHistory(), - iconData: Icons.history, - context: context, - localDatabase: localDatabase, - ), _getListTile( appLocalizations.view_profile, () async => LaunchUrlHelper.launchURL( @@ -415,14 +406,6 @@ class _UserPreferencesPageState extends State { } } - Future _getMyLocalCount( - final ProductList productList, - final LocalDatabase localDatabase, - ) async { - await DaoProductList(localDatabase).get(productList); - return productList.barcodes.length; - } - Widget _buildProductQueryTile({ required final PagedProductQuery productQuery, required final String title, @@ -444,30 +427,6 @@ class _UserPreferencesPageState extends State { myCount: myCount, ); - Widget _buildProductLocalTile({ - required final ProductList productList, - required final IconData iconData, - required final BuildContext context, - required final LocalDatabase localDatabase, - }) => - _getListTile( - ProductQueryPageHelper.getProductListLabel(productList, context), - () async { - await DaoProductList(localDatabase).get(productList); - if (!mounted) { - return; - } - await Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => ProductListPage(productList), - ), - ); - }, - iconData, - myCount: _getMyLocalCount(productList, localDatabase), - ); - Widget _getListTile( final String title, final VoidCallback onTap, 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 a3fe2f38998..829f357aa0f 100644 --- a/packages/smooth_app/lib/pages/preferences/user_preferences_page.dart +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_page.dart @@ -18,7 +18,6 @@ import 'package:smooth_app/pages/preferences/user_preferences_dev_mode.dart'; import 'package:smooth_app/pages/preferences/user_preferences_faq.dart'; import 'package:smooth_app/pages/preferences/user_preferences_food.dart'; import 'package:smooth_app/pages/preferences/user_preferences_settings.dart'; -import 'package:smooth_app/pages/preferences/user_preferences_user_lists.dart'; import 'package:smooth_app/pages/preferences/user_preferences_widgets.dart'; import 'package:smooth_app/themes/theme_provider.dart'; import 'package:smooth_app/widgets/smooth_app_bar.dart'; @@ -26,7 +25,6 @@ import 'package:smooth_app/widgets/smooth_scaffold.dart'; enum PreferencePageType { ACCOUNT('account'), - LISTS('lists'), FOOD('food'), DEV_MODE('dev_mode'), SETTINGS('settings'), @@ -74,7 +72,6 @@ class _UserPreferencesPageState extends State if (widget.type == null) { final List items = [ PreferencePageType.ACCOUNT, - PreferencePageType.LISTS, PreferencePageType.FOOD, PreferencePageType.SETTINGS, PreferencePageType.CONTRIBUTE, @@ -200,14 +197,6 @@ class _UserPreferencesPageState extends State appLocalizations: appLocalizations, themeData: themeData, ); - case PreferencePageType.LISTS: - return UserPreferencesUserLists( - setState: setState, - context: context, - userPreferences: userPreferences, - appLocalizations: appLocalizations, - themeData: themeData, - ); case PreferencePageType.FOOD: return UserPreferencesFood( productPreferences: productPreferences, diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_settings.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_settings.dart index 4ef6c8da7a2..7ce01f0ee3f 100644 --- a/packages/smooth_app/lib/pages/preferences/user_preferences_settings.dart +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_settings.dart @@ -187,8 +187,8 @@ class _ApplicationSettings extends StatelessWidget { label: appLocalizations.settings_app_app, ), Padding( - padding: const EdgeInsets.only( - left: LARGE_SPACE, + padding: const EdgeInsetsDirectional.only( + start: LARGE_SPACE, top: MEDIUM_SPACE, ), child: Row( @@ -202,8 +202,8 @@ class _ApplicationSettings extends StatelessWidget { ), ), Padding( - padding: const EdgeInsets.only( - right: LARGE_SPACE, + padding: const EdgeInsetsDirectional.only( + end: LARGE_SPACE, bottom: MEDIUM_SPACE, ), child: Row( @@ -269,10 +269,10 @@ class _ApplicationSettings extends StatelessWidget { style: themeData.textTheme.headlineMedium, ), subtitle: Padding( - padding: const EdgeInsets.only( + padding: const EdgeInsetsDirectional.only( top: SMALL_SPACE, bottom: SMALL_SPACE, - left: SMALL_SPACE, + start: SMALL_SPACE, ), child: CountrySelector( textStyle: themeData.textTheme.bodyMedium, @@ -287,10 +287,10 @@ class _ApplicationSettings extends StatelessWidget { style: themeData.textTheme.headlineMedium, ), subtitle: Padding( - padding: const EdgeInsets.only( + padding: const EdgeInsetsDirectional.only( top: SMALL_SPACE, bottom: SMALL_SPACE, - left: SMALL_SPACE, + start: SMALL_SPACE, ), child: LanguageSelector( setLanguage: (final OpenFoodFactsLanguage? language) async { @@ -311,8 +311,8 @@ class _ApplicationSettings extends StatelessWidget { ), const UserPreferencesListItemDivider(), Padding( - padding: const EdgeInsets.only( - left: LARGE_SPACE, + padding: const EdgeInsetsDirectional.only( + start: LARGE_SPACE, top: MEDIUM_SPACE, ), child: Row( @@ -326,8 +326,8 @@ class _ApplicationSettings extends StatelessWidget { ), ), Padding( - padding: const EdgeInsets.only( - right: LARGE_SPACE, + padding: const EdgeInsetsDirectional.only( + end: LARGE_SPACE, bottom: MEDIUM_SPACE, ), child: Row( @@ -392,8 +392,8 @@ class ChooseAccentColor extends StatelessWidget { } return Padding( - padding: const EdgeInsets.only( - right: LARGE_SPACE, + padding: const EdgeInsetsDirectional.only( + end: LARGE_SPACE, bottom: MEDIUM_SPACE, ), child: Row( diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_user_lists.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_user_lists.dart deleted file mode 100644 index 18612391bc1..00000000000 --- a/packages/smooth_app/lib/pages/preferences/user_preferences_user_lists.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:smooth_app/data_models/user_preferences.dart'; -import 'package:smooth_app/pages/all_user_product_list_page.dart'; -import 'package:smooth_app/pages/preferences/abstract_user_preferences.dart'; -import 'package:smooth_app/pages/preferences/user_preferences_page.dart'; - -class UserPreferencesUserLists extends AbstractUserPreferences { - UserPreferencesUserLists({ - required final Function(Function()) setState, - required final BuildContext context, - required final UserPreferences userPreferences, - required final AppLocalizations appLocalizations, - required final ThemeData themeData, - }) : super( - setState: setState, - context: context, - userPreferences: userPreferences, - appLocalizations: appLocalizations, - themeData: themeData, - ); - - @override - List getBody() { - return []; - } - - @override - PreferencePageType? getPreferencePageType() => PreferencePageType.LISTS; - - @override - Widget? getSubtitle() => null; - - @override - String getTitleString() => appLocalizations.user_list_all_title; - - @override - IconData getLeadingIconData() => Icons.playlist_add_check; - - @override - Future runHeaderAction() => Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => const AllUserProductList(), - ), - ); -} diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_widgets.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_widgets.dart index 52dbe65988d..ae70212176f 100644 --- a/packages/smooth_app/lib/pages/preferences/user_preferences_widgets.dart +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_widgets.dart @@ -92,6 +92,7 @@ class UserPreferencesSwitchItem extends StatelessWidget { style: const TextStyle(height: 1.5), ), ), + activeColor: Theme.of(context).primaryColor, value: value, onChanged: onChanged, isThreeLine: true, diff --git a/packages/smooth_app/lib/pages/product/add_new_product_page.dart b/packages/smooth_app/lib/pages/product/add_new_product_page.dart index b0ce1e75085..12132c4570c 100644 --- a/packages/smooth_app/lib/pages/product/add_new_product_page.dart +++ b/packages/smooth_app/lib/pages/product/add_new_product_page.dart @@ -6,6 +6,7 @@ import 'package:matomo_tracker/matomo_tracker.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:provider/provider.dart'; import 'package:smooth_app/data_models/product_list.dart'; +import 'package:smooth_app/data_models/up_to_date_mixin.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_large_button_with_icon.dart'; @@ -91,16 +92,13 @@ class AddNewProductPage extends StatefulWidget { } class _AddNewProductPageState extends State - with TraceableClientMixin { + with TraceableClientMixin, UpToDateMixin { // Just one file per main image field final Map _uploadedImages = {}; // Many possible files for "other" image field final List _otherUploadedImages = []; - late Product _product; - late final Product _initialProduct; - late final LocalDatabase _localDatabase; late DaoProductList _daoProductList; final ProductList _history = ProductList.history(); @@ -145,29 +143,20 @@ class _AddNewProductPageState extends State _detailsEditor, _nutritionEditor, ]; - _initialProduct = widget.product; - _localDatabase = context.read(); - _localDatabase.upToDate.showInterest(barcode); - _daoProductList = DaoProductList(_localDatabase); + final LocalDatabase localDatabase = context.read(); + initUpToDate(widget.product, localDatabase); + _daoProductList = DaoProductList(localDatabase); AnalyticsHelper.trackEvent( widget.events[EditProductAction.openPage]!, barcode: barcode, ); } - @override - void dispose() { - _localDatabase.upToDate.loseInterest(barcode); - super.dispose(); - } - - String get barcode => widget.product.barcode!; - @override Widget build(BuildContext context) { final AppLocalizations appLocalizations = AppLocalizations.of(context); context.watch(); - _product = _localDatabase.upToDate.getLocalUpToDate(_initialProduct); + refreshUpToDate(); _addToHistory(); @@ -203,7 +192,9 @@ class _AddNewProductPageState extends State child: SmoothScaffold( appBar: SmoothAppBar( title: ListTile( - title: Text(_product.productName ?? appLocalizations.new_product), + title: Text( + upToDateProduct.productName ?? appLocalizations.new_product, + ), subtitle: Text(barcode), ), ), @@ -235,8 +226,8 @@ class _AddNewProductPageState extends State return; } if (_isPopulated) { - _product.productName = _product.productName?.trim(); - _product.brands = _product.brands?.trim(); + upToDateProduct.productName = upToDateProduct.productName?.trim(); + upToDateProduct.brands = upToDateProduct.brands?.trim(); await _daoProductList.push(_history, barcode); _alreadyPushedToHistory = true; } @@ -245,7 +236,7 @@ class _AddNewProductPageState extends State /// Returns true if at least one field is populated. bool get _isPopulated { for (final ProductFieldEditor editor in _editors) { - if (editor.isPopulated(_product)) { + if (editor.isPopulated(upToDateProduct)) { return true; } } @@ -266,7 +257,7 @@ class _AddNewProductPageState extends State ); Attribute? _getAttribute(final String tag) => - _product.getAttributes([tag])[tag]; + upToDateProduct.getAttributes([tag])[tag]; List _getNutriscoreRows(final BuildContext context) { final AppLocalizations appLocalizations = AppLocalizations.of(context); @@ -340,7 +331,7 @@ class _AddNewProductPageState extends State _buildIngredientsButton( context, forceIconData: Icons.filter_2, - disabled: !_categoryEditor.isPopulated(_product), + disabled: !_categoryEditor.isPopulated(upToDateProduct), ), Row( mainAxisAlignment: MainAxisAlignment.start, @@ -428,7 +419,7 @@ class _AddNewProductPageState extends State Widget _buildNutritionInputButton(final BuildContext context) { if (!_trackedPopulatedNutrition) { - if (_nutritionEditor.isPopulated(_product)) { + if (_nutritionEditor.isPopulated(upToDateProduct)) { _trackedPopulatedNutrition = true; AnalyticsHelper.trackEvent( widget.events[EditProductAction.nutritionFacts]!, @@ -440,7 +431,7 @@ class _AddNewProductPageState extends State context, _nutritionEditor, forceIconData: Icons.filter_2, - disabled: !_categoryEditor.isPopulated(_product), + disabled: !_categoryEditor.isPopulated(upToDateProduct), ); } @@ -450,7 +441,7 @@ class _AddNewProductPageState extends State final IconData? forceIconData, final bool disabled = false, }) { - final bool done = editor.isPopulated(_product); + final bool done = editor.isPopulated(upToDateProduct); return _MyButton( editor.getLabel(AppLocalizations.of(context)), forceIconData ?? (done ? _doneIcon : _todoIcon), @@ -458,7 +449,7 @@ class _AddNewProductPageState extends State ? null : () async => editor.edit( context: context, - product: _product, + product: upToDateProduct, isLoggedInMandatory: widget.isLoggedInMandatory, ), done: done, @@ -467,7 +458,7 @@ class _AddNewProductPageState extends State Widget _buildCategoriesButton(final BuildContext context) { if (!_trackedPopulatedCategories) { - if (_categoryEditor.isPopulated(_product)) { + if (_categoryEditor.isPopulated(upToDateProduct)) { _trackedPopulatedCategories = true; AnalyticsHelper.trackEvent( widget.events[EditProductAction.category]!, @@ -499,7 +490,7 @@ class _AddNewProductPageState extends State final bool disabled = false, }) { if (!_trackedPopulatedIngredients) { - if (_ingredientsEditor.isPopulated(_product)) { + if (_ingredientsEditor.isPopulated(upToDateProduct)) { _trackedPopulatedIngredients = true; AnalyticsHelper.trackEvent( widget.events[EditProductAction.ingredients]!, diff --git a/packages/smooth_app/lib/pages/product/common/product_list_page.dart b/packages/smooth_app/lib/pages/product/common/product_list_page.dart index 1762c6d4b30..9842e08c206 100644 --- a/packages/smooth_app/lib/pages/product/common/product_list_page.dart +++ b/packages/smooth_app/lib/pages/product/common/product_list_page.dart @@ -1,5 +1,4 @@ -import 'dart:math'; - +import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -7,8 +6,8 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:matomo_tracker/matomo_tracker.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:provider/provider.dart'; -import 'package:share_plus/share_plus.dart'; import 'package:smooth_app/data_models/product_list.dart'; +import 'package:smooth_app/data_models/up_to_date_product_list_mixin.dart'; import 'package:smooth_app/database/dao_product.dart'; import 'package:smooth_app/database/dao_product_list.dart'; import 'package:smooth_app/database/local_database.dart'; @@ -16,21 +15,20 @@ import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/generic_lib/dialogs/smooth_alert_dialog.dart'; import 'package:smooth_app/generic_lib/duration_constants.dart'; import 'package:smooth_app/generic_lib/loading_dialog.dart'; -import 'package:smooth_app/helpers/analytics_helper.dart'; import 'package:smooth_app/helpers/app_helper.dart'; import 'package:smooth_app/helpers/robotoff_insight_helper.dart'; -import 'package:smooth_app/helpers/temp_product_list_share_helper.dart'; +import 'package:smooth_app/pages/all_product_list_page.dart'; import 'package:smooth_app/pages/inherited_data_manager.dart'; import 'package:smooth_app/pages/personalized_ranking_page.dart'; import 'package:smooth_app/pages/product/common/product_list_item_simple.dart'; +import 'package:smooth_app/pages/product/common/product_list_popup_items.dart'; import 'package:smooth_app/pages/product/common/product_query_page_helper.dart'; import 'package:smooth_app/pages/product/common/product_refresher.dart'; -import 'package:smooth_app/pages/product_list_user_dialog_helper.dart'; import 'package:smooth_app/query/product_query.dart'; import 'package:smooth_app/widgets/smooth_app_bar.dart'; import 'package:smooth_app/widgets/smooth_scaffold.dart'; -import 'package:url_launcher/url_launcher.dart'; +/// Displays the products of a product list, with access to other lists. class ProductListPage extends StatefulWidget { const ProductListPage(this.productList); @@ -41,16 +39,10 @@ class ProductListPage extends StatefulWidget { } class _ProductListPageState extends State - with TraceableClientMixin { - late ProductList productList; + with TraceableClientMixin, UpToDateProductListMixin { final Set _selectedBarcodes = {}; bool _selectionMode = false; - static const String _popupActionClear = 'clear'; - static const String _popupActionRename = 'rename'; - static const String _popupActionOpenInWeb = 'openInWeb'; - static const String _popupActionShare = 'share'; - @override String get traceName => 'Opened list_page'; @@ -60,9 +52,14 @@ class _ProductListPageState extends State @override void initState() { super.initState(); - productList = widget.productList; + initUpToDate(widget.productList, context.read()); } + final ProductListPopupItem _rename = ProductListPopupRename(); + final ProductListPopupItem _clear = ProductListPopupClear(); + final ProductListPopupItem _openInWeb = ProductListPopupOpenInWeb(); + final ProductListPopupItem _share = ProductListPopupShare(); + //returns bool to handle WillPopScope Future _handleUserBacktap() async { if (_selectionMode) { @@ -84,6 +81,7 @@ class _ProductListPageState extends State final DaoProductList daoProductList = DaoProductList(localDatabase); final ThemeData themeData = Theme.of(context); final AppLocalizations appLocalizations = AppLocalizations.of(context); + refreshUpToDate(); final List products = productList.getList(); final bool dismissible; switch (productList.listType) { @@ -108,7 +106,6 @@ class _ProductListPageState extends State return SmoothScaffold( floatingActionButton: products.isEmpty ? FloatingActionButton.extended( - heroTag: 'compare_fab_${Random(100)}', icon: const Icon(CupertinoIcons.barcode), label: Text(appLocalizations.product_list_empty_title), onPressed: () => @@ -129,100 +126,56 @@ class _ProductListPageState extends State ), appBar: SmoothAppBar( centerTitle: _selectionMode ? false : null, - actions: !(enableClear || enableRename) - ? null - : [ - PopupMenuButton( - onSelected: (final String action) async { - switch (action) { - case _popupActionClear: - await showDialog( - context: context, - builder: (BuildContext context) { - return SmoothAlertDialog( - body: Text( - productList.listType == ProductListType.USER - ? appLocalizations.confirm_clear_user_list( - productList.parameters) - : appLocalizations.confirm_clear, - ), - positiveAction: SmoothActionButton( - onPressed: () async { - await daoProductList.clear(productList); - await daoProductList.get(productList); - setState(() {}); - if (!mounted) { - return; - } - Navigator.of(context).pop(); - }, - text: appLocalizations.yes, - ), - negativeAction: SmoothActionButton( - onPressed: () { - Navigator.of(context).pop(); - }, - text: appLocalizations.no, - ), - ); - }, - ); - break; - case _popupActionRename: - final ProductList? renamedProductList = - await ProductListUserDialogHelper(daoProductList) - .showRenameUserListDialog(context, productList); - if (renamedProductList == null) { - return; - } - setState(() => productList = renamedProductList); - break; - case _popupActionShare: - final String url = - shareProductList(products).toString(); - - final RenderBox? box = - context.findRenderObject() as RenderBox?; - AnalyticsHelper.trackEvent(AnalyticsEvent.shareList); - Share.share( - appLocalizations.share_product_list_text(url), - sharePositionOrigin: - box!.localToGlobal(Offset.zero) & box.size, - ); - - break; - case _popupActionOpenInWeb: - AnalyticsHelper.trackEvent(AnalyticsEvent.openListWeb); - launchUrl(shareProductList(products)); - break; - } - }, - itemBuilder: (BuildContext context) => - >[ - if (enableRename) - PopupMenuItem( - value: _popupActionRename, - child: Text(appLocalizations.user_list_popup_rename), - ), - PopupMenuItem( - value: _popupActionShare, - child: Text(appLocalizations.share), - ), - PopupMenuItem( - value: _popupActionOpenInWeb, - child: Text(appLocalizations.label_web), - ), - if (enableClear) - PopupMenuItem( - value: _popupActionClear, - child: Text(appLocalizations.user_list_popup_clear), - ), - ], - ) + actions: [ + IconButton( + icon: const Icon(CupertinoIcons.square_list), + onPressed: () async { + final ProductList? selected = await Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => const AllProductListPage(), + fullscreenDialog: true, + ), + ); + if (selected == null) { + return; + } + if (context.mounted) { + await daoProductList.get(selected); + if (context.mounted) { + setState(() => productList = selected); + } + } + }, + ), + if (enableClear || enableRename) + PopupMenuButton( + onSelected: (final ProductListPopupItem action) async { + final ProductList? differentProductList = + await action.doSomething( + productList: productList, + localDatabase: localDatabase, + context: context, + ); + if (differentProductList != null) { + setState(() => productList = differentProductList); + } + }, + itemBuilder: (BuildContext context) => + >[ + if (enableRename) _rename.getMenuItem(appLocalizations), + _share.getMenuItem(appLocalizations), + _openInWeb.getMenuItem(appLocalizations), + if (enableClear) _clear.getMenuItem(appLocalizations), ], - title: Text( - ProductQueryPageHelper.getProductListLabel(productList, context), - overflow: TextOverflow.fade, + ), + ], + title: AutoSizeText( + ProductQueryPageHelper.getProductListLabel( + productList, + appLocalizations, + ), + maxLines: 2, ), actionMode: _selectionMode, onLeaveActionMode: () { @@ -241,7 +194,8 @@ class _ProductListPageState extends State builder: (BuildContext context) { return SmoothAlertDialog( body: Container( - padding: const EdgeInsets.only(left: SMALL_SPACE), + padding: const EdgeInsetsDirectional.only( + start: SMALL_SPACE), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -369,8 +323,8 @@ class _ProductListPageState extends State final Widget child = InkWell( onTap: _selectionMode ? onTap : null, child: Container( - padding: EdgeInsets.only( - left: _selectionMode ? SMALL_SPACE : 0, + padding: EdgeInsetsDirectional.only( + start: _selectionMode ? SMALL_SPACE : 0, ), child: Row( children: [ @@ -402,7 +356,7 @@ class _ProductListPageState extends State return Dismissible( direction: DismissDirection.endToStart, background: Container( - alignment: Alignment.centerRight, + alignment: AlignmentDirectional.centerEnd, margin: const EdgeInsets.symmetric(vertical: 14), color: RED_COLOR, padding: const EdgeInsetsDirectional.only(end: 30), @@ -428,7 +382,7 @@ class _ProductListPageState extends State SnackBar( content: Text( removed - ? appLocalizations.product_removed_history + ? appLocalizations.product_removed_list : appLocalizations.product_could_not_remove, ), duration: SnackBarDuration.medium, diff --git a/packages/smooth_app/lib/pages/product/common/product_list_popup_items.dart b/packages/smooth_app/lib/pages/product/common/product_list_popup_items.dart new file mode 100644 index 00000000000..ed888082476 --- /dev/null +++ b/packages/smooth_app/lib/pages/product/common/product_list_popup_items.dart @@ -0,0 +1,138 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:share_plus/share_plus.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/dialogs/smooth_alert_dialog.dart'; +import 'package:smooth_app/helpers/analytics_helper.dart'; +import 'package:smooth_app/helpers/temp_product_list_share_helper.dart'; +import 'package:smooth_app/pages/product_list_user_dialog_helper.dart'; +import 'package:url_launcher/url_launcher.dart'; + +/// Popup menu items for the product list page. +abstract class ProductListPopupItem { + /// Title of the popup menu item. + @protected + String getTitle(final AppLocalizations appLocalizations); + + /// Action of the popup menu item. + /// + /// Returns a different product list if there are changes, else null. + Future doSomething({ + required final ProductList productList, + required final LocalDatabase localDatabase, + required final BuildContext context, + }); + + /// Returns the popup menu item. + PopupMenuItem getMenuItem( + final AppLocalizations appLocalizations, + ) => + PopupMenuItem( + value: this, + child: Text(getTitle(appLocalizations)), + ); +} + +/// Popup menu item for the product list page: clear list. +class ProductListPopupClear extends ProductListPopupItem { + @override + String getTitle(final AppLocalizations appLocalizations) => + appLocalizations.clear; + + @override + Future doSomething({ + required final ProductList productList, + required final LocalDatabase localDatabase, + required final BuildContext context, + }) async { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + final DaoProductList daoProductList = DaoProductList(localDatabase); + final bool? ok = await showDialog( + context: context, + builder: (BuildContext context) => SmoothAlertDialog( + body: Text( + productList.listType == ProductListType.USER + ? appLocalizations.confirm_clear_user_list(productList.parameters) + : appLocalizations.confirm_clear, + ), + positiveAction: SmoothActionButton( + onPressed: () => Navigator.of(context).pop(true), + text: appLocalizations.yes, + ), + negativeAction: SmoothActionButton( + onPressed: () => Navigator.of(context).pop(), + text: appLocalizations.no, + ), + ), + ); + if (ok == true) { + await daoProductList.clear(productList); + await daoProductList.get(productList); + return productList; + } + return null; + } +} + +/// Popup menu item for the product list page: rename list. +class ProductListPopupRename extends ProductListPopupItem { + @override + String getTitle(final AppLocalizations appLocalizations) => + appLocalizations.user_list_popup_rename; + + @override + Future doSomething({ + required final ProductList productList, + required final LocalDatabase localDatabase, + required final BuildContext context, + }) async => + ProductListUserDialogHelper(DaoProductList(localDatabase)) + .showRenameUserListDialog(context, productList); +} + +/// Popup menu item for the product list page: share list. +class ProductListPopupShare extends ProductListPopupItem { + @override + String getTitle(final AppLocalizations appLocalizations) => + appLocalizations.share; + + @override + Future doSomething({ + required final ProductList productList, + required final LocalDatabase localDatabase, + required final BuildContext context, + }) async { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + final List products = productList.getList(); + final String url = shareProductList(products).toString(); + + final RenderBox? box = context.findRenderObject() as RenderBox?; + AnalyticsHelper.trackEvent(AnalyticsEvent.shareList); + Share.share( + appLocalizations.share_product_list_text(url), + sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, + ); + return null; + } +} + +/// Popup menu item for the product list page: open list in web. +class ProductListPopupOpenInWeb extends ProductListPopupItem { + @override + String getTitle(final AppLocalizations appLocalizations) => + appLocalizations.label_web; + + @override + Future doSomething({ + required final ProductList productList, + required final LocalDatabase localDatabase, + required final BuildContext context, + }) async { + final List products = productList.getList(); + AnalyticsHelper.trackEvent(AnalyticsEvent.openListWeb); + await launchUrl(shareProductList(products)); + return null; + } +} diff --git a/packages/smooth_app/lib/pages/product/common/product_query_page_helper.dart b/packages/smooth_app/lib/pages/product/common/product_query_page_helper.dart index 41ca4fd0db5..4e48b54d250 100644 --- a/packages/smooth_app/lib/pages/product/common/product_query_page_helper.dart +++ b/packages/smooth_app/lib/pages/product/common/product_query_page_helper.dart @@ -79,9 +79,8 @@ class ProductQueryPageHelper { static String getProductListLabel( final ProductList productList, - final BuildContext context, + final AppLocalizations appLocalizations, ) { - final AppLocalizations appLocalizations = AppLocalizations.of(context); switch (productList.listType) { case ProductListType.HTTP_SEARCH_KEYWORDS: case ProductListType.HTTP_SEARCH_CATEGORY: diff --git a/packages/smooth_app/lib/pages/product/edit_new_packagings.dart b/packages/smooth_app/lib/pages/product/edit_new_packagings.dart index 639adce9090..f9694a92320 100644 --- a/packages/smooth_app/lib/pages/product/edit_new_packagings.dart +++ b/packages/smooth_app/lib/pages/product/edit_new_packagings.dart @@ -4,6 +4,7 @@ import 'package:intl/intl.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:provider/provider.dart'; import 'package:smooth_app/background/background_task_details.dart'; +import 'package:smooth_app/data_models/up_to_date_mixin.dart'; import 'package:smooth_app/database/local_database.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/generic_lib/widgets/smooth_card.dart'; @@ -34,17 +35,13 @@ class EditNewPackagings extends StatefulWidget { State createState() => _EditNewPackagingsState(); } -class _EditNewPackagingsState extends State { - late final LocalDatabase _localDatabase; +class _EditNewPackagingsState extends State + with UpToDateMixin { late final NumberFormat _decimalNumberFormat; late final NumberFormat _unitNumberFormat; - late Product _product; - late final Product _initialProduct; late bool? _packagingsComplete; - String get _barcode => _initialProduct.barcode!; - final List _helpers = []; void _addPackagingToControllers( @@ -69,24 +66,21 @@ class _EditNewPackagingsState extends State { @override void initState() { super.initState(); - _initialProduct = widget.product; + initUpToDate(widget.product, context.read()); _decimalNumberFormat = SimpleInputNumberField.getNumberFormat( decimal: true, ); _unitNumberFormat = SimpleInputNumberField.getNumberFormat( decimal: false, ); - if (_initialProduct.packagings != null) { - _initialProduct.packagings!.forEach(_addPackagingToControllers); + if (initialProduct.packagings != null) { + initialProduct.packagings!.forEach(_addPackagingToControllers); } - _packagingsComplete = _initialProduct.packagingsComplete; - _localDatabase = context.read(); - _localDatabase.upToDate.showInterest(_barcode); + _packagingsComplete = initialProduct.packagingsComplete; } @override void dispose() { - _localDatabase.upToDate.loseInterest(_barcode); for (final EditNewPackagingsHelper helper in _helpers) { helper.dispose(); } @@ -97,12 +91,12 @@ class _EditNewPackagingsState extends State { Widget build(BuildContext context) { final AppLocalizations appLocalizations = AppLocalizations.of(context); context.watch(); - _product = _localDatabase.upToDate.getLocalUpToDate(_initialProduct); + refreshUpToDate(); final List children = []; children.add( Padding( padding: const EdgeInsets.all(SMALL_SPACE), - child: ImageField.PACKAGING.getPhotoButton(context, _product), + child: ImageField.PACKAGING.getPhotoButton(context, upToDateProduct), ), ); for (int index = 0; index < _helpers.length; index++) { @@ -116,7 +110,7 @@ class _EditNewPackagingsState extends State { deleteCallback: () => setState(() => _removePackagingAt(deleteIndex)), helper: _helpers[index], - categories: _product.categories, + categories: upToDateProduct.categories, ), ), ); @@ -145,10 +139,10 @@ class _EditNewPackagingsState extends State { ); children.add( Padding( - padding: const EdgeInsets.only( + padding: const EdgeInsetsDirectional.only( top: VERY_LARGE_SPACE, - left: SMALL_SPACE, - right: SMALL_SPACE, + start: SMALL_SPACE, + end: SMALL_SPACE, ), child: addPanelButton( appLocalizations.edit_packagings_element_add.toUpperCase(), @@ -164,17 +158,17 @@ class _EditNewPackagingsState extends State { ); children.add( Padding( - padding: const EdgeInsets.only( + padding: const EdgeInsetsDirectional.only( bottom: VERY_LARGE_SPACE, - left: SMALL_SPACE, - right: SMALL_SPACE, + start: SMALL_SPACE, + end: SMALL_SPACE, ), child: addPanelButton( appLocalizations.add_packaging_photo_button_label.toUpperCase(), onPressed: () async => confirmAndUploadNewPicture( this, imageField: ImageField.OTHER, - barcode: _barcode, + barcode: barcode, language: ProductQuery.getLanguage(), ), iconData: Icons.add_a_photo, @@ -189,9 +183,9 @@ class _EditNewPackagingsState extends State { fixKeyboard: true, appBar: SmoothAppBar( title: Text(appLocalizations.edit_packagings_title), - subTitle: _product.productName != null + subTitle: upToDateProduct.productName != null ? Text( - _product.productName!, + upToDateProduct.productName!, maxLines: 1, overflow: TextOverflow.ellipsis, ) @@ -241,16 +235,16 @@ class _EditNewPackagingsState extends State { p1.numberOfUnits != p2.numberOfUnits; bool _hasPackagingsChanged(final List packagings) { - if (_product.packagings == null) { + if (upToDateProduct.packagings == null) { return packagings.isNotEmpty; } - if (_product.packagings!.length != packagings.length) { + if (upToDateProduct.packagings!.length != packagings.length) { return true; } for (int i = 0; i < packagings.length; i++) { if (_isPackagingDifferent( packagings[i], - _product.packagings![i], + upToDateProduct.packagings![i], )) { return true; } @@ -263,7 +257,7 @@ class _EditNewPackagingsState extends State { /// Parameter [saving] tells about the context: are we leaving the page, /// or have we clicked on the "save" button? Future _mayExitPage({required final bool saving}) async { - final Product changedProduct = Product(barcode: _barcode); + final Product changedProduct = Product(barcode: barcode); bool changed = false; final List packagings = _getPackagingsFromControllers(); @@ -272,7 +266,7 @@ class _EditNewPackagingsState extends State { changedProduct.packagings = packagings; } - if (_packagingsComplete != _product.packagingsComplete) { + if (_packagingsComplete != upToDateProduct.packagingsComplete) { changed = true; changedProduct.packagingsComplete = _packagingsComplete; } @@ -297,7 +291,7 @@ class _EditNewPackagingsState extends State { AnalyticsHelper.trackProductEdit( AnalyticsEditEvents.packagingComponents, - _barcode, + barcode, true, ); diff --git a/packages/smooth_app/lib/pages/product/edit_ocr_page.dart b/packages/smooth_app/lib/pages/product/edit_ocr_page.dart index b6f9d304eb6..7ab99ac0f11 100644 --- a/packages/smooth_app/lib/pages/product/edit_ocr_page.dart +++ b/packages/smooth_app/lib/pages/product/edit_ocr_page.dart @@ -5,6 +5,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:provider/provider.dart'; import 'package:smooth_app/background/background_task_details.dart'; +import 'package:smooth_app/data_models/up_to_date_mixin.dart'; import 'package:smooth_app/database/local_database.dart'; import 'package:smooth_app/database/transient_file.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; @@ -41,11 +42,8 @@ class EditOcrPage extends StatefulWidget { State createState() => _EditOcrPageState(); } -class _EditOcrPageState extends State { +class _EditOcrPageState extends State with UpToDateMixin { final TextEditingController _controller = TextEditingController(); - late Product _product; - late final Product _initialProduct; - late final LocalDatabase _localDatabase; late final MultilingualHelper _multilingualHelper; OcrHelper get _helper => widget.helper; @@ -53,10 +51,7 @@ class _EditOcrPageState extends State { @override void initState() { super.initState(); - _initialProduct = widget.product; - _localDatabase = context.read(); - _localDatabase.upToDate.showInterest(_initialProduct.barcode!); - + initUpToDate(widget.product, context.read()); _multilingualHelper = MultilingualHelper(controller: _controller); _multilingualHelper.init( multilingualTexts: _helper.getMultilingualTexts(widget.product), @@ -67,12 +62,6 @@ class _EditOcrPageState extends State { ); } - @override - void dispose() { - _localDatabase.upToDate.loseInterest(_product.barcode!); - super.dispose(); - } - /// Extracts data with OCR from the image stored on the server. /// /// When done, populates the related page field. @@ -116,7 +105,7 @@ class _EditOcrPageState extends State { } AnalyticsHelper.trackProductEdit( _helper.getEditEventAnalyticsTag(), - _product.barcode!, + barcode, true, ); await BackgroundTaskDetails.addTask( @@ -131,9 +120,9 @@ class _EditOcrPageState extends State { Widget build(BuildContext context) { final AppLocalizations appLocalizations = AppLocalizations.of(context); context.watch(); - _product = _localDatabase.upToDate.getLocalUpToDate(_initialProduct); + refreshUpToDate(); final TransientFile transientFile = TransientFile.fromProduct( - _product, + upToDateProduct, _helper.getImageField(), _multilingualHelper.getCurrentLanguage(), ); @@ -157,9 +146,9 @@ class _EditOcrPageState extends State { _helper.getTitle(appLocalizations), style: appbarTextStyle, ), - subTitle: _product.productName != null + subTitle: upToDateProduct.productName != null ? Text( - _product.productName!, + upToDateProduct.productName!, maxLines: 1, overflow: TextOverflow.ellipsis, style: appbarTextStyle, @@ -242,7 +231,7 @@ class _EditOcrPageState extends State { Flexible( flex: 1, child: Align( - alignment: Alignment.bottomRight, + alignment: AlignmentDirectional.bottomEnd, child: Padding( padding: const EdgeInsetsDirectional.only( bottom: LARGE_SPACE, @@ -259,7 +248,7 @@ class _EditOcrPageState extends State { padding: const EdgeInsets.symmetric(horizontal: SMALL_SPACE), child: ProductImageServerButton( - product: _product, + product: upToDateProduct, imageField: _helper.getImageField(), language: language, isLoggedInMandatory: widget.isLoggedInMandatory, @@ -291,9 +280,9 @@ class _EditOcrPageState extends State { child: Container( decoration: BoxDecoration( color: Theme.of(context).colorScheme.background, - borderRadius: const BorderRadius.only( - topLeft: ANGULAR_RADIUS, - topRight: ANGULAR_RADIUS, + borderRadius: const BorderRadiusDirectional.only( + topStart: ANGULAR_RADIUS, + topEnd: ANGULAR_RADIUS, )), child: SingleChildScrollView( child: Padding( 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 fbe18c594db..c40f5879732 100644 --- a/packages/smooth_app/lib/pages/product/edit_product_page.dart +++ b/packages/smooth_app/lib/pages/product/edit_product_page.dart @@ -10,7 +10,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:provider/provider.dart'; -import 'package:smooth_app/background/background_task_manager.dart'; +import 'package:smooth_app/data_models/up_to_date_mixin.dart'; import 'package:smooth_app/database/local_database.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/generic_lib/widgets/smooth_back_button.dart'; @@ -38,32 +38,27 @@ class EditProductPage extends StatefulWidget { State createState() => _EditProductPageState(); } -class _EditProductPageState extends State { +class _EditProductPageState extends State with UpToDateMixin { final ScrollController _controller = ScrollController(); bool _barcodeVisibleInAppbar = false; - late Product _product; - late final Product _initialProduct; - late final LocalDatabase _localDatabase; - - String get _barcode => _initialProduct.barcode!; @override void initState() { super.initState(); - _initialProduct = widget.product; - _localDatabase = context.read(); - _localDatabase.upToDate.showInterest(_barcode); + initUpToDate(widget.product, context.read()); _controller.addListener(_onScrollChanged); } @override Widget build(BuildContext context) { - BackgroundTaskManager.getInstance(_localDatabase).run(); // no await final AppLocalizations appLocalizations = AppLocalizations.of(context); context.watch(); - _product = _localDatabase.upToDate.getLocalUpToDate(_initialProduct); + refreshUpToDate(); final ThemeData theme = Theme.of(context); - final String productName = getProductName(_product, appLocalizations); + final String productName = getProductName( + upToDateProduct, + appLocalizations, + ); return SmoothScaffold( appBar: SmoothAppBar( @@ -84,12 +79,12 @@ class _EditProductPageState extends State { style: theme.textTheme.titleLarge ?.copyWith(fontWeight: FontWeight.w500), ), - if (_barcode.isNotEmpty) + if (barcode.isNotEmpty) AnimatedContainer( duration: const Duration(milliseconds: 250), height: _barcodeVisibleInAppbar ? 14.0 : 0.0, child: Text( - _barcode, + barcode, style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.normal, ), @@ -109,12 +104,12 @@ class _EditProductPageState extends State { tooltip: appLocalizations.clipboard_barcode_copy, onPressed: () { Clipboard.setData( - ClipboardData(text: _barcode), + ClipboardData(text: barcode), ); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( - appLocalizations.clipboard_barcode_copied(_barcode), + appLocalizations.clipboard_barcode_copied(barcode), ), ), ); @@ -125,7 +120,7 @@ class _EditProductPageState extends State { ), body: RefreshIndicator( onRefresh: () async => ProductRefresher().fetchAndRefresh( - barcode: _barcode, + barcode: barcode, widget: this, ), child: PrimaryScrollController( @@ -133,15 +128,15 @@ class _EditProductPageState extends State { child: Scrollbar( child: ListView( children: [ - if (_ProductBarcode.isAValidBarcode(_product.barcode)) - _ProductBarcode(product: _product), + if (_ProductBarcode.isAValidBarcode(barcode)) + _ProductBarcode(product: upToDateProduct), _ListTitleItem( title: appLocalizations.edit_product_form_item_details_title, subtitle: appLocalizations.edit_product_form_item_details_subtitle, onTap: () async => ProductFieldDetailsEditor().edit( context: context, - product: _product, + product: upToDateProduct, ), ), _ListTitleItem( @@ -151,14 +146,16 @@ class _EditProductPageState extends State { appLocalizations.edit_product_form_item_photos_subtitle, onTap: () async { AnalyticsHelper.trackProductEdit( - AnalyticsEditEvents.photos, _barcode); + AnalyticsEditEvents.photos, + barcode, + ); await Navigator.push( context, MaterialPageRoute( builder: (BuildContext context) => ProductImageGalleryView( - product: _product, + product: upToDateProduct, ), fullscreenDialog: true, ), @@ -182,7 +179,7 @@ class _EditProductPageState extends State { appLocalizations.edit_product_form_item_ingredients_title, onTap: () async => ProductFieldOcrIngredientEditor().edit( context: context, - product: _product, + product: upToDateProduct, ), ), _getSimpleListTileItem(SimpleInputPageCategoryHelper()), @@ -195,9 +192,11 @@ class _EditProductPageState extends State { .edit_product_form_item_nutrition_facts_subtitle, onTap: () async { AnalyticsHelper.trackProductEdit( - AnalyticsEditEvents.nutrition_Facts, _barcode); + AnalyticsEditEvents.nutrition_Facts, + barcode, + ); await NutritionPageLoaded.showNutritionPage( - product: _product, + product: upToDateProduct, isLoggedInMandatory: true, context: context, ); @@ -208,7 +207,7 @@ class _EditProductPageState extends State { title: appLocalizations.edit_packagings_title, onTap: () async => ProductFieldPackagingEditor().edit( context: context, - product: _product, + product: upToDateProduct, ), ), _ListTitleItem( @@ -217,7 +216,7 @@ class _EditProductPageState extends State { appLocalizations.edit_product_form_item_packaging_title, onTap: () async => ProductFieldOcrPackagingEditor().edit( context: context, - product: _product, + product: upToDateProduct, ), ), _getSimpleListTileItem(SimpleInputPageStoreHelper()), @@ -234,11 +233,13 @@ class _EditProductPageState extends State { return; } AnalyticsHelper.trackProductEdit( - AnalyticsEditEvents.otherDetails, _barcode); + AnalyticsEditEvents.otherDetails, + barcode, + ); await Navigator.push( context, MaterialPageRoute( - builder: (_) => AddOtherDetailsPage(_product), + builder: (_) => AddOtherDetailsPage(upToDateProduct), fullscreenDialog: true, ), ); @@ -261,7 +262,7 @@ class _EditProductPageState extends State { subtitle: helper.getSubtitle(appLocalizations), onTap: () async => ProductFieldSimpleEditor(helper).edit( context: context, - product: _product, + product: upToDateProduct, ), ); } @@ -282,13 +283,15 @@ class _EditProductPageState extends State { return; } AnalyticsHelper.trackProductEdit( - AnalyticsEditEvents.powerEditScreen, _barcode); + AnalyticsEditEvents.powerEditScreen, + barcode, + ); await Navigator.push( context, MaterialPageRoute( builder: (BuildContext context) => SimpleInputPage.multiple( helpers: helpers, - product: _product, + product: upToDateProduct, ), fullscreenDialog: true, ), @@ -312,7 +315,6 @@ class _EditProductPageState extends State { void dispose() { _controller.removeListener(_onScrollChanged); _controller.dispose(); - _localDatabase.upToDate.loseInterest(_barcode); super.dispose(); } } 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 a4f47e15358..7c307fcda69 100644 --- a/packages/smooth_app/lib/pages/product/new_product_page.dart +++ b/packages/smooth_app/lib/pages/product/new_product_page.dart @@ -7,10 +7,10 @@ import 'package:matomo_tracker/matomo_tracker.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:provider/provider.dart'; import 'package:share_plus/share_plus.dart'; -import 'package:smooth_app/background/background_task_manager.dart'; import 'package:smooth_app/cards/product_cards/product_image_carousel.dart'; import 'package:smooth_app/data_models/product_list.dart'; import 'package:smooth_app/data_models/product_preferences.dart'; +import 'package:smooth_app/data_models/up_to_date_mixin.dart'; import 'package:smooth_app/database/dao_product_list.dart'; import 'package:smooth_app/database/local_database.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; @@ -51,12 +51,10 @@ class ProductPage extends StatefulWidget { State createState() => _ProductPageState(); } -class _ProductPageState extends State with TraceableClientMixin { +class _ProductPageState extends State + with TraceableClientMixin, UpToDateMixin { final ScrollController _carouselController = ScrollController(); - late Product _product; - late final Product _initialProduct; - late final LocalDatabase _localDatabase; late ProductPreferences _productPreferences; bool _keepRobotoffQuestionsAlive = true; @@ -68,35 +66,24 @@ class _ProductPageState extends State with TraceableClientMixin { @override String get traceTitle => 'product_page'; - String get _barcode => _initialProduct.barcode!; - @override void initState() { super.initState(); - _initialProduct = widget.product; - _localDatabase = context.read(); - _localDatabase.upToDate.showInterest(_barcode); + initUpToDate(widget.product, context.read()); WidgetsBinding.instance.addPostFrameCallback((_) { _updateLocalDatabaseWithProductHistory(context); }); } - @override - void dispose() { - _localDatabase.upToDate.loseInterest(_barcode); - super.dispose(); - } - @override Widget build(BuildContext context) { - BackgroundTaskManager.getInstance(_localDatabase).run(); // no await final InheritedDataManagerState inheritedDataManager = InheritedDataManager.of(context); - inheritedDataManager.setCurrentBarcode(_barcode); + inheritedDataManager.setCurrentBarcode(barcode); final ThemeData themeData = Theme.of(context); _productPreferences = context.watch(); context.watch(); - _product = _localDatabase.upToDate.getLocalUpToDate(_initialProduct); + refreshUpToDate(); return SmoothScaffold( contentBehindStatusBar: true, @@ -120,7 +107,7 @@ class _ProductPageState extends State with TraceableClientMixin { child: _buildProductBody(context), ), Padding( - padding: const EdgeInsets.only(left: SMALL_SPACE), + padding: const EdgeInsetsDirectional.only(start: SMALL_SPACE), child: SafeArea( child: AnimatedContainer( duration: SmoothAnimationsDuration.short, @@ -145,7 +132,7 @@ class _ProductPageState extends State with TraceableClientMixin { Future _refreshProduct(BuildContext context) async { final bool success = await ProductRefresher().fetchAndRefresh( - barcode: _product.barcode!, + barcode: barcode, widget: this, ); if (context.mounted) { @@ -162,7 +149,7 @@ class _ProductPageState extends State with TraceableClientMixin { final LocalDatabase localDatabase = context.read(); await DaoProductList(localDatabase).push( ProductList.history(), - _barcode, + barcode, ); localDatabase.notifyListeners(); } @@ -173,7 +160,7 @@ class _ProductPageState extends State with TraceableClientMixin { final DaoProductList daoProductList = DaoProductList(localDatabase); return RefreshIndicator( onRefresh: () => ProductRefresher().fetchAndRefresh( - barcode: _barcode, + barcode: barcode, widget: this, ), child: ListView( @@ -189,7 +176,7 @@ class _ProductPageState extends State with TraceableClientMixin { heightFactor: 0.7, alignment: AlignmentDirectional.topStart, child: ProductImageCarousel( - _product, + upToDateProduct, height: 200, controller: _carouselController, onUpload: _refreshProduct, @@ -207,7 +194,7 @@ class _ProductPageState extends State with TraceableClientMixin { child: KeepQuestionWidgetAlive( keepWidgetAlive: _keepRobotoffQuestionsAlive, child: SummaryCard( - _product, + upToDateProduct, _productPreferences, isFullVersion: true, showUnansweredQuestions: true, @@ -222,8 +209,9 @@ class _ProductPageState extends State with TraceableClientMixin { daoProductList, ), _buildKnowledgePanelCards(), - if (_product.website != null && _product.website!.trim().isNotEmpty) - _buildWebsiteWidget(_product.website!.trim()), + if (upToDateProduct.website != null && + upToDateProduct.website!.trim().isNotEmpty) + _buildWebsiteWidget(upToDateProduct.website!.trim()), ], ), ); @@ -240,12 +228,12 @@ class _ProductPageState extends State with TraceableClientMixin { borderRadius: ROUNDED_BORDER_RADIUS, child: Container( width: double.infinity, - padding: const EdgeInsets.only( - left: LARGE_SPACE, + padding: const EdgeInsetsDirectional.only( + start: LARGE_SPACE, top: LARGE_SPACE, bottom: LARGE_SPACE, // To be perfectly aligned with arrows - right: 21.0, + end: 21.0, ), child: Row( children: [ @@ -280,14 +268,14 @@ class _ProductPageState extends State with TraceableClientMixin { Widget _buildKnowledgePanelCards() { final List knowledgePanelWidgets = []; - if (_product.knowledgePanels != null) { + if (upToDateProduct.knowledgePanels != null) { final List elements = - KnowledgePanelWidget.getPanelElements(_product); + KnowledgePanelWidget.getPanelElements(upToDateProduct); for (final KnowledgePanelElement panelElement in elements) { final List children = KnowledgePanelWidget.getChildren( context, panelElement: panelElement, - product: _product, + product: upToDateProduct, onboardingMode: false, ); if (children.isNotEmpty) { @@ -319,14 +307,14 @@ class _ProductPageState extends State with TraceableClientMixin { Future _shareProduct() async { AnalyticsHelper.trackEvent( AnalyticsEvent.shareProduct, - barcode: _barcode, + barcode: barcode, ); final AppLocalizations appLocalizations = AppLocalizations.of(context); // We need to provide a sharePositionOrigin to make the plugin work on ipad final RenderBox? box = context.findRenderObject() as RenderBox?; final String url = 'https://' '${ProductQuery.getCountry()!.offTag}.openfoodfacts.org' - '/product/$_barcode'; + '/product/$barcode'; Share.share( appLocalizations.share_product_text(url), sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, @@ -352,14 +340,14 @@ class _ProductPageState extends State with TraceableClientMixin { AnalyticsHelper.trackEvent( AnalyticsEvent.openProductEditPage, - barcode: _barcode, + barcode: barcode, ); await Navigator.push( context, MaterialPageRoute( builder: (BuildContext context) => - EditProductPage(_product), + EditProductPage(upToDateProduct), ), ); @@ -411,7 +399,7 @@ class _ProductPageState extends State with TraceableClientMixin { final DaoProductList daoProductList, ) => FutureBuilder>( - future: daoProductList.getUserLists(withBarcodes: [_barcode]), + future: daoProductList.getUserListsWithBarcodes([barcode]), builder: ( final BuildContext context, final AsyncSnapshot> snapshot, @@ -436,9 +424,9 @@ class _ProductPageState extends State with TraceableClientMixin { for (final String productListName in productListNames) { children.add( Padding( - padding: const EdgeInsets.only( + padding: const EdgeInsetsDirectional.only( top: VERY_SMALL_SPACE, - right: VERY_SMALL_SPACE, + end: VERY_SMALL_SPACE, ), child: ElevatedButton( style: ButtonStyle( diff --git a/packages/smooth_app/lib/pages/product/nutrition_page_loaded.dart b/packages/smooth_app/lib/pages/product/nutrition_page_loaded.dart index 0fa718cbf0a..97e64489fc3 100644 --- a/packages/smooth_app/lib/pages/product/nutrition_page_loaded.dart +++ b/packages/smooth_app/lib/pages/product/nutrition_page_loaded.dart @@ -6,6 +6,7 @@ import 'package:intl/intl.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:provider/provider.dart'; import 'package:smooth_app/background/background_task_details.dart'; +import 'package:smooth_app/data_models/up_to_date_mixin.dart'; import 'package:smooth_app/database/local_database.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/generic_lib/widgets/smooth_card.dart'; @@ -79,8 +80,8 @@ class NutritionPageLoaded extends StatefulWidget { } } -class _NutritionPageLoadedState extends State { - late final LocalDatabase _localDatabase; +class _NutritionPageLoadedState extends State + with UpToDateMixin { late final NumberFormat _decimalNumberFormat; late final NutritionContainer _nutritionContainer; @@ -88,28 +89,25 @@ class _NutritionPageLoadedState extends State { {}; TextEditingControllerWithInitialValue? _servingController; final GlobalKey _formKey = GlobalKey(); - late Product _product; - late final Product _initialProduct; - - String get _barcode => _initialProduct.barcode!; + final List _focusNodes = []; @override void initState() { super.initState(); - _initialProduct = widget.product; + initUpToDate(widget.product, context.read()); _nutritionContainer = NutritionContainer( orderedNutrients: widget.orderedNutrients, - product: _initialProduct, + product: initialProduct, ); + _decimalNumberFormat = SimpleInputNumberField.getNumberFormat(decimal: true); - _localDatabase = context.read(); - _localDatabase.upToDate.showInterest(_barcode); } @override void dispose() { - _localDatabase.upToDate.loseInterest(_barcode); + _focusNodes.clear(); + for (final TextEditingControllerWithInitialValue controller in _controllers.values) { controller.dispose(); @@ -122,12 +120,11 @@ class _NutritionPageLoadedState extends State { Widget build(BuildContext context) { final AppLocalizations appLocalizations = AppLocalizations.of(context); context.watch(); - _product = _localDatabase.upToDate.getLocalUpToDate(_initialProduct); + refreshUpToDate(); final List children = []; // List of focus nodes for all text fields except the serving one. - final List focusNodes; children.add(_switchNoNutrition(appLocalizations)); @@ -135,7 +132,7 @@ class _NutritionPageLoadedState extends State { children.add( Padding( padding: const EdgeInsets.symmetric(vertical: MEDIUM_SPACE), - child: ImageField.NUTRITION.getPhotoButton(context, _product), + child: ImageField.NUTRITION.getPhotoButton(context, upToDateProduct), ), ); children.add(_getServingField(appLocalizations)); @@ -144,11 +141,16 @@ class _NutritionPageLoadedState extends State { final Iterable displayableNutrients = _nutritionContainer.getDisplayableNutrients(); - focusNodes = List.generate( - displayableNutrients.length, - (_) => FocusNode(), - growable: false, - ); + if (_focusNodes.length != displayableNutrients.length) { + _focusNodes.clear(); + _focusNodes.addAll( + List.generate( + displayableNutrients.length, + (_) => FocusNode(), + growable: false, + ), + ); + } for (int i = 0; i != displayableNutrients.length; i++) { final OrderedNutrient orderedNutrient = @@ -179,7 +181,7 @@ class _NutritionPageLoadedState extends State { ), ); } else { - focusNodes = []; + _focusNodes.clear(); } return WillPopScope( @@ -189,11 +191,11 @@ class _NutritionPageLoadedState extends State { appBar: SmoothAppBar( title: AutoSizeText( appLocalizations.nutrition_page_title, - maxLines: _product.productName?.isNotEmpty == true ? 1 : 2, + maxLines: upToDateProduct.productName?.isNotEmpty == true ? 1 : 2, ), - subTitle: _product.productName != null + subTitle: upToDateProduct.productName != null ? Text( - _product.productName!, + upToDateProduct.productName!, maxLines: 1, overflow: TextOverflow.ellipsis, ) @@ -207,7 +209,7 @@ class _NutritionPageLoadedState extends State { child: Form( key: _formKey, child: Provider>.value( - value: focusNodes, + value: _focusNodes, child: ListView(children: children), ), ), @@ -401,8 +403,9 @@ class _NutritionPageLoadedState extends State { } } - final Product? changedProduct = - _getChangedProduct(Product(barcode: _barcode)); + final Product? changedProduct = _getChangedProduct( + Product(barcode: barcode), + ); if (changedProduct == null) { if (!mounted) { return false; @@ -418,7 +421,7 @@ class _NutritionPageLoadedState extends State { AnalyticsHelper.trackProductEdit( AnalyticsEditEvents.nutrition_Facts, - _barcode, + barcode, true, ); await BackgroundTaskDetails.addTask( diff --git a/packages/smooth_app/lib/pages/product/portion_calculator.dart b/packages/smooth_app/lib/pages/product/portion_calculator.dart index dc6e72215e1..973679a2848 100644 --- a/packages/smooth_app/lib/pages/product/portion_calculator.dart +++ b/packages/smooth_app/lib/pages/product/portion_calculator.dart @@ -1,9 +1,10 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/generic_lib/dialogs/smooth_alert_dialog.dart'; +import 'package:smooth_app/generic_lib/duration_constants.dart'; import 'package:smooth_app/pages/product/ordered_nutrients_cache.dart'; import 'package:smooth_app/pages/product/portion_helper.dart'; @@ -18,73 +19,129 @@ class PortionCalculator extends StatefulWidget { } class _PortionCalculatorState extends State { - /// Typical size needed for [CupertinoPicker]. - static const double _kItemExtent = DEFAULT_ICON_SIZE; - /// Max value for the picker. static const int _maxGrams = 1000; + static const int _minGrams = 10; - /// Value for the picker, with an initial value. - int _grams = 100; - - late final FixedExtentScrollController _controllerUnit; + final TextEditingController _quantityController = TextEditingController( + text: '100', + ); @override void initState() { super.initState(); - _controllerUnit = FixedExtentScrollController( - initialItem: _fromGramsToIndex(_grams), - ); + _quantityController.addListener(_onInputChanged); } + void _onInputChanged() => setState(() {}); + @override Widget build(BuildContext context) { + final MediaQueryData data = MediaQuery.of(context); final AppLocalizations appLocalizations = AppLocalizations.of(context); + final bool isQuantityValid = _isInputValid(); + return Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Text( - appLocalizations.portion_calculator_description, - textAlign: TextAlign.center, + // We have to manually add a Semantic node here, otherwise the text is + // not read + Semantics( + value: appLocalizations.portion_calculator_description, + excludeSemantics: true, + child: Text( + appLocalizations.portion_calculator_description, + style: Theme.of(context).textTheme.headlineMedium, + ), ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox( - width: _kItemExtent * 2, - height: _kItemExtent * 5, - child: CupertinoPicker.builder( - scrollController: _controllerUnit, - itemExtent: _kItemExtent, - onSelectedItemChanged: (final int index) => - _grams = _fromIndexToGrams(index), - childCount: _fromGramsToIndex(_maxGrams) + 1, - itemBuilder: (final BuildContext context, final int index) => - Text( - '${_fromIndexToGrams(index)}', - style: Theme.of(context).textTheme.bodyMedium, + const SizedBox(height: MEDIUM_SPACE), + Container( + height: (data.textScaleFactor * (SMALL_SPACE * 2 + 15.0)) * 1.2, + padding: const EdgeInsets.symmetric(horizontal: MEDIUM_SPACE), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + children: [ + SizedBox( + width: data.size.width * 0.3, + child: Semantics( + value: + '${_quantityController.text} ${UnitHelper.unitToString(Unit.G)}', + hint: appLocalizations.portion_calculator_accessibility, + textField: true, + excludeSemantics: true, + child: TextField( + controller: _quantityController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp('[0-9]*')), + ], + enableSuggestions: false, + style: const TextStyle(letterSpacing: 5.0), + textAlign: TextAlign.center, + decoration: InputDecoration( + suffixText: UnitHelper.unitToString(Unit.G), + filled: true, + border: const OutlineInputBorder( + borderRadius: ANGULAR_BORDER_RADIUS, + borderSide: BorderSide.none, + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: SMALL_SPACE, + vertical: SMALL_SPACE, + ), + hintText: appLocalizations.portion_calculator_hint, + hintStyle: const TextStyle(letterSpacing: 1.0), + ), + textInputAction: TextInputAction.search, + onSubmitted: (_) { + if (_isInputValid()) { + _computeAndShow(); + } + }, + autofocus: false, + ), ), ), - ), - Text(UnitHelper.unitToString(Unit.G)!), - Padding( - padding: const EdgeInsets.only(left: SMALL_SPACE), - child: ElevatedButton( - onPressed: () async => _computeAndShow(), - child: Text(appLocalizations.calculate), + const SizedBox(width: MEDIUM_SPACE), + AnimatedOpacity( + opacity: isQuantityValid ? 1.0 : 0.5, + duration: SmoothAnimationsDuration.brief, + child: Tooltip( + message: !isQuantityValid + ? appLocalizations.portion_calculator_error( + _minGrams, + _maxGrams, + ) + : '', + excludeFromSemantics: isQuantityValid, + child: SizedBox( + height: double.infinity, + child: ElevatedButton( + onPressed: isQuantityValid + ? () async => _computeAndShow() + : null, + child: Text(appLocalizations.calculate), + ), + ), + ), ), - ), - ], + ], + ), ), ], ); } - int _fromIndexToGrams(final int index) => (index + 1) * 10; - - int _fromGramsToIndex(final int grams) => (grams ~/ 10) - 1; + bool _isInputValid() { + try { + final int value = int.parse(_quantityController.text); + return value >= _minGrams && value <= _maxGrams; + } on FormatException catch (_) { + return false; + } + } /// Computes all the nutrients with a portion factor, and displays a dialog. Future _computeAndShow() async { @@ -100,10 +157,12 @@ class _PortionCalculatorState extends State { if (!mounted) { return; } + + final int quantity = int.parse(_quantityController.text); final PortionHelper helper = PortionHelper( cache.orderedNutrients.nutrients, widget.product.nutriments!, - _grams, + quantity, ); if (helper.isEmpty) { return; @@ -111,7 +170,7 @@ class _PortionCalculatorState extends State { await showDialog( context: context, builder: (final BuildContext context) => SmoothAlertDialog( - title: appLocalizations.portion_calculator_result_title(_grams), + title: appLocalizations.portion_calculator_result_title(quantity), body: Column( children: List.generate( helper.length, @@ -132,4 +191,10 @@ class _PortionCalculatorState extends State { ), ); } + + @override + void dispose() { + _quantityController.addListener(_onInputChanged); + super.dispose(); + } } 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 ba0026aa8f1..ef6636ff7f0 100644 --- a/packages/smooth_app/lib/pages/product/product_compatibility_header.dart +++ b/packages/smooth_app/lib/pages/product/product_compatibility_header.dart @@ -35,9 +35,9 @@ class ProductCompatibilityHeader extends StatelessWidget { decoration: BoxDecoration( color: helper.getHeaderBackgroundColor(isDarkMode), // Ensure that the header has the same circular radius as the SmoothCard. - borderRadius: const BorderRadius.only( - topLeft: ROUNDED_RADIUS, - topRight: ROUNDED_RADIUS, + borderRadius: const BorderRadiusDirectional.only( + topStart: ROUNDED_RADIUS, + topEnd: ROUNDED_RADIUS, ), ), child: Row( diff --git a/packages/smooth_app/lib/pages/product/product_image_gallery_view.dart b/packages/smooth_app/lib/pages/product/product_image_gallery_view.dart index 2df6d2771ab..1521a8bac7c 100644 --- a/packages/smooth_app/lib/pages/product/product_image_gallery_view.dart +++ b/packages/smooth_app/lib/pages/product/product_image_gallery_view.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:provider/provider.dart'; -import 'package:smooth_app/background/background_task_manager.dart'; import 'package:smooth_app/data_models/product_image_data.dart'; +import 'package:smooth_app/data_models/up_to_date_mixin.dart'; import 'package:smooth_app/database/local_database.dart'; import 'package:smooth_app/generic_lib/widgets/smooth_list_tile_card.dart'; import 'package:smooth_app/helpers/analytics_helper.dart'; @@ -29,45 +29,34 @@ class ProductImageGalleryView extends StatefulWidget { _ProductImageGalleryViewState(); } -class _ProductImageGalleryViewState extends State { - late final LocalDatabase _localDatabase; - late final Product _initialProduct; - late Product _product; - +class _ProductImageGalleryViewState extends State + with UpToDateMixin { late List> _selectedImages; - String get _barcode => _initialProduct.barcode!; - @override void initState() { super.initState(); - _initialProduct = widget.product; - _localDatabase = context.read(); - _localDatabase.upToDate.showInterest(_barcode); - } - - @override - void dispose() { - _localDatabase.upToDate.loseInterest(_barcode); - super.dispose(); + initUpToDate(widget.product, context.read()); } @override Widget build(BuildContext context) { - BackgroundTaskManager.getInstance(_localDatabase).run(); // no await final AppLocalizations appLocalizations = AppLocalizations.of(context); final ThemeData theme = Theme.of(context); context.watch(); - _product = _localDatabase.upToDate.getLocalUpToDate(_initialProduct); - _selectedImages = getSelectedImages(_product, ProductQuery.getLanguage()); + refreshUpToDate(); + _selectedImages = getSelectedImages( + upToDateProduct, + ProductQuery.getLanguage(), + ); return SmoothScaffold( appBar: SmoothAppBar( centerTitle: false, title: Text(appLocalizations.edit_product_form_item_photos_title), - subTitle: _product.productName == null + subTitle: upToDateProduct.productName == null ? null : Text( - _product.productName!, + upToDateProduct.productName!, overflow: TextOverflow.ellipsis, maxLines: 1, ), @@ -75,11 +64,14 @@ class _ProductImageGalleryViewState extends State { floatingActionButton: FloatingActionButton.extended( onPressed: () async { AnalyticsHelper.trackProductEdit( - AnalyticsEditEvents.photos, _barcode, true); + AnalyticsEditEvents.photos, + barcode, + true, + ); await confirmAndUploadNewPicture( this, imageField: ImageField.OTHER, - barcode: _barcode, + barcode: barcode, language: ProductQuery.getLanguage(), ); }, @@ -88,7 +80,7 @@ class _ProductImageGalleryViewState extends State { ), body: RefreshIndicator( onRefresh: () async => ProductRefresher().fetchAndRefresh( - barcode: _barcode, + barcode: barcode, widget: this, ), child: ListView.builder( @@ -124,7 +116,7 @@ class _ProductImageGalleryViewState extends State { MaterialPageRoute( builder: (_) => ProductImageSwipeableView( initialImageIndex: initialImageIndex, - product: _product, + product: upToDateProduct, ), ), ); diff --git a/packages/smooth_app/lib/pages/product/product_image_swipeable_view.dart b/packages/smooth_app/lib/pages/product/product_image_swipeable_view.dart index d668ea1ae33..ed507b99a8c 100644 --- a/packages/smooth_app/lib/pages/product/product_image_swipeable_view.dart +++ b/packages/smooth_app/lib/pages/product/product_image_swipeable_view.dart @@ -4,6 +4,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:provider/provider.dart'; import 'package:smooth_app/data_models/product_image_data.dart'; +import 'package:smooth_app/data_models/up_to_date_mixin.dart'; import 'package:smooth_app/database/local_database.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/generic_lib/widgets/smooth_back_button.dart'; @@ -39,25 +40,19 @@ class ProductImageSwipeableView extends StatefulWidget { _ProductImageSwipeableViewState(); } -class _ProductImageSwipeableViewState extends State { - late final LocalDatabase _localDatabase; +class _ProductImageSwipeableViewState extends State + with UpToDateMixin { //Making use of [ValueNotifier] such that to avoid performance issues //while swiping between pages by making sure only [Text] widget for product title is rebuilt late final ValueNotifier _currentImageDataIndex; late List> _selectedImages; late PageController _controller; - late final Product _initialProduct; - late Product _product; late OpenFoodFactsLanguage _currentLanguage; - String get _barcode => _initialProduct.barcode!; - @override void initState() { super.initState(); - _initialProduct = widget.product; - _localDatabase = context.read(); - _localDatabase.upToDate.showInterest(_barcode); + initUpToDate(widget.product, context.read()); _controller = PageController( initialPage: widget.initialImageIndex, ); @@ -65,18 +60,12 @@ class _ProductImageSwipeableViewState extends State { _currentLanguage = ProductQuery.getLanguage(); } - @override - void dispose() { - _localDatabase.upToDate.loseInterest(_barcode); - super.dispose(); - } - @override Widget build(BuildContext context) { final AppLocalizations appLocalizations = AppLocalizations.of(context); context.watch(); - _product = _localDatabase.upToDate.getLocalUpToDate(_initialProduct); - _selectedImages = getSelectedImages(_product, _currentLanguage); + refreshUpToDate(); + _selectedImages = getSelectedImages(upToDateProduct, _currentLanguage); if (widget.imageField != null) { _selectedImages.removeWhere( ( diff --git a/packages/smooth_app/lib/pages/product/product_image_viewer.dart b/packages/smooth_app/lib/pages/product/product_image_viewer.dart index 009c98208d5..6fcddc9c9ad 100644 --- a/packages/smooth_app/lib/pages/product/product_image_viewer.dart +++ b/packages/smooth_app/lib/pages/product/product_image_viewer.dart @@ -10,6 +10,7 @@ import 'package:photo_view/photo_view.dart'; import 'package:provider/provider.dart'; import 'package:smooth_app/background/background_task_unselect.dart'; import 'package:smooth_app/data_models/product_image_data.dart'; +import 'package:smooth_app/data_models/up_to_date_mixin.dart'; import 'package:smooth_app/database/dao_int.dart'; import 'package:smooth_app/database/local_database.dart'; import 'package:smooth_app/database/transient_file.dart'; @@ -43,35 +44,23 @@ class ProductImageViewer extends StatefulWidget { State createState() => _ProductImageViewerState(); } -class _ProductImageViewerState extends State { - late Product _product; - late final Product _initialProduct; - late final LocalDatabase _localDatabase; +class _ProductImageViewerState extends State + with UpToDateMixin { late ProductImageData _imageData; - String get _barcode => _initialProduct.barcode!; - @override void initState() { super.initState(); - _initialProduct = widget.product; - _localDatabase = context.read(); - _localDatabase.upToDate.showInterest(_barcode); - } - - @override - void dispose() { - _localDatabase.upToDate.loseInterest(_barcode); - super.dispose(); + initUpToDate(widget.product, context.read()); } @override Widget build(BuildContext context) { final AppLocalizations appLocalizations = AppLocalizations.of(context); context.watch(); - _product = _localDatabase.upToDate.getLocalUpToDate(_initialProduct); + refreshUpToDate(); _imageData = getProductImageData( - _product, + upToDateProduct, widget.imageField, widget.language, forceLanguage: true, @@ -79,7 +68,7 @@ class _ProductImageViewerState extends State { final ImageProvider? imageProvider = _getTransientFile().getImageProvider(); final Iterable selectedLanguages = getProductImageLanguages( - _product, + upToDateProduct, widget.imageField, ); @@ -188,7 +177,7 @@ class _ProductImageViewerState extends State { child: Padding( padding: const EdgeInsets.symmetric(horizontal: SMALL_SPACE), child: ProductImageServerButton( - product: _product, + product: upToDateProduct, imageField: widget.imageField, language: widget.language, isLoggedInMandatory: true, @@ -200,7 +189,7 @@ class _ProductImageViewerState extends State { padding: const EdgeInsets.symmetric(horizontal: SMALL_SPACE), child: ProductImageLocalButton( firstPhoto: imageProvider == null, - barcode: _barcode, + barcode: barcode, imageField: widget.imageField, language: widget.language, isLoggedInMandatory: true, @@ -278,7 +267,7 @@ class _ProductImageViewerState extends State { imageFile = await downloadImageUrl( context, imageUrl, - DaoInt(_localDatabase), + DaoInt(context.read()), ); if (imageFile != null) { return _openCropPage(navigatorState, imageFile); @@ -289,7 +278,7 @@ class _ProductImageViewerState extends State { TransientFile _getTransientFile() => TransientFile.fromProductImageData( _imageData, - _barcode, + barcode, widget.language, ); @@ -322,12 +311,12 @@ class _ProductImageViewerState extends State { ); if (confirmed == true) { await BackgroundTaskUnselect.addTask( - _barcode, + barcode, imageField: widget.imageField, widget: this, language: widget.language, ); - _localDatabase.notifyListeners(); + context.read().notifyListeners(); navigatorState.pop(); } } @@ -343,7 +332,7 @@ class _ProductImageViewerState extends State { MaterialPageRoute( builder: (BuildContext context) => CropPage( language: widget.language, - barcode: _product.barcode!, + barcode: barcode, imageField: _imageData.imageField, inputFile: imageFile, imageId: imageId, @@ -364,7 +353,7 @@ class _ProductImageViewerState extends State { final File? imageFile = await downloadImageUrl( context, ImageHelper.getUploadedImageUrl( - _product.barcode!, + barcode, imageId, ImageSize.ORIGINAL, ), @@ -385,10 +374,10 @@ class _ProductImageViewerState extends State { } ProductImage? _getBestProductImage() { - if (_product.images == null) { + if (upToDateProduct.images == null) { return null; } - for (final ProductImage productImage in _product.images!) { + for (final ProductImage productImage in upToDateProduct.images!) { if (productImage.field != _imageData.imageField) { continue; } diff --git a/packages/smooth_app/lib/pages/product/product_question_card.dart b/packages/smooth_app/lib/pages/product/product_question_card.dart index 212913a8ff4..5c39b09c9ff 100644 --- a/packages/smooth_app/lib/pages/product/product_question_card.dart +++ b/packages/smooth_app/lib/pages/product/product_question_card.dart @@ -39,7 +39,7 @@ class ProductQuestionCard extends StatelessWidget { final ThemeData theme = Theme.of(context); final bool isDarkMode = theme.brightness == Brightness.dark; return Padding( - padding: const EdgeInsets.only(left: SMALL_SPACE), + padding: const EdgeInsetsDirectional.only(start: SMALL_SPACE), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/packages/smooth_app/lib/pages/product/simple_input_number_field.dart b/packages/smooth_app/lib/pages/product/simple_input_number_field.dart index 1b728a43fc1..09e074c226b 100644 --- a/packages/smooth_app/lib/pages/product/simple_input_number_field.dart +++ b/packages/smooth_app/lib/pages/product/simple_input_number_field.dart @@ -39,7 +39,7 @@ class SimpleInputNumberField extends StatelessWidget { @override Widget build(BuildContext context) => Padding( - padding: const EdgeInsets.only(left: LARGE_SPACE), + padding: const EdgeInsetsDirectional.only(start: LARGE_SPACE), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, diff --git a/packages/smooth_app/lib/pages/product/simple_input_text_field.dart b/packages/smooth_app/lib/pages/product/simple_input_text_field.dart index f9720a44ec3..e159e4c1774 100644 --- a/packages/smooth_app/lib/pages/product/simple_input_text_field.dart +++ b/packages/smooth_app/lib/pages/product/simple_input_text_field.dart @@ -46,7 +46,7 @@ class SimpleInputTextField extends StatelessWidget { ); return Padding( - padding: const EdgeInsets.only(left: LARGE_SPACE), + padding: const EdgeInsetsDirectional.only(start: LARGE_SPACE), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, diff --git a/packages/smooth_app/lib/pages/product/summary_attribute_group.dart b/packages/smooth_app/lib/pages/product/summary_attribute_group.dart index 21a234ca226..a270db98b18 100644 --- a/packages/smooth_app/lib/pages/product/summary_attribute_group.dart +++ b/packages/smooth_app/lib/pages/product/summary_attribute_group.dart @@ -25,7 +25,7 @@ class SummaryAttributeGroup extends StatelessWidget { groupName: groupName, ), Container( - alignment: Alignment.topLeft, + alignment: AlignmentDirectional.topStart, child: Wrap( runSpacing: 16, children: attributeChips, @@ -48,7 +48,7 @@ class _SummaryAttributeGroupHeader extends StatelessWidget { @override Widget build(BuildContext context) => groupName != null ? Container( - alignment: Alignment.topLeft, + alignment: AlignmentDirectional.topStart, padding: const EdgeInsetsDirectional.only( top: SMALL_SPACE, bottom: LARGE_SPACE, diff --git a/packages/smooth_app/lib/pages/product/summary_card.dart b/packages/smooth_app/lib/pages/product/summary_card.dart index 87786de0eab..81819e0b244 100644 --- a/packages/smooth_app/lib/pages/product/summary_card.dart +++ b/packages/smooth_app/lib/pages/product/summary_card.dart @@ -6,6 +6,7 @@ import 'package:smooth_app/cards/data_cards/score_card.dart'; import 'package:smooth_app/cards/product_cards/product_title_card.dart'; import 'package:smooth_app/data_models/continuous_scan_model.dart'; import 'package:smooth_app/data_models/product_preferences.dart'; +import 'package:smooth_app/data_models/up_to_date_mixin.dart'; import 'package:smooth_app/data_models/user_preferences.dart'; import 'package:smooth_app/database/local_database.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; @@ -47,6 +48,7 @@ class SummaryCard extends StatefulWidget { this.isSettingClickable = true, this.isProductEditable = true, this.attributeGroupsClickable = true, + this.padding, }); final Product _product; @@ -74,51 +76,41 @@ class SummaryCard extends StatefulWidget { /// If true, all chips / groups are clickable final bool attributeGroupsClickable; + final EdgeInsetsGeometry? padding; + @override State createState() => _SummaryCardState(); } -class _SummaryCardState extends State { - late Product _product; - late final Product _initialProduct; - late final LocalDatabase _localDatabase; - +class _SummaryCardState extends State with UpToDateMixin { // For some reason, special case for "label" attributes final Set _attributesToExcludeIfStatusIsUnknown = {}; @override void initState() { super.initState(); - _initialProduct = widget._product; - _localDatabase = context.read(); - _localDatabase.upToDate.showInterest(_initialProduct.barcode!); - if (ProductIncompleteCard.isProductIncomplete(_initialProduct)) { + initUpToDate(widget._product, context.read()); + if (ProductIncompleteCard.isProductIncomplete(initialProduct)) { AnalyticsHelper.trackEvent( AnalyticsEvent.showFastTrackProductEditCard, - barcode: _initialProduct.barcode, + barcode: barcode, ); } } - @override - void dispose() { - _localDatabase.upToDate.loseInterest(_initialProduct.barcode!); - super.dispose(); - } - @override Widget build(BuildContext context) { context.watch(); - _product = _localDatabase.upToDate.getLocalUpToDate(_initialProduct); + refreshUpToDate(); if (widget.isFullVersion) { return buildProductSmoothCard( header: ProductCompatibilityHeader( - product: _product, + product: upToDateProduct, productPreferences: widget._productPreferences, isSettingClickable: widget.isSettingClickable, ), body: Padding( - padding: SMOOTH_CARD_PADDING, + padding: widget.padding ?? SMOOTH_CARD_PADDING, child: _buildSummaryCardContent(context), ), margin: EdgeInsets.zero, @@ -132,10 +124,11 @@ class _SummaryCardState extends State { Widget _buildLimitedSizeSummaryCard(double parentHeight) { return Padding( - padding: const EdgeInsets.symmetric( - horizontal: SMALL_SPACE, - vertical: VERY_SMALL_SPACE, - ), + padding: widget.padding ?? + const EdgeInsets.symmetric( + horizontal: SMALL_SPACE, + vertical: VERY_SMALL_SPACE, + ), child: Stack( children: [ ClipRRect( @@ -146,7 +139,7 @@ class _SummaryCardState extends State { maxHeight: double.infinity, child: buildProductSmoothCard( header: ProductCompatibilityHeader( - product: _product, + product: upToDateProduct, productPreferences: widget._productPreferences, isSettingClickable: widget.isSettingClickable, ), @@ -173,7 +166,7 @@ class _SummaryCardState extends State { ), child: Center( child: Text( - AppLocalizations.of(context).tab_for_more, + AppLocalizations.of(context).tap_for_more, style: Theme.of(context).primaryTextTheme.bodyLarge?.copyWith( color: PRIMARY_BLUE_COLOR, @@ -195,7 +188,7 @@ class _SummaryCardState extends State { final List excludedAttributeIds = userPreferences.getExcludedAttributeIds(); final List scoreAttributes = getPopulatedAttributes( - _product, + upToDateProduct, SCORE_ATTRIBUTE_IDS, excludedAttributeIds, ); @@ -205,7 +198,7 @@ class _SummaryCardState extends State { // First, a virtual group with mandatory attributes of all groups final List attributeChips = _buildAttributeChips( getMandatoryAttributes( - _product, + upToDateProduct, _ATTRIBUTE_GROUP_ORDER, _attributesToExcludeIfStatusIsUnknown, widget._productPreferences, @@ -223,10 +216,11 @@ class _SummaryCardState extends State { } // Then, all groups, each with very important and important attributes for (final String groupId in _ATTRIBUTE_GROUP_ORDER) { - if (_product.attributeGroups == null) { + if (upToDateProduct.attributeGroups == null) { continue; } - final Iterable groupIterable = _product.attributeGroups! + final Iterable groupIterable = upToDateProduct + .attributeGroups! .where((AttributeGroup group) => group.id == groupId); if (groupIterable.isEmpty) { @@ -266,13 +260,13 @@ class _SummaryCardState extends State { String? categoryTag; String? categoryLabel; final List? labels = - _product.categoriesTagsInLanguages?[ProductQuery.getLanguage()]; - final List? tags = _product.categoriesTags; + upToDateProduct.categoriesTagsInLanguages?[ProductQuery.getLanguage()]; + final List? tags = upToDateProduct.categoriesTags; if (tags != null && labels != null && tags.isNotEmpty && tags.length == labels.length) { - categoryTag = _product.comparedToCategory; + categoryTag = upToDateProduct.comparedToCategory; if (categoryTag == null || blackListedCategories.contains(categoryTag)) { // fallback algorithm int index = tags.length - 1; @@ -292,7 +286,8 @@ class _SummaryCardState extends State { } } } - final List statesTags = _product.statesTags ?? List.empty(); + final List statesTags = + upToDateProduct.statesTags ?? List.empty(); final List summaryCardButtons = []; @@ -302,7 +297,7 @@ class _SummaryCardState extends State { .contains(ProductState.CATEGORIES_COMPLETED.toBeCompletedTag)) { summaryCardButtons.add( AddSimpleInputButton( - product: _product, + product: upToDateProduct, helper: SimpleInputPageCategoryHelper(), ), ); @@ -316,7 +311,7 @@ class _SummaryCardState extends State { iconData: Icons.leaderboard, onPressed: () async => ProductQueryPageHelper().openBestChoice( name: categoryLabel!, - localDatabase: _localDatabase, + localDatabase: context.read(), productQuery: CategoryProductQuery(categoryTag!), context: context, ), @@ -334,10 +329,7 @@ class _SummaryCardState extends State { addPanelButton( editor.getLabel(localizations), onPressed: () async => widget.isProductEditable - ? editor.edit( - context: context, - product: _product, - ) + ? editor.edit(context: context, product: upToDateProduct) : null, ), ); @@ -347,24 +339,24 @@ class _SummaryCardState extends State { return Column( children: [ ProductTitleCard( - _product, + upToDateProduct, widget.isFullVersion, isRemovable: widget.isRemovable, onRemove: (BuildContext context) async { HideableContainerState.of(context).hide(() async { final ContinuousScanModel model = context.read(); - await model.removeBarcode(_product.barcode!); + await model.removeBarcode(barcode); // Vibrate twice SmoothHapticFeedback.confirm(); }); }, ), - if (ProductIncompleteCard.isProductIncomplete(_product)) - ProductIncompleteCard(product: _product), + if (ProductIncompleteCard.isProductIncomplete(upToDateProduct)) + ProductIncompleteCard(product: upToDateProduct), ..._getAttributes(scoreAttributes), - if (widget.isFullVersion) ProductQuestionsWidget(_product), + if (widget.isFullVersion) ProductQuestionsWidget(upToDateProduct), attributesContainer, ...summaryCardButtons, ], @@ -451,7 +443,7 @@ class _SummaryCardState extends State { bool _isAttributeOpeningAllowed(Attribute attribute) => widget.isFullVersion && - _product.knowledgePanels != null && + upToDateProduct.knowledgePanels != null && attribute.panelId != null; Future _openFullKnowledgePanel({ @@ -467,7 +459,7 @@ class _SummaryCardState extends State { } final KnowledgePanel? knowledgePanel = KnowledgePanelWidget.getKnowledgePanel( - _product, + upToDateProduct, panelId, ); if (knowledgePanel == null) { @@ -479,7 +471,7 @@ class _SummaryCardState extends State { MaterialPageRoute( builder: (BuildContext context) => KnowledgePanelPage( panelId: panelId, - product: _product, + product: upToDateProduct, ), ), ); diff --git a/packages/smooth_app/lib/pages/product_list_user_dialog_helper.dart b/packages/smooth_app/lib/pages/product_list_user_dialog_helper.dart index 7e9f7adac4a..648899f37bc 100644 --- a/packages/smooth_app/lib/pages/product_list_user_dialog_helper.dart +++ b/packages/smooth_app/lib/pages/product_list_user_dialog_helper.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:smooth_app/data_models/product_list.dart'; import 'package:smooth_app/database/dao_product_list.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; @@ -22,7 +23,7 @@ class ProductListUserDialogHelper { final TextEditingController textEditingController = TextEditingController(); final GlobalKey formKey = GlobalKey(); - final List lists = await daoProductList.getUserLists(); + final List lists = daoProductList.getUserLists(); final String? title = await showDialog( context: context, builder: (final BuildContext context) { @@ -93,7 +94,7 @@ class ProductListUserDialogHelper { final String initialName = initialProductList.parameters; textEditingController.text = initialName; - final List lists = await daoProductList.getUserLists(); + final List lists = daoProductList.getUserLists(); final String? newName = await showDialog( context: context, builder: (final BuildContext context) => SmoothAlertDialog( @@ -185,7 +186,7 @@ class ProductListUserDialogHelper { final BuildContext context, final Set barcodes, ) async { - final List lists = await daoProductList.getUserLists(); + final List lists = daoProductList.getUserLists(); if (lists.isEmpty) { final bool? newListCreated = await showDialog( @@ -198,8 +199,9 @@ class ProductListUserDialogHelper { return false; } - final List selectedLists = await daoProductList.getUserLists( - withBarcodes: barcodes.toList(growable: false), + final List selectedLists = + await daoProductList.getUserListsWithBarcodes( + barcodes.toList(growable: false), ); return showDialog( @@ -324,8 +326,8 @@ class _UserEmptyListsState extends State<_UserEmptyLists> { return SmoothAlertDialog( body: Column( children: [ - const Icon(Icons.warning), - const SizedBox(height: VERY_SMALL_SPACE), + SvgPicture.asset('assets/misc/error.svg'), + const SizedBox(height: LARGE_SPACE), Container( alignment: Alignment.center, padding: const EdgeInsets.symmetric( @@ -337,12 +339,15 @@ class _UserEmptyListsState extends State<_UserEmptyLists> { textAlign: TextAlign.center, style: const TextStyle( fontWeight: FontWeight.bold, + fontSize: 18.0, ), ), ), - const SizedBox(height: LARGE_SPACE * 2.5), + const SizedBox(height: LARGE_SPACE), ], ), + actionsAxis: Axis.vertical, + actionsOrder: SmoothButtonsBarOrder.auto, positiveAction: SmoothActionButton( onPressed: () async { final ProductList? productList = diff --git a/packages/smooth_app/lib/pages/scan/scan_header.dart b/packages/smooth_app/lib/pages/scan/scan_header.dart index 32fb0571e87..e5bce002254 100644 --- a/packages/smooth_app/lib/pages/scan/scan_header.dart +++ b/packages/smooth_app/lib/pages/scan/scan_header.dart @@ -96,7 +96,7 @@ class _ScanHeaderState extends State { title: ProductQueryPageHelper .getProductListLabel( model.productList, - context, + appLocalizations, ), ), ), diff --git a/packages/smooth_app/lib/pages/scan/scan_page.dart b/packages/smooth_app/lib/pages/scan/scan_page.dart index e794f89909f..89c986e159f 100644 --- a/packages/smooth_app/lib/pages/scan/scan_page.dart +++ b/packages/smooth_app/lib/pages/scan/scan_page.dart @@ -97,7 +97,7 @@ class _ScanPageState extends State { Expanded( flex: _carouselHeightPct, child: Padding( - padding: const EdgeInsetsDirectional.only(bottom: 10), + padding: const EdgeInsetsDirectional.only(bottom: 10.0), child: SmoothProductCarousel( containSearchCard: true, onPageChangedTo: (int page, String? barcode) async { @@ -187,11 +187,9 @@ class _PermissionDeniedCard extends StatelessWidget { return Container( alignment: Alignment.topCenter, constraints: BoxConstraints.tightForFinite( - width: constraints.maxWidth * - SmoothProductCarousel.carouselViewPortFraction, + width: constraints.maxWidth, height: math.min(constraints.maxHeight * 0.9, 200), ), - padding: SmoothProductCarousel.carouselItemInternalPadding, child: SmoothCard( padding: const EdgeInsetsDirectional.only( top: 10.0, 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 1d35f7d120f..a4a362b6681 100644 --- a/packages/smooth_app/lib/pages/scan/scan_product_card.dart +++ b/packages/smooth_app/lib/pages/scan/scan_product_card.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:provider/provider.dart'; import 'package:smooth_app/data_models/product_preferences.dart'; +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'; @@ -34,6 +35,9 @@ class ScanProductCard extends StatelessWidget { product, productPreferences, attributeGroupsClickable: false, + padding: const EdgeInsets.symmetric( + vertical: VERY_SMALL_SPACE, + ), ), ), ), diff --git a/packages/smooth_app/lib/pages/scan/search_history_view.dart b/packages/smooth_app/lib/pages/scan/search_history_view.dart index 78eb1025e98..47877f468b5 100644 --- a/packages/smooth_app/lib/pages/scan/search_history_view.dart +++ b/packages/smooth_app/lib/pages/scan/search_history_view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:provider/provider.dart'; import 'package:smooth_app/database/dao_string_list.dart'; import 'package:smooth_app/database/local_database.dart'; @@ -43,47 +44,69 @@ class _SearchHistoryViewState extends State { } Widget _buildSearchHistoryTile(BuildContext context, String query) { + final AppLocalizations localizations = AppLocalizations.of(context); + return Dismissible( key: Key(query), direction: DismissDirection.endToStart, onDismissed: (DismissDirection direction) async => _handleDismissed(context, query), - background: Container(color: RED_COLOR), - child: ListTile( - leading: const Padding( - padding: EdgeInsetsDirectional.only(top: VERY_SMALL_SPACE), - child: Icon(Icons.search, size: 18.0), + background: Container( + color: RED_COLOR, + alignment: AlignmentDirectional.centerEnd, + padding: const EdgeInsetsDirectional.only(end: LARGE_SPACE * 2), + child: const Icon( + Icons.delete, + color: Colors.white, ), - trailing: InkWell( - customBorder: const CircleBorder(), - onTap: () { - final TextEditingController controller = - Provider.of( - context, - listen: false, - ); + ), + child: InkWell( + onTap: () => widget.onTap?.call(query), + child: Padding( + padding: const EdgeInsetsDirectional.only(start: 18.0, end: 13.0), + child: ListTile( + leading: const Padding( + padding: EdgeInsetsDirectional.only(top: VERY_SMALL_SPACE), + child: Icon( + Icons.search, + size: 18.0, + ), + ), + trailing: InkWell( + customBorder: const CircleBorder(), + onTap: () { + final TextEditingController controller = + Provider.of( + context, + listen: false, + ); - controller.text = query; - controller.selection = - TextSelection.fromPosition(TextPosition(offset: query.length)); + controller.text = query; + controller.selection = TextSelection.fromPosition( + TextPosition(offset: query.length)); - // If the keyboard is hidden, show it. - if (View.of(context).viewInsets.bottom == 0) { - widget.focusNode?.unfocus(); + // If the keyboard is hidden, show it. + if (View.of(context).viewInsets.bottom == 0) { + widget.focusNode?.unfocus(); - WidgetsBinding.instance.addPostFrameCallback((_) { - FocusScope.of(context).requestFocus(widget.focusNode); - }); - } - }, - child: const Padding( - padding: EdgeInsets.all(SMALL_SPACE), - child: Icon(Icons.edit, size: 18.0), + WidgetsBinding.instance.addPostFrameCallback((_) { + FocusScope.of(context).requestFocus(widget.focusNode); + }); + } + }, + child: Tooltip( + message: localizations.search_history_item_edit_tooltip, + enableFeedback: true, + child: const Padding( + padding: EdgeInsets.all(8.0), + child: Icon(Icons.edit, size: 18.0), + ), + ), + ), + minLeadingWidth: 10.0, + title: Text(query, style: const TextStyle(fontSize: 20.0)), ), ), - minLeadingWidth: 10, - title: Text(query, style: const TextStyle(fontSize: 20.0)), - onTap: () => widget.onTap?.call(query), ), ); } diff --git a/packages/smooth_app/lib/pages/scan/search_page.dart b/packages/smooth_app/lib/pages/scan/search_page.dart index a1b724a92a0..25882098749 100644 --- a/packages/smooth_app/lib/pages/scan/search_page.dart +++ b/packages/smooth_app/lib/pages/scan/search_page.dart @@ -231,7 +231,8 @@ class _SearchFieldState extends State { vertical: 17.0, ), hintText: localizations.search, - suffixIcon: widget.showClearButton ? _buildClearButton() : null, + suffixIcon: + widget.showClearButton ? _buildClearButton(localizations) : null, ); const TextStyle textStyle = TextStyle(fontSize: 18.0); @@ -282,19 +283,32 @@ class _SearchFieldState extends State { } } - Widget _buildClearButton() { + Widget _buildClearButton(AppLocalizations localizations) { return Padding( padding: const EdgeInsetsDirectional.only(end: MEDIUM_SPACE), - child: IconButton( - onPressed: _handleClear, - icon: AnimatedCrossFade( - duration: SmoothAnimationsDuration.brief, - crossFadeState: - _isEmpty ? CrossFadeState.showFirst : CrossFadeState.showSecond, - // Closes the page. - firstChild: const Icon(Icons.close), - // Clears the text. - secondChild: const Icon(Icons.cancel), + child: ClipOval( + child: Material( + type: MaterialType.transparency, + child: IconButton( + tooltip: localizations.clear_search, + onPressed: _handleClear, + icon: AnimatedCrossFade( + duration: SmoothAnimationsDuration.short, + crossFadeState: _isEmpty + ? CrossFadeState.showFirst + : CrossFadeState.showSecond, + // Closes the page. + firstChild: Icon( + Icons.close, + semanticLabel: localizations.clear_search, + ), + // Clears the text. + secondChild: Icon( + Icons.cancel, + semanticLabel: localizations.clear_search, + ), + ), + ), ), ), ); diff --git a/packages/smooth_app/lib/pages/scan/smooth_barcode_scanner_visor.dart b/packages/smooth_app/lib/pages/scan/smooth_barcode_scanner_visor.dart deleted file mode 100644 index c06335eb5fc..00000000000 --- a/packages/smooth_app/lib/pages/scan/smooth_barcode_scanner_visor.dart +++ /dev/null @@ -1,120 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:smooth_app/helpers/app_helper.dart'; - -// TODO(m123): Remove this file and place it into packages/scanner/shared/src - -class SmoothBarcodeScannerVisor extends StatelessWidget { - const SmoothBarcodeScannerVisor({super.key}); - - @override - Widget build(BuildContext context) => SizedBox.expand( - child: CustomPaint( - painter: _ScanVisorPainter(), - child: Center( - child: SvgPicture.asset( - 'assets/icons/visor_icon.svg', - width: 35.0, - height: 32.0, - package: AppHelper.APP_PACKAGE, - ), - ), - ), - ); -} - -class _ScanVisorPainter extends CustomPainter { - _ScanVisorPainter(); - - static const double strokeWidth = 3.0; - static const double _fullCornerSize = 31.0; - static const double _halfCornerSize = _fullCornerSize / 2; - static const Radius _borderRadius = Radius.circular(_halfCornerSize); - - final Paint _paint = Paint() - ..strokeWidth = strokeWidth - ..color = Colors.white - ..style = PaintingStyle.stroke; - - @override - void paint(Canvas canvas, Size size) { - final Rect rect = Rect.fromLTRB(0.0, 0.0, size.width, size.height); - canvas.drawPath(getPath(rect, false), _paint); - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) => false; - - /// Returns a path to draw the visor - /// [includeLineBetweenCorners] will draw lines between each corner, instead - /// of moving the cursor - static Path getPath(Rect rect, bool includeLineBetweenCorners) { - final double bottomPosition; - if (includeLineBetweenCorners) { - bottomPosition = rect.bottom - strokeWidth; - } else { - bottomPosition = rect.bottom; - } - - final Path path = Path() - // Top left - ..moveTo(rect.left, rect.top + _fullCornerSize) - ..lineTo(rect.left, rect.top + _halfCornerSize) - ..arcToPoint( - Offset(rect.left + _halfCornerSize, rect.top), - radius: _borderRadius, - ) - ..lineTo(rect.left + _fullCornerSize, rect.top); - - // Top right - if (includeLineBetweenCorners) { - path.lineTo(rect.right - _fullCornerSize, rect.top); - } else { - path.moveTo(rect.right - _fullCornerSize, rect.top); - } - - path - ..lineTo(rect.right - _halfCornerSize, rect.top) - ..arcToPoint( - Offset(rect.right, _halfCornerSize), - radius: _borderRadius, - ) - ..lineTo(rect.right, rect.top + _fullCornerSize); - - // Bottom right - if (includeLineBetweenCorners) { - path.lineTo(rect.right, bottomPosition - _fullCornerSize); - } else { - path.moveTo(rect.right, bottomPosition - _fullCornerSize); - } - - path - ..lineTo(rect.right, bottomPosition - _halfCornerSize) - ..arcToPoint( - Offset(rect.right - _halfCornerSize, bottomPosition), - radius: _borderRadius, - ) - ..lineTo(rect.right - _fullCornerSize, bottomPosition); - - // Bottom left - if (includeLineBetweenCorners) { - path.lineTo(rect.left + _fullCornerSize, bottomPosition); - } else { - path.moveTo(rect.left + _fullCornerSize, bottomPosition); - } - - path - ..lineTo(rect.left + _halfCornerSize, bottomPosition) - ..arcToPoint( - Offset(rect.left, bottomPosition - _halfCornerSize), - radius: _borderRadius, - ) - ..lineTo(rect.left, bottomPosition - _fullCornerSize); - - if (includeLineBetweenCorners) { - path.lineTo(rect.left, rect.top + _halfCornerSize); - } - - return path; - } -} diff --git a/packages/smooth_app/lib/pages/user_management/login_page.dart b/packages/smooth_app/lib/pages/user_management/login_page.dart index b0ab1a2669e..ad568b4eb5f 100644 --- a/packages/smooth_app/lib/pages/user_management/login_page.dart +++ b/packages/smooth_app/lib/pages/user_management/login_page.dart @@ -113,9 +113,9 @@ class _LoginPageState extends State with TraceableClientMixin { child: Container( alignment: Alignment.topCenter, width: double.infinity, - padding: EdgeInsets.only( - left: size.width * 0.15, - right: size.width * 0.15, + padding: EdgeInsetsDirectional.only( + start: size.width * 0.15, + end: size.width * 0.15, bottom: size.width * 0.05, ), child: AutofillGroup( diff --git a/packages/smooth_app/lib/pages/user_management/sign_up_page.dart b/packages/smooth_app/lib/pages/user_management/sign_up_page.dart index 09e5d92c7b6..6541c4ba540 100644 --- a/packages/smooth_app/lib/pages/user_management/sign_up_page.dart +++ b/packages/smooth_app/lib/pages/user_management/sign_up_page.dart @@ -11,6 +11,7 @@ import 'package:smooth_app/generic_lib/loading_dialog.dart'; import 'package:smooth_app/generic_lib/widgets/smooth_text_form_field.dart'; import 'package:smooth_app/helpers/analytics_helper.dart'; import 'package:smooth_app/helpers/user_management_helper.dart'; +import 'package:smooth_app/query/product_query.dart'; import 'package:smooth_app/widgets/smooth_app_bar.dart'; import 'package:smooth_app/widgets/smooth_scaffold.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -334,6 +335,8 @@ class _SignUpPageState extends State with TraceableClientMixin { email: _emailController.trimmedText, newsletter: _subscribe, orgName: _foodProducer ? _brandController.trimmedText : null, + country: ProductQuery.getCountry(), + language: ProductQuery.getLanguage(), ), title: appLocalisations.sign_up_page_action_doing_it, ); diff --git a/packages/smooth_app/lib/widgets/smooth_product_carousel.dart b/packages/smooth_app/lib/widgets/smooth_product_carousel.dart index 79497163932..9846521feb4 100644 --- a/packages/smooth_app/lib/widgets/smooth_product_carousel.dart +++ b/packages/smooth_app/lib/widgets/smooth_product_carousel.dart @@ -7,6 +7,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:provider/provider.dart'; +import 'package:scanner_shared/scanner_shared.dart' hide EMPTY_WIDGET; import 'package:smooth_app/cards/product_cards/smooth_product_base_card.dart'; import 'package:smooth_app/cards/product_cards/smooth_product_card_error.dart'; import 'package:smooth_app/cards/product_cards/smooth_product_card_loading.dart'; @@ -32,22 +33,13 @@ class SmoothProductCarousel extends StatefulWidget { final bool containSearchCard; final Function(int page, String? productBarcode)? onPageChangedTo; - static const EdgeInsetsGeometry carouselItemHorizontalPadding = - EdgeInsetsDirectional.only( - top: LARGE_SPACE, - start: VERY_LARGE_SPACE, - end: VERY_LARGE_SPACE, - bottom: VERY_LARGE_SPACE, - ); - static const EdgeInsetsGeometry carouselItemInternalPadding = - EdgeInsets.symmetric(horizontal: 2.0); - static const double carouselViewPortFraction = 0.91; - @override State createState() => _SmoothProductCarouselState(); } class _SmoothProductCarouselState extends State { + static const double HORIZONTAL_SPACE_BETWEEN_CARDS = 5.0; + final CarouselController _controller = CarouselController(); List barcodes = []; bool _returnToSearchCard = false; @@ -128,17 +120,21 @@ class _SmoothProductCarouselState extends State { itemCount: barcodes.length + _searchCardAdjustment, itemBuilder: (BuildContext context, int itemIndex, int itemRealIndex) { - return Padding( - padding: SmoothProductCarousel.carouselItemInternalPadding, - child: widget.containSearchCard && itemIndex == 0 - ? SearchCard(height: constraints.maxHeight) - : _getWidget(itemIndex - _searchCardAdjustment), + return SizedBox.expand( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: HORIZONTAL_SPACE_BETWEEN_CARDS, + ), + child: widget.containSearchCard && itemIndex == 0 + ? SearchCard(height: constraints.maxHeight) + : _getWidget(itemIndex - _searchCardAdjustment), + ), ); }, carouselController: _controller, options: CarouselOptions( enlargeCenterPage: false, - viewportFraction: SmoothProductCarousel.carouselViewPortFraction, + viewportFraction: _computeViewPortFraction(), height: constraints.maxHeight, enableInfiniteScroll: false, onPageChanged: (int index, CarouselPageChangedReason reason) { @@ -206,6 +202,15 @@ class _SmoothProductCarouselState extends State { ); } } + + double _computeViewPortFraction() { + final double screenWidth = MediaQuery.of(context).size.width; + return (screenWidth - + (SmoothBarcodeScannerVisor.CORNER_PADDING * 2) - + (SmoothBarcodeScannerVisor.STROKE_WIDTH * 2) + + (HORIZONTAL_SPACE_BETWEEN_CARDS * 4)) / + screenWidth; + } } class SearchCard extends StatelessWidget { @@ -223,6 +228,9 @@ class SearchCard extends StatelessWidget { return SmoothProductBaseCard( backgroundColorOpacity: OPACITY, + margin: const EdgeInsets.symmetric( + vertical: VERY_SMALL_SPACE, + ), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, diff --git a/packages/smooth_app/lib/widgets/tab_navigator.dart b/packages/smooth_app/lib/widgets/tab_navigator.dart index 4ad39e728ed..82aa8ed8135 100644 --- a/packages/smooth_app/lib/widgets/tab_navigator.dart +++ b/packages/smooth_app/lib/widgets/tab_navigator.dart @@ -21,7 +21,7 @@ class TabNavigator extends StatelessWidget { case BottomNavigationTab.Profile: child = const UserPreferencesPage(); break; - case BottomNavigationTab.History: + case BottomNavigationTab.List: child = const HistoryPage(); break; case BottomNavigationTab.Scan: diff --git a/packages/smooth_app/macos/Podfile b/packages/smooth_app/macos/Podfile index dade8dfad0d..049abe29542 100644 --- a/packages/smooth_app/macos/Podfile +++ b/packages/smooth_app/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.11' +platform :osx, '10.14' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/packages/smooth_app/macos/Podfile.lock b/packages/smooth_app/macos/Podfile.lock new file mode 100644 index 00000000000..c29e0e1cb54 --- /dev/null +++ b/packages/smooth_app/macos/Podfile.lock @@ -0,0 +1,110 @@ +PODS: + - audioplayers_darwin (0.0.1): + - FlutterMacOS + - device_info_plus (0.0.1): + - FlutterMacOS + - file_selector_macos (0.0.1): + - FlutterMacOS + - flutter_secure_storage_macos (3.3.1): + - FlutterMacOS + - FlutterMacOS (1.0.0) + - FMDB (2.7.5): + - FMDB/standard (= 2.7.5) + - FMDB/standard (2.7.5) + - in_app_review (0.2.0): + - FlutterMacOS + - mobile_scanner (3.0.0): + - FlutterMacOS + - package_info_plus (0.0.1): + - FlutterMacOS + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - Sentry/HybridSDK (7.31.5) + - sentry_flutter (0.0.1): + - Flutter + - FlutterMacOS + - Sentry/HybridSDK (= 7.31.5) + - share_plus (0.0.1): + - FlutterMacOS + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + - sqflite (0.0.2): + - FlutterMacOS + - FMDB (>= 2.7.5) + - url_launcher_macos (0.0.1): + - FlutterMacOS + +DEPENDENCIES: + - audioplayers_darwin (from `Flutter/ephemeral/.symlinks/plugins/audioplayers_darwin/macos`) + - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) + - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`) + - flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`) + - FlutterMacOS (from `Flutter/ephemeral`) + - in_app_review (from `Flutter/ephemeral/.symlinks/plugins/in_app_review/macos`) + - mobile_scanner (from `Flutter/ephemeral/.symlinks/plugins/mobile_scanner/macos`) + - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) + - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) + - sentry_flutter (from `Flutter/ephemeral/.symlinks/plugins/sentry_flutter/macos`) + - share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`) + - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) + - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`) + - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) + +SPEC REPOS: + trunk: + - FMDB + - Sentry + +EXTERNAL SOURCES: + audioplayers_darwin: + :path: Flutter/ephemeral/.symlinks/plugins/audioplayers_darwin/macos + device_info_plus: + :path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos + file_selector_macos: + :path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos + flutter_secure_storage_macos: + :path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos + FlutterMacOS: + :path: Flutter/ephemeral + in_app_review: + :path: Flutter/ephemeral/.symlinks/plugins/in_app_review/macos + mobile_scanner: + :path: Flutter/ephemeral/.symlinks/plugins/mobile_scanner/macos + package_info_plus: + :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos + path_provider_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin + sentry_flutter: + :path: Flutter/ephemeral/.symlinks/plugins/sentry_flutter/macos + share_plus: + :path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos + shared_preferences_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin + sqflite: + :path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos + url_launcher_macos: + :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos + +SPEC CHECKSUMS: + audioplayers_darwin: dcad41de4fbd0099cb3749f7ab3b0cb8f70b810c + device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f + file_selector_macos: 0f85c1108e2fd597b58246bc0b0c1cb483d7593b + flutter_secure_storage_macos: 6ceee8fbc7f484553ad17f79361b556259df89aa + FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 + FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a + in_app_review: a850789fad746e89bce03d4aeee8078b45a53fd0 + mobile_scanner: ed7618fb749adc6574563e053f3b8e5002c13994 + package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce + path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8 + Sentry: 4c9babff9034785067c896fd580b1f7de44da020 + sentry_flutter: 1346a880b24c0240807b53b10cf50ddad40f504e + share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7 + shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c + sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea + url_launcher_macos: 5335912b679c073563f29d89d33d10d459f95451 + +PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7 + +COCOAPODS: 1.12.1 diff --git a/packages/smooth_app/macos/Runner.xcodeproj/project.pbxproj b/packages/smooth_app/macos/Runner.xcodeproj/project.pbxproj index 5a8906299c8..ede8e8e666e 100644 --- a/packages/smooth_app/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/smooth_app/macos/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -256,6 +256,7 @@ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -404,7 +405,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -483,7 +484,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -530,7 +531,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/packages/smooth_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/smooth_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 82b6f9d9a33..c07ed39868e 100644 Binary files a/packages/smooth_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and b/packages/smooth_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/packages/smooth_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/smooth_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index 13b35eba55c..2b356a32109 100644 Binary files a/packages/smooth_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and b/packages/smooth_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/packages/smooth_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/smooth_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png index 0a3f5fa40fb..60ff11baf0c 100644 Binary files a/packages/smooth_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and b/packages/smooth_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/packages/smooth_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/smooth_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index bdb57226d5f..5b896d9c9d4 100644 Binary files a/packages/smooth_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and b/packages/smooth_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/packages/smooth_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/smooth_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png index f083318e09c..6e715cfbba5 100644 Binary files a/packages/smooth_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and b/packages/smooth_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/packages/smooth_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/smooth_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index 326c0e72c9d..351d273a1ba 100644 Binary files a/packages/smooth_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and b/packages/smooth_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/packages/smooth_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/smooth_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png index 2f1632cfddf..bc0be216422 100644 Binary files a/packages/smooth_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/packages/smooth_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/packages/smooth_app/macos/Runner/Info.plist b/packages/smooth_app/macos/Runner/Info.plist index 4789daa6a44..09045c12ccc 100644 --- a/packages/smooth_app/macos/Runner/Info.plist +++ b/packages/smooth_app/macos/Runner/Info.plist @@ -28,5 +28,7 @@ MainMenu NSPrincipalClass NSApplication + ITSAppUsesNonExemptEncryption + diff --git a/packages/smooth_app/pubspec.lock b/packages/smooth_app/pubspec.lock index 3fc4f850c26..4f450cec093 100644 --- a/packages/smooth_app/pubspec.lock +++ b/packages/smooth_app/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "405666cd3cf0ee0a48d21ec67e65406aad2c726d9fa58840d3375e7bdcd32a07" + sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a url: "https://pub.dev" source: hosted - version: "60.0.0" + version: "61.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "1952250bd005bacb895a01bf1b4dc00e3ba1c526cf47dca54dfe24979c65f5b3" + sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 url: "https://pub.dev" source: hosted - version: "5.12.0" + version: "5.13.0" app_settings: dependency: "direct main" description: @@ -57,18 +57,18 @@ packages: dependency: transitive description: name: archive - sha256: "80e5141fafcb3361653ce308776cfd7d45e6e9fbb429e14eec571382c0c5fecb" + sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a" url: "https://pub.dev" source: hosted - version: "3.3.2" + version: "3.3.7" args: dependency: transitive description: name: args - sha256: c372bb384f273f0c2a8aaaa226dad84dc27c8519a691b888725dec59518ad53a + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" assorted_layout_widgets: dependency: "direct main" description: @@ -153,10 +153,10 @@ packages: dependency: transitive description: name: barcode - sha256: "52570564684bbb0240a9f1fdb6bad12adc5e0540103c1c96d6dd550bd928b1c9" + sha256: "789f898eef0bd88312470bdb2cc996f895ad7dd5f89e9adde84b204546a90b45" url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.2.4" barcode_widget: dependency: "direct main" description: @@ -177,10 +177,10 @@ packages: dependency: transitive description: name: build - sha256: "43865b79fbb78532e4bff7c33087aa43b1d488c4fdef014eaef568af6d8016dc" + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" built_collection: dependency: transitive description: @@ -193,10 +193,10 @@ packages: dependency: transitive description: name: built_value - sha256: "2f17434bd5d52a26762043d6b43bb53b3acd029b4d9071a329f46d67ef297e6d" + sha256: "598a2a682e2a7a90f08ba39c0aaa9374c5112340f0a2e275f61b59389543d166" url: "https://pub.dev" source: hosted - version: "8.5.0" + version: "8.6.1" camera: dependency: "direct main" description: @@ -209,10 +209,10 @@ packages: dependency: transitive description: name: camera_android - sha256: f83e406d34f5faa80bf0f5c3beee4b4c11da94a94e9621c1bb8e312988621b4b + sha256: cac448df2567b35e2bd70b8c66368fcba4923f88bc898723516c2ce553765db6 url: "https://pub.dev" source: hosted - version: "0.10.8+2" + version: "0.10.8+4" camera_avfoundation: dependency: transitive description: @@ -321,18 +321,18 @@ packages: dependency: transitive description: name: crypto - sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" csslib: dependency: transitive description: name: csslib - sha256: b36c7f7e24c0bdf1bf9a3da461c837d1de64b9f8beb190c9011d8c72a3dfd745 + sha256: "831883fb353c8bdc1d71979e5b342c7d88acfbc643113c14ae51e2442ea0f20f" url: "https://pub.dev" source: hosted - version: "0.17.2" + version: "0.17.3" cupertino_icons: dependency: "direct main" description: @@ -345,10 +345,10 @@ packages: dependency: transitive description: name: dart_style - sha256: f4f1f73ab3fd2afcbcca165ee601fe980d966af6a21b5970c6c9376955c528ad + sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" data_importer: dependency: "direct main" description: @@ -431,34 +431,34 @@ packages: dependency: transitive description: name: file_selector_linux - sha256: d17c5e450192cdc40b718804dfb4eaf79a71bed60ee9530703900879ba50baa3 + sha256: "770eb1ab057b5ae4326d1c24cc57710758b9a46026349d021d6311bd27580046" url: "https://pub.dev" source: hosted - version: "0.9.1+3" + version: "0.9.2" file_selector_macos: dependency: transitive description: name: file_selector_macos - sha256: "6290eec24fc4cc62535fe609e0c6714d3c1306191dc8c3b0319eaecc09423a3a" + sha256: "4ada532862917bf16e3adb3891fe3a5917a58bae03293e497082203a80909412" url: "https://pub.dev" source: hosted - version: "0.9.2" + version: "0.9.3+1" file_selector_platform_interface: dependency: transitive description: name: file_selector_platform_interface - sha256: "2a7f4bbf7bd2f022ecea85bfb1754e87f7dd403a9abc17a84a4fa2ddfe2abc0a" + sha256: "412705a646a0ae90f33f37acfae6a0f7cbc02222d6cd34e479421c3e74d3853c" url: "https://pub.dev" source: hosted - version: "2.5.1" + version: "2.6.0" file_selector_windows: dependency: transitive description: name: file_selector_windows - sha256: ef246380b66d1fb9089fc65622c387bf3780bca79f533424c31d07f12c2c7fd8 + sha256: "1372760c6b389842b77156203308940558a2817360154084368608413835fc26" url: "https://pub.dev" source: hosted - version: "0.9.2" + version: "0.9.3" fimber: dependency: "direct main" description: @@ -513,18 +513,42 @@ packages: dependency: "direct main" description: name: flutter_email_sender - sha256: "39398c3e6cf4fbe99798d3ac6ae7d229290d57f1504a4a9a0d35f3737dc28930" + sha256: "52b713a67a966be4d9e6f68a323fc0a5bc2da71c567eb451af1aa90d30adbc3a" url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "6.0.1" flutter_image_compress: dependency: "direct main" description: name: flutter_image_compress - sha256: "37f1b26399098e5f97b74c1483f534855e7dff68ead6ddaccf747029fb03f29f" + sha256: "2725cce5c58fdeaf1db8f4203688228bb67e3523a66305ccaa6f99071beb6dc2" url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "2.0.4" + flutter_image_compress_common: + dependency: transitive + description: + name: flutter_image_compress_common + sha256: "8e7299afe109dc4b97fda34bf0f4967cc1fc10bc8050c374d449cab262d095b3" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + flutter_image_compress_platform_interface: + dependency: transitive + description: + name: flutter_image_compress_platform_interface + sha256: "3c7e86da7540b1adfa919b461885a41a018d4a26544d0fcbeaa769f6542e603d" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + flutter_image_compress_web: + dependency: transitive + description: + name: flutter_image_compress_web + sha256: e879189dc7f246dcf8f06c07ee849231341508bf51e8ed7d5dcbe778ddde0e81 + url: "https://pub.dev" + source: hosted + version: "0.1.3+1" flutter_launcher_icons: dependency: "direct dev" description: @@ -566,10 +590,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "96af49aa6b57c10a312106ad6f71deed5a754029c24789bbf620ba784f0bd0b0" + sha256: "950e77c2bbe1692bc0874fc7fb491b96a4dc340457f4ea1641443d0a6c1ea360" url: "https://pub.dev" source: hosted - version: "2.0.14" + version: "2.0.15" flutter_secure_storage: dependency: "direct main" description: @@ -648,10 +672,10 @@ packages: dependency: transitive description: name: freezed_annotation - sha256: aeac15850ef1b38ee368d4c53ba9a847e900bb2c53a4db3f6881cbb3cb684338 + sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.4.1" fuchsia_remote_debug_protocol: dependency: transitive description: flutter @@ -669,18 +693,18 @@ packages: dependency: transitive description: name: fwfh_text_style - sha256: "37806ee0222f79b6e8d4c698c322c897eae6a817258156f40aeece4e588fac60" + sha256: f0883ccb64b7bb3f2a7a091542c2e834fc3e2a6aa54158f46b3c43b55675d8f7 url: "https://pub.dev" source: hosted - version: "2.22.08+1" + version: "2.22.8+3" glob: dependency: transitive description: name: glob - sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c" + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" go_router: dependency: "direct main" description: @@ -689,6 +713,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.2" + graphs: + dependency: transitive + description: + name: graphs + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + url: "https://pub.dev" + source: hosted + version: "2.3.1" hive: dependency: "direct main" description: @@ -709,10 +741,10 @@ packages: dependency: transitive description: name: html - sha256: "58e3491f7bf0b6a4ea5110c0c688877460d1a6366731155c4a4580e7ded773e8" + sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" url: "https://pub.dev" source: hosted - version: "0.15.3" + version: "0.15.4" http: dependency: "direct main" description: @@ -749,26 +781,26 @@ packages: dependency: transitive description: name: image_picker_android - sha256: "89ba2aa6904d8180ca44fd5f5014523f02319101904e3e571fbe792e395b77ed" + sha256: d2bab152deb2547ea6f53d82ebca9b7e77386bb706e5789e815d37e08ea475bb url: "https://pub.dev" source: hosted - version: "0.8.6+14" + version: "0.8.7+3" image_picker_for_web: dependency: transitive description: name: image_picker_for_web - sha256: "98f50d6b9f294c8ba35e25cc0d13b04bfddd25dbc8d32fa9d566a6572f2c081c" + sha256: "869fe8a64771b7afbc99fc433a5f7be2fea4d1cb3d7c11a48b6b579eb9c797f0" url: "https://pub.dev" source: hosted - version: "2.1.12" + version: "2.2.0" image_picker_ios: dependency: transitive description: name: image_picker_ios - sha256: d779210bda268a03b57e923fb1e410f32f5c5e708ad256348bcbf1f44f558fd0 + sha256: b3e2f21feb28b24dd73a35d7ad6e83f568337c70afab5eabac876e23803f264b url: "https://pub.dev" source: hosted - version: "0.8.7+4" + version: "0.8.8" image_picker_linux: dependency: transitive description: @@ -866,10 +898,10 @@ packages: dependency: transitive description: name: lints - sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.1.1" lists: dependency: transitive description: @@ -882,10 +914,10 @@ packages: dependency: transitive description: name: logging - sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d" + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" lottie: dependency: "direct main" description: @@ -914,10 +946,10 @@ packages: dependency: "direct main" description: name: matomo_tracker - sha256: "4500ed4ee9385f95e9195b448742e75c9115ffa59b4960e6ad3b79bdcd903c47" + sha256: a96ef1e0c03cd8b73db31a50352bbde31a7718333af85afbe36e8ae176cc23a4 url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "3.1.0" matrix4_transform: dependency: transitive description: @@ -979,16 +1011,16 @@ packages: dependency: "direct main" description: name: openfoodfacts - sha256: "3573e7024423dbce1823d70ae0ba56363bd7f26ed89200d4cdc502d3990f695b" + sha256: a39131388148839f08fe4772cfd5dd77f2393bd7995edb9712e70742201d41fb url: "https://pub.dev" source: hosted - version: "2.6.0" + version: "2.7.1" openfoodfacts_flutter_lints: dependency: "direct dev" description: path: "." ref: HEAD - resolved-ref: "3462ba8ce07287bb2c46818660a0f03f00884804" + resolved-ref: "215b1027d8df69f621e18c855c8cb09db2fa9640" url: "https://github.com/openfoodfacts/openfoodfacts_flutter_lints.git" source: git version: "1.0.0" @@ -1052,18 +1084,18 @@ packages: dependency: transitive description: name: path_provider_foundation - sha256: "1995d88ec2948dac43edf8fe58eb434d35d22a2940ecee1a9fefcd62beee6eb3" + sha256: "916731ccbdce44d545414dd9961f26ba5fbaa74bcbb55237d8e65a623a8c7297" url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.2.4" path_provider_linux: dependency: transitive description: name: path_provider_linux - sha256: "2ae08f2216225427e64ad224a24354221c2c7907e448e6e0e8b57b1eb9f10ad1" + sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57 url: "https://pub.dev" source: hosted - version: "2.1.10" + version: "2.1.11" path_provider_platform_interface: dependency: "direct dev" description: @@ -1076,10 +1108,10 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: d3f80b32e83ec208ac95253e0cd4d298e104fbc63cb29c5c69edaed43b0c69d6 + sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96" url: "https://pub.dev" source: hosted - version: "2.1.6" + version: "2.1.7" percent_indicator: dependency: "direct main" description: @@ -1100,42 +1132,42 @@ packages: dependency: transitive description: name: permission_handler_android - sha256: "8028362b40c4a45298f1cbfccd227c8dd6caf0e27088a69f2ba2ab15464159e2" + sha256: c0c9754479a4c4b1c1f3862ddc11930c9b3f03bef2816bb4ea6eed1e13551d6f url: "https://pub.dev" source: hosted - version: "10.2.0" + version: "10.3.2" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: "08dcb6ce628ac0b257e429944b4c652c2a4e6af725bdf12b498daa2c6b2b1edb" + sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5" url: "https://pub.dev" source: hosted - version: "9.1.0" + version: "9.1.4" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface - sha256: de20a5c3269229c1ae2e5a6b822f6cb59578b23e8255c93fbeebfc82116e6b11 + sha256: "7c6b1500385dd1d2ca61bb89e2488ca178e274a69144d26bbd65e33eae7c02a9" url: "https://pub.dev" source: hosted - version: "3.10.0" + version: "3.11.3" permission_handler_windows: dependency: transitive description: name: permission_handler_windows - sha256: f67cab14b4328574938ecea2db3475dad7af7ead6afab6338772c5f88963e38b + sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098 url: "https://pub.dev" source: hosted - version: "0.1.2" + version: "0.1.3" petitparser: dependency: transitive description: name: petitparser - sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4" + sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 url: "https://pub.dev" source: hosted - version: "5.1.0" + version: "5.4.0" photo_view: dependency: "direct main" description: @@ -1160,6 +1192,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + url: "https://pub.dev" + source: hosted + version: "3.7.3" polylabel: dependency: transitive description: @@ -1232,6 +1272,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.1" + rive: + dependency: "direct main" + description: + name: rive + sha256: f3b8af0898c987d68019e91d92257edd902c28c816e49de033a7272e86bd5425 + url: "https://pub.dev" + source: hosted + version: "0.11.4" + rive_common: + dependency: transitive + description: + name: rive_common + sha256: f6687f9d70e6fd3888a9b0e9c0b307966d2ce74cf00cfb01dce906c3bbada52f + url: "https://pub.dev" + source: hosted + version: "0.1.0" scanner_ml_kit: dependency: "direct main" description: @@ -1253,6 +1309,22 @@ packages: relative: true source: path version: "1.0.0" + sensors_plus: + dependency: "direct main" + description: + name: sensors_plus + sha256: "10d3aa4071121d06351e9ba555cc25e83273314a3faab1cee12f9d886eff7426" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + sensors_plus_platform_interface: + dependency: transitive + description: + name: sensors_plus_platform_interface + sha256: "95f0cc08791b8bf0c41c5fa99c84be2a7d5bf60a811ddc17e1438b1e68caf0d3" + url: "https://pub.dev" + source: hosted + version: "1.1.3" sentry: dependency: transitive description: @@ -1297,50 +1369,50 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: "6478c6bbbecfe9aced34c483171e90d7c078f5883558b30ec3163cf18402c749" + sha256: fe8401ec5b6dcd739a0fe9588802069e608c3fdbfd3c3c93e546cf2f90438076 url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: e014107bb79d6d3297196f4f2d0db54b5d1f85b8ea8ff63b8e8b391a02700feb + sha256: f39696b83e844923b642ce9dd4bd31736c17e697f6731a5adf445b1274cf3cd4 url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.3.2" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - sha256: "9d387433ca65717bbf1be88f4d5bb18f10508917a8fa2fb02e0fd0d7479a9afa" + sha256: "71d6806d1449b0a9d4e85e0c7a917771e672a3d5dc61149cc9fac871115018e1" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.0" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - sha256: fb5cf25c0235df2d0640ac1b1174f6466bd311f621574997ac59018a6664548d + sha256: "23b052f17a25b90ff2b61aad4cc962154da76fb62848a9ce088efe30d7c50ab1" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.0" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - sha256: "74083203a8eae241e0de4a0d597dbedab3b8fef5563f33cf3c12d7e93c655ca5" + sha256: "7347b194fb0bbeb4058e6a4e87ee70350b6b2b90f8ac5f8bd5b3a01548f6d33a" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.0" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - sha256: "5e588e2efef56916a3b229c3bfe81e6a525665a454519ca51dbcc4236a274173" + sha256: f95e6a43162bce43c9c3405f3eb6f39e5b5d11f65fab19196cf8225e2777624d url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.0" shimmer: dependency: "direct main" description: @@ -1358,10 +1430,10 @@ packages: dependency: transitive description: name: source_gen - sha256: "373f96cf5a8744bc9816c1ff41cf5391bbdbe3d7a96fe98c622b6738a8a7bd33" + sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16 url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.0" source_span: dependency: transitive description: @@ -1382,10 +1454,10 @@ packages: dependency: transitive description: name: sqflite_common - sha256: e77abf6ff961d69dfef41daccbb66b51e9983cdd5cb35bf30733598057401555 + sha256: "8f7603f3f8f126740bc55c4ca2d1027aab4b74a1267a3e31ce51fe40e3b65b8f" url: "https://pub.dev" source: hosted - version: "2.4.5" + version: "2.4.5+1" stack_trace: dependency: transitive description: @@ -1454,18 +1526,18 @@ packages: dependency: transitive description: name: tuple - sha256: "0ea99cd2f9352b2586583ab2ce6489d1f95a5f6de6fb9492faaf97ae2060f0aa" + sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" typed_data: dependency: transitive description: name: typed_data - sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" unicode: dependency: transitive description: @@ -1478,10 +1550,10 @@ packages: dependency: transitive description: name: universal_io - sha256: "06866290206d196064fd61df4c7aea1ffe9a4e7c4ccaa8fcded42dd41948005d" + sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.2" url_launcher: dependency: "direct main" description: @@ -1494,10 +1566,10 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "7aac14be5f4731b923cc697ae2d42043945076cd0dbb8806baecc92c1dc88891" + sha256: "15f5acbf0dce90146a0f5a2c4a002b1814a6303c4c5c075aa2623b2d16156f03" url: "https://pub.dev" source: hosted - version: "6.0.33" + version: "6.0.36" url_launcher_ios: dependency: transitive description: @@ -1518,34 +1590,34 @@ packages: dependency: transitive description: name: url_launcher_macos - sha256: "91ee3e75ea9dadf38036200c5d3743518f4a5eb77a8d13fda1ee5764373f185e" + sha256: "1c4fdc0bfea61a70792ce97157e5cc17260f61abbe4f39354513f39ec6fd73b1" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.6" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370" + sha256: bfdfa402f1f3298637d71ca8ecfe840b4696698213d5346e9d12d4ab647ee2ea url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "81fe91b6c4f84f222d186a9d23c73157dc4c8e1c71489c4d08be1ad3b228f1aa" + sha256: cc26720eefe98c1b71d85f9dc7ef0cada5132617046369d9dc296b3ecaa5cbb4 url: "https://pub.dev" source: hosted - version: "2.0.16" + version: "2.0.18" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "254708f17f7c20a9c8c471f67d86d76d4a3f9c1591aad1e15292008aceb82771" + sha256: "7967065dd2b5fccc18c653b97958fdf839c5478c28e767c61ee879f4e7882422" url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.0.7" uuid: dependency: "direct main" description: @@ -1558,26 +1630,26 @@ packages: dependency: transitive description: name: vector_graphics - sha256: ea8d3fc7b2e0f35de38a7465063ecfcf03d8217f7962aa2a6717132cb5d43a79 + sha256: "670f6e07aca990b4a2bcdc08a784193c4ccdd1932620244c3a86bb72a0eac67f" url: "https://pub.dev" source: hosted - version: "1.1.5" + version: "1.1.7" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: a5eaa5d19e123ad4f61c3718ca1ed921c4e6254238d9145f82aa214955d9aced + sha256: "7451721781d967db9933b63f5733b1c4533022c0ba373a01bdd79d1a5457f69f" url: "https://pub.dev" source: hosted - version: "1.1.5" + version: "1.1.7" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: "15edc42f7eaa478ce854eaf1fbb9062a899c0e4e56e775dd73b7f4709c97c4ca" + sha256: "80a13c613c8bde758b1464a1755a7b3a8f2b6cec61fbf0f5a53c94c30f03ba2e" url: "https://pub.dev" source: hosted - version: "1.1.5" + version: "1.1.7" vector_math: dependency: transitive description: @@ -1606,10 +1678,10 @@ packages: dependency: transitive description: name: watcher - sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.1.0" webdriver: dependency: transitive description: @@ -1638,18 +1710,18 @@ packages: dependency: transitive description: name: xdg_directories - sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1 + sha256: e0b1147eec179d3911f1f19b59206448f78195ca1d20514134e10641b7d7fbff url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.1" xml: dependency: transitive description: name: xml - sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5" + sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" url: "https://pub.dev" source: hosted - version: "6.2.2" + version: "6.3.0" yaml: dependency: transitive description: diff --git a/packages/smooth_app/pubspec.yaml b/packages/smooth_app/pubspec.yaml index b6d10d156e4..70b2802382a 100644 --- a/packages/smooth_app/pubspec.yaml +++ b/packages/smooth_app/pubspec.yaml @@ -28,7 +28,7 @@ dependencies: image_picker: ^0.8.9 iso_countries: 2.1.0 latlong2: 0.8.1 - matomo_tracker: 2.0.0 + matomo_tracker: 3.1.0 package_info_plus: 3.0.3 device_info_plus: 8.1.0 permission_handler: 10.3.0 @@ -64,8 +64,11 @@ dependencies: fimber: 0.6.6 shimmer: ^3.0.0 lottie: 2.2.0 + rive: 0.11.4 + sensors_plus: 3.0.2 + webview_flutter: 3.0.4 flutter_custom_tabs: ^1.0.4 - flutter_image_compress: 1.1.3 + flutter_image_compress: 2.0.4 # According to the build variant, only one "app store" implementation must be added when building a release # Call "flutter pub remove xxxx" to remove unused dependencies @@ -95,7 +98,7 @@ dependencies: - openfoodfacts: 2.6.0 + openfoodfacts: 2.7.1 # openfoodfacts: # path: ../../../openfoodfacts-dart diff --git a/packages/smooth_app/test/dialogs/generic_lib/dialogs_test.dart b/packages/smooth_app/test/dialogs/generic_lib/dialogs_test.dart index ba6ae441f41..db25a110f66 100644 --- a/packages/smooth_app/test/dialogs/generic_lib/dialogs_test.dart +++ b/packages/smooth_app/test/dialogs/generic_lib/dialogs_test.dart @@ -10,6 +10,7 @@ import 'package:smooth_app/pages/preferences/user_preferences_page.dart'; import 'package:smooth_app/themes/color_provider.dart'; import 'package:smooth_app/themes/contrast_provider.dart'; import 'package:smooth_app/themes/theme_provider.dart'; + import '../../tests_utils/goldens.dart'; import '../../tests_utils/mocks.dart'; diff --git a/packages/smooth_app/test/dialogs/generic_lib/goldens/user_preferences_page_dialogs_Software development-amoled.png b/packages/smooth_app/test/dialogs/generic_lib/goldens/user_preferences_page_dialogs_Software development-amoled.png index 509f14dace7..c9745182b80 100644 Binary files a/packages/smooth_app/test/dialogs/generic_lib/goldens/user_preferences_page_dialogs_Software development-amoled.png and b/packages/smooth_app/test/dialogs/generic_lib/goldens/user_preferences_page_dialogs_Software development-amoled.png differ diff --git a/packages/smooth_app/test/dialogs/generic_lib/goldens/user_preferences_page_dialogs_Software development-dark.png b/packages/smooth_app/test/dialogs/generic_lib/goldens/user_preferences_page_dialogs_Software development-dark.png index 5fe2213b6ca..781019430e0 100644 Binary files a/packages/smooth_app/test/dialogs/generic_lib/goldens/user_preferences_page_dialogs_Software development-dark.png and b/packages/smooth_app/test/dialogs/generic_lib/goldens/user_preferences_page_dialogs_Software development-dark.png differ diff --git a/packages/smooth_app/test/dialogs/generic_lib/goldens/user_preferences_page_dialogs_Software development-light.png b/packages/smooth_app/test/dialogs/generic_lib/goldens/user_preferences_page_dialogs_Software development-light.png index 92c69f87716..2f7613f2fb3 100644 Binary files a/packages/smooth_app/test/dialogs/generic_lib/goldens/user_preferences_page_dialogs_Software development-light.png and b/packages/smooth_app/test/dialogs/generic_lib/goldens/user_preferences_page_dialogs_Software development-light.png differ diff --git a/packages/smooth_app/test/dialogs/generic_lib/goldens/user_preferences_page_dialogs_Translate-amoled.png b/packages/smooth_app/test/dialogs/generic_lib/goldens/user_preferences_page_dialogs_Translate-amoled.png index e8cd7186650..a443af21fb3 100644 Binary files a/packages/smooth_app/test/dialogs/generic_lib/goldens/user_preferences_page_dialogs_Translate-amoled.png and b/packages/smooth_app/test/dialogs/generic_lib/goldens/user_preferences_page_dialogs_Translate-amoled.png differ diff --git a/packages/smooth_app/test/dialogs/generic_lib/goldens/user_preferences_page_dialogs_Translate-dark.png b/packages/smooth_app/test/dialogs/generic_lib/goldens/user_preferences_page_dialogs_Translate-dark.png index be2b4dcc531..be07715e3ff 100644 Binary files a/packages/smooth_app/test/dialogs/generic_lib/goldens/user_preferences_page_dialogs_Translate-dark.png and b/packages/smooth_app/test/dialogs/generic_lib/goldens/user_preferences_page_dialogs_Translate-dark.png differ diff --git a/packages/smooth_app/test/dialogs/generic_lib/goldens/user_preferences_page_dialogs_Translate-light.png b/packages/smooth_app/test/dialogs/generic_lib/goldens/user_preferences_page_dialogs_Translate-light.png index da6afb5aa96..028a39256c8 100644 Binary files a/packages/smooth_app/test/dialogs/generic_lib/goldens/user_preferences_page_dialogs_Translate-light.png and b/packages/smooth_app/test/dialogs/generic_lib/goldens/user_preferences_page_dialogs_Translate-light.png differ diff --git a/packages/smooth_app/test/pages/goldens/user_preferences_page-amoled.png b/packages/smooth_app/test/pages/goldens/user_preferences_page-amoled.png index aa928f55ca8..3d17eb3f0e3 100644 Binary files a/packages/smooth_app/test/pages/goldens/user_preferences_page-amoled.png and b/packages/smooth_app/test/pages/goldens/user_preferences_page-amoled.png differ diff --git a/packages/smooth_app/test/pages/goldens/user_preferences_page-dark.png b/packages/smooth_app/test/pages/goldens/user_preferences_page-dark.png index a59b64ac99c..98c3d9f01a8 100644 Binary files a/packages/smooth_app/test/pages/goldens/user_preferences_page-dark.png and b/packages/smooth_app/test/pages/goldens/user_preferences_page-dark.png differ diff --git a/packages/smooth_app/test/pages/goldens/user_preferences_page-light.png b/packages/smooth_app/test/pages/goldens/user_preferences_page-light.png index 13c4e4efc64..80dec683e09 100644 Binary files a/packages/smooth_app/test/pages/goldens/user_preferences_page-light.png and b/packages/smooth_app/test/pages/goldens/user_preferences_page-light.png differ diff --git a/packages/smooth_app/test/tests_utils/mocks.dart b/packages/smooth_app/test/tests_utils/mocks.dart index a63116c0e9b..eb2c3efef75 100644 --- a/packages/smooth_app/test/tests_utils/mocks.dart +++ b/packages/smooth_app/test/tests_utils/mocks.dart @@ -1,16 +1,19 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'dart:typed_data'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:matomo_tracker/matomo_tracker.dart'; import 'package:mockito/mockito.dart'; import 'package:provider/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'; import 'package:smooth_app/database/local_database.dart'; +import 'package:smooth_app/helpers/analytics_helper.dart'; import 'package:smooth_app/themes/color_provider.dart'; import 'package:smooth_app/themes/contrast_provider.dart'; import 'package:smooth_app/themes/smooth_theme.dart'; @@ -18,7 +21,7 @@ import 'package:smooth_app/themes/theme_provider.dart'; /// A wrapper for testing various pages of the app with a simple state. class MockSmoothApp extends StatelessWidget { - const MockSmoothApp( + MockSmoothApp( this.userPreferences, this.userManagementProvider, this.productPreferences, @@ -27,7 +30,9 @@ class MockSmoothApp extends StatelessWidget { this.colorProvider, this.child, { this.localDatabase, - }); + }) { + mockMatomo(); + } final UserPreferences userPreferences; final UserManagementProvider userManagementProvider; @@ -174,3 +179,46 @@ class _MockHttpClientSVGResponse extends Mock implements HttpClientResponse { final Uint8List svgBytes = utf8.encode(svgStr) as Uint8List; } + +Future mockMatomo() async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler( + const MethodChannel('dev.fluttercommunity.plus/device_info'), + (MethodCall call) async { + if (call.method == 'getDeviceInfo') { + return { + 'computerName': '_', + 'hostName': '_', + 'arch': '_', + 'model': '_', + 'kernelVersion': '_', + 'osRelease': '_', + 'activeCPUs': 1, + 'memorySize': 1, + 'cpuFrequency': 1, + }; + } + return null; + }); + + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler( + const MethodChannel('dev.fluttercommunity.plus/package_info'), + (MethodCall call) async { + if (call.method == 'getAll') { + return { + 'appName': '_', + 'packageName': '_', + 'version': '_', + 'buildNumber': '_', + 'buildSignature': '_', + 'installerStore': '_', + }; + } + return null; + }); + + await AnalyticsHelper.initMatomo(false); + MatomoTracker.instance.setOptOut(optOut: true); + MatomoTracker.instance.timer.cancel(); +} diff --git a/packages/smooth_app/test/users/goldens/login_page-dark.png b/packages/smooth_app/test/users/goldens/login_page-dark.png index 39fadc9d20a..a132476bd9c 100644 Binary files a/packages/smooth_app/test/users/goldens/login_page-dark.png and b/packages/smooth_app/test/users/goldens/login_page-dark.png differ diff --git a/packages/smooth_app/test/users/goldens/login_page-light.png b/packages/smooth_app/test/users/goldens/login_page-light.png index 39ec4b740d5..494960258aa 100644 Binary files a/packages/smooth_app/test/users/goldens/login_page-light.png and b/packages/smooth_app/test/users/goldens/login_page-light.png differ diff --git a/packages/smooth_app/test/users/goldens/signup_page-dark.png b/packages/smooth_app/test/users/goldens/signup_page-dark.png index 0c898098c81..a1723737423 100644 Binary files a/packages/smooth_app/test/users/goldens/signup_page-dark.png and b/packages/smooth_app/test/users/goldens/signup_page-dark.png differ diff --git a/packages/smooth_app/test/users/goldens/signup_page-light.png b/packages/smooth_app/test/users/goldens/signup_page-light.png index f33dc72fa02..25d4be9e9cc 100644 Binary files a/packages/smooth_app/test/users/goldens/signup_page-light.png and b/packages/smooth_app/test/users/goldens/signup_page-light.png differ diff --git a/packages/smooth_app/windows/flutter/generated_plugin_registrant.cc b/packages/smooth_app/windows/flutter/generated_plugin_registrant.cc index 6fb7e4520cc..b176efadf9d 100644 --- a/packages/smooth_app/windows/flutter/generated_plugin_registrant.cc +++ b/packages/smooth_app/windows/flutter/generated_plugin_registrant.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -23,6 +24,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); PermissionHandlerWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); + RivePluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("RivePlugin")); SentryFlutterPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("SentryFlutterPlugin")); SharePlusWindowsPluginCApiRegisterWithRegistrar( diff --git a/packages/smooth_app/windows/flutter/generated_plugins.cmake b/packages/smooth_app/windows/flutter/generated_plugins.cmake index 76bb945ba85..01df5c70637 100644 --- a/packages/smooth_app/windows/flutter/generated_plugins.cmake +++ b/packages/smooth_app/windows/flutter/generated_plugins.cmake @@ -7,6 +7,7 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_windows flutter_secure_storage_windows permission_handler_windows + rive_common sentry_flutter share_plus url_launcher_windows diff --git a/packages/smooth_app/windows/runner/resources/app_icon.ico b/packages/smooth_app/windows/runner/resources/app_icon.ico index c04e20caf63..68fd3e4f9d7 100644 Binary files a/packages/smooth_app/windows/runner/resources/app_icon.ico and b/packages/smooth_app/windows/runner/resources/app_icon.ico differ