diff --git a/packages/smooth_app/android/app/build.gradle b/packages/smooth_app/android/app/build.gradle index a9b1054d7110..32e739346a42 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/animations/off.riv b/packages/smooth_app/assets/animations/off.riv new file mode 100644 index 000000000000..56224533e7f7 Binary files /dev/null and b/packages/smooth_app/assets/animations/off.riv differ diff --git a/packages/smooth_app/lib/cards/product_cards/smooth_product_card_loading.dart b/packages/smooth_app/lib/cards/product_cards/smooth_product_card_loading.dart index b9aa282b164a..10edba381227 100644 --- a/packages/smooth_app/lib/cards/product_cards/smooth_product_card_loading.dart +++ b/packages/smooth_app/lib/cards/product_cards/smooth_product_card_loading.dart @@ -1,39 +1,176 @@ +import 'dart:async'; +import 'dart:ui'; + import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; +import 'package:rive/rive.dart'; +import 'package:smooth_app/data_models/continuous_scan_model.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/duration_constants.dart'; -class SmoothProductCardLoading extends StatelessWidget { +class SmoothProductCardLoading extends StatefulWidget { const SmoothProductCardLoading({required this.barcode}); final String barcode; + @override + State createState() => + _SmoothProductCardLoadingState(); +} + +class _SmoothProductCardLoadingState extends State { + late Timer _timer; + _SmoothProductCardLoadingProgress _progress = + _SmoothProductCardLoadingProgress.initial; + + @override + void initState() { + super.initState(); + _timer = Timer(const Duration(seconds: 7), _onLongRequest); + } + @override Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); final ThemeData themeData = Theme.of(context); - return Container( - decoration: BoxDecoration( - color: themeData.brightness == Brightness.light - ? Colors.white - : Colors.black, - borderRadius: ROUNDED_BORDER_RADIUS, - ), - child: Column( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text(barcode, style: Theme.of(context).textTheme.titleMedium), - ], - ), - const SizedBox( - height: 12.0, - ), - const CircularProgressIndicator.adaptive() - ], + return DefaultTextStyle.merge( + textAlign: TextAlign.center, + style: const TextStyle(height: 1.4), + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric( + vertical: SMALL_SPACE, + horizontal: MEDIUM_SPACE, + ), + decoration: BoxDecoration( + color: themeData.brightness == Brightness.light + ? Colors.white + : Colors.black, + borderRadius: ROUNDED_BORDER_RADIUS, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + const Spacer(), + Text( + appLocalizations.scan_product_loading, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18.0, + ), + ), + const Spacer(flex: 2), + Container( + padding: const EdgeInsets.symmetric( + horizontal: SMALL_SPACE, + vertical: SMALL_SPACE, + ), + color: Colors.grey.withOpacity(0.2), + child: Text( + '<${widget.barcode}>', + style: const TextStyle( + letterSpacing: 6.0, + fontFeatures: [ + FontFeature.tabularFigures(), + ], + ), + ), + ), + const Spacer(flex: 2), + AnimatedSwitcher( + duration: SmoothAnimationsDuration.long, + child: Text(_description(appLocalizations)), + transitionBuilder: (Widget child, Animation animation) { + return FadeTransition( + opacity: Tween( + begin: 0.0, + end: 1.0, + ).animate(animation), + child: child, + ); + }, + ), + const Spacer(), + if (_progress == _SmoothProductCardLoadingProgress.unresponsive) + SmoothSimpleButton( + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.restart_alt), + const SizedBox( + width: SMALL_SPACE, + ), + Text(appLocalizations.scan_product_loading_restart_button) + ], + ), + onPressed: () { + final ContinuousScanModel model = + context.read(); + + model.retryBarcodeFetch(widget.barcode); + }, + ), + const Spacer(), + Expanded( + flex: 10, + child: ConstrainedBox( + constraints: const BoxConstraints( + maxHeight: 300, + ), + child: const RiveAnimation.asset( + 'assets/animations/off.riv', + artboard: 'Loading', + alignment: Alignment.topCenter, + fit: BoxFit.fitHeight, + ), + ), + ), + const Spacer(), + ], + ), ), ); } + + String _description(AppLocalizations appLocalizations) { + return switch (_progress) { + _SmoothProductCardLoadingProgress.longRequest => + appLocalizations.scan_product_loading_long_request, + _SmoothProductCardLoadingProgress.unresponsive => + appLocalizations.scan_product_loading_unresponsive, + _ => appLocalizations.scan_product_loading_initial, + }; + } + + void _onLongRequest() { + if (!mounted) { + return; + } + setState(() => _progress = _SmoothProductCardLoadingProgress.longRequest); + _timer = Timer(const Duration(seconds: 5), _onUnresponsiveRequest); + } + + void _onUnresponsiveRequest() { + if (!mounted) { + return; + } + setState(() => _progress = _SmoothProductCardLoadingProgress.unresponsive); + } + + @override + void dispose() { + _timer.cancel(); + super.dispose(); + } +} + +enum _SmoothProductCardLoadingProgress { + initial, + longRequest, + unresponsive, } diff --git a/packages/smooth_app/lib/l10n/app_en.arb b/packages/smooth_app/lib/l10n/app_en.arb index 828cec42163e..599df215e0c5 100644 --- a/packages/smooth_app/lib/l10n/app_en.arb +++ b/packages/smooth_app/lib/l10n/app_en.arb @@ -1939,6 +1939,26 @@ "@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" }, + "scan_product_loading": "You have scanned\nthe barcode:", + "@scan_product_loading": { + "description": "Title when a product is loading (carousel card). Please ensure to keep the line break." + }, + "scan_product_loading_initial": "We're looking for this product!\nPlease wait a few seconds…", + "@scan_product_loading_initial": { + "description": "Message when a product is loading (carousel card). Please ensure to keep the line break." + }, + "scan_product_loading_long_request": "We're still looking for this product!\nDo you find it takes a long time to load? So are we…", + "@scan_product_loading_long_request": { + "description": "Message when a product is long to load (carousel card). Please ensure to keep the line break." + }, + "scan_product_loading_unresponsive": "We're still looking for this product.\nWould you like to restart the search?", + "@scan_product_loading_unresponsive": { + "description": "Message when a product is too long to load (carousel card). Please ensure to keep the line break." + }, + "scan_product_loading_restart_button": "Restart search", + "@scan_product_loading_restart_button": { + "description": "Button to force restart a product search" + }, "portion_calculator_description": "Calculate nutrition facts for a specific quantity", "@portion_calculator_description": { "description": "Sort of title that describes the portion calculator." diff --git a/packages/smooth_app/lib/l10n/app_fr.arb b/packages/smooth_app/lib/l10n/app_fr.arb index fa0f0f6d84b8..5ea09caf8341 100644 --- a/packages/smooth_app/lib/l10n/app_fr.arb +++ b/packages/smooth_app/lib/l10n/app_fr.arb @@ -1927,6 +1927,26 @@ "@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" }, + "scan_product_loading": "Vous avez scanné\nle code-barres :", + "@scan_product_loading": { + "description": "Title when a product is loading (carousel card). Please ensure to keep the line break." + }, + "scan_product_loading_initial": "Nous cherchons ce produit !\nMerci de patienter quelques instants…", + "@scan_product_loading_initial": { + "description": "Message when a product is loading (carousel card). Please ensure to keep the line break." + }, + "scan_product_loading_long_request": "Nous cherchons toujours ce produit !\nLe chargement vous paraît long ? Nous aussi…", + "@scan_product_loading_long_request": { + "description": "Message when a product is long to load (carousel card). Please ensure to keep the line break." + }, + "scan_product_loading_unresponsive": "Nous cherchons toujours ce produit !\nVoulez-vous relancer la recherche ?", + "@scan_product_loading_unresponsive": { + "description": "Message when a product is too long to load (carousel card). Please ensure to keep the line break." + }, + "scan_product_loading_restart_button": "Relancer la recherche", + "@scan_product_loading_restart_button": { + "description": "Button to force restart a product search" + }, "portion_calculator_description": "Calculer les valeurs nutritionnelles pour une quantité spécifique", "@portion_calculator_description": { "description": "Sort of title that describes the portion calculator." diff --git a/packages/smooth_app/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/smooth_app/macos/Flutter/GeneratedPluginRegistrant.swift index fefcf3dac9df..f2845c125d81 100644 --- a/packages/smooth_app/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/packages/smooth_app/macos/Flutter/GeneratedPluginRegistrant.swift @@ -13,6 +13,7 @@ import in_app_review import mobile_scanner import package_info_plus import path_provider_foundation +import rive_common import sentry_flutter import share_plus import shared_preferences_foundation @@ -28,6 +29,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + RivePlugin.register(with: registry.registrar(forPlugin: "RivePlugin")) SentryFlutterPlugin.register(with: registry.registrar(forPlugin: "SentryFlutterPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) diff --git a/packages/smooth_app/macos/Podfile.lock b/packages/smooth_app/macos/Podfile.lock index c29e0e1cb54b..31cce45a894a 100644 --- a/packages/smooth_app/macos/Podfile.lock +++ b/packages/smooth_app/macos/Podfile.lock @@ -20,6 +20,8 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS + - rive_common (0.0.1): + - FlutterMacOS - Sentry/HybridSDK (7.31.5) - sentry_flutter (0.0.1): - Flutter @@ -46,6 +48,7 @@ DEPENDENCIES: - 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`) + - rive_common (from `Flutter/ephemeral/.symlinks/plugins/rive_common/macos`) - 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`) @@ -76,6 +79,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos path_provider_foundation: :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin + rive_common: + :path: Flutter/ephemeral/.symlinks/plugins/rive_common/macos sentry_flutter: :path: Flutter/ephemeral/.symlinks/plugins/sentry_flutter/macos share_plus: @@ -98,6 +103,7 @@ SPEC CHECKSUMS: mobile_scanner: ed7618fb749adc6574563e053f3b8e5002c13994 package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8 + rive_common: acedcab7802c0ece4b0d838b71d7deb637e1309a Sentry: 4c9babff9034785067c896fd580b1f7de44da020 sentry_flutter: 1346a880b24c0240807b53b10cf50ddad40f504e share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7 diff --git a/packages/smooth_app/pubspec.lock b/packages/smooth_app/pubspec.lock index 53735b41c36e..74dd0c14787b 100644 --- a/packages/smooth_app/pubspec.lock +++ b/packages/smooth_app/pubspec.lock @@ -78,7 +78,7 @@ packages: source: hosted version: "7.0.0" async: - dependency: "direct main" + dependency: transitive description: name: async sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" @@ -689,6 +689,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: @@ -1232,6 +1240,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: diff --git a/packages/smooth_app/pubspec.yaml b/packages/smooth_app/pubspec.yaml index 80d2c92e8c6f..939d7501e457 100644 --- a/packages/smooth_app/pubspec.yaml +++ b/packages/smooth_app/pubspec.yaml @@ -64,6 +64,7 @@ dependencies: fimber: 0.6.6 shimmer: ^3.0.0 lottie: 2.2.0 + rive: 0.11.4 webview_flutter: 3.0.4 flutter_custom_tabs: ^1.0.4 flutter_image_compress: 1.1.3 diff --git a/packages/smooth_app/windows/flutter/generated_plugin_registrant.cc b/packages/smooth_app/windows/flutter/generated_plugin_registrant.cc index 6fb7e4520ccb..b176efadf9d4 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 76bb945ba85f..01df5c706373 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