From d6e3872732cb9624b9f74fc14727ccf60752e55c Mon Sep 17 00:00:00 2001 From: chetanr25 <1ds22ai010@dsce.edu.com> Date: Sun, 20 Apr 2025 22:43:23 +0530 Subject: [PATCH 01/12] Built dashboard --- .../lib/data_models/users_profile_data.dart | 81 +++++++ .../preferences/user_preferences_prices.dart | 10 + .../lib/pages/prices/prices_dashboard.dart | 78 +++++++ .../pages/prices/prices_dashboard_widget.dart | 202 ++++++++++++++++++ .../lib/pages/prices/prices_user_profile.dart | 92 ++++++++ 5 files changed, 463 insertions(+) create mode 100644 packages/smooth_app/lib/data_models/users_profile_data.dart create mode 100644 packages/smooth_app/lib/pages/prices/prices_dashboard.dart create mode 100644 packages/smooth_app/lib/pages/prices/prices_dashboard_widget.dart create mode 100644 packages/smooth_app/lib/pages/prices/prices_user_profile.dart diff --git a/packages/smooth_app/lib/data_models/users_profile_data.dart b/packages/smooth_app/lib/data_models/users_profile_data.dart new file mode 100644 index 000000000000..cc73d4c007b9 --- /dev/null +++ b/packages/smooth_app/lib/data_models/users_profile_data.dart @@ -0,0 +1,81 @@ +// TODO(chetanr25): To be implemented in OpenFoodFacts flutter package in [https://github.com/openfoodfacts/smooth-app/tree/develop/packages/smooth_app/lib/data_models] as [UserProfile] JsonSerializable +class UserProfile { + UserProfile({ + required this.userId, + required this.priceCount, + required this.priceTypeProductCount, + required this.priceTypeCategoryCount, + required this.priceKindCommunityCount, + required this.priceKindConsumptionCount, + required this.priceCurrencyCount, + required this.priceInProofOwnedCount, + required this.priceInProofNotOwnedCount, + required this.priceNotOwnedInProofOwnedCount, + required this.proofCount, + required this.proofKindCommunityCount, + required this.proofKindConsumptionCount, + required this.locationCount, + required this.locationTypeOsmCountryCount, + required this.productCount, + }); + + factory UserProfile.fromJson(Map json) { + return UserProfile( + userId: json['user_id'] as String, + priceCount: json['price_count'] as int, + priceTypeProductCount: json['price_type_product_count'] as int, + priceTypeCategoryCount: json['price_type_category_count'] as int, + priceKindCommunityCount: json['price_kind_community_count'] as int, + priceKindConsumptionCount: json['price_kind_consumption_count'] as int, + priceCurrencyCount: json['price_currency_count'] as int, + priceInProofOwnedCount: json['price_in_proof_owned_count'] as int, + priceInProofNotOwnedCount: json['price_in_proof_not_owned_count'] as int, + priceNotOwnedInProofOwnedCount: + json['price_not_owned_in_proof_owned_count'] as int, + proofCount: json['proof_count'] as int, + proofKindCommunityCount: json['proof_kind_community_count'] as int, + proofKindConsumptionCount: json['proof_kind_consumption_count'] as int, + locationCount: json['location_count'] as int, + locationTypeOsmCountryCount: + json['location_type_osm_country_count'] as int, + productCount: json['product_count'] as int, + ); + } + final String userId; + final int priceCount; + final int priceTypeProductCount; + final int priceTypeCategoryCount; + final int priceKindCommunityCount; + final int priceKindConsumptionCount; + final int priceCurrencyCount; + final int priceInProofOwnedCount; + final int priceInProofNotOwnedCount; + final int priceNotOwnedInProofOwnedCount; + final int proofCount; + final int proofKindCommunityCount; + final int proofKindConsumptionCount; + final int locationCount; + final int locationTypeOsmCountryCount; + final int productCount; + + Map toJson() { + return { + 'user_id': userId, + 'price_count': priceCount, + 'price_type_product_count': priceTypeProductCount, + 'price_type_category_count': priceTypeCategoryCount, + 'price_kind_community_count': priceKindCommunityCount, + 'price_kind_consumption_count': priceKindConsumptionCount, + 'price_currency_count': priceCurrencyCount, + 'price_in_proof_owned_count': priceInProofOwnedCount, + 'price_in_proof_not_owned_count': priceInProofNotOwnedCount, + 'price_not_owned_in_proof_owned_count': priceNotOwnedInProofOwnedCount, + 'proof_count': proofCount, + 'proof_kind_community_count': proofKindCommunityCount, + 'proof_kind_consumption_count': proofKindConsumptionCount, + 'location_count': locationCount, + 'location_type_osm_country_count': locationTypeOsmCountryCount, + 'product_count': productCount, + }; + } +} diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_prices.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_prices.dart index 24ddbaa8300f..f13e0a8fa90a 100644 --- a/packages/smooth_app/lib/pages/preferences/user_preferences_prices.dart +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_prices.dart @@ -13,6 +13,7 @@ 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_button.dart'; import 'package:smooth_app/pages/prices/price_user_button.dart'; +import 'package:smooth_app/pages/prices/prices_dashboard.dart'; import 'package:smooth_app/pages/prices/prices_locations_page.dart'; import 'package:smooth_app/pages/prices/prices_page.dart'; import 'package:smooth_app/pages/prices/prices_products_page.dart'; @@ -45,6 +46,15 @@ class UserPreferencesPrices extends AbstractUserPreferences { final String userId = ProductQuery.getWriteUser().userId; final bool isConnected = OpenFoodAPIConfiguration.globalUser != null; return [ + if (isConnected) + _getListTile( + 'My Dashboard', + () => Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) => PricesDashboard(), + ), + ), + Icons.dashboard), if (isConnected) _getListTile( PriceUserButton.showUserTitle( diff --git a/packages/smooth_app/lib/pages/prices/prices_dashboard.dart b/packages/smooth_app/lib/pages/prices/prices_dashboard.dart new file mode 100644 index 000000000000..2d7955e29c73 --- /dev/null +++ b/packages/smooth_app/lib/pages/prices/prices_dashboard.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'package:openfoodfacts/openfoodfacts.dart'; +import 'package:smooth_app/data_models/users_profile_data.dart'; +import 'package:smooth_app/helpers/launch_url_helper.dart'; +import 'package:smooth_app/pages/prices/prices_dashboard_widget.dart'; +import 'package:smooth_app/pages/prices/prices_user_profile.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'; + +class PricesDashboard extends StatelessWidget { + PricesDashboard({super.key}); + + late Future> _userProfile = _fetchUserProfile(); + + @override + Widget build(BuildContext context) { + return SmoothScaffold( + appBar: SmoothAppBar( + title: const Text('My Dashboard'), + actions: [ + IconButton( + tooltip: 'Open Prices Dashboard in browser', + icon: const Icon(Icons.open_in_new), + onPressed: () async => LaunchUrlHelper.launchURL( + OpenPricesAPIClient.getUri( + path: 'dashboard', + uriHelper: ProductQuery.uriPricesHelper, + ).toString(), + ), + ), + ], + ), + body: FutureBuilder>( + future: _userProfile, + builder: (BuildContext context, + AsyncSnapshot> snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } + if (snapshot.hasError) { + return Text(snapshot.error!.toString()); + } + final UserProfile userProfile = snapshot.data!.value; + return Column( + children: [ + PricesUserProfile(profile: userProfile), + Expanded( + child: PricesDashboardWidget(userProfile: userProfile)), + ], + ); + }), + ); + } + + // TODO(chetanr25): To be implemented in OpenFoodFacts flutter package + static Future> _fetchUserProfile() async { + final String? userId = OpenFoodAPIConfiguration.globalUser?.userId; + final Uri uri = OpenPricesAPIClient.getUri( + path: '/api/v1/users/$userId', + ); + + final http.Response response = + await HttpHelper().doGetRequest(uri, uriHelper: uriHelperFoodProd); + try { + if (response.statusCode == 200) { + final dynamic decodedResponse = HttpHelper().jsonDecodeUtf8(response); + return MaybeError.value( + UserProfile.fromJson(decodedResponse), + ); + } + } catch (e) { + // + } + return MaybeError.responseError(response); + } +} diff --git a/packages/smooth_app/lib/pages/prices/prices_dashboard_widget.dart b/packages/smooth_app/lib/pages/prices/prices_dashboard_widget.dart new file mode 100644 index 000000000000..076e33c0e1a1 --- /dev/null +++ b/packages/smooth_app/lib/pages/prices/prices_dashboard_widget.dart @@ -0,0 +1,202 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:openfoodfacts/openfoodfacts.dart'; +import 'package:smooth_app/data_models/users_profile_data.dart'; +import 'package:smooth_app/generic_lib/design_constants.dart'; +import 'package:smooth_app/generic_lib/widgets/smooth_card.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_proofs_page.dart'; +import 'package:smooth_app/pages/prices/product_prices_list.dart'; +import 'package:smooth_app/query/product_query.dart'; + +class PricesDashboardWidget extends StatefulWidget { + const PricesDashboardWidget({super.key, required this.userProfile}); + final UserProfile? userProfile; + @override + State createState() => _PricesDashboardWidgetState(); +} + +class _PricesDashboardWidgetState extends State { + int selectedIndex = 0; + late Future> pricesFuture = _getUserPrices(); + + @override + Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + + return Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + spacing: MEDIUM_SPACE, + children: [ + categorySwitch(), + const SizedBox(height: SMALL_SPACE), + priceProofButton(widget.userProfile!, appLocalizations), + FutureBuilder>( + future: _getUserPrices(), + builder: (BuildContext context, + AsyncSnapshot> snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } + if (snapshot.hasError) { + return Center(child: Text(snapshot.error.toString())); + } + + return Expanded( + child: ProductPricesList( + GetPricesModel( + title: appLocalizations.prices_generic_title, + parameters: GetPricesParameters(), + uri: OpenPricesAPIClient.getUri( + path: 'users/${widget.userProfile!.userId}', + uriHelper: ProductQuery.uriPricesHelper, + ), + ), + pricesResult: snapshot.data!.value, + ), + ); + }, + ), + ], + ); + } + + Future> _getUserPrices() async { + final MaybeError prices = + await OpenPricesAPIClient.getPrices( + GetPricesParameters() + ..owner = OpenFoodAPIConfiguration.globalUser?.userId + ..kind = selectedIndex == 0 + ? 'CONSUMPTION' + : 'COMMUNITY', // TODO(chetanr25): This must be implemented in OpenFoodFacts flutter package + uriHelper: ProductQuery.uriPricesHelper, + ); + return prices; + } + + /// Toggle between "My Consumption" and "Other Contributions" + Widget categorySwitch() { + return Padding( + padding: const EdgeInsets.all(VERY_LARGE_SPACE), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + flex: 1, + child: customToggleButton( + 0, + Icons.shopping_cart, + 'My Consumption', + () => setState(() { + selectedIndex = 0; + pricesFuture = _getUserPrices(); + }))), + Expanded( + flex: 1, + child: + customToggleButton(1, Icons.people, 'Other Contributions', () { + setState(() { + selectedIndex = 1; + pricesFuture = _getUserPrices(); + }); + }), + ), + ], + ), + ); + } + + Widget customToggleButton( + int index, IconData icon, String label, VoidCallback onTap) { + final Color selectedColor = Theme.of(context).colorScheme.onSurface; + final Color unselectedColor = selectedColor.withAlpha(128); + final bool isSelected = selectedIndex == index; + return GestureDetector( + onTap: onTap, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + spacing: VERY_SMALL_SPACE, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(icon, color: isSelected ? selectedColor : unselectedColor), + const SizedBox(width: VERY_SMALL_SPACE), + Text( + label, + textAlign: TextAlign.center, + style: TextStyle( + color: isSelected ? selectedColor : unselectedColor, + fontWeight: FontWeight.bold, + fontSize: 12, + ), + ), + ], + ), + if (isSelected) + Container( + alignment: Alignment.center, + width: double.infinity, + height: VERY_SMALL_SPACE, + color: selectedColor, + ) + else + const SizedBox(height: VERY_SMALL_SPACE), + ], + ), + ); + } + + Widget priceProofButton( + UserProfile profile, AppLocalizations appLocalizations) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + flex: 1, + child: SmoothCard( + child: ListTile( + onTap: () { + PriceUserButton.showUserPrices( + user: profile.userId, + context: context, + ); + }, + subtitle: Text(appLocalizations.prices_generic_title), + title: Text(selectedIndex == 0 + ? profile.priceKindConsumptionCount.toString() + : profile.priceKindCommunityCount.toString()), + trailing: const Icon(Icons.arrow_forward), + ), + ), + ), + Expanded( + flex: 1, + child: SmoothCard( + child: ListTile( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) => + const PricesProofsPage(selectProof: false), + ), + ); + }, + subtitle: Text(appLocalizations.prices_proof_subtitle), + title: Text(selectedIndex == 0 + ? profile.proofKindConsumptionCount.toString() + : profile.proofKindCommunityCount.toString()), + trailing: const Icon(Icons.arrow_forward), + ), + ), + ), + ], + ); + } +} diff --git a/packages/smooth_app/lib/pages/prices/prices_user_profile.dart b/packages/smooth_app/lib/pages/prices/prices_user_profile.dart new file mode 100644 index 000000000000..1a5636b44e3f --- /dev/null +++ b/packages/smooth_app/lib/pages/prices/prices_user_profile.dart @@ -0,0 +1,92 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:smooth_app/data_models/users_profile_data.dart'; +import 'package:smooth_app/generic_lib/design_constants.dart'; +import 'package:smooth_app/generic_lib/widgets/smooth_card.dart'; + +class PricesUserProfile extends StatelessWidget { + const PricesUserProfile({super.key, required this.profile}); + final UserProfile profile; + + @override + Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + return SmoothCard( + child: ListView( + shrinkWrap: true, + children: [ + ListTile( + leading: const Icon(Icons.person, size: DEFAULT_ICON_SIZE * 2), + title: Text( + profile.userId, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20, + ), + ), + ), + Wrap( + alignment: WrapAlignment.spaceEvenly, + children: [ + profileStatsButton( + Icons.sell_outlined, + profile.priceCount, + appLocalizations.prices_generic_title, + context, + ), + profileStatsButton( + Icons.location_on, + profile.locationCount, + 'locations', + context, + ), + profileStatsButton( + Icons.restaurant_menu, + profile.productCount, + appLocalizations.settings_app_products, + context, + ), + profileStatsButton( + Icons.image, + profile.proofCount, + appLocalizations.prices_proof_subtitle, + context, + ) + ], + ), + ], + ), + ); + } + + Widget profileStatsButton( + IconData icon, int count, String label, BuildContext context, + {Color? color}) { + return SmoothCard( + color: Theme.of(context).colorScheme.onSurface.withAlpha(24), + child: Container( + width: MediaQuery.sizeOf(context).width / 2 - 3 * LARGE_SPACE, + padding: const EdgeInsets.all(SMALL_SPACE), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(icon, color: color, size: DEFAULT_ICON_SIZE), + const SizedBox(width: VERY_SMALL_SPACE), + Text(count.toString(), + style: const TextStyle(fontWeight: FontWeight.bold)), + ], + ), + Text(label, + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + )), + ], + ), + ), + ); + } +} From f3938c8060121ce62dbbf189f0bee416f6381eb3 Mon Sep 17 00:00:00 2001 From: chetanr25 <1ds22ai010@dsce.edu.com> Date: Mon, 21 Apr 2025 00:19:06 +0530 Subject: [PATCH 02/12] fix: build errors --- packages/smooth_app/lib/data_models/users_profile_data.dart | 2 +- packages/smooth_app/lib/pages/prices/prices_dashboard.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/smooth_app/lib/data_models/users_profile_data.dart b/packages/smooth_app/lib/data_models/users_profile_data.dart index cc73d4c007b9..55b32f3956bc 100644 --- a/packages/smooth_app/lib/data_models/users_profile_data.dart +++ b/packages/smooth_app/lib/data_models/users_profile_data.dart @@ -59,7 +59,7 @@ class UserProfile { final int productCount; Map toJson() { - return { + return { 'user_id': userId, 'price_count': priceCount, 'price_type_product_count': priceTypeProductCount, diff --git a/packages/smooth_app/lib/pages/prices/prices_dashboard.dart b/packages/smooth_app/lib/pages/prices/prices_dashboard.dart index 2d7955e29c73..fcc297c79d03 100644 --- a/packages/smooth_app/lib/pages/prices/prices_dashboard.dart +++ b/packages/smooth_app/lib/pages/prices/prices_dashboard.dart @@ -12,7 +12,7 @@ import 'package:smooth_app/widgets/smooth_scaffold.dart'; class PricesDashboard extends StatelessWidget { PricesDashboard({super.key}); - late Future> _userProfile = _fetchUserProfile(); + late final Future> _userProfile = _fetchUserProfile(); @override Widget build(BuildContext context) { From 829a90c41f5006e85cc22579eb1970c161754fd2 Mon Sep 17 00:00:00 2001 From: chetanr25 <1ds22ai010@dsce.edu.com> Date: Sat, 26 Apr 2025 20:48:29 +0530 Subject: [PATCH 03/12] fixed new kind parameter using ContributionKind, replaced my contributions and other contributions --- .../smooth_app/lib/pages/prices/prices_dashboard_widget.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/smooth_app/lib/pages/prices/prices_dashboard_widget.dart b/packages/smooth_app/lib/pages/prices/prices_dashboard_widget.dart index 076e33c0e1a1..aee5a0e21d0c 100644 --- a/packages/smooth_app/lib/pages/prices/prices_dashboard_widget.dart +++ b/packages/smooth_app/lib/pages/prices/prices_dashboard_widget.dart @@ -71,8 +71,8 @@ class _PricesDashboardWidgetState extends State { GetPricesParameters() ..owner = OpenFoodAPIConfiguration.globalUser?.userId ..kind = selectedIndex == 0 - ? 'CONSUMPTION' - : 'COMMUNITY', // TODO(chetanr25): This must be implemented in OpenFoodFacts flutter package + ? ContributionKind.consumption + : ContributionKind.community, uriHelper: ProductQuery.uriPricesHelper, ); return prices; From 78ded75d15a3bd78a06541088c50dad67be8b04b Mon Sep 17 00:00:00 2001 From: chetanr25 <1ds22ai010@dsce.edu.com> Date: Sat, 26 Apr 2025 20:51:08 +0530 Subject: [PATCH 04/12] Replaced title of category as suggested --- .../smooth_app/lib/pages/prices/prices_dashboard_widget.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/smooth_app/lib/pages/prices/prices_dashboard_widget.dart b/packages/smooth_app/lib/pages/prices/prices_dashboard_widget.dart index aee5a0e21d0c..5973fa347554 100644 --- a/packages/smooth_app/lib/pages/prices/prices_dashboard_widget.dart +++ b/packages/smooth_app/lib/pages/prices/prices_dashboard_widget.dart @@ -90,15 +90,14 @@ class _PricesDashboardWidgetState extends State { child: customToggleButton( 0, Icons.shopping_cart, - 'My Consumption', + 'Receipts & GDPR requests', () => setState(() { selectedIndex = 0; pricesFuture = _getUserPrices(); }))), Expanded( flex: 1, - child: - customToggleButton(1, Icons.people, 'Other Contributions', () { + child: customToggleButton(1, Icons.people, 'Price labels', () { setState(() { selectedIndex = 1; pricesFuture = _getUserPrices(); From dbd0c5917bef68e43a5a27782893211c17738572 Mon Sep 17 00:00:00 2001 From: chetanr25 <1ds22ai010@dsce.edu.com> Date: Tue, 27 May 2025 22:07:36 +0530 Subject: [PATCH 05/12] made profileStatsButton function to private as suggested, replaced temporvary UserProfile model with PriceUser model which is created in openfoodfacts flutter package --- .../lib/pages/prices/prices_user_profile.dart | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/smooth_app/lib/pages/prices/prices_user_profile.dart b/packages/smooth_app/lib/pages/prices/prices_user_profile.dart index 1a5636b44e3f..d27988b6d922 100644 --- a/packages/smooth_app/lib/pages/prices/prices_user_profile.dart +++ b/packages/smooth_app/lib/pages/prices/prices_user_profile.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:smooth_app/data_models/users_profile_data.dart'; +import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/generic_lib/widgets/smooth_card.dart'; class PricesUserProfile extends StatelessWidget { const PricesUserProfile({super.key, required this.profile}); - final UserProfile profile; + final PriceUser profile; @override Widget build(BuildContext context) { @@ -16,39 +16,38 @@ class PricesUserProfile extends StatelessWidget { shrinkWrap: true, children: [ ListTile( - leading: const Icon(Icons.person, size: DEFAULT_ICON_SIZE * 2), + leading: const Icon(Icons.person), title: Text( profile.userId, style: const TextStyle( fontWeight: FontWeight.bold, - fontSize: 20, ), ), ), Wrap( alignment: WrapAlignment.spaceEvenly, children: [ - profileStatsButton( + _profileStatsButton( Icons.sell_outlined, - profile.priceCount, + profile.priceCount ?? 0, appLocalizations.prices_generic_title, context, ), - profileStatsButton( + _profileStatsButton( Icons.location_on, - profile.locationCount, + profile.locationCount ?? 0, 'locations', context, ), - profileStatsButton( + _profileStatsButton( Icons.restaurant_menu, - profile.productCount, + profile.productCount ?? 0, appLocalizations.settings_app_products, context, ), - profileStatsButton( + _profileStatsButton( Icons.image, - profile.proofCount, + profile.proofCount ?? 0, appLocalizations.prices_proof_subtitle, context, ) @@ -59,7 +58,7 @@ class PricesUserProfile extends StatelessWidget { ); } - Widget profileStatsButton( + Widget _profileStatsButton( IconData icon, int count, String label, BuildContext context, {Color? color}) { return SmoothCard( From 4e366ca5f60602b537798d4405a87fbffd1977f5 Mon Sep 17 00:00:00 2001 From: chetanr25 <1ds22ai010@dsce.edu.com> Date: Tue, 27 May 2025 22:09:20 +0530 Subject: [PATCH 06/12] Deleted un-necessary temporary model UserProfile, as we have PriceUser in dart package --- .../lib/data_models/users_profile_data.dart | 81 ------------------- 1 file changed, 81 deletions(-) delete mode 100644 packages/smooth_app/lib/data_models/users_profile_data.dart diff --git a/packages/smooth_app/lib/data_models/users_profile_data.dart b/packages/smooth_app/lib/data_models/users_profile_data.dart deleted file mode 100644 index 55b32f3956bc..000000000000 --- a/packages/smooth_app/lib/data_models/users_profile_data.dart +++ /dev/null @@ -1,81 +0,0 @@ -// TODO(chetanr25): To be implemented in OpenFoodFacts flutter package in [https://github.com/openfoodfacts/smooth-app/tree/develop/packages/smooth_app/lib/data_models] as [UserProfile] JsonSerializable -class UserProfile { - UserProfile({ - required this.userId, - required this.priceCount, - required this.priceTypeProductCount, - required this.priceTypeCategoryCount, - required this.priceKindCommunityCount, - required this.priceKindConsumptionCount, - required this.priceCurrencyCount, - required this.priceInProofOwnedCount, - required this.priceInProofNotOwnedCount, - required this.priceNotOwnedInProofOwnedCount, - required this.proofCount, - required this.proofKindCommunityCount, - required this.proofKindConsumptionCount, - required this.locationCount, - required this.locationTypeOsmCountryCount, - required this.productCount, - }); - - factory UserProfile.fromJson(Map json) { - return UserProfile( - userId: json['user_id'] as String, - priceCount: json['price_count'] as int, - priceTypeProductCount: json['price_type_product_count'] as int, - priceTypeCategoryCount: json['price_type_category_count'] as int, - priceKindCommunityCount: json['price_kind_community_count'] as int, - priceKindConsumptionCount: json['price_kind_consumption_count'] as int, - priceCurrencyCount: json['price_currency_count'] as int, - priceInProofOwnedCount: json['price_in_proof_owned_count'] as int, - priceInProofNotOwnedCount: json['price_in_proof_not_owned_count'] as int, - priceNotOwnedInProofOwnedCount: - json['price_not_owned_in_proof_owned_count'] as int, - proofCount: json['proof_count'] as int, - proofKindCommunityCount: json['proof_kind_community_count'] as int, - proofKindConsumptionCount: json['proof_kind_consumption_count'] as int, - locationCount: json['location_count'] as int, - locationTypeOsmCountryCount: - json['location_type_osm_country_count'] as int, - productCount: json['product_count'] as int, - ); - } - final String userId; - final int priceCount; - final int priceTypeProductCount; - final int priceTypeCategoryCount; - final int priceKindCommunityCount; - final int priceKindConsumptionCount; - final int priceCurrencyCount; - final int priceInProofOwnedCount; - final int priceInProofNotOwnedCount; - final int priceNotOwnedInProofOwnedCount; - final int proofCount; - final int proofKindCommunityCount; - final int proofKindConsumptionCount; - final int locationCount; - final int locationTypeOsmCountryCount; - final int productCount; - - Map toJson() { - return { - 'user_id': userId, - 'price_count': priceCount, - 'price_type_product_count': priceTypeProductCount, - 'price_type_category_count': priceTypeCategoryCount, - 'price_kind_community_count': priceKindCommunityCount, - 'price_kind_consumption_count': priceKindConsumptionCount, - 'price_currency_count': priceCurrencyCount, - 'price_in_proof_owned_count': priceInProofOwnedCount, - 'price_in_proof_not_owned_count': priceInProofNotOwnedCount, - 'price_not_owned_in_proof_owned_count': priceNotOwnedInProofOwnedCount, - 'proof_count': proofCount, - 'proof_kind_community_count': proofKindCommunityCount, - 'proof_kind_consumption_count': proofKindConsumptionCount, - 'location_count': locationCount, - 'location_type_osm_country_count': locationTypeOsmCountryCount, - 'product_count': productCount, - }; - } -} From 5c67691bd5c2e528f84fce9dd4bf5c275330d539 Mon Sep 17 00:00:00 2001 From: chetanr25 <1ds22ai010@dsce.edu.com> Date: Wed, 18 Jun 2025 00:54:37 +0530 Subject: [PATCH 07/12] Localized --- packages/smooth_app/lib/l10n/app_en.arb | 18 +++++++++++++++++- .../preferences/user_preferences_prices.dart | 6 +++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/smooth_app/lib/l10n/app_en.arb b/packages/smooth_app/lib/l10n/app_en.arb index 8f7e239f8f0e..31c987b50dd5 100644 --- a/packages/smooth_app/lib/l10n/app_en.arb +++ b/packages/smooth_app/lib/l10n/app_en.arb @@ -4438,5 +4438,21 @@ "prices_proof_error": "Authentication failed, unable to fetch proofs", "@prices_proof_error": { "description": "Error message shown when unable to fetch proofs" + }, + "prices_dashboard_title": "My Dashboard", + "@prices_dashboard_title": { + "description": "Title for the dashboard page" + }, + "prices_dashboard_receipts_and_gdpr_requests": "Receipts & GDPR requests", + "@prices_dashboard_receipts_and_gdpr_requests": { + "description": "Label for receipts and GDPR requests" + }, + "prices_dashboard_price_labels": "Price labels", + "@prices_dashboard_price_labels": { + "description": "Label for price labels" + }, + "prices_dashboard_open_in_browser": "Open Prices Dashboard in browser", + "@prices_dashboard_open_in_browser": { + "description": "Tooltip for the button to open the Prices Dashboard in browser" } -} \ No newline at end of file +} diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_prices.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_prices.dart index 6141af4467fe..2cd8f60b3ef8 100644 --- a/packages/smooth_app/lib/pages/preferences/user_preferences_prices.dart +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_prices.dart @@ -13,7 +13,7 @@ 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_button.dart'; import 'package:smooth_app/pages/prices/price_user_button.dart'; -import 'package:smooth_app/pages/prices/prices_dashboard.dart'; +import 'package:smooth_app/pages/prices/prices_dashboard_page.dart'; import 'package:smooth_app/pages/prices/prices_locations_page.dart'; import 'package:smooth_app/pages/prices/prices_page.dart'; import 'package:smooth_app/pages/prices/prices_products_page.dart'; @@ -49,10 +49,10 @@ class UserPreferencesPrices extends AbstractUserPreferences { return [ if (isConnected) _getListTile( - 'My Dashboard', + appLocalizations.prices_dashboard_title, () => Navigator.of(context).push( MaterialPageRoute( - builder: (BuildContext context) => PricesDashboard(), + builder: (BuildContext context) => PricesDashboardPage(), ), ), Icons.dashboard), From 6eea995349501dfbdd131aa146929b40bbe8eca7 Mon Sep 17 00:00:00 2001 From: chetanr25 <1ds22ai010@dsce.edu.com> Date: Wed, 18 Jun 2025 12:12:39 +0530 Subject: [PATCH 08/12] deleted UserProfile and replaced it with PriceUser --- ...hboard.dart => prices_dashboard_page.dart} | 30 +++++++------- .../pages/prices/prices_dashboard_widget.dart | 39 ++++++++++--------- .../lib/pages/prices/prices_user_profile.dart | 2 +- 3 files changed, 38 insertions(+), 33 deletions(-) rename packages/smooth_app/lib/pages/prices/{prices_dashboard.dart => prices_dashboard_page.dart} (68%) diff --git a/packages/smooth_app/lib/pages/prices/prices_dashboard.dart b/packages/smooth_app/lib/pages/prices/prices_dashboard_page.dart similarity index 68% rename from packages/smooth_app/lib/pages/prices/prices_dashboard.dart rename to packages/smooth_app/lib/pages/prices/prices_dashboard_page.dart index fcc297c79d03..295a79c39628 100644 --- a/packages/smooth_app/lib/pages/prices/prices_dashboard.dart +++ b/packages/smooth_app/lib/pages/prices/prices_dashboard_page.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:http/http.dart' as http; import 'package:openfoodfacts/openfoodfacts.dart'; -import 'package:smooth_app/data_models/users_profile_data.dart'; import 'package:smooth_app/helpers/launch_url_helper.dart'; import 'package:smooth_app/pages/prices/prices_dashboard_widget.dart'; import 'package:smooth_app/pages/prices/prices_user_profile.dart'; @@ -9,19 +9,20 @@ 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'; -class PricesDashboard extends StatelessWidget { - PricesDashboard({super.key}); +class PricesDashboardPage extends StatelessWidget { + PricesDashboardPage(); - late final Future> _userProfile = _fetchUserProfile(); + late final Future> _userProfile = _fetchUserProfile(); @override Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); return SmoothScaffold( appBar: SmoothAppBar( - title: const Text('My Dashboard'), + title: Text(appLocalizations.prices_dashboard_title), actions: [ IconButton( - tooltip: 'Open Prices Dashboard in browser', + tooltip: appLocalizations.prices_dashboard_open_in_browser, icon: const Icon(Icons.open_in_new), onPressed: () async => LaunchUrlHelper.launchURL( OpenPricesAPIClient.getUri( @@ -32,17 +33,20 @@ class PricesDashboard extends StatelessWidget { ), ], ), - body: FutureBuilder>( + body: FutureBuilder>( future: _userProfile, builder: (BuildContext context, - AsyncSnapshot> snapshot) { + AsyncSnapshot> snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } + if (snapshot.connectionState != ConnectionState.done) { + return const Center(child: CircularProgressIndicator.adaptive()); + } if (snapshot.hasError) { return Text(snapshot.error!.toString()); } - final UserProfile userProfile = snapshot.data!.value; + final PriceUser userProfile = snapshot.data!.value; return Column( children: [ PricesUserProfile(profile: userProfile), @@ -55,7 +59,7 @@ class PricesDashboard extends StatelessWidget { } // TODO(chetanr25): To be implemented in OpenFoodFacts flutter package - static Future> _fetchUserProfile() async { + static Future> _fetchUserProfile() async { final String? userId = OpenFoodAPIConfiguration.globalUser?.userId; final Uri uri = OpenPricesAPIClient.getUri( path: '/api/v1/users/$userId', @@ -66,13 +70,13 @@ class PricesDashboard extends StatelessWidget { try { if (response.statusCode == 200) { final dynamic decodedResponse = HttpHelper().jsonDecodeUtf8(response); - return MaybeError.value( - UserProfile.fromJson(decodedResponse), + return MaybeError.value( + PriceUser.fromJson(decodedResponse), ); } } catch (e) { // } - return MaybeError.responseError(response); + return MaybeError.responseError(response); } } diff --git a/packages/smooth_app/lib/pages/prices/prices_dashboard_widget.dart b/packages/smooth_app/lib/pages/prices/prices_dashboard_widget.dart index 5973fa347554..9fa87e63d10e 100644 --- a/packages/smooth_app/lib/pages/prices/prices_dashboard_widget.dart +++ b/packages/smooth_app/lib/pages/prices/prices_dashboard_widget.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; -import 'package:smooth_app/data_models/users_profile_data.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/generic_lib/widgets/smooth_card.dart'; import 'package:smooth_app/pages/prices/get_prices_model.dart'; @@ -13,13 +12,13 @@ import 'package:smooth_app/query/product_query.dart'; class PricesDashboardWidget extends StatefulWidget { const PricesDashboardWidget({super.key, required this.userProfile}); - final UserProfile? userProfile; + final PriceUser userProfile; @override State createState() => _PricesDashboardWidgetState(); } class _PricesDashboardWidgetState extends State { - int selectedIndex = 0; + String selectedCategory = 'consumption'; late Future> pricesFuture = _getUserPrices(); @override @@ -32,9 +31,9 @@ class _PricesDashboardWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, spacing: MEDIUM_SPACE, children: [ - categorySwitch(), + _categorySwitch(), const SizedBox(height: SMALL_SPACE), - priceProofButton(widget.userProfile!, appLocalizations), + _priceProofButton(widget.userProfile, appLocalizations), FutureBuilder>( future: _getUserPrices(), builder: (BuildContext context, @@ -52,7 +51,7 @@ class _PricesDashboardWidgetState extends State { title: appLocalizations.prices_generic_title, parameters: GetPricesParameters(), uri: OpenPricesAPIClient.getUri( - path: 'users/${widget.userProfile!.userId}', + path: 'users/${widget.userProfile.userId}', uriHelper: ProductQuery.uriPricesHelper, ), ), @@ -70,7 +69,7 @@ class _PricesDashboardWidgetState extends State { await OpenPricesAPIClient.getPrices( GetPricesParameters() ..owner = OpenFoodAPIConfiguration.globalUser?.userId - ..kind = selectedIndex == 0 + ..kind = selectedCategory == 'consumption' ? ContributionKind.consumption : ContributionKind.community, uriHelper: ProductQuery.uriPricesHelper, @@ -79,7 +78,8 @@ class _PricesDashboardWidgetState extends State { } /// Toggle between "My Consumption" and "Other Contributions" - Widget categorySwitch() { + Widget _categorySwitch() { + final AppLocalizations appLocalizations = AppLocalizations.of(context); return Padding( padding: const EdgeInsets.all(VERY_LARGE_SPACE), child: Row( @@ -88,18 +88,19 @@ class _PricesDashboardWidgetState extends State { Expanded( flex: 1, child: customToggleButton( - 0, + 'consumption', Icons.shopping_cart, - 'Receipts & GDPR requests', + appLocalizations.prices_dashboard_receipts_and_gdpr_requests, () => setState(() { - selectedIndex = 0; + selectedCategory = 'consumption'; pricesFuture = _getUserPrices(); }))), Expanded( flex: 1, - child: customToggleButton(1, Icons.people, 'Price labels', () { + child: customToggleButton('community', Icons.people, + appLocalizations.prices_dashboard_price_labels, () { setState(() { - selectedIndex = 1; + selectedCategory = 'community'; pricesFuture = _getUserPrices(); }); }), @@ -110,10 +111,10 @@ class _PricesDashboardWidgetState extends State { } Widget customToggleButton( - int index, IconData icon, String label, VoidCallback onTap) { + String category, IconData icon, String label, VoidCallback onTap) { final Color selectedColor = Theme.of(context).colorScheme.onSurface; final Color unselectedColor = selectedColor.withAlpha(128); - final bool isSelected = selectedIndex == index; + final bool isSelected = selectedCategory == category; return GestureDetector( onTap: onTap, child: Column( @@ -151,8 +152,8 @@ class _PricesDashboardWidgetState extends State { ); } - Widget priceProofButton( - UserProfile profile, AppLocalizations appLocalizations) { + Widget _priceProofButton( + PriceUser profile, AppLocalizations appLocalizations) { return Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, @@ -168,7 +169,7 @@ class _PricesDashboardWidgetState extends State { ); }, subtitle: Text(appLocalizations.prices_generic_title), - title: Text(selectedIndex == 0 + title: Text(selectedCategory == 'consumption' ? profile.priceKindConsumptionCount.toString() : profile.priceKindCommunityCount.toString()), trailing: const Icon(Icons.arrow_forward), @@ -188,7 +189,7 @@ class _PricesDashboardWidgetState extends State { ); }, subtitle: Text(appLocalizations.prices_proof_subtitle), - title: Text(selectedIndex == 0 + title: Text(selectedCategory == 'consumption' ? profile.proofKindConsumptionCount.toString() : profile.proofKindCommunityCount.toString()), trailing: const Icon(Icons.arrow_forward), diff --git a/packages/smooth_app/lib/pages/prices/prices_user_profile.dart b/packages/smooth_app/lib/pages/prices/prices_user_profile.dart index d27988b6d922..478f0bcebbbc 100644 --- a/packages/smooth_app/lib/pages/prices/prices_user_profile.dart +++ b/packages/smooth_app/lib/pages/prices/prices_user_profile.dart @@ -62,7 +62,7 @@ class PricesUserProfile extends StatelessWidget { IconData icon, int count, String label, BuildContext context, {Color? color}) { return SmoothCard( - color: Theme.of(context).colorScheme.onSurface.withAlpha(24), + // color: Theme.of(context).colorScheme.onSurface.withAlpha(24), child: Container( width: MediaQuery.sizeOf(context).width / 2 - 3 * LARGE_SPACE, padding: const EdgeInsets.all(SMALL_SPACE), From 933bda2c528850f08ea8b5c05d9a5935671375d7 Mon Sep 17 00:00:00 2001 From: chetanr25 <1ds22ai010@dsce.edu.com> Date: Wed, 18 Jun 2025 15:20:53 +0530 Subject: [PATCH 09/12] Remove _fetchUserProfile which is no longer used, added a placeholder to TitleElementType.PERCENTAGE in knowledge_pannel_builder.dart --- .../knowledge_panels_builder.dart | 7 +++++ .../pages/prices/prices_dashboard_page.dart | 27 +++---------------- .../pages/prices/prices_dashboard_widget.dart | 2 +- .../lib/pages/prices/prices_user_profile.dart | 3 +-- 4 files changed, 12 insertions(+), 27 deletions(-) diff --git a/packages/smooth_app/lib/knowledge_panel/knowledge_panels_builder.dart b/packages/smooth_app/lib/knowledge_panel/knowledge_panels_builder.dart index 5b00e0256762..164cf5a67c32 100644 --- a/packages/smooth_app/lib/knowledge_panel/knowledge_panels_builder.dart +++ b/packages/smooth_app/lib/knowledge_panel/knowledge_panels_builder.dart @@ -330,6 +330,13 @@ class KnowledgePanelsBuilder { isClickable: isClickable, ), ); + case TitleElementType.PERCENTAGE: + // TODO: Implement percentage case for title element. + /// The case is added to fix type errors but functionality is not implemented yet. + /// TitleElementType.PERCENTAGE was introduced after PR #1086 + /// (https://github.com/openfoodfacts/openfoodfacts-dart/pull/1086). + /// Implementation for percentage case still needs to be added. + throw UnimplementedError(); } } } diff --git a/packages/smooth_app/lib/pages/prices/prices_dashboard_page.dart b/packages/smooth_app/lib/pages/prices/prices_dashboard_page.dart index 295a79c39628..5a601cb8a453 100644 --- a/packages/smooth_app/lib/pages/prices/prices_dashboard_page.dart +++ b/packages/smooth_app/lib/pages/prices/prices_dashboard_page.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:http/http.dart' as http; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:smooth_app/helpers/launch_url_helper.dart'; import 'package:smooth_app/pages/prices/prices_dashboard_widget.dart'; @@ -12,7 +11,9 @@ import 'package:smooth_app/widgets/smooth_scaffold.dart'; class PricesDashboardPage extends StatelessWidget { PricesDashboardPage(); - late final Future> _userProfile = _fetchUserProfile(); + late final Future> _userProfile = + OpenPricesAPIClient.getUser(OpenFoodAPIConfiguration.globalUser!.userId, + uriHelper: ProductQuery.uriPricesHelper); @override Widget build(BuildContext context) { @@ -57,26 +58,4 @@ class PricesDashboardPage extends StatelessWidget { }), ); } - - // TODO(chetanr25): To be implemented in OpenFoodFacts flutter package - static Future> _fetchUserProfile() async { - final String? userId = OpenFoodAPIConfiguration.globalUser?.userId; - final Uri uri = OpenPricesAPIClient.getUri( - path: '/api/v1/users/$userId', - ); - - final http.Response response = - await HttpHelper().doGetRequest(uri, uriHelper: uriHelperFoodProd); - try { - if (response.statusCode == 200) { - final dynamic decodedResponse = HttpHelper().jsonDecodeUtf8(response); - return MaybeError.value( - PriceUser.fromJson(decodedResponse), - ); - } - } catch (e) { - // - } - return MaybeError.responseError(response); - } } diff --git a/packages/smooth_app/lib/pages/prices/prices_dashboard_widget.dart b/packages/smooth_app/lib/pages/prices/prices_dashboard_widget.dart index 9fa87e63d10e..54d6b45e246d 100644 --- a/packages/smooth_app/lib/pages/prices/prices_dashboard_widget.dart +++ b/packages/smooth_app/lib/pages/prices/prices_dashboard_widget.dart @@ -35,7 +35,7 @@ class _PricesDashboardWidgetState extends State { const SizedBox(height: SMALL_SPACE), _priceProofButton(widget.userProfile, appLocalizations), FutureBuilder>( - future: _getUserPrices(), + future: pricesFuture, builder: (BuildContext context, AsyncSnapshot> snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { diff --git a/packages/smooth_app/lib/pages/prices/prices_user_profile.dart b/packages/smooth_app/lib/pages/prices/prices_user_profile.dart index 478f0bcebbbc..033bdfbcd9a2 100644 --- a/packages/smooth_app/lib/pages/prices/prices_user_profile.dart +++ b/packages/smooth_app/lib/pages/prices/prices_user_profile.dart @@ -12,8 +12,7 @@ class PricesUserProfile extends StatelessWidget { Widget build(BuildContext context) { final AppLocalizations appLocalizations = AppLocalizations.of(context); return SmoothCard( - child: ListView( - shrinkWrap: true, + child: Column( children: [ ListTile( leading: const Icon(Icons.person), From 53e31fc5708a2ec9a2c8495953d4de6b94caea2c Mon Sep 17 00:00:00 2001 From: chetanr25 <1ds22ai010@dsce.edu.com> Date: Wed, 18 Jun 2025 15:23:44 +0530 Subject: [PATCH 10/12] Changed openfoodfact package version --- packages/smooth_app/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/smooth_app/pubspec.yaml b/packages/smooth_app/pubspec.yaml index 714654c6d9c2..c128269d637f 100644 --- a/packages/smooth_app/pubspec.yaml +++ b/packages/smooth_app/pubspec.yaml @@ -101,7 +101,7 @@ dependencies: path: ../scanner/zxing - openfoodfacts: 3.22.0 + openfoodfacts: 3.23.0 # openfoodfacts: # path: ../../../openfoodfacts-dart From efed84a1d07dc68a60a32cd40b6c99f2292a1297 Mon Sep 17 00:00:00 2001 From: chetanr25 <1ds22ai010@dsce.edu.com> Date: Thu, 19 Jun 2025 23:02:36 +0530 Subject: [PATCH 11/12] Added username in Knowledge panel todo --- .../lib/knowledge_panel/knowledge_panels_builder.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/smooth_app/lib/knowledge_panel/knowledge_panels_builder.dart b/packages/smooth_app/lib/knowledge_panel/knowledge_panels_builder.dart index 164cf5a67c32..cc66595f451a 100644 --- a/packages/smooth_app/lib/knowledge_panel/knowledge_panels_builder.dart +++ b/packages/smooth_app/lib/knowledge_panel/knowledge_panels_builder.dart @@ -331,7 +331,7 @@ class KnowledgePanelsBuilder { ), ); case TitleElementType.PERCENTAGE: - // TODO: Implement percentage case for title element. + // TODO(PrimaelQuemerais): Implement percentage case for title element. /// The case is added to fix type errors but functionality is not implemented yet. /// TitleElementType.PERCENTAGE was introduced after PR #1086 /// (https://github.com/openfoodfacts/openfoodfacts-dart/pull/1086). From 7075c4d46a3632dae85c1f075b0184e1397541a3 Mon Sep 17 00:00:00 2001 From: chetanr25 <1ds22ai010@dsce.edu.com> Date: Fri, 20 Jun 2025 02:08:03 +0530 Subject: [PATCH 12/12] Scrolling issue fixed, minor UI improvements --- .../pages/prices/prices_dashboard_page.dart | 15 ++-- .../pages/prices/prices_dashboard_widget.dart | 72 +++++++++++++------ 2 files changed, 61 insertions(+), 26 deletions(-) diff --git a/packages/smooth_app/lib/pages/prices/prices_dashboard_page.dart b/packages/smooth_app/lib/pages/prices/prices_dashboard_page.dart index 5a601cb8a453..be56bf55a961 100644 --- a/packages/smooth_app/lib/pages/prices/prices_dashboard_page.dart +++ b/packages/smooth_app/lib/pages/prices/prices_dashboard_page.dart @@ -1,6 +1,7 @@ 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/helpers/launch_url_helper.dart'; import 'package:smooth_app/pages/prices/prices_dashboard_widget.dart'; import 'package:smooth_app/pages/prices/prices_user_profile.dart'; @@ -48,12 +49,14 @@ class PricesDashboardPage extends StatelessWidget { return Text(snapshot.error!.toString()); } final PriceUser userProfile = snapshot.data!.value; - return Column( - children: [ - PricesUserProfile(profile: userProfile), - Expanded( - child: PricesDashboardWidget(userProfile: userProfile)), - ], + return SingleChildScrollView( + child: Column( + children: [ + PricesUserProfile(profile: userProfile), + PricesDashboardWidget(userProfile: userProfile), + const SizedBox(height: VERY_LARGE_SPACE), + ], + ), ); }), ); diff --git a/packages/smooth_app/lib/pages/prices/prices_dashboard_widget.dart b/packages/smooth_app/lib/pages/prices/prices_dashboard_widget.dart index 54d6b45e246d..b5f349e3b27c 100644 --- a/packages/smooth_app/lib/pages/prices/prices_dashboard_widget.dart +++ b/packages/smooth_app/lib/pages/prices/prices_dashboard_widget.dart @@ -5,9 +5,10 @@ import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/generic_lib/widgets/smooth_card.dart'; import 'package:smooth_app/pages/prices/get_prices_model.dart'; +import 'package:smooth_app/pages/prices/price_data_widget.dart'; +import 'package:smooth_app/pages/prices/price_product_widget.dart'; import 'package:smooth_app/pages/prices/price_user_button.dart'; import 'package:smooth_app/pages/prices/prices_proofs_page.dart'; -import 'package:smooth_app/pages/prices/product_prices_list.dart'; import 'package:smooth_app/query/product_query.dart'; class PricesDashboardWidget extends StatefulWidget { @@ -24,12 +25,10 @@ class _PricesDashboardWidgetState extends State { @override Widget build(BuildContext context) { final AppLocalizations appLocalizations = AppLocalizations.of(context); - return Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, - spacing: MEDIUM_SPACE, children: [ _categorySwitch(), const SizedBox(height: SMALL_SPACE), @@ -45,19 +44,54 @@ class _PricesDashboardWidgetState extends State { return Center(child: Text(snapshot.error.toString())); } - return Expanded( - child: ProductPricesList( - GetPricesModel( - title: appLocalizations.prices_generic_title, - parameters: GetPricesParameters(), - uri: OpenPricesAPIClient.getUri( - path: 'users/${widget.userProfile.userId}', - uriHelper: ProductQuery.uriPricesHelper, - ), + if (snapshot.data?.value == null || + snapshot.data?.value!.items == null || + snapshot.data!.value!.items!.isEmpty) { + return const Center( + child: Padding( + padding: EdgeInsets.all(LARGE_SPACE), + child: + Text('No prices found', style: TextStyle(fontSize: 14)), ), - pricesResult: snapshot.data!.value, + ); + } + final List prices = snapshot.data!.value!.items!; + + final GetPricesModel model = GetPricesModel( + title: appLocalizations.prices_generic_title, + parameters: GetPricesParameters() + ..owner = widget.userProfile.userId + ..kind = selectedCategory == 'consumption' + ? ContributionKind.consumption + : ContributionKind.community, + uri: OpenPricesAPIClient.getUri( + path: 'users/${widget.userProfile.userId}', + uriHelper: ProductQuery.uriPricesHelper, ), ); + + return Column( + children: prices.map((Price item) { + final PriceProduct? priceProduct = item.product; + return SmoothCard( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (model.displayEachProduct && priceProduct != null) + PriceProductWidget( + priceProduct, + enableCountButton: model.enableCountButton, + ), + PriceDataWidget( + item, + model: model, + ), + ], + ), + ); + }).toList(), + ); }, ), ], @@ -65,16 +99,14 @@ class _PricesDashboardWidgetState extends State { } Future> _getUserPrices() async { - final MaybeError prices = - await OpenPricesAPIClient.getPrices( + return OpenPricesAPIClient.getPrices( GetPricesParameters() - ..owner = OpenFoodAPIConfiguration.globalUser?.userId + ..owner = widget.userProfile.userId ..kind = selectedCategory == 'consumption' ? ContributionKind.consumption : ContributionKind.community, uriHelper: ProductQuery.uriPricesHelper, ); - return prices; } /// Toggle between "My Consumption" and "Other Contributions" @@ -87,7 +119,7 @@ class _PricesDashboardWidgetState extends State { children: [ Expanded( flex: 1, - child: customToggleButton( + child: _categoryToggleButton( 'consumption', Icons.shopping_cart, appLocalizations.prices_dashboard_receipts_and_gdpr_requests, @@ -97,7 +129,7 @@ class _PricesDashboardWidgetState extends State { }))), Expanded( flex: 1, - child: customToggleButton('community', Icons.people, + child: _categoryToggleButton('community', Icons.people, appLocalizations.prices_dashboard_price_labels, () { setState(() { selectedCategory = 'community'; @@ -110,7 +142,7 @@ class _PricesDashboardWidgetState extends State { ); } - Widget customToggleButton( + Widget _categoryToggleButton( String category, IconData icon, String label, VoidCallback onTap) { final Color selectedColor = Theme.of(context).colorScheme.onSurface; final Color unselectedColor = selectedColor.withAlpha(128);