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 95e24525693..b3ae0e6edb6 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,9 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:provider/provider.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/generic_lib/duration_constants.dart'; import 'package:smooth_app/generic_lib/widgets/picture_not_found.dart'; +import 'package:smooth_app/themes/theme_provider.dart'; /// Container to display a product image on a product card. /// @@ -163,9 +165,16 @@ class _SmoothAnimatedLogoState extends State<_SmoothAnimatedLogo> Animation? _animation; @override - Widget build(BuildContext context) { - _attachAnimation(); + void initState() { + super.initState(); + SchedulerBinding.instance.addPostFrameCallback((_) { + _attachAnimation(); + }); + } + + @override + Widget build(BuildContext context) { return Opacity( opacity: _animation?.value ?? widget.opacityMin, child: const _SmoothAppLogo(), @@ -173,10 +182,6 @@ class _SmoothAnimatedLogoState extends State<_SmoothAnimatedLogo> } void _attachAnimation() { - if (_animation != null) { - return; - } - AnimationController? controller = _SmoothSharedAnimationControllerState.of(context); @@ -214,7 +219,13 @@ class _SmoothAppLogo extends StatelessWidget { @override Widget build(BuildContext context) { - return SvgPicture.asset('assets/app/release_icon_transparent.svg'); + final ThemeProvider themeProvider = context.watch(); + + return SvgPicture.asset( + !themeProvider.isDarkMode(context) + ? 'assets/app/release_icon_transparent.svg' + : 'assets/app/release_icon_dark_transparent_no_border.svg', + ); } } @@ -248,8 +259,8 @@ class _SmoothSharedAnimationControllerState @override Widget build(BuildContext context) { - return Provider<_SmoothSharedAnimationControllerState>.value( - value: this, + return Provider<_SmoothSharedAnimationControllerState>( + create: (_) => this, child: widget.child, ); } @@ -262,8 +273,10 @@ class _SmoothSharedAnimationControllerState static AnimationController? of(BuildContext context) { try { - return Provider.of<_SmoothSharedAnimationControllerState>(context) - ._controller; + return Provider.of<_SmoothSharedAnimationControllerState>( + context, + listen: false, + )._controller; } catch (_) { return null; } 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 83a5076534b..d9f89d8bd28 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 @@ -17,6 +17,7 @@ import 'package:smooth_app/generic_lib/bottom_sheets/smooth_bottom_sheet.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/generic_lib/duration_constants.dart'; import 'package:smooth_app/generic_lib/loading_dialog.dart'; +import 'package:smooth_app/generic_lib/widgets/images/smooth_image.dart'; import 'package:smooth_app/generic_lib/widgets/smooth_responsive.dart'; import 'package:smooth_app/helpers/app_helper.dart'; import 'package:smooth_app/helpers/robotoff_insight_helper.dart'; @@ -130,138 +131,142 @@ class _ProductListPageState extends State final bool enableClear = products.isNotEmpty; final bool enableRename = productList.listType == ProductListType.USER; - return SmoothScaffold( - floatingActionButton: products.isEmpty - ? FloatingActionButton.extended( - icon: const Icon(CupertinoIcons.barcode), - label: Text(appLocalizations.product_list_empty_title), - onPressed: () => - ExternalScanCarouselManager.read(context).showSearchCard(), - ) - : _selectionMode - ? null - : FloatingActionButton.extended( - onPressed: () => setState(() => _selectionMode = true), - label: const Text('Multi-select'), - icon: const Icon(Icons.checklist), - ), - appBar: SmoothAppBar( - centerTitle: false, - actions: [ - SmoothPopupMenuButton( - onSelected: (final ProductListPopupItem action) async { - final ProductList? differentProductList = - await action.doSomething( - productList: productList, - localDatabase: localDatabase, - context: context, - ); - if (differentProductList != null) { - setState(() => productList = differentProductList); - } - }, - itemBuilder: (_) => >[ - if (enableRename) _rename.getMenuItem(appLocalizations), - _share.getMenuItem(appLocalizations), - _openInWeb.getMenuItem(appLocalizations), - if (enableClear) _clear.getMenuItem(appLocalizations), - ], + return SmoothSharedAnimationController( + child: SmoothScaffold( + floatingActionButton: products.isEmpty + ? FloatingActionButton.extended( + icon: const Icon(CupertinoIcons.barcode), + label: Text(appLocalizations.product_list_empty_title), + onPressed: () => + ExternalScanCarouselManager.read(context).showSearchCard(), + ) + : _selectionMode + ? null + : FloatingActionButton.extended( + onPressed: () => setState(() => _selectionMode = true), + label: const Text('Multi-select'), + icon: const Icon(Icons.checklist), + ), + appBar: SmoothAppBar( + centerTitle: false, + actions: [ + SmoothPopupMenuButton( + onSelected: (final ProductListPopupItem action) async { + final ProductList? differentProductList = + await action.doSomething( + productList: productList, + localDatabase: localDatabase, + context: context, + ); + if (differentProductList != null) { + setState(() => productList = differentProductList); + } + }, + itemBuilder: (_) => >[ + if (enableRename) _rename.getMenuItem(appLocalizations), + _share.getMenuItem(appLocalizations), + _openInWeb.getMenuItem(appLocalizations), + if (enableClear) _clear.getMenuItem(appLocalizations), + ], + ), + ], + title: _ProductListAppBarTitle( + productList: productList, + onTap: () => _onChangeList(appLocalizations, daoProductList), + enabled: widget.allowToSwitchBetweenLists, ), - ], - title: _ProductListAppBarTitle( - productList: productList, - onTap: () => _onChangeList(appLocalizations, daoProductList), - enabled: widget.allowToSwitchBetweenLists, - ), - titleSpacing: 0.0, - actionMode: _selectionMode, - onLeaveActionMode: () { - setState(() => _selectionMode = false); - }, - actionModeTitle: Text('${_selectedBarcodes.length}'), - actionModeActions: [ - SmoothPopupMenuButton( - onSelected: (final ProductListItemPopupItem action) async { - final bool andThenSetState = await action.doSomething( - productList: productList, - localDatabase: localDatabase, - context: context, - selectedBarcodes: _selectedBarcodes, - ); - if (andThenSetState) { - if (context.mounted) { - setState(() {}); + titleSpacing: 0.0, + actionMode: _selectionMode, + onLeaveActionMode: () { + setState(() => _selectionMode = false); + }, + actionModeTitle: Text('${_selectedBarcodes.length}'), + actionModeActions: [ + SmoothPopupMenuButton( + onSelected: (final ProductListItemPopupItem action) async { + final bool andThenSetState = await action.doSomething( + productList: productList, + localDatabase: localDatabase, + context: context, + selectedBarcodes: _selectedBarcodes, + ); + if (andThenSetState) { + if (context.mounted) { + setState(() {}); + } } - } - }, - itemBuilder: (_) => >[ - if (userPreferences.getFlag(UserPreferencesDevMode - .userPreferencesFlagBoostedComparison) == - true) - _sideBySideItems.getMenuItem( + }, + itemBuilder: (_) => + >[ + if (userPreferences.getFlag(UserPreferencesDevMode + .userPreferencesFlagBoostedComparison) == + true) + _sideBySideItems.getMenuItem( + appLocalizations, + _selectedBarcodes.length >= 2 && + _selectedBarcodes.length <= 3, + ), + _rankItems.getMenuItem( appLocalizations, - _selectedBarcodes.length >= 2 && - _selectedBarcodes.length <= 3, - ), - _rankItems.getMenuItem( - appLocalizations, - _selectedBarcodes.length >= 2, - ), - _deleteItems.getMenuItem( - appLocalizations, - _selectedBarcodes.isNotEmpty, - ), - ], - ), - ], - ), - body: products.isEmpty - ? Center( - child: Padding( - padding: const EdgeInsets.all(SMALL_SPACE), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - SvgPicture.asset( - 'assets/misc/empty-list.svg', - package: AppHelper.APP_PACKAGE, - width: MediaQuery.sizeOf(context).width / 2, - ), - Text( - appLocalizations.product_list_empty_message, - textAlign: TextAlign.center, - style: themeData.textTheme.bodyMedium?.apply( - color: themeData.colorScheme.onSurface, - ), - ), - EMPTY_WIDGET, - ], + _selectedBarcodes.length >= 2, ), - ), - ) - : WillPopScope2( - onWillPop: () async => (await _handleUserBacktap(), null), - child: RefreshIndicator( - //if it is in selectmode then refresh indicator is not shown - notificationPredicate: - _selectionMode ? (_) => false : (_) => true, - onRefresh: () async => _refreshListProducts( - products, - localDatabase, + _deleteItems.getMenuItem( appLocalizations, + _selectedBarcodes.isNotEmpty, + ), + ], + ), + ], + ), + body: products.isEmpty + ? Center( + child: Padding( + padding: const EdgeInsets.all(SMALL_SPACE), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + SvgPicture.asset( + 'assets/misc/empty-list.svg', + package: AppHelper.APP_PACKAGE, + width: MediaQuery.sizeOf(context).width / 2, + ), + Text( + appLocalizations.product_list_empty_message, + textAlign: TextAlign.center, + style: themeData.textTheme.bodyMedium?.apply( + color: themeData.colorScheme.onSurface, + ), + ), + EMPTY_WIDGET, + ], + ), ), - child: ListView.builder( - itemCount: products.length, - itemBuilder: (BuildContext context, int index) => _buildItem( - dismissible, + ) + : WillPopScope2( + onWillPop: () async => (await _handleUserBacktap(), null), + child: RefreshIndicator( + //if it is in selectmode then refresh indicator is not shown + notificationPredicate: + _selectionMode ? (_) => false : (_) => true, + onRefresh: () async => _refreshListProducts( products, - index, localDatabase, appLocalizations, ), + child: ListView.builder( + itemCount: products.length, + itemBuilder: (BuildContext context, int index) => + _buildItem( + dismissible, + products, + index, + localDatabase, + appLocalizations, + ), + ), ), ), - ), + ), ); } 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 c517696c74e..101797b386a 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 @@ -53,115 +53,117 @@ class _ProductImageGalleryViewState extends State final AppLocalizations appLocalizations = AppLocalizations.of(context); context.watch(); refreshUpToDate(); - return SmoothScaffold( - appBar: buildEditProductAppBar( - context: context, - title: appLocalizations.edit_product_form_item_photos_title, - product: upToDateProduct, - ), - floatingActionButton: FloatingActionButton.extended( - onPressed: () async { - AnalyticsHelper.trackProductEdit( - AnalyticsEditEvents.photos, - barcode, - true, - ); - await confirmAndUploadNewPicture( - context, - imageField: ImageField.OTHER, - barcode: barcode, - language: ProductQuery.getLanguage(), - isLoggedInMandatory: true, - ); - }, - label: Text(appLocalizations.add_photo_button_label), - icon: const Icon(Icons.add_a_photo), - ), - body: Column( - children: [ - LanguageSelector( - setLanguage: (final OpenFoodFactsLanguage? newLanguage) async { - if (newLanguage == null || newLanguage == _language) { - return; - } - setState(() => _language = newLanguage); - }, - displayedLanguage: _language, - selectedLanguages: null, - padding: const EdgeInsetsDirectional.symmetric( - horizontal: 13.0, - vertical: SMALL_SPACE, - ), - ), - Expanded( - child: RefreshIndicator( - onRefresh: () async => ProductRefresher().fetchAndRefresh( - barcode: barcode, - context: context, + return SmoothSharedAnimationController( + child: SmoothScaffold( + appBar: buildEditProductAppBar( + context: context, + title: appLocalizations.edit_product_form_item_photos_title, + product: upToDateProduct, + ), + floatingActionButton: FloatingActionButton.extended( + onPressed: () async { + AnalyticsHelper.trackProductEdit( + AnalyticsEditEvents.photos, + barcode, + true, + ); + await confirmAndUploadNewPicture( + context, + imageField: ImageField.OTHER, + barcode: barcode, + language: ProductQuery.getLanguage(), + isLoggedInMandatory: true, + ); + }, + label: Text(appLocalizations.add_photo_button_label), + icon: const Icon(Icons.add_a_photo), + ), + body: Column( + children: [ + LanguageSelector( + setLanguage: (final OpenFoodFactsLanguage? newLanguage) async { + if (newLanguage == null || newLanguage == _language) { + return; + } + setState(() => _language = newLanguage); + }, + displayedLanguage: _language, + selectedLanguages: null, + padding: const EdgeInsetsDirectional.symmetric( + horizontal: 13.0, + vertical: SMALL_SPACE, ), - child: CustomScrollView( - slivers: [ - SliverGrid( - gridDelegate: - SliverGridDelegateWithFixedCrossAxisCountAndFixedHeight( - crossAxisCount: 2, - height: _computeItemHeight(), - ), - delegate: SliverChildBuilderDelegate( - (BuildContext context, int index) { - return _PhotoRow( - position: index, - product: upToDateProduct, - language: _language, - ); - }, - childCount: 4, - ), - ), - SliverPadding( - padding: const EdgeInsetsDirectional.symmetric( - vertical: MEDIUM_SPACE, - horizontal: SMALL_SPACE, + ), + Expanded( + child: RefreshIndicator( + onRefresh: () async => ProductRefresher().fetchAndRefresh( + barcode: barcode, + context: context, + ), + child: CustomScrollView( + slivers: [ + SliverGrid( + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCountAndFixedHeight( + crossAxisCount: 2, + height: _computeItemHeight(), + ), + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) { + return _PhotoRow( + position: index, + product: upToDateProduct, + language: _language, + ); + }, + childCount: 4, + ), ), - sliver: SliverToBoxAdapter( - child: Text( - appLocalizations.more_photos, - style: Theme.of(context).textTheme.displayMedium, + SliverPadding( + padding: const EdgeInsetsDirectional.symmetric( + vertical: MEDIUM_SPACE, + horizontal: SMALL_SPACE, + ), + sliver: SliverToBoxAdapter( + child: Text( + appLocalizations.more_photos, + style: Theme.of(context).textTheme.displayMedium, + ), ), ), - ), - if (_shouldDisplayRawGallery()) - ProductImageGalleryOtherView(product: upToDateProduct) - else - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.all(SMALL_SPACE), - child: SmoothLargeButtonWithIcon( - text: appLocalizations.view_more_photo_button, - icon: Icons.photo_camera_rounded, - onPressed: () => setState( - () => _clickedOtherPictureButton = true, + if (_shouldDisplayRawGallery()) + ProductImageGalleryOtherView(product: upToDateProduct) + else + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.all(SMALL_SPACE), + child: SmoothLargeButtonWithIcon( + text: appLocalizations.view_more_photo_button, + icon: Icons.photo_camera_rounded, + onPressed: () => setState( + () => _clickedOtherPictureButton = true, + ), ), ), ), + // Extra space to be above the FAB + SliverFillRemaining( + hasScrollBody: false, + child: SizedBox( + height: (Theme.of(context) + .floatingActionButtonTheme + .extendedSizeConstraints + ?.maxHeight ?? + 56.0) + + 16.0, + ), ), - // Extra space to be above the FAB - SliverFillRemaining( - hasScrollBody: false, - child: SizedBox( - height: (Theme.of(context) - .floatingActionButtonTheme - .extendedSizeConstraints - ?.maxHeight ?? - 56.0) + - 16.0, - ), - ), - ], + ], + ), ), ), - ), - ], + ], + ), ), ); }