Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 4996 - reordering product languages #5025

Merged
merged 1 commit into from
Feb 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/smooth_app/lib/database/dao_string_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,15 @@ class DaoStringList extends AbstractDao {
/// Key for the list of task ids.
static const String keyTasks = 'tasks';

/// Key for the list of latest languages used in the app.
static const String keyLanguages = 'languages';

/// Max lengths of each key (null means no limit).
static const Map<String, int?> _maxLengths = <String, int?>{
keySearchHistory: 10,
keyTasks: null,
// TODO(monsieurtanuki): more "latest" languages are possible if we create a page to remove some of them
keyLanguages: 1,
};

@override
Expand Down
92 changes: 92 additions & 0 deletions packages/smooth_app/lib/generic_lib/widgets/language_priority.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:smooth_app/database/dao_string_list.dart';
import 'package:smooth_app/query/product_query.dart';

/// Helper around the language priority.
///
/// cf. https://github.com/openfoodfacts/smooth-app/issues/4996
class LanguagePriority {
LanguagePriority({
required final Product? product,
required final Iterable<OpenFoodFactsLanguage>? selectedLanguages,
required final DaoStringList daoStringList,
}) {
_addAll(selectedLanguages);
_addStringList(daoStringList.getAll(DaoStringList.keyLanguages));
_add(ProductQuery.getLanguage());
if (product == null) {
return;
}
_add(product.lang);
_addMap(product.productNameInLanguages);
_addImages(product.images);
_addMap(product.packagingTextInLanguages);
_addMap(product.ingredientsTextInLanguages);
_addMap(product.labelsTagsInLanguages);
_addMap(product.categoriesTagsInLanguages);
_addMap(product.countriesTagsInLanguages);
}

final List<OpenFoodFactsLanguage> _languages = <OpenFoodFactsLanguage>[];

void _addMap(final Map<OpenFoodFactsLanguage, dynamic>? map) {
if (map == null) {
return;
}
_addAll(map.keys);
}

void _addImages(final Iterable<ProductImage>? productImages) {
if (productImages == null) {
return;
}
for (final ProductImage productImage in productImages) {
_add(productImage.language);
}
}

void _add(final OpenFoodFactsLanguage? language) {
if (language == null) {
return;
}
if (_languages.contains(language)) {
return;
}
_languages.add(language);
}

void _addString(final String languageCode) {
try {
final OpenFoodFactsLanguage? language =
OpenFoodFactsLanguage.fromOffTag(languageCode);
_add(language);
} catch (e) {
// just ignore
}
}

void _addStringList(final List<String> languageCodes) =>
languageCodes.forEach(_addString);

void _addAll(final Iterable<OpenFoodFactsLanguage>? languages) {
if (languages == null) {
return;
}
languages.forEach(_add);
}

int? compare(final OpenFoodFactsLanguage a, final OpenFoodFactsLanguage b) {
final bool selectedA = _languages.contains(a);
final bool selectedB = _languages.contains(b);
if (selectedA) {
if (!selectedB) {
return -1;
}
} else {
if (selectedB) {
return 1;
}
}
return null;
}
}
61 changes: 38 additions & 23 deletions packages/smooth_app/lib/generic_lib/widgets/language_selector.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:provider/provider.dart';
import 'package:smooth_app/database/dao_string_list.dart';
import 'package:smooth_app/database/local_database.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/generic_lib/widgets/language_priority.dart';
import 'package:smooth_app/generic_lib/widgets/smooth_text_form_field.dart';
import 'package:smooth_app/pages/preferences/user_preferences_languages_list.dart';
import 'package:smooth_app/query/product_query.dart';
Expand All @@ -16,6 +20,7 @@ class LanguageSelector extends StatelessWidget {
this.foregroundColor,
this.icon,
this.padding,
this.product,
});

