Skip to content

Commit

Permalink
feat: Improvements for the search experience (#5519)
Browse files Browse the repository at this point in the history
* Improvements

* Fix typo

* One more typo
  • Loading branch information
g123k authored Aug 7, 2024
1 parent 27b2c96 commit 30ca6b6
Show file tree
Hide file tree
Showing 20 changed files with 744 additions and 405 deletions.
4 changes: 4 additions & 0 deletions packages/smooth_app/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,10 @@
"@search": {
"description": "Hint text of a search text input field"
},
"search_store": "Search for a store",
"@search_store": {
"description": "Hint text of a search store text input field"
},
"tap_for_more": "Tap to see more info…",
"@Product": {},
"product": "Product",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import 'package:smooth_app/pages/product/common/search_helper.dart';

/// Search helper dedicated to location search.
class SearchLocationHelper extends SearchHelper {
const SearchLocationHelper();
SearchLocationHelper();

@override
String get historyKey => DaoStringList.keySearchLocationHistory;

@override
String getHintText(final AppLocalizations appLocalizations) =>
'Rechercher un magasin';
appLocalizations.search_store;

@override
void search(
Expand Down
13 changes: 10 additions & 3 deletions packages/smooth_app/lib/pages/navigator/app_navigator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import 'package:smooth_app/pages/product/edit_product_page.dart';
import 'package:smooth_app/pages/product/new_product_page.dart';
import 'package:smooth_app/pages/product/product_loader_page.dart';
import 'package:smooth_app/pages/scan/carousel/scan_carousel_manager.dart';
import 'package:smooth_app/pages/scan/search_page.dart';
import 'package:smooth_app/pages/scan/search_product_helper.dart';
import 'package:smooth_app/pages/search/search_page.dart';
import 'package:smooth_app/pages/search/search_product_helper.dart';
import 'package:smooth_app/pages/user_management/sign_up_page.dart';
import 'package:smooth_app/query/product_query.dart';

Expand Down Expand Up @@ -204,7 +204,13 @@ class _SmoothGoRouter {
),
GoRoute(
path: _InternalAppRoutes.SEARCH_PAGE,
builder: (_, __) => const SearchPage(SearchProductHelper()),
builder: (_, GoRouterState state) {
if (state.extra != null) {
return SearchPage.fromExtra(state.extra! as SearchPageExtra);
} else {
return SearchPage(SearchProductHelper());
}
},
),
GoRoute(
path: _InternalAppRoutes._GUIDES,
Expand Down Expand Up @@ -452,6 +458,7 @@ class AppRoutes {
'/${_InternalAppRoutes._GUIDES}/${_InternalAppRoutes.GUIDE_NUTRISCORE_V2_PAGE}';

static String get SIGNUP => '/${_InternalAppRoutes.SIGNUP_PAGE}';

// Open an external link (where path is relative to the OFF website)
static String EXTERNAL(String path) =>
'/${_InternalAppRoutes.EXTERNAL_PAGE}/?path=$path';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ class UserPreferencesAccount extends AbstractUserPreferences {
}) =>
_getListTile(
title,
() async => ProductQueryPageHelper().openBestChoice(
() async => ProductQueryPageHelper.openBestChoice(
name: title,
localDatabase: localDatabase,
productQuery: productQuery,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ class UserPreferencesContribute extends AbstractUserPreferences {
final LocalDatabase localDatabase =
context.read<LocalDatabase>();
Navigator.of(context).pop();
ProductQueryPageHelper().openBestChoice(
ProductQueryPageHelper.openBestChoice(
name: appLocalizations.all_search_to_be_completed_title,
localDatabase: localDatabase,
productQuery: PagedToBeCompletedProductQuery(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import 'package:smooth_app/pages/preferences/user_preferences_item.dart';
import 'package:smooth_app/pages/preferences/user_preferences_page.dart';
import 'package:smooth_app/pages/preferences/user_preferences_search_page.dart';
import 'package:smooth_app/pages/preferences/user_preferences_widgets.dart';
import 'package:smooth_app/pages/scan/search_page.dart';
import 'package:smooth_app/pages/search/search_page.dart';
import 'package:smooth_app/query/product_query.dart';

/// Full page display of "dev mode" for the preferences page.
Expand Down Expand Up @@ -421,7 +421,7 @@ class UserPreferencesDevMode extends AbstractUserPreferences {
context,
MaterialPageRoute<OsmLocation>(
builder: (BuildContext context) => SearchPage(
const SearchLocationHelper(),
SearchLocationHelper(),
preloadedList: preloadedList,
autofocus: false,
),
Expand Down
4 changes: 2 additions & 2 deletions packages/smooth_app/lib/pages/prices/price_location_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import 'package:smooth_app/pages/locations/osm_location.dart';
import 'package:smooth_app/pages/locations/search_location_helper.dart';
import 'package:smooth_app/pages/locations/search_location_preloaded_item.dart';
import 'package:smooth_app/pages/prices/price_model.dart';
import 'package:smooth_app/pages/scan/search_page.dart';
import 'package:smooth_app/pages/search/search_page.dart';

/// Card that displays the location for price adding.
class PriceLocationCard extends StatelessWidget {
Expand Down Expand Up @@ -52,7 +52,7 @@ class PriceLocationCard extends StatelessWidget {
context,
MaterialPageRoute<OsmLocation>(
builder: (BuildContext context) => SearchPage(
const SearchLocationHelper(),
SearchLocationHelper(),
preloadedList: preloadedList,
autofocus: false,
),
Expand Down
100 changes: 62 additions & 38 deletions packages/smooth_app/lib/pages/product/common/product_query_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,19 @@ import 'package:smooth_app/widgets/ranking_floating_action_button.dart';
import 'package:smooth_app/widgets/smooth_app_bar.dart';
import 'package:smooth_app/widgets/smooth_scaffold.dart';

/// A page that can be used like a screen, if [includeAppBar] is true.
/// Otherwise, it can be embedded in another screen.
class ProductQueryPage extends StatefulWidget {
const ProductQueryPage({
required this.productListSupplier,
required this.name,
required this.editableAppBarTitle,
this.includeAppBar = true,
this.searchResult = true,
});

final ProductListSupplier productListSupplier;
final String name;
final bool editableAppBarTitle;
final bool includeAppBar;
final bool searchResult;

@override
Expand All @@ -57,7 +59,7 @@ class _ProductQueryPageState extends State<ProductQueryPage>
late ScrollController _scrollController;

late ProductQueryModel _model;
late final OpenFoodFactsCountry? _country;
late OpenFoodFactsCountry? _country;

@override
String get actionName => 'Opened search_page';
Expand Down Expand Up @@ -88,9 +90,13 @@ class _ProductQueryPageState extends State<ProductQueryPage>
}

@override
void dispose() {
_scrollController.dispose();
super.dispose();
void didUpdateWidget(ProductQueryPage oldWidget) {
super.didUpdateWidget(oldWidget);

if (oldWidget.productListSupplier != widget.productListSupplier) {
_model = _getModel(widget.productListSupplier);
_country = widget.productListSupplier.productQuery.country;
}
}

@override
Expand Down Expand Up @@ -124,11 +130,11 @@ class _ProductQueryPageState extends State<ProductQueryPage>
// TODO(monsieurtanuki): should be tracked as well, shouldn't it?
return SearchEmptyScreen(
name: widget.name,
includeAppBar: widget.includeAppBar,
emptiness: _getEmptyText(
themeData,
appLocalizations.no_product_found,
),
actions: _getAppBarButtons(),
);
}
AnalyticsHelper.trackSearch(
Expand All @@ -149,6 +155,12 @@ class _ProductQueryPageState extends State<ProductQueryPage>
);
}

@override
void dispose() {
_scrollController.dispose();
super.dispose();
}

// TODO(monsieurtanuki): put that in a specific Widget class
Widget _getNotEmptyScreen(
final Size screenSize,
Expand All @@ -162,8 +174,8 @@ class _ProductQueryPageState extends State<ProductQueryPage>
children: <Widget>[
Expanded(
child: RankingFloatingActionButton(
onPressed: () => Navigator.push<Widget>(
context,
onPressed: () =>
Navigator.of(context, rootNavigator: true).push<Widget>(
MaterialPageRoute<Widget>(
builder: (BuildContext context) =>
PersonalizedRankingPage(
Expand Down Expand Up @@ -212,26 +224,27 @@ class _ProductQueryPageState extends State<ProductQueryPage>
),
],
),
appBar: SmoothAppBar(
backgroundColor: themeData.scaffoldBackgroundColor,
elevation: 2,
automaticallyImplyLeading: false,
leading: const SmoothBackButton(),
title: SearchAppBarTitle(
title: widget.searchResult
? widget.name
: appLocalizations.product_search_same_category,
editableAppBarTitle:
widget.searchResult && widget.editableAppBarTitle,
multiLines: !widget.searchResult,
),
subTitle: !widget.searchResult ? Text(widget.name) : null,
actions: _getAppBarButtons(),
),
appBar: widget.includeAppBar
? SmoothAppBar(
backgroundColor: themeData.scaffoldBackgroundColor,
elevation: 2,
automaticallyImplyLeading: false,
leading: const SmoothBackButton(),
title: SearchAppBarTitle(
title: widget.searchResult
? widget.name
: appLocalizations.product_search_same_category,
editableAppBarTitle: widget.searchResult,
multiLines: !widget.searchResult,
),
subTitle: !widget.searchResult ? Text(widget.name) : null,
)
: null,
body: RefreshIndicator(
onRefresh: () async => _refreshList(),
child: ListView.builder(
controller: _scrollController,
padding: widget.includeAppBar ? null : EdgeInsets.zero,
// To allow refresh even when not the whole page is filled
physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
Expand Down Expand Up @@ -298,6 +311,7 @@ class _ProductQueryPageState extends State<ProductQueryPage>
) {
return SearchEmptyScreen(
name: widget.name,
includeAppBar: false,
emptiness: Padding(
padding: const EdgeInsets.all(SMALL_SPACE),
child: SmoothErrorCard(
Expand Down Expand Up @@ -331,25 +345,21 @@ class _ProductQueryPageState extends State<ProductQueryPage>
),
if (worldQuery != null)
_getLargeButtonWithIcon(
_getWorldAction(appLocalizations, worldQuery),
_getWorldAction(
appLocalizations,
worldQuery,
widget.includeAppBar,
),
),
],
),
);
}

List<Widget> _getAppBarButtons() {
final AppLocalizations appLocalizations = AppLocalizations.of(context);
Widget _getTopMessagesCard() {
final PagedProductQuery pagedProductQuery = _model.supplier.productQuery;
final PagedProductQuery? worldQuery = pagedProductQuery.getWorldQuery();
return <Widget>[
if (worldQuery != null)
_getIconButton(_getWorldAction(appLocalizations, worldQuery)),
];
}

Widget _getTopMessagesCard() {
final PagedProductQuery pagedProductQuery = _model.supplier.productQuery;
return FutureBuilder<String?>(
future: _getTranslatedCountry(),
builder: (
Expand Down Expand Up @@ -383,7 +393,19 @@ class _ProductQueryPageState extends State<ProductQueryPage>
child: SmoothCard(
child: Padding(
padding: const EdgeInsets.all(SMALL_SPACE),
child: Text(messages.join('\n')),
child: Row(
children: <Widget>[
Expanded(child: Text(messages.join('\n'))),
if (pagedProductQuery.getWorldQuery() != null)
_getIconButton(
_getWorldAction(
appLocalizations,
worldQuery!,
widget.includeAppBar,
),
),
],
),
),
),
);
Expand All @@ -399,7 +421,7 @@ class _ProductQueryPageState extends State<ProductQueryPage>
final List<Country> localizedCountries =
await IsoCountries.isoCountriesForLocale(locale);
for (final Country country in localizedCountries) {
if (country.countryCode.toLowerCase() == _country.offTag.toLowerCase()) {
if (country.countryCode.toLowerCase() == _country?.offTag.toLowerCase()) {
return country.name;
}
}
Expand All @@ -422,15 +444,17 @@ class _ProductQueryPageState extends State<ProductQueryPage>
_Action _getWorldAction(
final AppLocalizations appLocalizations,
final PagedProductQuery worldQuery,
final bool editableAppBarTitle,
) =>
_Action(
text: appLocalizations.world_results_action,
iconData: Icons.public,
onPressed: () async => ProductQueryPageHelper().openBestChoice(
onPressed: () async => ProductQueryPageHelper.openBestChoice(
productQuery: worldQuery,
localDatabase: context.read<LocalDatabase>(),
name: widget.name,
context: context,
editableAppBarTitle: editableAppBarTitle,
),
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,55 @@ import 'package:smooth_app/pages/product/common/search_helper.dart';
import 'package:smooth_app/query/paged_product_query.dart';

class ProductQueryPageHelper {
Future<void> openBestChoice({
const ProductQueryPageHelper._();

static Future<Widget> getBestChoiceWidget({
required final PagedProductQuery productQuery,
required final LocalDatabase localDatabase,
required final String name,
required final BuildContext context,
bool editableAppBarTitle = true,
bool searchResult = true,
SearchQueryCallback? editQueryCallback,
}) async {
final ProductListSupplier supplier =
await ProductListSupplier.getBestSupplier(
productQuery,
localDatabase,
);

return ProductQueryPage(
productListSupplier: supplier,
name: name,
includeAppBar: editableAppBarTitle,
searchResult: searchResult,
);
}

static Future<void> openBestChoice({
required final PagedProductQuery productQuery,
required final LocalDatabase localDatabase,
required final String name,
required final BuildContext context,
bool editableAppBarTitle = true,
bool searchResult = true,
SearchQueryCallback? editQueryCallback,
}) async {
final Widget widget = await getBestChoiceWidget(
productQuery: productQuery,
localDatabase: localDatabase,
name: name,
context: context,
editableAppBarTitle: editableAppBarTitle,
searchResult: searchResult,
);

if (!context.mounted) {
return;
}
final bool? result = await Navigator.push<bool>(
context,

final bool? result = await Navigator.of(context).push<bool>(
MaterialPageRoute<bool>(
builder: (BuildContext context) => ProductQueryPage(
productListSupplier: supplier,
name: name,
editableAppBarTitle: editableAppBarTitle,
searchResult: searchResult,
),
builder: (BuildContext context) => widget,
),
);

Expand Down
Loading

0 comments on commit 30ca6b6

Please sign in to comment.