From f9d4dc81b9da2fd680970c379a44def21b2388b1 Mon Sep 17 00:00:00 2001 From: monsieurtanuki Date: Sun, 19 Jun 2022 20:35:12 +0200 Subject: [PATCH] feat: #2315 - added 4 user-related searches in the profile screen (#2321) New files: * `contributor_product_query.dart`: User Product Search, in "contributor" mode. * `informer_product_query.dart`: User Product Search, in "informer" mode. * `paged_search_product_query.dart`: Back-end paged queries around search. * `paged_user_product_query.dart`: Back-end paged queries around User. * `photographer_product_query.dart`: User Product Search, in "photographer" mode. * `to_be_completed_product_query.dart`: User Product Search, in "to be completed" mode. Impacted files: * `category_product_query.dart`: refactored with new class `PagedSearchProductQuery` * `keywords_product_query.dart`: refactored with new class `PagedSearchProductQuery` * `paged_product_query.dart`: now limited to page management * `product_list.dart`: added `enum`s and constructors around user queries * `product_list_page.dart`: added cases for the new 4 product list types * `product_query_page_helper.dart`: added cases for the new 4 product list types * `user_preferences_account.dart` --- .../lib/data_models/product_list.dart | 68 +++++++++++++++++++ .../lib/database/category_product_query.dart | 4 +- .../database/contributor_product_query.dart | 21 ++++++ .../lib/database/informer_product_query.dart | 21 ++++++ .../lib/database/keywords_product_query.dart | 4 +- .../lib/database/paged_product_query.dart | 19 ------ .../database/paged_search_product_query.dart | 24 +++++++ .../database/paged_user_product_query.dart | 59 ++++++++++++++++ .../database/photographer_product_query.dart | 21 ++++++ .../to_be_completed_product_query.dart | 21 ++++++ .../preferences/user_preferences_account.dart | 59 ++++++++++++++++ .../product/common/product_list_page.dart | 4 ++ .../common/product_query_page_helper.dart | 12 ++++ 13 files changed, 314 insertions(+), 23 deletions(-) create mode 100644 packages/smooth_app/lib/database/contributor_product_query.dart create mode 100644 packages/smooth_app/lib/database/informer_product_query.dart create mode 100644 packages/smooth_app/lib/database/paged_search_product_query.dart create mode 100644 packages/smooth_app/lib/database/paged_user_product_query.dart create mode 100644 packages/smooth_app/lib/database/photographer_product_query.dart create mode 100644 packages/smooth_app/lib/database/to_be_completed_product_query.dart diff --git a/packages/smooth_app/lib/data_models/product_list.dart b/packages/smooth_app/lib/data_models/product_list.dart index 761308145011..4a7c993ccc27 100644 --- a/packages/smooth_app/lib/data_models/product_list.dart +++ b/packages/smooth_app/lib/data_models/product_list.dart @@ -15,12 +15,28 @@ enum ProductListType { /// End-user product list USER, + + /// End-user as a contributor + HTTP_USER_CONTRIBUTOR, + + /// End-user as an informer + HTTP_USER_INFORMER, + + /// End-user as a photographer + HTTP_USER_PHOTOGRAPHER, + + /// End-user for products to be completed + HTTP_USER_TO_BE_COMPLETED, } extension ProductListTypeExtension on ProductListType { static const Map _keys = { ProductListType.HTTP_SEARCH_KEYWORDS: 'http/search/keywords', ProductListType.HTTP_SEARCH_CATEGORY: 'http/search/category', + ProductListType.HTTP_USER_CONTRIBUTOR: 'http/user/contributor', + ProductListType.HTTP_USER_INFORMER: 'http/user/informer', + ProductListType.HTTP_USER_PHOTOGRAPHER: 'http/user/photographer', + ProductListType.HTTP_USER_TO_BE_COMPLETED: 'http/user/to_be_completed', ProductListType.SCAN_SESSION: 'scan_session', ProductListType.HISTORY: 'history', ProductListType.USER: 'user', @@ -59,6 +75,50 @@ class ProductList { pageNumber: pageNumber, ); + ProductList.contributor( + final String userId, { + required int pageSize, + required int pageNumber, + }) : this._( + listType: ProductListType.HTTP_USER_CONTRIBUTOR, + parameters: userId, + pageSize: pageSize, + pageNumber: pageNumber, + ); + + ProductList.informer( + final String userId, { + required int pageSize, + required int pageNumber, + }) : this._( + listType: ProductListType.HTTP_USER_INFORMER, + parameters: userId, + pageSize: pageSize, + pageNumber: pageNumber, + ); + + ProductList.photographer( + final String userId, { + required int pageSize, + required int pageNumber, + }) : this._( + listType: ProductListType.HTTP_USER_PHOTOGRAPHER, + parameters: userId, + pageSize: pageSize, + pageNumber: pageNumber, + ); + + ProductList.toBeCompleted( + final String userId, { + required int pageSize, + required int pageNumber, + }) : this._( + listType: ProductListType.HTTP_USER_TO_BE_COMPLETED, + parameters: userId, + pageSize: pageSize, + pageNumber: pageNumber, + ); + ProductList.history() : this._(listType: ProductListType.HISTORY); ProductList.scanSession() : this._(listType: ProductListType.SCAN_SESSION); @@ -151,6 +211,10 @@ class ProductList { switch (listType) { case ProductListType.HTTP_SEARCH_KEYWORDS: case ProductListType.HTTP_SEARCH_CATEGORY: + case ProductListType.HTTP_USER_CONTRIBUTOR: + case ProductListType.HTTP_USER_INFORMER: + case ProductListType.HTTP_USER_PHOTOGRAPHER: + case ProductListType.HTTP_USER_TO_BE_COMPLETED: case ProductListType.USER: return false; case ProductListType.SCAN_SESSION: @@ -167,6 +231,10 @@ class ProductList { return parameters; case ProductListType.HTTP_SEARCH_KEYWORDS: case ProductListType.HTTP_SEARCH_CATEGORY: + case ProductListType.HTTP_USER_CONTRIBUTOR: + case ProductListType.HTTP_USER_INFORMER: + case ProductListType.HTTP_USER_PHOTOGRAPHER: + case ProductListType.HTTP_USER_TO_BE_COMPLETED: return '$parameters,$pageSize,$pageNumber'; } } diff --git a/packages/smooth_app/lib/database/category_product_query.dart b/packages/smooth_app/lib/database/category_product_query.dart index 0fd547afd151..2a8256d68e43 100644 --- a/packages/smooth_app/lib/database/category_product_query.dart +++ b/packages/smooth_app/lib/database/category_product_query.dart @@ -1,10 +1,10 @@ import 'package:openfoodfacts/model/parameter/TagFilter.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:smooth_app/data_models/product_list.dart'; -import 'package:smooth_app/database/paged_product_query.dart'; +import 'package:smooth_app/database/paged_search_product_query.dart'; /// Back-end query about a category. -class CategoryProductQuery extends PagedProductQuery { +class CategoryProductQuery extends PagedSearchProductQuery { CategoryProductQuery(this.categoryTag); // e.g. 'en:unsweetened-natural-soy-milks' diff --git a/packages/smooth_app/lib/database/contributor_product_query.dart b/packages/smooth_app/lib/database/contributor_product_query.dart new file mode 100644 index 000000000000..692c767dea20 --- /dev/null +++ b/packages/smooth_app/lib/database/contributor_product_query.dart @@ -0,0 +1,21 @@ +import 'package:smooth_app/data_models/product_list.dart'; +import 'package:smooth_app/database/paged_user_product_query.dart'; + +/// User Product Search, in "contributor" mode. +class ContributorProductQuery extends PagedUserProductQuery { + ContributorProductQuery(final String userId) : super(userId); + + @override + ProductList getProductList() => ProductList.contributor( + userId, + pageSize: pageSize, + pageNumber: pageNumber, + ); + + @override + String getPath() => 'contributor/$userId.json'; + + @override + String toString() => + 'ContributorProductQuery("$userId", $pageSize, $pageNumber)'; +} diff --git a/packages/smooth_app/lib/database/informer_product_query.dart b/packages/smooth_app/lib/database/informer_product_query.dart new file mode 100644 index 000000000000..51fa11b30031 --- /dev/null +++ b/packages/smooth_app/lib/database/informer_product_query.dart @@ -0,0 +1,21 @@ +import 'package:smooth_app/data_models/product_list.dart'; +import 'package:smooth_app/database/paged_user_product_query.dart'; + +/// User Product Search, in "informer" mode. +class InformerProductQuery extends PagedUserProductQuery { + InformerProductQuery(final String userId) : super(userId); + + @override + ProductList getProductList() => ProductList.informer( + userId, + pageSize: pageSize, + pageNumber: pageNumber, + ); + + @override + String getPath() => 'informer/$userId.json'; + + @override + String toString() => + 'InformerProductQuery("$userId", $pageSize, $pageNumber)'; +} diff --git a/packages/smooth_app/lib/database/keywords_product_query.dart b/packages/smooth_app/lib/database/keywords_product_query.dart index 96f841d91679..17d099954316 100644 --- a/packages/smooth_app/lib/database/keywords_product_query.dart +++ b/packages/smooth_app/lib/database/keywords_product_query.dart @@ -1,10 +1,10 @@ import 'package:openfoodfacts/model/parameter/SearchTerms.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:smooth_app/data_models/product_list.dart'; -import 'package:smooth_app/database/paged_product_query.dart'; +import 'package:smooth_app/database/paged_search_product_query.dart'; /// Back-end query around user-entered keywords. -class KeywordsProductQuery extends PagedProductQuery { +class KeywordsProductQuery extends PagedSearchProductQuery { KeywordsProductQuery(this.keywords); final String keywords; diff --git a/packages/smooth_app/lib/database/paged_product_query.dart b/packages/smooth_app/lib/database/paged_product_query.dart index 29cc184faab4..1b4daaa782f1 100644 --- a/packages/smooth_app/lib/database/paged_product_query.dart +++ b/packages/smooth_app/lib/database/paged_product_query.dart @@ -1,4 +1,3 @@ -import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:smooth_app/database/product_query.dart'; /// Paged product query (with [pageSize] and [pageNumber]). @@ -16,22 +15,4 @@ abstract class PagedProductQuery implements ProductQuery { void toNextPage() => _pageNumber++; void toTopPage() => _pageNumber = _startPageNumber; - - Parameter getParameter(); - - @override - Future getSearchResult() async => - OpenFoodAPIClient.searchProducts( - ProductQuery.getUser(), - ProductSearchQueryConfiguration( - fields: ProductQuery.fields, - parametersList: [ - PageSize(size: pageSize), - PageNumber(page: _pageNumber), - getParameter(), - ], - language: ProductQuery.getLanguage(), - country: ProductQuery.getCountry(), - ), - ); } diff --git a/packages/smooth_app/lib/database/paged_search_product_query.dart b/packages/smooth_app/lib/database/paged_search_product_query.dart new file mode 100644 index 000000000000..4a7b5184abe3 --- /dev/null +++ b/packages/smooth_app/lib/database/paged_search_product_query.dart @@ -0,0 +1,24 @@ +import 'package:openfoodfacts/openfoodfacts.dart'; +import 'package:smooth_app/database/paged_product_query.dart'; +import 'package:smooth_app/database/product_query.dart'; + +/// Back-end paged queries around search. +abstract class PagedSearchProductQuery extends PagedProductQuery { + Parameter getParameter(); + + @override + Future getSearchResult() async => + OpenFoodAPIClient.searchProducts( + ProductQuery.getUser(), + ProductSearchQueryConfiguration( + fields: ProductQuery.fields, + parametersList: [ + PageSize(size: pageSize), + PageNumber(page: pageNumber), + getParameter(), + ], + language: ProductQuery.getLanguage(), + country: ProductQuery.getCountry(), + ), + ); +} diff --git a/packages/smooth_app/lib/database/paged_user_product_query.dart b/packages/smooth_app/lib/database/paged_user_product_query.dart new file mode 100644 index 000000000000..f0b95048e5fa --- /dev/null +++ b/packages/smooth_app/lib/database/paged_user_product_query.dart @@ -0,0 +1,59 @@ +import 'dart:convert'; + +import 'package:http/http.dart'; +import 'package:openfoodfacts/openfoodfacts.dart'; +import 'package:openfoodfacts/utils/OpenFoodAPIConfiguration.dart'; +import 'package:openfoodfacts/utils/QueryType.dart'; +import 'package:openfoodfacts/utils/UriHelper.dart'; +import 'package:smooth_app/database/paged_product_query.dart'; +import 'package:smooth_app/database/product_query.dart'; + +/// Back-end paged queries around User. +abstract class PagedUserProductQuery extends PagedProductQuery { + PagedUserProductQuery(this.userId); + + final String userId; + + @override + Future getSearchResult() async => _searchProducts( + getPath(), + ProductQuery.getUser(), + pageSize, + pageNumber, + OpenFoodAPIConfiguration.globalQueryType, + ); + + String getPath(); + + static Future _searchProducts( + // TODO(monsieurtanuki): move to off-dart, but probably not as is + final String path, + final User user, + final int pageSize, + final int pageNumber, + final QueryType? queryType, + ) async { + final Uri uri = UriHelper.getUri( + path: path, + queryType: queryType, + queryParameters: { + 'page_size': '$pageSize', + 'page': '$pageNumber', + }, + ); + final Response response = await HttpHelper().doGetRequest( + uri, + queryType: queryType, + user: OpenFoodAPIConfiguration.globalUser, + ); + final String jsonStr = response + .body; // TODO(monsieurtanuki): what about _replaceQuotes(response.body); + final SearchResult result = SearchResult.fromJson( + json.decode(jsonStr) as Map, + ); + + // TODO(monsieurtanuki): what about _removeImages(result, configuration); + + return result; + } +} diff --git a/packages/smooth_app/lib/database/photographer_product_query.dart b/packages/smooth_app/lib/database/photographer_product_query.dart new file mode 100644 index 000000000000..bd95af62158f --- /dev/null +++ b/packages/smooth_app/lib/database/photographer_product_query.dart @@ -0,0 +1,21 @@ +import 'package:smooth_app/data_models/product_list.dart'; +import 'package:smooth_app/database/paged_user_product_query.dart'; + +/// User Product Search, in "photographer" mode. +class PhotographerProductQuery extends PagedUserProductQuery { + PhotographerProductQuery(final String userId) : super(userId); + + @override + ProductList getProductList() => ProductList.photographer( + userId, + pageSize: pageSize, + pageNumber: pageNumber, + ); + + @override + String getPath() => 'photographer/$userId.json'; + + @override + String toString() => + 'PhotographerProductQuery("$userId", $pageSize, $pageNumber)'; +} diff --git a/packages/smooth_app/lib/database/to_be_completed_product_query.dart b/packages/smooth_app/lib/database/to_be_completed_product_query.dart new file mode 100644 index 000000000000..dbb15d50cdb7 --- /dev/null +++ b/packages/smooth_app/lib/database/to_be_completed_product_query.dart @@ -0,0 +1,21 @@ +import 'package:smooth_app/data_models/product_list.dart'; +import 'package:smooth_app/database/paged_user_product_query.dart'; + +/// User Product Search, in "to be completed" mode. +class ToBeCompletedProductQuery extends PagedUserProductQuery { + ToBeCompletedProductQuery(final String userId) : super(userId); + + @override + ProductList getProductList() => ProductList.toBeCompleted( + userId, + pageSize: pageSize, + pageNumber: pageNumber, + ); + + @override + String getPath() => 'informer/$userId/state/to-be-completed.json'; + + @override + String toString() => + 'ToBeCompletedProductQuery("$userId", $pageSize, $pageNumber)'; +} 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 4b589f94e2bb..19da2237e4aa 100644 --- a/packages/smooth_app/lib/pages/preferences/user_preferences_account.dart +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_account.dart @@ -5,6 +5,11 @@ import 'package:openfoodfacts/utils/OpenFoodAPIConfiguration.dart'; import 'package:provider/provider.dart'; import 'package:smooth_app/data_models/user_management_provider.dart'; import 'package:smooth_app/data_models/user_preferences.dart'; +import 'package:smooth_app/database/contributor_product_query.dart'; +import 'package:smooth_app/database/informer_product_query.dart'; +import 'package:smooth_app/database/local_database.dart'; +import 'package:smooth_app/database/photographer_product_query.dart'; +import 'package:smooth_app/database/to_be_completed_product_query.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/generic_lib/dialogs/smooth_alert_dialog.dart'; import 'package:smooth_app/helpers/launch_url_helper.dart'; @@ -12,6 +17,7 @@ import 'package:smooth_app/helpers/user_management_helper.dart'; import 'package:smooth_app/pages/preferences/abstract_user_preferences.dart'; import 'package:smooth_app/pages/preferences/user_preferences_page.dart'; import 'package:smooth_app/pages/preferences/user_preferences_widgets.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:url_launcher/url_launcher.dart'; @@ -218,6 +224,7 @@ class _UserPreferencesPageState extends State { // We need to listen to reflect login's from outside of the preferences page // e.g. question card, ... context.watch(); + final LocalDatabase localDatabase = context.read(); final ThemeData theme = Theme.of(context); final AppLocalizations appLocalizations = AppLocalizations.of(context); @@ -230,6 +237,58 @@ class _UserPreferencesPageState extends State { final String userId = OpenFoodAPIConfiguration.globalUser!.userId; result = [ + ListTile( + onTap: () async => ProductQueryPageHelper().openBestChoice( + heroTag: 'contributor', + name: userId, + localDatabase: localDatabase, + productQuery: ContributorProductQuery(userId), + context: context, + ), + title: + const Text('Products I added'), // TODO(monsieurtanuki): translate + leading: const Icon(Icons.add_circle_outline), + ), + const UserPreferencesListItemDivider(), + ListTile( + onTap: () async => ProductQueryPageHelper().openBestChoice( + heroTag: 'informer', + name: userId, + localDatabase: localDatabase, + productQuery: InformerProductQuery(userId), + context: context, + ), + title: const Text( + 'Products I informed?'), // TODO(monsieurtanuki): translate + leading: const Icon(Icons.edit), + ), + const UserPreferencesListItemDivider(), + ListTile( + onTap: () async => ProductQueryPageHelper().openBestChoice( + heroTag: 'photographer', + name: userId, + localDatabase: localDatabase, + productQuery: PhotographerProductQuery(userId), + context: context, + ), + title: const Text( + 'Products I photographed'), // TODO(monsieurtanuki): translate + leading: const Icon(Icons.add_a_photo), + ), + const UserPreferencesListItemDivider(), + ListTile( + onTap: () async => ProductQueryPageHelper().openBestChoice( + heroTag: 'to_be_completed', + name: userId, + localDatabase: localDatabase, + productQuery: ToBeCompletedProductQuery(userId), + context: context, + ), + title: const Text( + 'Products that need to be completed'), // TODO(monsieurtanuki): translate + leading: const Icon(Icons.more_horiz), + ), + const UserPreferencesListItemDivider(), ListTile( onTap: () async => LaunchUrlHelper.launchURL( 'https://openfoodfacts.org/editor/$userId', 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 af62d042ce43..68d0252e2947 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 @@ -68,6 +68,10 @@ class _ProductListPageState extends State break; case ProductListType.HTTP_SEARCH_CATEGORY: case ProductListType.HTTP_SEARCH_KEYWORDS: + case ProductListType.HTTP_USER_CONTRIBUTOR: + case ProductListType.HTTP_USER_INFORMER: + case ProductListType.HTTP_USER_PHOTOGRAPHER: + case ProductListType.HTTP_USER_TO_BE_COMPLETED: dismissible = false; } final bool enableClear = products.isNotEmpty; diff --git a/packages/smooth_app/lib/pages/product/common/product_query_page_helper.dart b/packages/smooth_app/lib/pages/product/common/product_query_page_helper.dart index 2768c094d53b..48664df18793 100644 --- a/packages/smooth_app/lib/pages/product/common/product_query_page_helper.dart +++ b/packages/smooth_app/lib/pages/product/common/product_query_page_helper.dart @@ -81,6 +81,18 @@ class ProductQueryPageHelper { case ProductListType.HTTP_SEARCH_CATEGORY: return '${productList.parameters}' '${verbose ? ' ${appLocalizations.category_search}' : ''}'; + case ProductListType.HTTP_USER_CONTRIBUTOR: + return '${productList.parameters}' + '${verbose ? ' as a contributor' : ''}'; // TODO(monsieurtanuki): translate + case ProductListType.HTTP_USER_INFORMER: + return '${productList.parameters}' + '${verbose ? ' as an informer' : ''}'; // TODO(monsieurtanuki): translate + case ProductListType.HTTP_USER_PHOTOGRAPHER: + return '${productList.parameters}' + '${verbose ? ' as a photographer' : ''}'; // TODO(monsieurtanuki): translate + case ProductListType.HTTP_USER_TO_BE_COMPLETED: + return '${productList.parameters}' + '${verbose ? ' to be completed' : ''}'; // TODO(monsieurtanuki): translate case ProductListType.SCAN_SESSION: return appLocalizations.scan; case ProductListType.HISTORY: