-
-
Notifications
You must be signed in to change notification settings - Fork 282
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Create a screen listing all attributes for a product (#4714)
* feat: Create a screen listing all attributes for a product - resolves: #4673 - I have just added a simple button in edit product page just to test the new page. It will be removed and added in the paticular screen. - taking `edit_product_page.dart` as a base `product_attribute_page.dart` is designed as suggested by teolemon. - Added all the mentioned section field in the issue. * refactor: chnages done according to the feedback - created seperte file for `svg_icon.dar` - created newFile for `attribute_first_row_widget.dart` - removed refreshIndicator - useed stringbuffer for string concatenation - fix: linting errors * fix: move nutrients extraction to init state - to avoid recalculation on every build * remove usage of knowledgepanel, use product.nutriments * move ingredient extraction to initstate * helper abstract class for attribute_first_row_widget * remove ui file and abstract ontap function * tiny fix * change ontap function to future<void> * Update packages/smooth_app/lib/pages/product/attribute_first_row_helper.dart --------- Co-authored-by: monsieurtanuki <[email protected]>
- Loading branch information
1 parent
4ab2150
commit 21469af
Showing
5 changed files
with
354 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import 'dart:ui' as ui; | ||
|
||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_svg/flutter_svg.dart'; | ||
import 'package:smooth_app/generic_lib/design_constants.dart'; | ||
import 'package:smooth_app/helpers/app_helper.dart'; | ||
|
||
/// SVG that looks like a ListTile icon. | ||
class SvgIcon extends StatelessWidget { | ||
const SvgIcon(this.assetName, {this.dontAddColor = false}); | ||
|
||
final String assetName; | ||
final bool dontAddColor; | ||
|
||
@override | ||
Widget build(BuildContext context) => SvgPicture.asset( | ||
assetName, | ||
height: DEFAULT_ICON_SIZE, | ||
width: DEFAULT_ICON_SIZE, | ||
colorFilter: dontAddColor | ||
? null | ||
: ui.ColorFilter.mode( | ||
_iconColor(Theme.of(context)), | ||
ui.BlendMode.srcIn, | ||
), | ||
package: AppHelper.APP_PACKAGE, | ||
); | ||
|
||
/// Returns the standard icon color in a [ListTile]. | ||
/// | ||
/// Simplified version from [ListTile], which was anyway not kind enough | ||
/// to make it public. | ||
Color _iconColor(ThemeData theme) { | ||
switch (theme.brightness) { | ||
case Brightness.light: | ||
return Colors.black45; | ||
case Brightness.dark: | ||
return Colors.white; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
193 changes: 193 additions & 0 deletions
193
packages/smooth_app/lib/pages/product/attribute_first_row_helper.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||
import 'package:openfoodfacts/openfoodfacts.dart'; | ||
import 'package:smooth_app/generic_lib/widgets/svg_icon.dart'; | ||
import 'package:smooth_app/helpers/analytics_helper.dart'; | ||
import 'package:smooth_app/pages/product/common/product_refresher.dart'; | ||
import 'package:smooth_app/pages/product/nutrition_page_loaded.dart'; | ||
import 'package:smooth_app/pages/product/product_field_editor.dart'; | ||
import 'package:smooth_app/pages/product/simple_input_page_helpers.dart'; | ||
|
||
class StringPair { | ||
const StringPair({ | ||
required this.first, | ||
this.second, | ||
}); | ||
|
||
final String first; | ||
final String? second; | ||
} | ||
|
||
abstract class AttributeFirstRowHelper { | ||
List<StringPair> getAllTerms(); | ||
|
||
Widget? getLeadingIcon(); | ||
|
||
String getTitle(BuildContext context); | ||
|
||
Future<void> onTap({ | ||
required BuildContext context, | ||
}); | ||
} | ||
|
||
class AttributeFirstRowSimpleHelper extends AttributeFirstRowHelper { | ||
AttributeFirstRowSimpleHelper({ | ||
required this.helper, | ||
}); | ||
|
||
final AbstractSimpleInputPageHelper helper; | ||
|
||
@override | ||
List<StringPair> getAllTerms() { | ||
final List<StringPair> allTerms = <StringPair>[]; | ||
|
||
for (final String element in helper.terms) { | ||
allTerms.add( | ||
StringPair( | ||
first: element, | ||
), | ||
); | ||
} | ||
|
||
return allTerms; | ||
} | ||
|
||
@override | ||
Widget? getLeadingIcon() { | ||
return helper.getIcon(); | ||
} | ||
|
||
@override | ||
String getTitle(BuildContext context) { | ||
final AppLocalizations appLocalizations = AppLocalizations.of(context); | ||
return helper.getTitle( | ||
appLocalizations, | ||
); | ||
} | ||
|
||
@override | ||
Future<void> onTap({ | ||
required BuildContext context, | ||
}) { | ||
return ProductFieldSimpleEditor(helper).edit( | ||
context: context, | ||
product: helper.product, | ||
); | ||
} | ||
} | ||
|
||
class AttributeFirstRowNutritionHelper extends AttributeFirstRowHelper { | ||
AttributeFirstRowNutritionHelper({ | ||
required this.product, | ||
}); | ||
|
||
final Product product; | ||
|
||
@override | ||
List<StringPair> getAllTerms() { | ||
final List<StringPair> allNutrients = <StringPair>[]; | ||
product.nutriments?.toData().forEach( | ||
(String nutrientName, String quantity) { | ||
allNutrients.add( | ||
StringPair( | ||
first: nutrientName.split('_100g')[0], | ||
second: quantity, | ||
), | ||
); | ||
}, | ||
); | ||
|
||
return allNutrients; | ||
} | ||
|
||
@override | ||
Widget? getLeadingIcon() { | ||
return const SvgIcon( | ||
'assets/cacheTintable/scale-balance.svg', | ||
dontAddColor: true, | ||
); | ||
} | ||
|
||
@override | ||
String getTitle(BuildContext context) { | ||
final AppLocalizations appLocalizations = AppLocalizations.of(context); | ||
return appLocalizations.nutrition_page_title; | ||
} | ||
|
||
@override | ||
Future<void> onTap({ | ||
required BuildContext context, | ||
}) async { | ||
if (!await ProductRefresher().checkIfLoggedIn( | ||
context, | ||
isLoggedInMandatory: true, | ||
)) { | ||
return; | ||
} | ||
|
||
AnalyticsHelper.trackProductEdit( | ||
AnalyticsEditEvents.nutrition_Facts, | ||
product.barcode!, | ||
); | ||
|
||
if (!context.mounted) { | ||
return; | ||
} | ||
|
||
await NutritionPageLoaded.showNutritionPage( | ||
product: product, | ||
isLoggedInMandatory: true, | ||
context: context, | ||
); | ||
} | ||
} | ||
|
||
class AttributeFirstRowIngredientsHelper extends AttributeFirstRowHelper { | ||
AttributeFirstRowIngredientsHelper({ | ||
required this.product, | ||
}); | ||
|
||
final Product product; | ||
|
||
@override | ||
List<StringPair> getAllTerms() { | ||
final List<StringPair> allIngredients = <StringPair>[]; | ||
product.ingredients?.forEach( | ||
(Ingredient element) { | ||
if (element.text != null) { | ||
allIngredients.add( | ||
StringPair( | ||
first: element.text!, | ||
), | ||
); | ||
} | ||
}, | ||
); | ||
|
||
return allIngredients; | ||
} | ||
|
||
@override | ||
Widget? getLeadingIcon() { | ||
return const SvgIcon( | ||
'assets/cacheTintable/ingredients.svg', | ||
dontAddColor: true, | ||
); | ||
} | ||
|
||
@override | ||
String getTitle(BuildContext context) { | ||
final AppLocalizations appLocalizations = AppLocalizations.of(context); | ||
return appLocalizations.ingredients; | ||
} | ||
|
||
@override | ||
Future<void> onTap({ | ||
required BuildContext context, | ||
}) { | ||
return ProductFieldOcrIngredientEditor().edit( | ||
context: context, | ||
product: product, | ||
); | ||
} | ||
} |
112 changes: 112 additions & 0 deletions
112
packages/smooth_app/lib/pages/product/attribute_first_row_widget.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||
import 'package:smooth_app/pages/product/attribute_first_row_helper.dart'; | ||
|
||
class AttributeFirstRowWidget extends StatefulWidget { | ||
const AttributeFirstRowWidget({ | ||
required this.helper, | ||
}); | ||
|
||
final AttributeFirstRowHelper helper; | ||
|
||
@override | ||
State<AttributeFirstRowWidget> createState() => | ||
_AttributeFirstRowWidgetState(); | ||
} | ||
|
||
class _AttributeFirstRowWidgetState extends State<AttributeFirstRowWidget> { | ||
bool _showAllTerms = false; | ||
late final List<StringPair> allTerms; | ||
|
||
@override | ||
void initState() { | ||
super.initState(); | ||
allTerms = widget.helper.getAllTerms(); | ||
} | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
final AppLocalizations appLocalizations = AppLocalizations.of(context); | ||
final ThemeData theme = Theme.of(context); | ||
const int numberThreshold = 4; | ||
final bool hasManyTerms = allTerms.length > numberThreshold; | ||
final List<StringPair> firstTerms = allTerms | ||
.take( | ||
numberThreshold, | ||
) | ||
.toList(); | ||
|
||
if (firstTerms.isEmpty) { | ||
firstTerms.add( | ||
StringPair(first: appLocalizations.no_data_available), | ||
); | ||
} | ||
return Column( | ||
children: <Widget>[ | ||
ListTile( | ||
leading: widget.helper.getLeadingIcon(), | ||
title: Text( | ||
widget.helper.getTitle(context), | ||
), | ||
trailing: const Icon( | ||
Icons.edit, | ||
), | ||
titleTextStyle: TextStyle( | ||
fontWeight: FontWeight.w500, | ||
fontSize: 20.0, | ||
color: theme.primaryColor, | ||
), | ||
iconColor: theme.primaryColor, | ||
tileColor: theme.colorScheme.secondary, | ||
onTap: () async => widget.helper.onTap(context: context), | ||
), | ||
_termsList( | ||
_showAllTerms ? allTerms : firstTerms, | ||
borderFlag: !hasManyTerms, | ||
), | ||
if (hasManyTerms) ...<Widget>[ | ||
Padding( | ||
padding: const EdgeInsets.only(left: 100.0), | ||
child: ExpansionTile( | ||
onExpansionChanged: (bool value) => setState(() { | ||
_showAllTerms = value; | ||
}), | ||
title: const Text( | ||
'Expand', | ||
style: TextStyle( | ||
decoration: TextDecoration.underline, | ||
), | ||
), | ||
), | ||
) | ||
] | ||
], | ||
); | ||
} | ||
|
||
Widget _termsList( | ||
List<StringPair> terms, { | ||
bool borderFlag = false, | ||
}) { | ||
return ListView.builder( | ||
padding: const EdgeInsets.only(left: 100.0), | ||
itemCount: terms.length, | ||
shrinkWrap: true, | ||
itemBuilder: (_, int index) { | ||
return ListTile( | ||
title: Text( | ||
terms[index].first, | ||
style: const TextStyle(fontWeight: FontWeight.bold), | ||
), | ||
shape: (index == terms.length - 1 && borderFlag) | ||
? null | ||
: const Border( | ||
bottom: BorderSide(), | ||
), | ||
trailing: | ||
terms[index].second != null ? Text(terms[index].second!) : null, | ||
); | ||
}, | ||
); | ||
} | ||
} |
Oops, something went wrong.