/// What to do when the language is selected.
Expand All @@ -31,6 +36,9 @@ class LanguageSelector extends StatelessWidget {
final IconData? icon;
final EdgeInsetsGeometry? padding;

/// Product from which we can extract the languages that matter.
final Product? product;

static const Languages _languages = Languages();

@override
Expand All @@ -42,16 +50,28 @@ class LanguageSelector extends StatelessWidget {
final String currentLanguageCode = ProductQuery.getLanguage().code;
language = LanguageHelper.fromJson(currentLanguageCode);
}
final String nameInEnglish = _languages.getNameInEnglish(language);
final String nameInLanguage = _languages.getNameInLanguage(language);
final DaoStringList daoStringList =
DaoStringList(context.read<LocalDatabase>());
final LanguagePriority languagePriority = LanguagePriority(
product: product,
selectedLanguages: selectedLanguages,
daoStringList: daoStringList,
);
return Material(
type: MaterialType.transparency,
child: InkWell(
onTap: () async {
final OpenFoodFactsLanguage? language = await openLanguageSelector(
final OpenFoodFactsLanguage? language = await _openLanguageSelector(
context,
selectedLanguages: selectedLanguages,
languagePriority: languagePriority,
);
if (language != null) {
await daoStringList.add(
DaoStringList.keyLanguages,
language.offTag,
);
}
await setLanguage(language);
},
borderRadius: ANGULAR_BORDER_RADIUS,
Expand All @@ -72,7 +92,7 @@ class LanguageSelector extends StatelessWidget {
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: LARGE_SPACE),
child: Text(
'$nameInLanguage ($nameInEnglish)',
_getCompleteName(language),
softWrap: false,
overflow: TextOverflow.fade,
style: Theme.of(context)
Expand All @@ -97,9 +117,10 @@ class LanguageSelector extends StatelessWidget {
/// Returns the language selected by the user.
///
/// [selectedLanguages] have a specific "more important" display.
static Future<OpenFoodFactsLanguage?> openLanguageSelector(
Future<OpenFoodFactsLanguage?> _openLanguageSelector(
final BuildContext context, {
final Iterable<OpenFoodFactsLanguage>? selectedLanguages,
required final LanguagePriority languagePriority,
}) async {
final ScrollController scrollController = ScrollController();
final AppLocalizations appLocalizations = AppLocalizations.of(context);
Expand All @@ -109,19 +130,9 @@ class LanguageSelector extends StatelessWidget {
_languages.getSupportedLanguagesNameInEnglish();
leftovers.sort(
(OpenFoodFactsLanguage a, OpenFoodFactsLanguage b) {
// Selected languages first.
final bool selectedA =
selectedLanguages != null && selectedLanguages.contains(a);
final bool selectedB =
selectedLanguages != null && selectedLanguages.contains(b);
if (selectedA) {
if (!selectedB) {
return -1;
}
} else {
if (selectedB) {
return 1;
}
final int? compare = languagePriority.compare(a, b);
if (compare != null) {
return compare;
}
// Sorted in English
return _languages
Expand Down Expand Up @@ -170,17 +181,13 @@ class LanguageSelector extends StatelessWidget {
controller: scrollController,
itemBuilder: (BuildContext context, int index) {
final OpenFoodFactsLanguage language = filteredList[index];
final String nameInLanguage =
_languages.getNameInLanguage(language);
final String nameInEnglish =
_languages.getNameInEnglish(language);
final bool selected = selectedLanguages != null &&
selectedLanguages.contains(language);
return ListTile(
dense: true,
trailing: selected ? const Icon(Icons.check) : null,
title: TextHighlighter(
text: '$nameInLanguage ($nameInEnglish)',
text: _getCompleteName(language),
filter: languageSelectorController.text,
selected: selected,
),
Expand All @@ -204,4 +211,12 @@ class LanguageSelector extends StatelessWidget {
),
);
}

String _getCompleteName(
final OpenFoodFactsLanguage language,
) {
final String nameInLanguage = _languages.getNameInLanguage(language);
final String nameInEnglish = _languages.getNameInEnglish(language);
return '$nameInLanguage ($nameInEnglish)';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,10 @@ class _AddBasicDetailsPageState extends State<AddBasicDetailsPage> {
Card(
child: Column(
children: <Widget>[
_multilingualHelper
.getLanguageSelector(setState),
_multilingualHelper.getLanguageSelector(
setState: setState,
product: _product,
),
Padding(
padding: const EdgeInsets.all(8.0),
child: SmoothTextFormField(
Expand Down
5 changes: 4 additions & 1 deletion packages/smooth_app/lib/pages/product/edit_ocr_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,10 @@ class _EditOcrPageState extends State<EditOcrPage> with UpToDateMixin {
child: Column(
children: <Widget>[
if (!_multilingualHelper.isMonolingual())
_multilingualHelper.getLanguageSelector(setState),
_multilingualHelper.getLanguageSelector(
setState: setState,
product: upToDateProduct,
),
if (transientFile.isServerImage())
SmoothActionButtonsBar.single(
action: SmoothActionButton(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,12 @@ class MultilingualHelper {
// TODO(monsieurtanuki): we would be better off always never monolingual
bool isMonolingual() => _initialMultilingualTexts.isEmpty;

Widget getLanguageSelector(void Function(void Function()) setState) =>
Widget getLanguageSelector({
required void Function(void Function()) setState,
required Product product,
}) =>
LanguageSelector(
product: product,
setLanguage: (
final OpenFoodFactsLanguage? newLanguage,
) async {
Expand Down
Loading