From 72f6d5ad52fcb119d68346ed7a712a0490d053ed Mon Sep 17 00:00:00 2001 From: Edouard Marquez Date: Fri, 15 Sep 2023 11:27:06 +0200 Subject: [PATCH 1/8] When we highlight a suggestion, we can't rely on the `removeDiacritics`, as it replaces some letters of 1 word into 2 or + --- .../smooth_app/lib/widgets/smooth_text.dart | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/smooth_app/lib/widgets/smooth_text.dart b/packages/smooth_app/lib/widgets/smooth_text.dart index 12820b00cc7..f247466b1d9 100644 --- a/packages/smooth_app/lib/widgets/smooth_text.dart +++ b/packages/smooth_app/lib/widgets/smooth_text.dart @@ -1,4 +1,3 @@ -import 'package:diacritic/diacritic.dart'; import 'package:flutter/material.dart'; /// An extension on [TextStyle] that allows to have "well spaced" variant @@ -92,8 +91,8 @@ class TextHighlighter extends StatelessWidget { required TextStyle? highlightedStyle, }) { final Iterable highlightedParts = - RegExp(removeDiacritics(filter).toLowerCase().trim()).allMatches( - removeDiacritics(text).toLowerCase(), + RegExp(_removeAccents(filter).toLowerCase().trim()).allMatches( + _removeAccents(text).toLowerCase(), ); final List<(String, TextStyle?)> parts = <(String, TextStyle?)>[]; @@ -123,4 +122,20 @@ class TextHighlighter extends StatelessWidget { } return parts; } + + /// We can't rely on [removeDiacritics] here, as it replaces some characters + /// of 1 letter into 2 or more. + /// That's why this simpler algorithm is used instead. + String _removeAccents(String str) { + const String withAccents = + 'ÀÁÂÃÄÅàáâãäåÒÓÔÕÕÖØòóôõöøÈÉÊËèéêëðÇçÐÌÍÎÏìíîïÙÚÛÜùúûüÑñŠšŸÿýŽž'; + const String withoutAccents = + 'AAAAAAaaaaaaOOOOOOOooooooEEEEeeeeeCcDIIIIiiiiUUUUuuuuNnSsYyyZz'; + + for (int i = 0; i < withAccents.length; i++) { + str = str.replaceAll(withAccents[i], withoutAccents[i]); + } + + return str; + } } From 7af2c89f18e1da87b10057fcfec129168d0a3f68 Mon Sep 17 00:00:00 2001 From: Edouard Marquez Date: Sat, 9 Dec 2023 02:47:05 +0100 Subject: [PATCH 2/8] Better algorithm --- .../widgets/language_selector.dart | 16 +-- .../pages/onboarding/country_selector.dart | 21 ++-- .../user_preferences_search_page.dart | 5 +- .../nutrition_add_nutrient_button.dart | 11 +- .../smooth_app/lib/widgets/smooth_text.dart | 111 ++++++++++++++---- .../test/utils/smooth_text_test.dart | 21 ++++ 6 files changed, 140 insertions(+), 45 deletions(-) create mode 100644 packages/smooth_app/test/utils/smooth_text_test.dart diff --git a/packages/smooth_app/lib/generic_lib/widgets/language_selector.dart b/packages/smooth_app/lib/generic_lib/widgets/language_selector.dart index bf813b3100e..76279771889 100644 --- a/packages/smooth_app/lib/generic_lib/widgets/language_selector.dart +++ b/packages/smooth_app/lib/generic_lib/widgets/language_selector.dart @@ -146,19 +146,21 @@ class LanguageSelector extends StatelessWidget { prefixIcon: const Icon(Icons.search), controller: languageSelectorController, onChanged: (String? query) { - query = removeDiacritics(query!.trim().toLowerCase()); + query = query!.trim().toLowerCase().removeDiacritics(); setState( () { filteredList = leftovers .where((OpenFoodFactsLanguage item) => - removeDiacritics(_languages - .getNameInEnglish(item) - .toLowerCase()) + _languages + .getNameInEnglish(item) + .toLowerCase() + .removeDiacritics() .contains(query!.toLowerCase()) || - removeDiacritics(_languages - .getNameInLanguage(item) - .toLowerCase()) + _languages + .getNameInLanguage(item) + .toLowerCase() + .removeDiacritics() .contains(query.toLowerCase()) || item.code.contains(query)) .toList(); diff --git a/packages/smooth_app/lib/pages/onboarding/country_selector.dart b/packages/smooth_app/lib/pages/onboarding/country_selector.dart index bb5ff8dbb97..1511233a5d3 100644 --- a/packages/smooth_app/lib/pages/onboarding/country_selector.dart +++ b/packages/smooth_app/lib/pages/onboarding/country_selector.dart @@ -104,23 +104,26 @@ class _CountrySelectorState extends State { prefixIcon: const Icon(Icons.search), controller: _countryController, onChanged: (String? query) { - query = removeDiacritics(query!.trim().toLowerCase()); + query = + query!.trim().toLowerCase().removeDiacritics(); setState( () { filteredList = _countryList .where( (Country item) => - removeDiacritics( - item.name.toLowerCase()) + item.name + .toLowerCase() + .removeDiacritics() .contains( - query!, - ) || - removeDiacritics( - item.countryCode.toLowerCase()) + query!, + ) || + item.countryCode + .toLowerCase() + .removeDiacritics() .contains( - query, - ), + query, + ), ) .toList(growable: false); }, diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_search_page.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_search_page.dart index ded2608cd82..3ba5ed641b9 100644 --- a/packages/smooth_app/lib/pages/preferences/user_preferences_search_page.dart +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_search_page.dart @@ -6,6 +6,7 @@ import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/pages/preferences/abstract_user_preferences.dart'; import 'package:smooth_app/pages/preferences/user_preferences_item.dart'; import 'package:smooth_app/pages/preferences/user_preferences_page.dart'; +import 'package:smooth_app/widgets/smooth_text.dart'; /// Search page for preferences, with TextField filter. class UserPreferencesSearchPage extends StatefulWidget { @@ -70,7 +71,7 @@ class _UserPreferencesSearchPageState extends State { final String searchString, final UserPreferences userPreferences, ) { - final String needle = removeDiacritics(searchString.toLowerCase()); + final String needle = searchString.toLowerCase().removeDiacritics(); final List result = []; final List types = PreferencePageType.getPreferencePageTypes(userPreferences); @@ -101,7 +102,7 @@ class _UserPreferencesSearchPageState extends State { bool _findLabels(final String needle, final Iterable labels) { for (final String label in labels) { - if (removeDiacritics(label.toLowerCase()).contains(needle)) { + if (label.toLowerCase().removeDiacritics().contains(needle)) { return true; } } diff --git a/packages/smooth_app/lib/pages/product/nutrition_add_nutrient_button.dart b/packages/smooth_app/lib/pages/product/nutrition_add_nutrient_button.dart index b513ed827bf..9ffb61a8edb 100644 --- a/packages/smooth_app/lib/pages/product/nutrition_add_nutrient_button.dart +++ b/packages/smooth_app/lib/pages/product/nutrition_add_nutrient_button.dart @@ -55,9 +55,14 @@ class NutritionAddNutrientButton extends StatelessWidget { controller: nutritionTextController, onChanged: (String? query) => setState( () => filteredList = leftovers - .where((OrderedNutrient item) => - removeDiacritics(item.name!).toLowerCase().contains( - removeDiacritics(query!).toLowerCase().trim())) + .where( + (OrderedNutrient item) => item.name! + .removeDiacritics() + .toLowerCase() + .contains( + query!.removeDiacritics().toLowerCase().trim(), + ), + ) .toList(), ), ), diff --git a/packages/smooth_app/lib/widgets/smooth_text.dart b/packages/smooth_app/lib/widgets/smooth_text.dart index f247466b1d9..8caa8c21fd8 100644 --- a/packages/smooth_app/lib/widgets/smooth_text.dart +++ b/packages/smooth_app/lib/widgets/smooth_text.dart @@ -1,4 +1,30 @@ +import 'package:diacritic/diacritic.dart' as lib show removeDiacritics; import 'package:flutter/material.dart'; +import 'package:smooth_app/services/smooth_services.dart'; + +/// An extension on [String] +extension StringExtension on String { + /// Simple algorithm to only remove accents AND not diacritics. + String removeAccents() { + const String withAccents = + 'ÀÁÂÃÄÅàáâãäåÒÓÔÕÕÖØòóôõöøÈÉÊËèéêëðÇçÐÌÍÎÏìíîïÙÚÛÜùúûüÑñŠšŸÿýŽž'; + const String withoutAccents = + 'AAAAAAaaaaaaOOOOOOOooooooEEEEeeeeeCcDIIIIiiiiUUUUuuuuNnSsYyyZz'; + + String str = this; + for (int i = 0; i < withAccents.length; i++) { + str = str.replaceAll(withAccents[i], withoutAccents[i]); + } + + return str; + } + + /// Please use this method instead of directly calling the library. + /// It will use the migration if we decide to remove/change it. + String removeDiacritics() { + return lib.removeDiacritics(this); + } +} /// An extension on [TextStyle] that allows to have "well spaced" variant extension TextStyleExtension on TextStyle { @@ -59,13 +85,23 @@ class TextHighlighter extends StatelessWidget { @override Widget build(BuildContext context) { - final List<(String, TextStyle?)> parts = _getParts( - defaultStyle: TextStyle(fontWeight: selected ? FontWeight.bold : null), - highlightedStyle: TextStyle( - fontWeight: selected ? FontWeight.bold : null, - backgroundColor: Theme.of(context).primaryColor.withOpacity(0.2), - ), - ); + List<(String, TextStyle?)> parts; + try { + parts = _getParts( + defaultStyle: TextStyle(fontWeight: selected ? FontWeight.bold : null), + highlightedStyle: TextStyle( + fontWeight: selected ? FontWeight.bold : null, + backgroundColor: Theme.of(context).primaryColor.withOpacity(0.2), + ), + ); + } catch (e, trace) { + parts = <(String, TextStyle?)>[(text, null)]; + Logs.e( + 'Unable to parse text "$text" with filter "$filter".', + ex: e, + stacktrace: trace, + ); + } final TextStyle defaultTextStyle = DefaultTextStyle.of(context).style; @@ -90,9 +126,15 @@ class TextHighlighter extends StatelessWidget { required TextStyle? defaultStyle, required TextStyle? highlightedStyle, }) { + final String filterWithoutDiacritics = filter.removeDiacritics(); + final int filterDiacriticsLength = + filterWithoutDiacritics.length - filter.length; + final String textWithoutDiacritics = text.removeDiacritics(); + final int textDiacriticsLength = textWithoutDiacritics.length - text.length; + final Iterable highlightedParts = - RegExp(_removeAccents(filter).toLowerCase().trim()).allMatches( - _removeAccents(text).toLowerCase(), + RegExp(filterWithoutDiacritics.toLowerCase().trim()).allMatches( + textWithoutDiacritics.toLowerCase(), ); final List<(String, TextStyle?)> parts = <(String, TextStyle?)>[]; @@ -102,40 +144,61 @@ class TextHighlighter extends StatelessWidget { } else { parts .add((text.substring(0, highlightedParts.first.start), defaultStyle)); + int diff = 0; + for (int i = 0; i != highlightedParts.length; i++) { final RegExpMatch subPart = highlightedParts.elementAt(i); + final int startPosition = subPart.start - diff; + final int endPosition = _computeEndPosition( + startPosition, + subPart.end - diff, + subPart, + textWithoutDiacritics, + filterWithoutDiacritics, + ); + diff = subPart.end - endPosition; parts.add( - (text.substring(subPart.start, subPart.end), highlightedStyle), + (text.substring(startPosition, endPosition), highlightedStyle), ); if (i < highlightedParts.length - 1) { parts.add(( text.substring( - subPart.end, highlightedParts.elementAt(i + 1).start), + endPosition, highlightedParts.elementAt(i + 1).start - diff), defaultStyle )); - } else if (subPart.end < text.length) { - parts.add((text.substring(subPart.end, text.length), defaultStyle)); + } else if (endPosition < text.length) { + parts.add((text.substring(endPosition, text.length), defaultStyle)); } } } return parts; } - /// We can't rely on [removeDiacritics] here, as it replaces some characters - /// of 1 letter into 2 or more. - /// That's why this simpler algorithm is used instead. - String _removeAccents(String str) { - const String withAccents = - 'ÀÁÂÃÄÅàáâãäåÒÓÔÕÕÖØòóôõöøÈÉÊËèéêëðÇçÐÌÍÎÏìíîïÙÚÛÜùúûüÑñŠšŸÿýŽž'; - const String withoutAccents = - 'AAAAAAaaaaaaOOOOOOOooooooEEEEeeeeeCcDIIIIiiiiUUUUuuuuNnSsYyyZz'; + int _computeEndPosition( + int startPosition, + int endPosition, + RegExpMatch subPart, + String textWithoutDiacritics, + String filterWithoutDiacritics, + ) { + final String subText = text.substring(startPosition); + if (subText.startsWith(filterWithoutDiacritics)) { + return endPosition; + } - for (int i = 0; i < withAccents.length; i++) { - str = str.replaceAll(withAccents[i], withoutAccents[i]); + int diff = 0; + for (int pos = 0; pos < endPosition; pos++) { + if (pos == subText.length - 1) { + diff = pos - (subText.length); + break; + } + + final int charLength = subText[pos].removeDiacritics().length; + diff -= charLength > 1 ? charLength - 1 : 0; } - return str; + return endPosition + diff; } } diff --git a/packages/smooth_app/test/utils/smooth_text_test.dart b/packages/smooth_app/test/utils/smooth_text_test.dart new file mode 100644 index 00000000000..9a4004af6d1 --- /dev/null +++ b/packages/smooth_app/test/utils/smooth_text_test.dart @@ -0,0 +1,21 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:smooth_app/widgets/smooth_text.dart' show StringExtension; + +void main() { + group('Smooth text', () { + group('String extension', () { + test('Remove accents', () { + expect( + 'àáâãäåéèêëòóôõöìíîïùúûüñšÿýž'.removeAccents(), + equals('aaaaaaeeeeoooooiiiiuuuunsyyz'), + ); + }); + test('Remove diacritics', () { + expect( + 'àáâãäåéèêëòóôõöìíîïùúûüñšÿýž'.removeDiacritics(), + equals('aaaaaaeeeeoooooiiiiuuuunsyyz'), + ); + }); + }); + }); +} From 6a775a6b64ad2fd0526fa559522e4bce1edeb5e0 Mon Sep 17 00:00:00 2001 From: Edouard Marquez Date: Sat, 9 Dec 2023 02:50:03 +0100 Subject: [PATCH 3/8] Remove unused things --- .../smooth_app/lib/generic_lib/widgets/language_selector.dart | 1 - packages/smooth_app/lib/pages/onboarding/country_selector.dart | 1 - .../lib/pages/preferences/user_preferences_search_page.dart | 1 - .../lib/pages/product/nutrition_add_nutrient_button.dart | 1 - packages/smooth_app/lib/widgets/smooth_text.dart | 3 --- 5 files changed, 7 deletions(-) diff --git a/packages/smooth_app/lib/generic_lib/widgets/language_selector.dart b/packages/smooth_app/lib/generic_lib/widgets/language_selector.dart index 76279771889..bf9f2d97ed9 100644 --- a/packages/smooth_app/lib/generic_lib/widgets/language_selector.dart +++ b/packages/smooth_app/lib/generic_lib/widgets/language_selector.dart @@ -1,4 +1,3 @@ -import 'package:diacritic/diacritic.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; diff --git a/packages/smooth_app/lib/pages/onboarding/country_selector.dart b/packages/smooth_app/lib/pages/onboarding/country_selector.dart index 1511233a5d3..da5c4699860 100644 --- a/packages/smooth_app/lib/pages/onboarding/country_selector.dart +++ b/packages/smooth_app/lib/pages/onboarding/country_selector.dart @@ -1,4 +1,3 @@ -import 'package:diacritic/diacritic.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_search_page.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_search_page.dart index 3ba5ed641b9..4bd879f7ad4 100644 --- a/packages/smooth_app/lib/pages/preferences/user_preferences_search_page.dart +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_search_page.dart @@ -1,4 +1,3 @@ -import 'package:diacritic/diacritic.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:smooth_app/data_models/preferences/user_preferences.dart'; diff --git a/packages/smooth_app/lib/pages/product/nutrition_add_nutrient_button.dart b/packages/smooth_app/lib/pages/product/nutrition_add_nutrient_button.dart index 9ffb61a8edb..21fd7e56e77 100644 --- a/packages/smooth_app/lib/pages/product/nutrition_add_nutrient_button.dart +++ b/packages/smooth_app/lib/pages/product/nutrition_add_nutrient_button.dart @@ -1,4 +1,3 @@ -import 'package:diacritic/diacritic.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; diff --git a/packages/smooth_app/lib/widgets/smooth_text.dart b/packages/smooth_app/lib/widgets/smooth_text.dart index 8caa8c21fd8..78b3ee63bbb 100644 --- a/packages/smooth_app/lib/widgets/smooth_text.dart +++ b/packages/smooth_app/lib/widgets/smooth_text.dart @@ -127,10 +127,7 @@ class TextHighlighter extends StatelessWidget { required TextStyle? highlightedStyle, }) { final String filterWithoutDiacritics = filter.removeDiacritics(); - final int filterDiacriticsLength = - filterWithoutDiacritics.length - filter.length; final String textWithoutDiacritics = text.removeDiacritics(); - final int textDiacriticsLength = textWithoutDiacritics.length - text.length; final Iterable highlightedParts = RegExp(filterWithoutDiacritics.toLowerCase().trim()).allMatches( From a8fdddaae9b752486c069d458a346652dfc4e894 Mon Sep 17 00:00:00 2001 From: Edouard Marquez Date: Sat, 9 Dec 2023 02:52:18 +0100 Subject: [PATCH 4/8] Fix typo --- packages/smooth_app/lib/widgets/smooth_text.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/smooth_app/lib/widgets/smooth_text.dart b/packages/smooth_app/lib/widgets/smooth_text.dart index 78b3ee63bbb..1a95eb8b59d 100644 --- a/packages/smooth_app/lib/widgets/smooth_text.dart +++ b/packages/smooth_app/lib/widgets/smooth_text.dart @@ -20,7 +20,7 @@ extension StringExtension on String { } /// Please use this method instead of directly calling the library. - /// It will use the migration if we decide to remove/change it. + /// It will ease the migration if we decide to remove/change it. String removeDiacritics() { return lib.removeDiacritics(this); } From daecba15c3c356733ea814e934780a03f4924de5 Mon Sep 17 00:00:00 2001 From: Edouard Marquez Date: Sat, 9 Dec 2023 02:54:21 +0100 Subject: [PATCH 5/8] Unnecessary brackets --- packages/smooth_app/lib/widgets/smooth_text.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/smooth_app/lib/widgets/smooth_text.dart b/packages/smooth_app/lib/widgets/smooth_text.dart index 1a95eb8b59d..262cbb27cef 100644 --- a/packages/smooth_app/lib/widgets/smooth_text.dart +++ b/packages/smooth_app/lib/widgets/smooth_text.dart @@ -188,7 +188,7 @@ class TextHighlighter extends StatelessWidget { int diff = 0; for (int pos = 0; pos < endPosition; pos++) { if (pos == subText.length - 1) { - diff = pos - (subText.length); + diff = pos - subText.length; break; } From 23caf4fb142ea4dc0223700939730531db1af9b7 Mon Sep 17 00:00:00 2001 From: Edouard Marquez Date: Mon, 11 Dec 2023 14:17:29 +0100 Subject: [PATCH 6/8] Replace all `toLowerCase` + `removeDiacritics` by a single `getComparisonSafeString` --- .../lib/generic_lib/widgets/language_selector.dart | 8 +++----- .../lib/pages/onboarding/country_selector.dart | 8 +++----- .../preferences/user_preferences_search_page.dart | 4 ++-- .../product/nutrition_add_nutrient_button.dart | 6 +++--- packages/smooth_app/lib/widgets/smooth_text.dart | 14 ++++++++++---- 5 files changed, 21 insertions(+), 19 deletions(-) diff --git a/packages/smooth_app/lib/generic_lib/widgets/language_selector.dart b/packages/smooth_app/lib/generic_lib/widgets/language_selector.dart index bf9f2d97ed9..3239402d9f8 100644 --- a/packages/smooth_app/lib/generic_lib/widgets/language_selector.dart +++ b/packages/smooth_app/lib/generic_lib/widgets/language_selector.dart @@ -145,7 +145,7 @@ class LanguageSelector extends StatelessWidget { prefixIcon: const Icon(Icons.search), controller: languageSelectorController, onChanged: (String? query) { - query = query!.trim().toLowerCase().removeDiacritics(); + query = query!.trim().getComparisonSafeString(); setState( () { @@ -153,13 +153,11 @@ class LanguageSelector extends StatelessWidget { .where((OpenFoodFactsLanguage item) => _languages .getNameInEnglish(item) - .toLowerCase() - .removeDiacritics() + .getComparisonSafeString() .contains(query!.toLowerCase()) || _languages .getNameInLanguage(item) - .toLowerCase() - .removeDiacritics() + .getComparisonSafeString() .contains(query.toLowerCase()) || item.code.contains(query)) .toList(); diff --git a/packages/smooth_app/lib/pages/onboarding/country_selector.dart b/packages/smooth_app/lib/pages/onboarding/country_selector.dart index da5c4699860..a64ca7c52c9 100644 --- a/packages/smooth_app/lib/pages/onboarding/country_selector.dart +++ b/packages/smooth_app/lib/pages/onboarding/country_selector.dart @@ -104,7 +104,7 @@ class _CountrySelectorState extends State { controller: _countryController, onChanged: (String? query) { query = - query!.trim().toLowerCase().removeDiacritics(); + query!.trim()..getComparisonSafeString(); setState( () { @@ -112,14 +112,12 @@ class _CountrySelectorState extends State { .where( (Country item) => item.name - .toLowerCase() - .removeDiacritics() + .getComparisonSafeString() .contains( query!, ) || item.countryCode - .toLowerCase() - .removeDiacritics() + .getComparisonSafeString() .contains( query, ), diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_search_page.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_search_page.dart index 4bd879f7ad4..82e908eb516 100644 --- a/packages/smooth_app/lib/pages/preferences/user_preferences_search_page.dart +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_search_page.dart @@ -70,7 +70,7 @@ class _UserPreferencesSearchPageState extends State { final String searchString, final UserPreferences userPreferences, ) { - final String needle = searchString.toLowerCase().removeDiacritics(); + final String needle = searchString.getComparisonSafeString(); final List result = []; final List types = PreferencePageType.getPreferencePageTypes(userPreferences); @@ -101,7 +101,7 @@ class _UserPreferencesSearchPageState extends State { bool _findLabels(final String needle, final Iterable labels) { for (final String label in labels) { - if (label.toLowerCase().removeDiacritics().contains(needle)) { + if (label.getComparisonSafeString().contains(needle)) { return true; } } diff --git a/packages/smooth_app/lib/pages/product/nutrition_add_nutrient_button.dart b/packages/smooth_app/lib/pages/product/nutrition_add_nutrient_button.dart index 21fd7e56e77..1234051f5cf 100644 --- a/packages/smooth_app/lib/pages/product/nutrition_add_nutrient_button.dart +++ b/packages/smooth_app/lib/pages/product/nutrition_add_nutrient_button.dart @@ -56,10 +56,10 @@ class NutritionAddNutrientButton extends StatelessWidget { () => filteredList = leftovers .where( (OrderedNutrient item) => item.name! - .removeDiacritics() - .toLowerCase() + .trim() + .getComparisonSafeString() .contains( - query!.removeDiacritics().toLowerCase().trim(), + query!.trim().getComparisonSafeString(), ), ) .toList(), diff --git a/packages/smooth_app/lib/widgets/smooth_text.dart b/packages/smooth_app/lib/widgets/smooth_text.dart index 262cbb27cef..199d47cf083 100644 --- a/packages/smooth_app/lib/widgets/smooth_text.dart +++ b/packages/smooth_app/lib/widgets/smooth_text.dart @@ -24,6 +24,12 @@ extension StringExtension on String { String removeDiacritics() { return lib.removeDiacritics(this); } + + /// Same as [removeDiacritics] but also lowercases the string. + /// Prefer this method when you want to compare two strings. + String getComparisonSafeString() { + return toLowerCase().removeDiacritics(); + } } /// An extension on [TextStyle] that allows to have "well spaced" variant @@ -126,12 +132,12 @@ class TextHighlighter extends StatelessWidget { required TextStyle? defaultStyle, required TextStyle? highlightedStyle, }) { - final String filterWithoutDiacritics = filter.removeDiacritics(); - final String textWithoutDiacritics = text.removeDiacritics(); + final String filterWithoutDiacritics = filter.getComparisonSafeString(); + final String textWithoutDiacritics = text.getComparisonSafeString(); final Iterable highlightedParts = - RegExp(filterWithoutDiacritics.toLowerCase().trim()).allMatches( - textWithoutDiacritics.toLowerCase(), + RegExp(filterWithoutDiacritics.trim()).allMatches( + textWithoutDiacritics, ); final List<(String, TextStyle?)> parts = <(String, TextStyle?)>[]; From d29e34e7fe17ea3ad7ef2f47c36efdd80c16b0f9 Mon Sep 17 00:00:00 2001 From: Edouard Marquez Date: Mon, 11 Dec 2023 14:23:21 +0100 Subject: [PATCH 7/8] Reformat CountrySelector --- packages/smooth_app/lib/pages/onboarding/country_selector.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/smooth_app/lib/pages/onboarding/country_selector.dart b/packages/smooth_app/lib/pages/onboarding/country_selector.dart index a64ca7c52c9..c49845a1be5 100644 --- a/packages/smooth_app/lib/pages/onboarding/country_selector.dart +++ b/packages/smooth_app/lib/pages/onboarding/country_selector.dart @@ -103,8 +103,7 @@ class _CountrySelectorState extends State { prefixIcon: const Icon(Icons.search), controller: _countryController, onChanged: (String? query) { - query = - query!.trim()..getComparisonSafeString(); + query = query!.trim()..getComparisonSafeString(); setState( () { From b963ca240d030ffd9f698b9b9eb4ab1468cfd720 Mon Sep 17 00:00:00 2001 From: Edouard Marquez Date: Tue, 12 Dec 2023 11:23:03 +0100 Subject: [PATCH 8/8] `removeAccents` removed + 1 new test --- packages/smooth_app/lib/widgets/smooth_text.dart | 15 --------------- .../smooth_app/test/utils/smooth_text_test.dart | 12 ++++++------ 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/packages/smooth_app/lib/widgets/smooth_text.dart b/packages/smooth_app/lib/widgets/smooth_text.dart index 199d47cf083..6447bf15e7b 100644 --- a/packages/smooth_app/lib/widgets/smooth_text.dart +++ b/packages/smooth_app/lib/widgets/smooth_text.dart @@ -4,21 +4,6 @@ import 'package:smooth_app/services/smooth_services.dart'; /// An extension on [String] extension StringExtension on String { - /// Simple algorithm to only remove accents AND not diacritics. - String removeAccents() { - const String withAccents = - 'ÀÁÂÃÄÅàáâãäåÒÓÔÕÕÖØòóôõöøÈÉÊËèéêëðÇçÐÌÍÎÏìíîïÙÚÛÜùúûüÑñŠšŸÿýŽž'; - const String withoutAccents = - 'AAAAAAaaaaaaOOOOOOOooooooEEEEeeeeeCcDIIIIiiiiUUUUuuuuNnSsYyyZz'; - - String str = this; - for (int i = 0; i < withAccents.length; i++) { - str = str.replaceAll(withAccents[i], withoutAccents[i]); - } - - return str; - } - /// Please use this method instead of directly calling the library. /// It will ease the migration if we decide to remove/change it. String removeDiacritics() { diff --git a/packages/smooth_app/test/utils/smooth_text_test.dart b/packages/smooth_app/test/utils/smooth_text_test.dart index 9a4004af6d1..1f7ed724b63 100644 --- a/packages/smooth_app/test/utils/smooth_text_test.dart +++ b/packages/smooth_app/test/utils/smooth_text_test.dart @@ -4,16 +4,16 @@ import 'package:smooth_app/widgets/smooth_text.dart' show StringExtension; void main() { group('Smooth text', () { group('String extension', () { - test('Remove accents', () { + test('Remove diacritics (oeuf)', () { expect( - 'àáâãäåéèêëòóôõöìíîïùúûüñšÿýž'.removeAccents(), - equals('aaaaaaeeeeoooooiiiiuuuunsyyz'), + 'œuf'.removeDiacritics(), + equals('oeuf'), ); }); - test('Remove diacritics', () { + test('Comparison Safe String', () { expect( - 'àáâãäåéèêëòóôõöìíîïùúûüñšÿýž'.removeDiacritics(), - equals('aaaaaaeeeeoooooiiiiuuuunsyyz'), + 'œuF'.getComparisonSafeString(), + equals('oeuf'), ); }); });