Files
smooth-app/packages/smooth_app/lib/knowledge_panel/knowledge_panels_builder.dart
monsieurtanuki 60971d1344 feat: 4628 - new "reorder KP" feature from dev mode (#4778)
New files:
* `reorderable_knowledge_panel_page.dart`: Page where the user can reorder the Knowledge Panel Cards.
* `reordered_knowledge_panel_cards.dart`: Knowledge Panel Cards as reordered by the user.
* `standard_knowledge_panel_page.dart`: Knowledge Panel Cards as provided by the server.

Impacted files:
* `app_en.arb`: added a "reorder the attributes" label
* `knowledge_panel_card.dart`: added an `isClickable` parameter to be get rid of the click icon+effect when reordering attributes
* `knowledge_panel_expanded_card.dart`: added an `isClickable` parameter
* `knowledge_panel_group_card.dart`: added an `isClickable` parameter
* `knowledge_panel_page.dart`: explicitly setting `isClickable: true`
* `knowledge_panels_builder.dart`: added an `isClickable` parameter
* `new_product_page.dart`: now using a dev-mode flag to determine if we display the reorder feature; moved code to new class `StandardKnowledgePanelCards`
* `pubspec.lock`: wtf
* `score_card.dart`: unrelated minor refactoring
* `user_preferences.dart`: added the user ordered attribute list as preference
* `user_preferences_dev_mode.dart`: added a flag for user reordered attribute feature
2023-12-08 16:25:44 +01:00

278 lines
9.2 KiB
Dart

import 'package:flutter/material.dart';
import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:provider/provider.dart';
import 'package:smooth_app/cards/data_cards/score_card.dart';
import 'package:smooth_app/data_models/preferences/user_preferences.dart';
import 'package:smooth_app/generic_lib/design_constants.dart';
import 'package:smooth_app/knowledge_panel/knowledge_panels/knowledge_panel_action_card.dart';
import 'package:smooth_app/knowledge_panel/knowledge_panels/knowledge_panel_card.dart';
import 'package:smooth_app/knowledge_panel/knowledge_panels/knowledge_panel_group_card.dart';
import 'package:smooth_app/knowledge_panel/knowledge_panels/knowledge_panel_image_card.dart';
import 'package:smooth_app/knowledge_panel/knowledge_panels/knowledge_panel_table_card.dart';
import 'package:smooth_app/knowledge_panel/knowledge_panels/knowledge_panel_text_card.dart';
import 'package:smooth_app/knowledge_panel/knowledge_panels/knowledge_panel_title_card.dart';
import 'package:smooth_app/knowledge_panel/knowledge_panels/knowledge_panel_world_map_card.dart';
import 'package:smooth_app/pages/preferences/user_preferences_dev_mode.dart';
import 'package:smooth_app/pages/product/add_nutrition_button.dart';
import 'package:smooth_app/pages/product/add_ocr_button.dart';
import 'package:smooth_app/pages/product/product_field_editor.dart';
import 'package:smooth_app/services/smooth_services.dart';
/// "Knowledge Panel" builder
class KnowledgePanelsBuilder {
const KnowledgePanelsBuilder._();
static List<Widget> getChildren(
BuildContext context, {
required KnowledgePanelElement panelElement,
required Product product,
required bool onboardingMode,
}) {
final String? panelId = panelElement.panelElement?.panelId;
final KnowledgePanel? rootPanel =
panelId == null ? null : getKnowledgePanel(product, panelId);
final List<Widget> children = <Widget>[];
if (rootPanel != null) {
children.add(
Padding(
padding: const EdgeInsets.symmetric(
vertical: VERY_SMALL_SPACE,
horizontal: SMALL_SPACE,
),
child: Text(
rootPanel.titleElement!.title,
style: Theme.of(context).textTheme.displaySmall,
),
),
);
if (rootPanel.elements != null) {
for (final KnowledgePanelElement element in rootPanel.elements!) {
final Widget? widget = getElementWidget(
knowledgePanelElement: element,
product: product,
isInitiallyExpanded: false,
isClickable: true,
);
if (widget != null) {
children.add(widget);
}
}
}
}
if (!onboardingMode) {
if (panelId == 'health_card') {
final bool nutritionAddOrUpdate = product.statesTags?.contains(
ProductState.NUTRITION_FACTS_COMPLETED.toBeCompletedTag) ??
false;
if (nutritionAddOrUpdate) {
children.add(AddNutritionButton(product));
}
final bool needEditIngredients = context
.read<UserPreferences>()
.getFlag(UserPreferencesDevMode
.userPreferencesFlagEditIngredients) ??
false;
if ((product.ingredientsText == null ||
product.ingredientsText!.isEmpty) &&
needEditIngredients) {
// When the flag is removed, this should be the following:
// if (product.statesTags?.contains('en:ingredients-to-be-completed') ?? false) {
children.add(
AddOcrButton(
product: product,
editor: ProductFieldOcrIngredientEditor(),
),
);
}
}
}
if (children.isEmpty) {
Logs.e(
'Unexpected empty panel data for product "${product.barcode}" and panelId "$panelId"');
}
return children;
}
/// Returns all the panel elements from "root".
///
/// Typically, we get only the "health_card" and "environment_card" panels.
/// In option, only the one matching [panelId].
static List<KnowledgePanelElement> getRootPanelElements(
final Product product, {
final String? panelId,
}) {
final List<KnowledgePanelElement> result = <KnowledgePanelElement>[];
final KnowledgePanel? root = getKnowledgePanel(product, 'root');
if (root == null) {
return result;
}
if (root.elements == null) {
return result;
}
for (final KnowledgePanelElement panelElement in root.elements!) {
if (panelElement.elementType != KnowledgePanelElementType.PANEL) {
continue;
}
// no filter
if (panelId == null) {
result.add(panelElement);
} else {
if (panelId == panelElement.panelElement!.panelId) {
result.add(panelElement);
return result;
}
}
}
return result;
}
/// Returns the KP that matches the [panelId].
static KnowledgePanel? getKnowledgePanel(
final Product product,
final String panelId,
) =>
product.knowledgePanels?.panelIdToPanelMap[panelId];
/// Returns the unique "root" panel element that matches [panelId], or `null`.
static KnowledgePanelElement? getRootPanelElement(
final Product product,
final String panelId,
) {
final List<KnowledgePanelElement> elements = getRootPanelElements(
product,
panelId: panelId,
);
if (elements.length != 1) {
return null;
}
return elements.first;
}
/// Returns a padded widget that displays the KP element, or rarely null.
static Widget? getElementWidget({
required final KnowledgePanelElement knowledgePanelElement,
required final Product product,
required final bool isInitiallyExpanded,
required final bool isClickable,
}) {
final Widget? result = _getElementWidget(
element: knowledgePanelElement,
product: product,
isInitiallyExpanded: isInitiallyExpanded,
isClickable: isClickable,
);
if (result == null) {
return null;
}
if (<KnowledgePanelElementType>[
KnowledgePanelElementType.PANEL,
KnowledgePanelElementType.PANEL_GROUP,
].contains(knowledgePanelElement.elementType)) {
return result;
}
return Padding(
padding: const EdgeInsets.symmetric(horizontal: SMALL_SPACE),
child: result,
);
}
/// Returns the widget that displays the KP element, or rarely null.
static Widget? _getElementWidget({
required final KnowledgePanelElement element,
required final Product product,
required final bool isInitiallyExpanded,
required final bool isClickable,
}) {
switch (element.elementType) {
case KnowledgePanelElementType.TEXT:
return KnowledgePanelTextCard(
textElement: element.textElement!,
);
case KnowledgePanelElementType.IMAGE:
return KnowledgePanelImageCard(
imageElement: element.imageElement!,
);
case KnowledgePanelElementType.PANEL:
final String panelId = element.panelElement!.panelId;
final KnowledgePanel? panel = getKnowledgePanel(product, panelId);
if (panel == null) {
// happened in https://github.com/openfoodfacts/smooth-app/issues/2682
// due to some inconsistencies in the data sent by the server
Logs.w(
'unknown panel "$panelId" for barcode "${product.barcode}"',
);
return null;
}
return KnowledgePanelCard(
panelId: panelId,
product: product,
isClickable: isClickable,
);
case KnowledgePanelElementType.PANEL_GROUP:
return KnowledgePanelGroupCard(
groupElement: element.panelGroupElement!,
product: product,
isClickable: isClickable,
);
case KnowledgePanelElementType.TABLE:
return KnowledgePanelTableCard(
tableElement: element.tableElement!,
isInitiallyExpanded: isInitiallyExpanded,
product: product,
);
case KnowledgePanelElementType.MAP:
return KnowledgePanelWorldMapCard(element.mapElement!);
case KnowledgePanelElementType.UNKNOWN:
return null;
case KnowledgePanelElementType.ACTION:
return KnowledgePanelActionCard(
element.actionElement!,
product,
);
default:
Logs.e('unexpected element type: ${element.elementType}');
return null;
}
}
/// Title card of a knowledge panel, like a one-line score widget, or title.
static Widget? getPanelSummaryWidget(
final KnowledgePanel knowledgePanel, {
required final bool isClickable,
final EdgeInsets? margin,
}) {
if (knowledgePanel.titleElement == null) {
return null;
}
switch (knowledgePanel.titleElement!.type) {
case TitleElementType.GRADE:
return ScoreCard.titleElement(
titleElement: knowledgePanel.titleElement!,
isClickable: isClickable,
margin: margin,
);
case null:
case TitleElementType.UNKNOWN:
return Padding(
padding: const EdgeInsets.symmetric(horizontal: SMALL_SPACE),
child: KnowledgePanelTitleCard(
knowledgePanelTitleElement: knowledgePanel.titleElement!,
evaluation: knowledgePanel.evaluation,
isClickable: isClickable,
),
);
}
}
}