diff --git a/packages/smooth_app/lib/l10n/app_en.arb b/packages/smooth_app/lib/l10n/app_en.arb index 884cc740c29..50390b78bd9 100644 --- a/packages/smooth_app/lib/l10n/app_en.arb +++ b/packages/smooth_app/lib/l10n/app_en.arb @@ -1709,6 +1709,18 @@ } } }, + "prices_users_list_length_many_pages": "Top {pageSize} contributors (total: {total})", + "@prices_users_list_length_many_pages": { + "description": "Number of users for one-page result", + "placeholders": { + "pageSize": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, "prices_amount_subtitle": "Amount", "prices_amount_is_discounted": "Is discounted?", "prices_amount_price_normal": "Price", @@ -1801,6 +1813,10 @@ "@user_search_prices_title": { "description": "User prices: list tile title" }, + "user_any_search_prices_title": "Contributor prices", + "@user_any_search_prices_title": { + "description": "User prices (everybody except me): list tile title" + }, "all_search_prices_latest_title": "Latest Prices added", "@all_search_prices_latest_title": { "description": "Latest prices: list tile title" diff --git a/packages/smooth_app/lib/l10n/app_fr.arb b/packages/smooth_app/lib/l10n/app_fr.arb index 1b8811a31c8..e6a2fbb625d 100644 --- a/packages/smooth_app/lib/l10n/app_fr.arb +++ b/packages/smooth_app/lib/l10n/app_fr.arb @@ -1711,6 +1711,18 @@ } } }, + "prices_users_list_length_many_pages": "Top {pageSize} contributeurs (total : {total})", + "@prices_users_list_length_many_pages": { + "description": "Number of users for one-page result", + "placeholders": { + "pageSize": { + "type": "int" + }, + "total": { + "type": "int" + } + } + }, "prices_amount_subtitle": "Montant", "prices_amount_is_discounted": "En promotion ?", "prices_amount_price_normal": "Prix", @@ -1798,6 +1810,10 @@ "@user_search_prices_title": { "description": "User prices: list tile title" }, + "user_any_search_prices_title": "Prix d'un contributeur", + "@user_any_search_prices_title": { + "description": "User prices (everybody except me): list tile title" + }, "all_search_prices_latest_title": "Derniers prix ajoutés", "@all_search_prices_latest_title": { "description": "Latest prices: list tile title" 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 a8b3e6499b7..63d179ddd82 100644 --- a/packages/smooth_app/lib/pages/preferences/user_preferences_account.dart +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_account.dart @@ -18,7 +18,9 @@ import 'package:smooth_app/pages/preferences/user_preferences_item.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/prices/get_prices_model.dart'; +import 'package:smooth_app/pages/prices/price_user_button.dart'; import 'package:smooth_app/pages/prices/prices_page.dart'; +import 'package:smooth_app/pages/prices/prices_users_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'; @@ -214,32 +216,13 @@ class UserPreferencesAccount extends AbstractUserPreferences { myCount: _getMyCount(UserSearchType.TO_BE_COMPLETED), ), _getListTile( - appLocalizations.user_search_prices_title, - () async => Navigator.of(context).push( - MaterialPageRoute( - builder: (BuildContext context) => PricesPage( - GetPricesModel( - parameters: GetPricesParameters() - ..owner = userId - ..orderBy = >[ - const OrderBy( - field: GetPricesOrderField.created, - ascending: false, - ), - ] - ..pageSize = GetPricesModel.pageSize - ..pageNumber = 1, - displayOwner: false, - displayProduct: true, - uri: OpenPricesAPIClient.getUri( - path: 'app/users/${ProductQuery.getWriteUser().userId}', - uriHelper: ProductQuery.uriProductHelper, - ), - title: appLocalizations.user_search_prices_title, - subtitle: ProductQuery.getWriteUser().userId, - ), - ), - ), + PriceUserButton.showUserTitle( + user: ProductQuery.getWriteUser().userId, + context: context, + ), + () async => PriceUserButton.showUserPrices( + user: ProductQuery.getWriteUser().userId, + context: context, ), CupertinoIcons.money_dollar_circle, myCount: _getPricesCount(owner: ProductQuery.getWriteUser().userId), @@ -273,9 +256,14 @@ class UserPreferencesAccount extends AbstractUserPreferences { CupertinoIcons.money_dollar_circle, myCount: _getPricesCount(), ), - _getPriceListTile( + _getListTile( appLocalizations.all_search_prices_top_user_title, - 'app/users', + () async => Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) => const PricesUsersPage(), + ), + ), + Icons.account_box, ), _getPriceListTile( appLocalizations.all_search_prices_top_location_title, diff --git a/packages/smooth_app/lib/pages/prices/price_count_widget.dart b/packages/smooth_app/lib/pages/prices/price_count_widget.dart index a1604d978e5..0b1fa92a402 100644 --- a/packages/smooth_app/lib/pages/prices/price_count_widget.dart +++ b/packages/smooth_app/lib/pages/prices/price_count_widget.dart @@ -49,21 +49,23 @@ class PriceCountWidget extends StatelessWidget { title: '$count', buttonStyle: ElevatedButton.styleFrom( disabledForegroundColor: - enableCountButton ? null : _getForegroundColor(), + enableCountButton ? null : getForegroundColor(count), disabledBackgroundColor: - enableCountButton ? null : _getBackgroundColor(), - foregroundColor: !enableCountButton ? null : _getForegroundColor(), - backgroundColor: !enableCountButton ? null : _getBackgroundColor(), + enableCountButton ? null : getBackgroundColor(count), + foregroundColor: + !enableCountButton ? null : getForegroundColor(count), + backgroundColor: + !enableCountButton ? null : getBackgroundColor(count), ), ); - Color? _getForegroundColor() => switch (count) { + static Color? getForegroundColor(final int count) => switch (count) { 0 => Colors.red, 1 => Colors.orange, _ => Colors.green, }; - Color? _getBackgroundColor() => switch (count) { + static Color? getBackgroundColor(final int count) => switch (count) { 0 => Colors.red[100], 1 => Colors.orange[100], _ => Colors.green[100], diff --git a/packages/smooth_app/lib/pages/prices/price_data_widget.dart b/packages/smooth_app/lib/pages/prices/price_data_widget.dart index cf2bf4cdefd..c16daeece66 100644 --- a/packages/smooth_app/lib/pages/prices/price_data_widget.dart +++ b/packages/smooth_app/lib/pages/prices/price_data_widget.dart @@ -7,6 +7,7 @@ import 'package:smooth_app/helpers/launch_url_helper.dart'; import 'package:smooth_app/pages/prices/emoji_helper.dart'; import 'package:smooth_app/pages/prices/get_prices_model.dart'; import 'package:smooth_app/pages/prices/price_button.dart'; +import 'package:smooth_app/pages/prices/price_user_button.dart'; import 'package:smooth_app/pages/product/common/product_query_page_helper.dart'; import 'package:smooth_app/query/product_query.dart'; @@ -79,13 +80,7 @@ class PriceDataWidget extends StatelessWidget { iconData: Icons.location_on_outlined, onPressed: () {}, ), - if (model.displayOwner) - PriceButton( - // TODO(monsieurtanuki): open a still-to-be-done "price x owner" page - title: price.owner, - iconData: Icons.account_box, - onPressed: () {}, - ), + if (model.displayOwner) PriceUserButton(price.owner), Tooltip( message: '${dateFormat.format(price.created)}' ' ' diff --git a/packages/smooth_app/lib/pages/prices/price_user_button.dart b/packages/smooth_app/lib/pages/prices/price_user_button.dart new file mode 100644 index 00000000000..6d8e86b8229 --- /dev/null +++ b/packages/smooth_app/lib/pages/prices/price_user_button.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:openfoodfacts/openfoodfacts.dart'; +import 'package:smooth_app/pages/prices/get_prices_model.dart'; +import 'package:smooth_app/pages/prices/price_button.dart'; +import 'package:smooth_app/pages/prices/prices_page.dart'; +import 'package:smooth_app/query/product_query.dart'; + +/// Widget that displays a user, for Prices. +class PriceUserButton extends StatelessWidget { + const PriceUserButton(this.user); + + final String user; + + static String showUserTitle({ + required final String user, + required final BuildContext context, + }) => + user == ProductQuery.getWriteUser().userId + ? AppLocalizations.of(context).user_search_prices_title + : AppLocalizations.of(context).user_any_search_prices_title; + + static Future showUserPrices({ + required final String user, + required final BuildContext context, + }) async => + Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) => PricesPage( + GetPricesModel( + parameters: GetPricesParameters() + ..owner = user + ..orderBy = >[ + const OrderBy( + field: GetPricesOrderField.created, + ascending: false, + ), + ] + ..pageSize = GetPricesModel.pageSize + ..pageNumber = 1, + displayOwner: false, + displayProduct: true, + uri: OpenPricesAPIClient.getUri( + path: 'app/users/$user', + uriHelper: ProductQuery.uriProductHelper, + ), + title: showUserTitle(user: user, context: context), + subtitle: user, + ), + ), + ), + ); + + @override + Widget build(BuildContext context) => PriceButton( + title: user, + iconData: Icons.account_box, + onPressed: () async => showUserPrices( + user: user, + context: context, + ), + ); +} diff --git a/packages/smooth_app/lib/pages/prices/prices_users_page.dart b/packages/smooth_app/lib/pages/prices/prices_users_page.dart new file mode 100644 index 00000000000..07a32bbe50f --- /dev/null +++ b/packages/smooth_app/lib/pages/prices/prices_users_page.dart @@ -0,0 +1,143 @@ +import 'package:flutter/material.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/widgets/smooth_back_button.dart'; +import 'package:smooth_app/generic_lib/widgets/smooth_card.dart'; +import 'package:smooth_app/helpers/launch_url_helper.dart'; +import 'package:smooth_app/pages/prices/price_button.dart'; +import 'package:smooth_app/pages/prices/price_count_widget.dart'; +import 'package:smooth_app/pages/prices/price_user_button.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'; + +/// Page that displays the top prices users. +class PricesUsersPage extends StatefulWidget { + const PricesUsersPage(); + + @override + State createState() => _PricesUsersPageState(); +} + +class _PricesUsersPageState extends State { + late final Future> _users = _showTopUsers(); + + // In this specific page, let's never try to go beyond the top 10. + // cf. https://github.com/openfoodfacts/smooth-app/pull/5383#issuecomment-2171117141 + static const int _pageSize = 10; + + @override + Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + return SmoothScaffold( + appBar: SmoothAppBar( + centerTitle: false, + leading: const SmoothBackButton(), + title: Text( + appLocalizations.all_search_prices_top_user_title, + ), + actions: [ + IconButton( + tooltip: appLocalizations.prices_app_button, + icon: const Icon(Icons.open_in_new), + onPressed: () async => LaunchUrlHelper.launchURL( + OpenPricesAPIClient.getUri( + path: 'app/users', + uriHelper: ProductQuery.uriProductHelper, + ).toString(), + ), + ), + ], + ), + body: FutureBuilder>( + future: _users, + builder: ( + final BuildContext context, + final AsyncSnapshot> snapshot, + ) { + if (snapshot.connectionState != ConnectionState.done) { + return const CircularProgressIndicator(); + } + if (snapshot.hasError) { + return Text(snapshot.error!.toString()); + } + // highly improbable + if (!snapshot.hasData) { + return const Text('no data'); + } + if (snapshot.data!.isError) { + return Text(snapshot.data!.error!); + } + final GetUsersResult result = snapshot.data!.value; + // highly improbable + if (result.items == null) { + return const Text('empty list'); + } + final List children = []; + + for (final PriceUser item in result.items!) { + children.add( + SmoothCard( + child: Wrap( + spacing: VERY_SMALL_SPACE, + children: [ + PriceUserButton(item.userId), + PriceButton( + onPressed: () async => PriceUserButton.showUserPrices( + user: item.userId, + context: context, + ), + iconData: Icons.label, + title: '${item.priceCount}', + buttonStyle: ElevatedButton.styleFrom( + foregroundColor: PriceCountWidget.getForegroundColor( + item.priceCount, + ), + backgroundColor: PriceCountWidget.getBackgroundColor( + item.priceCount, + ), + ), + ), + ], + ), + ), + ); + } + final AppLocalizations appLocalizations = + AppLocalizations.of(context); + final String title = + appLocalizations.prices_users_list_length_many_pages( + _pageSize, + result.total!, + ); + children.insert( + 0, + SmoothCard(child: ListTile(title: Text(title))), + ); + // so that the last content gets not hidden by the FAB + children.add( + const SizedBox(height: 2 * MINIMUM_TOUCH_SIZE), + ); + return ListView( + children: children, + ); + }, + ), + ); + } + + static Future> _showTopUsers() async => + OpenPricesAPIClient.getUsers( + GetUsersParameters() + ..orderBy = >[ + const OrderBy( + field: GetUsersOrderField.priceCount, + ascending: false, + ), + ] + ..pageSize = _pageSize + ..pageNumber = 1, + uriHelper: ProductQuery.uriProductHelper, + ); +}