From b938153f592662c53d3d47d158b2c42387d9ca2c Mon Sep 17 00:00:00 2001 From: Edouard Marquez Date: Fri, 2 Aug 2024 17:32:57 +0200 Subject: [PATCH 1/3] Fix UK flag (#5518) --- packages/smooth_app/lib/pages/prices/emoji_helper.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/smooth_app/lib/pages/prices/emoji_helper.dart b/packages/smooth_app/lib/pages/prices/emoji_helper.dart index 5b56715dccf..57a5d2b6aff 100644 --- a/packages/smooth_app/lib/pages/prices/emoji_helper.dart +++ b/packages/smooth_app/lib/pages/prices/emoji_helper.dart @@ -17,7 +17,10 @@ class EmojiHelper { static String? getEmojiByCountryCode(final String countryCode) { if (countryCode.isEmpty) { return null; + } else if (countryCode.toUpperCase() == 'UK') { + return _getCountryEmojiFromUnicode('GB'); } + return _getCountryEmojiFromUnicode(countryCode); } From 344518d048a9e5f9291055978ae2f9e06e62a079 Mon Sep 17 00:00:00 2001 From: Edouard Marquez Date: Fri, 2 Aug 2024 17:36:02 +0200 Subject: [PATCH 2/3] feat: Photo gallery for "Others" (#5501) * Photo gallery for "Others" * Error + loading * Photo details * Fix weird animation --- packages/smooth_app/ios/Podfile.lock | 316 --------------- packages/smooth_app/lib/l10n/app_en.arb | 45 +++ .../product_image_gallery_other_view.dart | 15 +- .../pages/image/product_image_other_page.dart | 363 +++++++++++++++++- .../lib/pages/image/product_image_widget.dart | 3 + packages/smooth_app/macos/Podfile.lock | 131 ------- 6 files changed, 402 insertions(+), 471 deletions(-) delete mode 100644 packages/smooth_app/ios/Podfile.lock delete mode 100644 packages/smooth_app/macos/Podfile.lock diff --git a/packages/smooth_app/ios/Podfile.lock b/packages/smooth_app/ios/Podfile.lock deleted file mode 100644 index d81cc120361..00000000000 --- a/packages/smooth_app/ios/Podfile.lock +++ /dev/null @@ -1,316 +0,0 @@ -PODS: - - app_settings (5.1.1): - - Flutter - - audioplayers_darwin (0.0.1): - - Flutter - - camera_avfoundation (0.0.1): - - Flutter - - connectivity_plus (0.0.1): - - Flutter - - ReachabilitySwift - - device_info_plus (0.0.1): - - Flutter - - Flutter (1.0.0) - - flutter_custom_tabs_ios (2.0.0): - - Flutter - - flutter_email_sender (0.0.1): - - Flutter - - flutter_icmp_ping (0.0.1): - - Flutter - - flutter_image_compress_common (1.0.0): - - Flutter - - Mantle - - SDWebImage - - SDWebImageWebPCoder - - flutter_native_splash (0.0.1): - - Flutter - - flutter_secure_storage (6.0.0): - - Flutter - - GoogleDataTransport (9.4.1): - - GoogleUtilities/Environment (~> 7.7) - - nanopb (< 2.30911.0, >= 2.30908.0) - - PromisesObjC (< 3.0, >= 1.2) - - GoogleMLKit/BarcodeScanning (4.0.0): - - GoogleMLKit/MLKitCore - - MLKitBarcodeScanning (~> 3.0.0) - - GoogleMLKit/MLKitCore (4.0.0): - - MLKitCommon (~> 9.0.0) - - GoogleToolboxForMac/DebugUtils (2.3.2): - - GoogleToolboxForMac/Defines (= 2.3.2) - - GoogleToolboxForMac/Defines (2.3.2) - - GoogleToolboxForMac/Logger (2.3.2): - - GoogleToolboxForMac/Defines (= 2.3.2) - - "GoogleToolboxForMac/NSData+zlib (2.3.2)": - - GoogleToolboxForMac/Defines (= 2.3.2) - - "GoogleToolboxForMac/NSDictionary+URLArguments (2.3.2)": - - GoogleToolboxForMac/DebugUtils (= 2.3.2) - - GoogleToolboxForMac/Defines (= 2.3.2) - - "GoogleToolboxForMac/NSString+URLArguments (= 2.3.2)" - - "GoogleToolboxForMac/NSString+URLArguments (2.3.2)" - - GoogleUtilities/Environment (7.13.3): - - GoogleUtilities/Privacy - - PromisesObjC (< 3.0, >= 1.2) - - GoogleUtilities/Logger (7.13.3): - - GoogleUtilities/Environment - - GoogleUtilities/Privacy - - GoogleUtilities/Privacy (7.13.3) - - GoogleUtilities/UserDefaults (7.13.3): - - GoogleUtilities/Logger - - GoogleUtilities/Privacy - - GoogleUtilitiesComponents (1.1.0): - - GoogleUtilities/Logger - - GTMSessionFetcher/Core (2.3.0) - - image_picker_ios (0.0.1): - - Flutter - - in_app_review (0.2.0): - - Flutter - - integration_test (0.0.1): - - Flutter - - iso_countries (0.0.1): - - Flutter - - libwebp (1.3.2): - - libwebp/demux (= 1.3.2) - - libwebp/mux (= 1.3.2) - - libwebp/sharpyuv (= 1.3.2) - - libwebp/webp (= 1.3.2) - - libwebp/demux (1.3.2): - - libwebp/webp - - libwebp/mux (1.3.2): - - libwebp/demux - - libwebp/sharpyuv (1.3.2) - - libwebp/webp (1.3.2): - - libwebp/sharpyuv - - Mantle (2.2.0): - - Mantle/extobjc (= 2.2.0) - - Mantle/extobjc (2.2.0) - - MLImage (1.0.0-beta4) - - MLKitBarcodeScanning (3.0.0): - - MLKitCommon (~> 9.0) - - MLKitVision (~> 5.0) - - MLKitCommon (9.0.0): - - GoogleDataTransport (~> 9.0) - - GoogleToolboxForMac/Logger (~> 2.1) - - "GoogleToolboxForMac/NSData+zlib (~> 2.1)" - - "GoogleToolboxForMac/NSDictionary+URLArguments (~> 2.1)" - - GoogleUtilities/UserDefaults (~> 7.0) - - GoogleUtilitiesComponents (~> 1.0) - - GTMSessionFetcher/Core (< 3.0, >= 1.1) - - MLKitVision (5.0.0): - - GoogleToolboxForMac/Logger (~> 2.1) - - "GoogleToolboxForMac/NSData+zlib (~> 2.1)" - - GTMSessionFetcher/Core (< 3.0, >= 1.1) - - MLImage (= 1.0.0-beta4) - - MLKitCommon (~> 9.0) - - mobile_scanner (3.5.6): - - Flutter - - GoogleMLKit/BarcodeScanning (~> 4.0.0) - - MTBBarcodeScanner (5.0.11) - - nanopb (2.30910.0): - - nanopb/decode (= 2.30910.0) - - nanopb/encode (= 2.30910.0) - - nanopb/decode (2.30910.0) - - nanopb/encode (2.30910.0) - - package_info_plus (0.4.5): - - Flutter - - path_provider_foundation (0.0.1): - - Flutter - - FlutterMacOS - - permission_handler_apple (9.3.0): - - Flutter - - PromisesObjC (2.4.0) - - qr_code_scanner (0.2.0): - - Flutter - - MTBBarcodeScanner - - ReachabilitySwift (5.2.2) - - rive_common (0.0.1): - - Flutter - - SDWebImage (5.19.2): - - SDWebImage/Core (= 5.19.2) - - SDWebImage/Core (5.19.2) - - SDWebImageWebPCoder (0.14.6): - - libwebp (~> 1.0) - - SDWebImage/Core (~> 5.17) - - Sentry/HybridSDK (8.21.0): - - SentryPrivate (= 8.21.0) - - sentry_flutter (0.0.1): - - Flutter - - FlutterMacOS - - Sentry/HybridSDK (= 8.21.0) - - SentryPrivate (8.21.0) - - share_plus (0.0.1): - - Flutter - - shared_preferences_foundation (0.0.1): - - Flutter - - FlutterMacOS - - sqflite (0.0.3): - - Flutter - - FlutterMacOS - - url_launcher_ios (0.0.1): - - Flutter - - webview_flutter_wkwebview (0.0.1): - - Flutter - -DEPENDENCIES: - - app_settings (from `.symlinks/plugins/app_settings/ios`) - - audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/ios`) - - camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`) - - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - - Flutter (from `Flutter`) - - flutter_custom_tabs_ios (from `.symlinks/plugins/flutter_custom_tabs_ios/ios`) - - flutter_email_sender (from `.symlinks/plugins/flutter_email_sender/ios`) - - flutter_icmp_ping (from `.symlinks/plugins/flutter_icmp_ping/ios`) - - flutter_image_compress_common (from `.symlinks/plugins/flutter_image_compress_common/ios`) - - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - - in_app_review (from `.symlinks/plugins/in_app_review/ios`) - - integration_test (from `.symlinks/plugins/integration_test/ios`) - - iso_countries (from `.symlinks/plugins/iso_countries/ios`) - - mobile_scanner (from `.symlinks/plugins/mobile_scanner/ios`) - - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - - qr_code_scanner (from `.symlinks/plugins/qr_code_scanner/ios`) - - rive_common (from `.symlinks/plugins/rive_common/ios`) - - sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`) - - share_plus (from `.symlinks/plugins/share_plus/ios`) - - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - - sqflite (from `.symlinks/plugins/sqflite/darwin`) - - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`) - -SPEC REPOS: - trunk: - - GoogleDataTransport - - GoogleMLKit - - GoogleToolboxForMac - - GoogleUtilities - - GoogleUtilitiesComponents - - GTMSessionFetcher - - libwebp - - Mantle - - MLImage - - MLKitBarcodeScanning - - MLKitCommon - - MLKitVision - - MTBBarcodeScanner - - nanopb - - PromisesObjC - - ReachabilitySwift - - SDWebImage - - SDWebImageWebPCoder - - Sentry - - SentryPrivate - -EXTERNAL SOURCES: - app_settings: - :path: ".symlinks/plugins/app_settings/ios" - audioplayers_darwin: - :path: ".symlinks/plugins/audioplayers_darwin/ios" - camera_avfoundation: - :path: ".symlinks/plugins/camera_avfoundation/ios" - connectivity_plus: - :path: ".symlinks/plugins/connectivity_plus/ios" - device_info_plus: - :path: ".symlinks/plugins/device_info_plus/ios" - Flutter: - :path: Flutter - flutter_custom_tabs_ios: - :path: ".symlinks/plugins/flutter_custom_tabs_ios/ios" - flutter_email_sender: - :path: ".symlinks/plugins/flutter_email_sender/ios" - flutter_icmp_ping: - :path: ".symlinks/plugins/flutter_icmp_ping/ios" - flutter_image_compress_common: - :path: ".symlinks/plugins/flutter_image_compress_common/ios" - flutter_native_splash: - :path: ".symlinks/plugins/flutter_native_splash/ios" - flutter_secure_storage: - :path: ".symlinks/plugins/flutter_secure_storage/ios" - image_picker_ios: - :path: ".symlinks/plugins/image_picker_ios/ios" - in_app_review: - :path: ".symlinks/plugins/in_app_review/ios" - integration_test: - :path: ".symlinks/plugins/integration_test/ios" - iso_countries: - :path: ".symlinks/plugins/iso_countries/ios" - mobile_scanner: - :path: ".symlinks/plugins/mobile_scanner/ios" - package_info_plus: - :path: ".symlinks/plugins/package_info_plus/ios" - path_provider_foundation: - :path: ".symlinks/plugins/path_provider_foundation/darwin" - permission_handler_apple: - :path: ".symlinks/plugins/permission_handler_apple/ios" - qr_code_scanner: - :path: ".symlinks/plugins/qr_code_scanner/ios" - rive_common: - :path: ".symlinks/plugins/rive_common/ios" - sentry_flutter: - :path: ".symlinks/plugins/sentry_flutter/ios" - share_plus: - :path: ".symlinks/plugins/share_plus/ios" - shared_preferences_foundation: - :path: ".symlinks/plugins/shared_preferences_foundation/darwin" - sqflite: - :path: ".symlinks/plugins/sqflite/darwin" - url_launcher_ios: - :path: ".symlinks/plugins/url_launcher_ios/ios" - webview_flutter_wkwebview: - :path: ".symlinks/plugins/webview_flutter_wkwebview/ios" - -SPEC CHECKSUMS: - app_settings: 017320c6a680cdc94c799949d95b84cb69389ebc - audioplayers_darwin: 877d9a4d06331c5c374595e46e16453ac7eafa40 - camera_avfoundation: 759172d1a77ae7be0de08fc104cfb79738b8a59e - connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d - device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 - Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_custom_tabs_ios: a651b18786388923b62de8c0537607de87c2eccf - flutter_email_sender: 10a22605f92809a11ef52b2f412db806c6082d40 - flutter_icmp_ping: 2b159955eee0c487c766ad83fec224ae35e7c935 - flutter_image_compress_common: ec1d45c362c9d30a3f6a0426c297f47c52007e3e - flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778 - flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 - GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a - GoogleMLKit: 2bd0dc6253c4d4f227aad460f69215a504b2980e - GoogleToolboxForMac: 8bef7c7c5cf7291c687cf5354f39f9db6399ad34 - GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15 - GoogleUtilitiesComponents: 679b2c881db3b615a2777504623df6122dd20afe - GTMSessionFetcher: 3a63d75eecd6aa32c2fc79f578064e1214dfdec2 - image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 - in_app_review: 318597b3a06c22bb46dc454d56828c85f444f99d - integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4 - iso_countries: eb09d40f388e4c65e291e0bb36a701dfe7de6c74 - libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009 - Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d - MLImage: 7bb7c4264164ade9bf64f679b40fb29c8f33ee9b - MLKitBarcodeScanning: 04e264482c5f3810cb89ebc134ef6b61e67db505 - MLKitCommon: c1b791c3e667091918d91bda4bba69a91011e390 - MLKitVision: 8baa5f46ee3352614169b85250574fde38c36f49 - mobile_scanner: 38dcd8a49d7d485f632b7de65e4900010187aef2 - MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb - nanopb: 438bc412db1928dac798aa6fd75726007be04262 - package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 - PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 - qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e - ReachabilitySwift: 2128f3a8c9107e1ad33574c6e58e8285d460b149 - rive_common: cbbac3192af00d7341f19dae2f26298e9e37d99e - SDWebImage: dfe95b2466a9823cf9f0c6d01217c06550d7b29a - SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380 - Sentry: ebc12276bd17613a114ab359074096b6b3725203 - sentry_flutter: dff1df05dc39c83d04f9330b36360fc374574c5e - SentryPrivate: d651efb234cf385ec9a1cdd3eff94b5e78a0e0fe - share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5 - shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 - sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec - url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe - webview_flutter_wkwebview: be0f0d33777f1bfd0c9fdcb594786704dbf65f36 - -PODFILE CHECKSUM: 31bd95b3ebe08a1371aec307f223993946c837a3 - -COCOAPODS: 1.15.2 diff --git a/packages/smooth_app/lib/l10n/app_en.arb b/packages/smooth_app/lib/l10n/app_en.arb index fac43512480..bfad1e2f3de 100644 --- a/packages/smooth_app/lib/l10n/app_en.arb +++ b/packages/smooth_app/lib/l10n/app_en.arb @@ -775,6 +775,7 @@ } } }, + "product_image_outdated": "This image may be outdated", "homepage_main_card_logo_description": "Welcome to Open Food Facts", "@homepage_main_card_logo_description": { "description": "Description for accessibility of the Open Food Facts logo on the homepage" @@ -2971,5 +2972,49 @@ "menu_button_list_actions": "Select an action", "@menu_button_list_actions": { "description": "Button to select an action in a list (eg: Share, Delete, …)" + }, + "error_loading_photo": "Error loading photo", + "@error_loading_photo": { + "description": "Error message when loading a photo fails to load" + }, + "photo_viewer_details_button": "Details", + "@photo_viewer_details_button": { + "description": "Button to show details of the photo" + }, + "photo_viewer_details_button_accessibility_label": "Details of this photo", + "@photo_viewer_details_button_accessibility_label": { + "description": "Accessibility label for the Details button on a photo" + }, + "photo_viewer_details_title": "Details of the photo", + "@photo_viewer_details_title": { + "description": "Title of the photo details dialog" + }, + "photo_viewer_details_contributor_title": "Contributor", + "@photo_viewer_details_contributor_title": { + "description": "Label for the author of a photo" + }, + "photo_viewer_details_size_title": "Size", + "@photo_viewer_details_size_title": { + "description": "Label for the size of a photo" + }, + "photo_viewer_details_size_value": "{width} x {height} pixels", + "@photo_viewer_details_size_value": { + "description": "Value for the size of a photo", + "placeholders": { + "width": { + "type": "int" + }, + "height": { + "type": "int" + } + } + }, + "photo_viewer_details_date_title": "Date", + "@photo_viewer_details_date_title": { + "description": "Label for the uploaded date of a photo" + }, + "photo_viewer_details_url_title": "URL", + "@photo_viewer_details_url_title": { + "description": "Label for the link of a photo" } } \ No newline at end of file diff --git a/packages/smooth_app/lib/pages/image/product_image_gallery_other_view.dart b/packages/smooth_app/lib/pages/image/product_image_gallery_other_view.dart index 3936ac36b1a..1a25b281bef 100644 --- a/packages/smooth_app/lib/pages/image/product_image_gallery_other_view.dart +++ b/packages/smooth_app/lib/pages/image/product_image_gallery_other_view.dart @@ -120,6 +120,8 @@ class _RawGridGallery extends StatelessWidget { // order by descending ids index = rawImages.length - 1 - index; final ProductImage productImage = rawImages[index]; + final String? heroTag = productImage.imgid; + return Padding( padding: EdgeInsetsDirectional.only( start: VERY_SMALL_SPACE, @@ -130,10 +132,14 @@ class _RawGridGallery extends StatelessWidget { onTap: () async => Navigator.push( context, MaterialPageRoute( - builder: (BuildContext context) => ProductImageOtherPage( - product, - int.parse(productImage.imgid!), - ), + builder: (BuildContext context) { + return ProductImageOtherPage( + product: product, + images: rawImages.reversed.toList(growable: false), + currentImage: productImage, + heroTag: heroTag, + ); + }, ), ), child: ProductImageWidget( @@ -141,6 +147,7 @@ class _RawGridGallery extends StatelessWidget { barcode: product.barcode!, squareSize: squareSize, imageSize: imageSize, + heroTag: heroTag, ), ), ); diff --git a/packages/smooth_app/lib/pages/image/product_image_other_page.dart b/packages/smooth_app/lib/pages/image/product_image_other_page.dart index d8722384468..72babd739d7 100644 --- a/packages/smooth_app/lib/pages/image/product_image_other_page.dart +++ b/packages/smooth_app/lib/pages/image/product_image_other_page.dart @@ -1,40 +1,363 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:intl/intl.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; +import 'package:provider/provider.dart'; +import 'package:smooth_app/generic_lib/bottom_sheets/smooth_bottom_sheet.dart'; +import 'package:smooth_app/generic_lib/design_constants.dart'; +import 'package:smooth_app/helpers/launch_url_helper.dart'; import 'package:smooth_app/helpers/product_cards_helper.dart'; +import 'package:smooth_app/pages/image/product_image_helper.dart'; import 'package:smooth_app/query/product_query.dart'; +import 'package:smooth_app/resources/app_icons.dart' as icons; +import 'package:smooth_app/themes/smooth_theme_colors.dart'; import 'package:smooth_app/widgets/smooth_scaffold.dart'; /// Full page display of a raw product image. -class ProductImageOtherPage extends StatelessWidget { - const ProductImageOtherPage( - this.product, - this.imageId, - ); +class ProductImageOtherPage extends StatefulWidget { + const ProductImageOtherPage({ + required this.product, + required this.images, + required this.currentImage, + this.heroTag, + }); final Product product; - final int imageId; + final List images; + final ProductImage currentImage; + final String? heroTag; + + @override + State createState() => _ProductImageOtherPageState(); +} + +class _ProductImageOtherPageState extends State { + late PageController _pageController; + + @override + void initState() { + super.initState(); + _pageController = PageController( + initialPage: widget.images.indexOf(widget.currentImage), + ); + } + + @override + Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + return ChangeNotifierProvider.value( + value: _pageController, + child: SmoothScaffold( + appBar: buildEditProductAppBar( + context: context, + title: appLocalizations.edit_product_form_item_photos_title, + product: widget.product, + ), + body: Stack( + alignment: Alignment.bottomCenter, + children: [ + Positioned.fill( + child: PageView( + controller: _pageController, + children: widget.images.map( + (final ProductImage image) { + return _ProductImageViewer( + image: image, + barcode: widget.product.barcode!, + heroTag: + widget.currentImage == image ? widget.heroTag : null, + ); + }, + ).toList(growable: false), + ), + ), + Positioned( + top: SMALL_SPACE, + child: _ProductImagePageIndicator( + items: widget.images.length, + ), + ), + ], + ), + ), + ); + } +} + +class _ProductImageViewer extends StatelessWidget { + const _ProductImageViewer({ + required this.image, + required this.barcode, + this.heroTag, + }); + + final ProductImage image; + final String barcode; + final String? heroTag; + + @override + Widget build(BuildContext context) { + final SmoothColorsThemeExtension colors = + Theme.of(context).extension()!; + + return Stack( + children: [ + Positioned.fill( + child: HeroMode( + enabled: heroTag?.isNotEmpty == true, + child: Hero( + tag: heroTag ?? '', + child: Image( + image: NetworkImage( + image.getUrl( + barcode, + uriHelper: ProductQuery.uriProductHelper, + ), + ), + fit: BoxFit.cover, + loadingBuilder: ( + _, + final Widget child, + final ImageChunkEvent? loadingProgress, + ) { + if (loadingProgress != null) { + return const Center( + child: CircularProgressIndicator.adaptive(), + ); + } else { + return child; + } + }, + errorBuilder: (_, __, ___) => Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const icons.Warning( + size: 48.0, + color: Colors.red, + ), + const SizedBox(height: SMALL_SPACE), + Text(AppLocalizations.of(context).error_loading_photo), + ], + ), + ), + ), + ), + ), + Positioned( + bottom: SMALL_SPACE + MediaQuery.viewPaddingOf(context).bottom, + left: SMALL_SPACE, + right: SMALL_SPACE, + child: IntrinsicHeight( + child: Row( + children: [ + _ProductImageDetailsButton( + image: image, + barcode: barcode, + ), + const Spacer(), + if (image.expired) _ProductImageOutdatedLabel(colors: colors), + ], + ), + ), + ), + ], + ); + } +} + +class _ProductImageOutdatedLabel extends StatelessWidget { + const _ProductImageOutdatedLabel({ + required this.colors, + }); + + final SmoothColorsThemeExtension colors; + + @override + Widget build(BuildContext context) { + return Semantics( + child: SizedBox( + height: double.infinity, + child: DecoratedBox( + decoration: BoxDecoration( + color: colors.red.withOpacity(0.9), + borderRadius: CIRCULAR_BORDER_RADIUS, + ), + child: Padding( + padding: const EdgeInsets.all(SMALL_SPACE), + child: Row( + children: [ + const icons.Outdated( + size: 18.0, + color: Colors.white, + ), + const SizedBox(width: SMALL_SPACE), + Text( + AppLocalizations.of(context).product_image_outdated, + style: const TextStyle( + fontSize: 13.0, + color: Colors.white, + ), + ), + ], + ), + ), + ), + ), + ); + } +} + +class _ProductImageDetailsButton extends StatelessWidget { + const _ProductImageDetailsButton({ + required this.image, + required this.barcode, + }); + + final ProductImage image; + final String barcode; @override Widget build(BuildContext context) { final AppLocalizations appLocalizations = AppLocalizations.of(context); - return SmoothScaffold( - appBar: buildEditProductAppBar( - context: context, - title: appLocalizations.edit_product_form_item_photos_title, - product: product, + final String url = image.url ?? + image.getUrl( + barcode, + uriHelper: ProductQuery.uriProductHelper, + ); + + return DecoratedBox( + decoration: const BoxDecoration( + color: Colors.black45, + borderRadius: CIRCULAR_BORDER_RADIUS, ), - body: Image( - image: NetworkImage( - ProductImage.raw( - imgid: imageId.toString(), - size: ImageSize.ORIGINAL, - ).getUrl( - product.barcode!, - uriHelper: ProductQuery.uriProductHelper, + child: Material( + type: MaterialType.transparency, + child: InkWell( + borderRadius: CIRCULAR_BORDER_RADIUS, + onTap: () { + showSmoothModalSheet( + context: context, + builder: (BuildContext context) { + return SmoothModalSheet( + title: appLocalizations.photo_viewer_details_title, + body: Column( + children: [ + ListTile( + title: Text(appLocalizations + .photo_viewer_details_contributor_title), + // TODO(g123k): add contributor + subtitle: const Text('TODO'), + ), + ListTile( + title: Text( + appLocalizations.photo_viewer_details_date_title), + subtitle: Text(image.uploaded != null + ? DateFormat.yMMMMEEEEd().format(image.uploaded!) + : '-'), + ), + ListTile( + title: Text( + appLocalizations.photo_viewer_details_size_title), + subtitle: Text( + image.width != null && image.height != null + ? appLocalizations + .photo_viewer_details_size_value( + image.width!, + image.height!, + ) + : '-', + ), + ), + if (url.isNotEmpty) + ListTile( + title: Text(appLocalizations + .photo_viewer_details_url_title), + subtitle: Text(url), + trailing: const Icon(Icons.open_in_new_rounded), + onTap: () { + LaunchUrlHelper.launchURL(url); + }, + ), + SizedBox( + height: MediaQuery.viewPaddingOf(context).bottom), + ], + ), + ); + }); + }, + child: Padding( + padding: const EdgeInsetsDirectional.only( + start: SMALL_SPACE, + top: SMALL_SPACE, + bottom: SMALL_SPACE, + end: MEDIUM_SPACE, + ), + child: Semantics( + label: appLocalizations + .photo_viewer_details_button_accessibility_label, + button: true, + excludeSemantics: true, + child: Row( + children: [ + const icons.Info( + size: 15.0, + color: Colors.white, + ), + const SizedBox(width: SMALL_SPACE), + Text( + appLocalizations.photo_viewer_details_button, + style: const TextStyle( + color: Colors.white, + ), + ), + ], + ), + ), ), ), - fit: BoxFit.cover, + ), + ); + } +} + +class _ProductImagePageIndicator extends StatelessWidget { + const _ProductImagePageIndicator({required this.items}); + + final int items; + + @override + Widget build(BuildContext context) { + return DecoratedBox( + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.5), + borderRadius: CIRCULAR_BORDER_RADIUS, + ), + child: Padding( + padding: const EdgeInsets.all(SMALL_SPACE), + child: Selector( + selector: (_, PageController value) { + if (!value.position.hasPixels) { + return 0; + } + + final int page = + (value.offset / value.position.viewportDimension).round(); + if (page < 0) { + return 0; + } else if (page > items - 1) { + return items - 1; + } else { + return page; + } + }, + shouldRebuild: (int previous, int next) => previous != next, + builder: (BuildContext context, int progress, _) { + return Text( + '${progress + 1} / $items', + style: const TextStyle(color: Colors.white), + ); + }, + ), ), ); } diff --git a/packages/smooth_app/lib/pages/image/product_image_widget.dart b/packages/smooth_app/lib/pages/image/product_image_widget.dart index 7393a6bd841..030bef83cd2 100644 --- a/packages/smooth_app/lib/pages/image/product_image_widget.dart +++ b/packages/smooth_app/lib/pages/image/product_image_widget.dart @@ -18,11 +18,13 @@ class ProductImageWidget extends StatelessWidget { required this.barcode, required this.squareSize, this.imageSize, + this.heroTag, }); final ProductImage productImage; final String barcode; final double squareSize; + final String? heroTag; /// Allows to fetch the optimized version of the image final ImageSize? imageSize; @@ -47,6 +49,7 @@ class ProductImageWidget extends StatelessWidget { imageSize: imageSize, ), ), + heroTag: heroTag, rounded: false, ); final DateTime? uploaded = productImage.uploaded; diff --git a/packages/smooth_app/macos/Podfile.lock b/packages/smooth_app/macos/Podfile.lock deleted file mode 100644 index c802b6a34ca..00000000000 --- a/packages/smooth_app/macos/Podfile.lock +++ /dev/null @@ -1,131 +0,0 @@ -PODS: - - audioplayers_darwin (0.0.1): - - FlutterMacOS - - connectivity_plus (0.0.1): - - FlutterMacOS - - ReachabilitySwift - - device_info_plus (0.0.1): - - FlutterMacOS - - file_selector_macos (0.0.1): - - FlutterMacOS - - flutter_image_compress_macos (1.0.0): - - FlutterMacOS - - flutter_secure_storage_macos (6.1.1): - - FlutterMacOS - - FlutterMacOS (1.0.0) - - in_app_review (0.2.0): - - FlutterMacOS - - mobile_scanner (3.5.6): - - FlutterMacOS - - package_info_plus (0.0.1): - - FlutterMacOS - - path_provider_foundation (0.0.1): - - Flutter - - FlutterMacOS - - ReachabilitySwift (5.2.2) - - rive_common (0.0.1): - - FlutterMacOS - - Sentry/HybridSDK (8.21.0): - - SentryPrivate (= 8.21.0) - - sentry_flutter (0.0.1): - - Flutter - - FlutterMacOS - - Sentry/HybridSDK (= 8.21.0) - - SentryPrivate (8.21.0) - - share_plus (0.0.1): - - FlutterMacOS - - shared_preferences_foundation (0.0.1): - - Flutter - - FlutterMacOS - - sqflite (0.0.3): - - Flutter - - FlutterMacOS - - url_launcher_macos (0.0.1): - - FlutterMacOS - -DEPENDENCIES: - - audioplayers_darwin (from `Flutter/ephemeral/.symlinks/plugins/audioplayers_darwin/macos`) - - connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/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_image_compress_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_image_compress_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`) - - 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`) - - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/darwin`) - - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) - -SPEC REPOS: - trunk: - - ReachabilitySwift - - Sentry - - SentryPrivate - -EXTERNAL SOURCES: - audioplayers_darwin: - :path: Flutter/ephemeral/.symlinks/plugins/audioplayers_darwin/macos - connectivity_plus: - :path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/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_image_compress_macos: - :path: Flutter/ephemeral/.symlinks/plugins/flutter_image_compress_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 - rive_common: - :path: Flutter/ephemeral/.symlinks/plugins/rive_common/macos - 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/darwin - url_launcher_macos: - :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos - -SPEC CHECKSUMS: - audioplayers_darwin: dcad41de4fbd0099cb3749f7ab3b0cb8f70b810c - connectivity_plus: 18d3c32514c886e046de60e9c13895109866c747 - device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f - file_selector_macos: 54fdab7caa3ac3fc43c9fac4d7d8d231277f8cf2 - flutter_image_compress_macos: c26c3c13ea0f28ae6dea4e139b3292e7729f99f1 - flutter_secure_storage_macos: 59459653abe1adb92abbc8ea747d79f8d19866c9 - FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - in_app_review: a850789fad746e89bce03d4aeee8078b45a53fd0 - mobile_scanner: 54ceceae0c8da2457e26a362a6be5c61154b1829 - package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - ReachabilitySwift: 2128f3a8c9107e1ad33574c6e58e8285d460b149 - rive_common: cf5ab646aa576b2d742d0e2d528126fbf032c856 - Sentry: ebc12276bd17613a114ab359074096b6b3725203 - sentry_flutter: dff1df05dc39c83d04f9330b36360fc374574c5e - SentryPrivate: d651efb234cf385ec9a1cdd3eff94b5e78a0e0fe - share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7 - shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 - sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec - url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399 - -PODFILE CHECKSUM: 0d3963a09fc94f580682bd88480486da345dc3f0 - -COCOAPODS: 1.15.2 From 7b3cfab9d5dd08f2b108ce6aabc02887046a0b5d Mon Sep 17 00:00:00 2001 From: Edouard Marquez Date: Mon, 5 Aug 2024 10:03:32 +0200 Subject: [PATCH 3/3] feat: User lists: change the way to switch between lists (#5521) * User lists: change the way the switch between lists * Improve a11n --- .../product/common/product_list_page.dart | 152 +++++++++++++----- .../smooth_app/lib/resources/app_icons.dart | 26 +-- 2 files changed, 127 insertions(+), 51 deletions(-) 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 2336258b8a8..5a183073e83 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 @@ -30,6 +30,8 @@ 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/pages/scan/carousel/scan_carousel_manager.dart'; import 'package:smooth_app/query/product_query.dart'; +import 'package:smooth_app/resources/app_icons.dart' as icons; +import 'package:smooth_app/themes/theme_provider.dart'; import 'package:smooth_app/widgets/smooth_app_bar.dart'; import 'package:smooth_app/widgets/smooth_menu_button.dart'; import 'package:smooth_app/widgets/smooth_scaffold.dart'; @@ -146,42 +148,6 @@ class _ProductListPageState extends State appBar: SmoothAppBar( centerTitle: false, actions: [ - if (widget.allowToSwitchBetweenLists) - IconButton( - icon: const Icon(CupertinoIcons.square_list), - tooltip: appLocalizations.action_change_list, - onPressed: () async { - final ProductList? selected = - await showSmoothDraggableModalSheet( - context: context, - header: SmoothModalSheetHeader( - title: appLocalizations.product_list_select, - suffix: SmoothModalSheetHeaderButton( - label: appLocalizations.product_list_create, - prefix: const Icon(Icons.add_circle_outline_sharp), - tooltip: appLocalizations.product_list_create_tooltip, - onTap: () async => - ProductListUserDialogHelper(daoProductList) - .showCreateUserListDialog(context), - ), - ), - bodyBuilder: (BuildContext context) => AllProductListModal( - currentList: productList, - ), - initHeight: _computeModalInitHeight(context), - ); - - if (selected == null) { - return; - } - if (context.mounted) { - await daoProductList.get(selected); - if (context.mounted) { - setState(() => productList = selected); - } - } - }, - ), SmoothPopupMenuButton( onSelected: (final ProductListPopupItem action) async { final ProductList? differentProductList = @@ -202,13 +168,12 @@ class _ProductListPageState extends State ], ), ], - title: AutoSizeText( - ProductQueryPageHelper.getProductListLabel( - productList, - appLocalizations, - ), - maxLines: 2, + title: _ProductListAppBarTitle( + productList: productList, + onTap: () => _onChangeList(appLocalizations, daoProductList), + enabled: widget.allowToSwitchBetweenLists, ), + titleSpacing: 0.0, actionMode: _selectionMode, onLeaveActionMode: () { setState(() => _selectionMode = false); @@ -502,4 +467,107 @@ class _ProductListPageState extends State } return false; } + + Future _onChangeList( + AppLocalizations appLocalizations, + DaoProductList daoProductList, + ) async { + final ProductList? selected = + await showSmoothDraggableModalSheet( + context: context, + header: SmoothModalSheetHeader( + title: appLocalizations.product_list_select, + suffix: SmoothModalSheetHeaderButton( + label: appLocalizations.product_list_create, + prefix: const Icon(Icons.add_circle_outline_sharp), + tooltip: appLocalizations.product_list_create_tooltip, + onTap: () async => ProductListUserDialogHelper(daoProductList) + .showCreateUserListDialog(context), + ), + ), + bodyBuilder: (BuildContext context) => AllProductListModal( + currentList: productList, + ), + initHeight: _computeModalInitHeight(context), + ); + + if (selected == null) { + return; + } + if (context.mounted) { + await daoProductList.get(selected); + if (context.mounted) { + setState(() => productList = selected); + } + } + } +} + +class _ProductListAppBarTitle extends StatelessWidget { + const _ProductListAppBarTitle({ + required this.productList, + required this.onTap, + required this.enabled, + }); + + final ProductList productList; + final VoidCallback onTap; + final bool enabled; + + @override + Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + final String title = ProductQueryPageHelper.getProductListLabel( + productList, + appLocalizations, + ); + + return Semantics( + label: enabled ? appLocalizations.action_change_list : null, + value: title, + button: enabled, + excludeSemantics: true, + child: SizedBox( + height: kToolbarHeight, + child: InkWell( + borderRadius: context.read().isAmoledTheme + ? ANGULAR_BORDER_RADIUS + : null, + onTap: enabled ? onTap : null, + child: Padding( + padding: const EdgeInsetsDirectional.symmetric( + horizontal: NavigationToolbar.kMiddleSpacing, + ), + child: LayoutBuilder( + builder: (_, BoxConstraints constraints) { + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ConstrainedBox( + constraints: BoxConstraints( + maxWidth: constraints.maxWidth * 0.9 - + (enabled ? (MEDIUM_SPACE - 15.0) : 0), + ), + child: AutoSizeText( + title, + maxLines: 2, + ), + ), + if (enabled) ...[ + const SizedBox(width: MEDIUM_SPACE), + icons.AppIconTheme( + semanticLabel: appLocalizations.action_change_list, + size: 15.0, + child: const icons.Chevron.down(), + ) + ] + ], + ); + }, + ), + ), + ), + ), + ); + } } diff --git a/packages/smooth_app/lib/resources/app_icons.dart b/packages/smooth_app/lib/resources/app_icons.dart index 2d6dc46fdaa..0f9306227df 100644 --- a/packages/smooth_app/lib/resources/app_icons.dart +++ b/packages/smooth_app/lib/resources/app_icons.dart @@ -695,6 +695,7 @@ abstract class AppIcon extends StatelessWidget { this.color, this.shadow, this.size, + this.semanticLabel, super.key, }) : assert(size == null || size >= 0); @@ -702,6 +703,7 @@ abstract class AppIcon extends StatelessWidget { final Color? color; final double? size; final Shadow? shadow; + final String? semanticLabel; @override @mustCallSuper @@ -719,14 +721,17 @@ abstract class AppIcon extends StatelessWidget { Theme.of(context).iconTheme.color, }; - return Icon(icon, - color: color, - size: size ?? iconTheme?.size, - shadows: shadow != null - ? [shadow!] - : iconTheme?.shadow != null - ? [iconTheme!.shadow!] - : null); + return Icon( + icon, + color: color, + size: size ?? iconTheme?.size, + semanticLabel: iconTheme?.semanticLabel ?? semanticLabel, + shadows: shadow != null + ? [shadow!] + : iconTheme?.shadow != null + ? [iconTheme!.shadow!] + : null, + ); } } @@ -739,11 +744,13 @@ class AppIconTheme extends InheritedWidget { this.color, this.size, this.shadow, + this.semanticLabel, }); final Color? color; final double? size; final Shadow? shadow; + final String? semanticLabel; static AppIconTheme of(BuildContext context) { final AppIconTheme? result = maybeOf(context); @@ -758,7 +765,8 @@ class AppIconTheme extends InheritedWidget { @override bool updateShouldNotify(AppIconTheme oldWidget) { return color != oldWidget.color || - size != oldWidget.size || + semanticLabel != oldWidget.semanticLabel || + shadow != oldWidget.shadow || shadow != oldWidget.shadow; } }