Skip to content

Commit 995cb1b

Browse files
authored
feat: Folksonomy Editor (updated UI, modal sheet…) (#6194)
* Taxonomy UI * Italic hint style * Remove dead code
1 parent 23288df commit 995cb1b

File tree

10 files changed

+904
-245
lines changed

10 files changed

+904
-245
lines changed

packages/smooth_app/lib/generic_lib/bottom_sheets/smooth_bottom_sheet.dart

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Future<T?> showSmoothModalSheet<T>({
1515
required WidgetBuilder builder,
1616
double? minHeight,
1717
double? maxHeight,
18+
bool? isScrollControlled,
1819
bool? useRootNavigator,
1920
}) {
2021
BoxConstraints? constraints;
@@ -37,7 +38,7 @@ Future<T?> showSmoothModalSheet<T>({
3738

3839
return showModalBottomSheet<T>(
3940
constraints: constraints,
40-
isScrollControlled: minHeight != null,
41+
isScrollControlled: isScrollControlled ?? minHeight != null,
4142
context: context,
4243
shape: const RoundedRectangleBorder(
4344
borderRadius: BorderRadius.vertical(top: ROUNDED_RADIUS),
@@ -48,6 +49,30 @@ Future<T?> showSmoothModalSheet<T>({
4849
);
4950
}
5051

52+
Future<T?> showSmoothModalSheetForTextField<T>({
53+
required BuildContext context,
54+
required SmoothModalSheetHeader header,
55+
required WidgetBuilder bodyBuilder,
56+
}) {
57+
return showSmoothModalSheet<T>(
58+
context: context,
59+
builder: (BuildContext context) => SizedBox(
60+
width: double.infinity,
61+
child: ClipRRect(
62+
borderRadius: const BorderRadius.vertical(top: ROUNDED_RADIUS),
63+
child: Column(
64+
mainAxisSize: MainAxisSize.min,
65+
children: <Widget>[
66+
header,
67+
bodyBuilder(context),
68+
],
69+
),
70+
),
71+
),
72+
isScrollControlled: true,
73+
);
74+
}
75+
5176
Future<T?> showSmoothDraggableModalSheet<T>({
5277
required BuildContext context,
5378
required SmoothModalSheetHeader header,

packages/smooth_app/lib/l10n/app_en.arb

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -884,18 +884,31 @@
884884
"@product_tags_title": {
885885
"description": "The title for showing product properties, aka folksonomy data"
886886
},
887-
"no_product_tags_found_message": "No product properties found. Properties can be used to describe products in more details, in a flexible way. Tap to add.",
887+
"no_product_tags_found_message": "No product properties found. Properties can be used to describe products in more details, in a flexible way.",
888888
"@no_product_tags_found_message": {
889889
"description": "Message to show if there are no product properties found"
890890
},
891-
"add_tag": "Add property",
891+
"add_tag": "Add a property",
892+
"add_tags": "Add properties",
893+
"add_edit_tags": "Add or edit properties",
892894
"edit_tag": "Edit property",
893895
"remove_tag": "Remove property",
894896
"tag_key": "Property",
897+
"tag_key_uneditable": "Property (uneditable)",
898+
"tag_key_input_hint": "Input a property",
895899
"tag_value": "Value",
896-
"invalid_key_format": "Invalid property format. It must be lowercase and without any spaces.",
897-
"@invalid_key_format": {
898-
"description": "Message to show if the property's value for a new product property is invalid. This logic is identical to whats being validated in the Folksonomy API"
900+
"tag_value_input_hint": "Input a value",
901+
"tag_key_item": "Property:",
902+
"tag_value_item": "Value:",
903+
"tag_key_explanations": "A key must be lowercase and without any spaces.",
904+
"tag_key_already_exists": "A tag with a property {property} already exists!",
905+
"@tag_key_already_exists": {
906+
"placeholders": {
907+
"property": {
908+
"type": "String",
909+
"description": "The property name"
910+
}
911+
}
899912
},
900913
"product_internet_error": "Impossible to fetch information about this product due to a network error.",
901914
"cached_results_from": "Show results from:",

packages/smooth_app/lib/l10n/app_fr.arb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -884,7 +884,7 @@
884884
"@product_tags_title": {
885885
"description": "The title for showing product properties, aka folksonomy data"
886886
},
887-
"no_product_tags_found_message": "Aucune propriété de produit trouvée. Les propriétés peuvent être utilisées pour décrire les produits de manière plus détaillée et flexible. Appuyez pour ajouter.",
887+
"no_product_tags_found_message": "Aucune propriété de produit trouvée. Les propriétés peuvent être utilisées pour décrire les produits de manière plus détaillée et flexible.",
888888
"@no_product_tags_found_message": {
889889
"description": "Message to show if there are no product properties found"
890890
},

packages/smooth_app/lib/pages/folksonomy/folksonomy_card.dart

Lines changed: 155 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -8,119 +8,199 @@ import 'package:smooth_app/pages/folksonomy/folksonomy_page.dart';
88
import 'package:smooth_app/pages/folksonomy/folksonomy_provider.dart';
99
import 'package:smooth_app/pages/folksonomy/tag.dart';
1010
import 'package:smooth_app/pages/product/common/product_refresher.dart';
11-
import 'package:smooth_app/themes/constant_icons.dart';
11+
import 'package:smooth_app/resources/app_icons.dart' as icons;
1212

1313
class FolksonomyCard extends StatelessWidget {
1414
const FolksonomyCard(this.product);
15+
1516
final Product product;
1617

1718
@override
1819
Widget build(BuildContext context) {
1920
return ChangeNotifierProvider<FolksonomyProvider>(
2021
create: (_) => FolksonomyProvider(product.barcode!),
21-
child: _FolksonomyCard(product),
22+
child: Provider<Product>.value(
23+
value: product,
24+
child: const _FolksonomyCard(),
25+
),
2226
);
2327
}
2428
}
2529

2630
class _FolksonomyCard extends StatelessWidget {
27-
const _FolksonomyCard(this.product);
28-
final Product product;
31+
const _FolksonomyCard();
2932

3033
@override
3134
Widget build(BuildContext context) {
3235
final AppLocalizations appLocalizations = AppLocalizations.of(context);
3336

34-
return InkWell(
35-
onTap: () async {
36-
if (!await ProductRefresher().checkIfLoggedIn(
37-
context,
38-
isLoggedInMandatory: true,
39-
)) {
40-
return;
41-
}
42-
if (!context.mounted) {
43-
return;
44-
}
37+
return Padding(
38+
padding: const EdgeInsetsDirectional.only(
39+
top: LARGE_SPACE,
40+
start: SMALL_SPACE,
41+
end: SMALL_SPACE,
42+
),
43+
child: buildProductSmoothCard(
44+
margin: EdgeInsets.zero,
45+
padding: EdgeInsets.zero,
46+
titlePadding: const EdgeInsetsDirectional.symmetric(
47+
horizontal: SMALL_SPACE,
48+
),
49+
title: Row(
50+
children: <Widget>[
51+
Expanded(child: Text(appLocalizations.product_tags_title)),
52+
IconButton(
53+
onPressed: () => _openFolksonomyPage(
54+
context,
55+
context.read<Product>(),
56+
),
57+
icon: Consumer<FolksonomyProvider>(
58+
builder: (
59+
BuildContext context,
60+
FolksonomyProvider provider,
61+
_,
62+
) {
63+
Widget getIcon(List<ProductTag> tags) {
64+
if (tags.isNotEmpty == true) {
65+
return Tooltip(
66+
message: appLocalizations.add_edit_tags,
67+
child: const icons.Edit(size: 15.0),
68+
);
69+
} else {
70+
return Tooltip(
71+
message: appLocalizations.add_tags,
72+
child: const icons.Add(),
73+
);
74+
}
75+
}
4576

46-
await Navigator.of(context).push(
47-
MaterialPageRoute<void>(
48-
builder: (BuildContext context) => FolksonomyPage(product),
49-
),
50-
);
51-
if (context.mounted) {
52-
await Provider.of<FolksonomyProvider>(context, listen: false)
53-
.fetchProductTags();
54-
}
55-
},
56-
child: Padding(
57-
padding: const EdgeInsets.only(top: LARGE_SPACE),
58-
child: buildProductSmoothCard(
59-
title: Center(child: Text(appLocalizations.product_tags_title)),
60-
body: Container(
61-
width: double.infinity,
62-
padding: const EdgeInsetsDirectional.all(LARGE_SPACE),
63-
child: _FolksonomyCardList(),
77+
return switch (provider.value) {
78+
FolksonomyStateError(action: final FolksonomyAction? action)
79+
when action == null =>
80+
EMPTY_WIDGET,
81+
FolksonomyStateError(
82+
tags: final List<ProductTag> tags,
83+
) =>
84+
getIcon(tags),
85+
FolksonomyStateLoaded(
86+
tags: final List<ProductTag> tags,
87+
) =>
88+
getIcon(tags),
89+
FolksonomyStateAddedItem(
90+
tags: final List<ProductTag> tags
91+
) =>
92+
getIcon(tags),
93+
FolksonomyStateRemovedItem(
94+
tags: final List<ProductTag> tags
95+
) =>
96+
getIcon(tags),
97+
_ => EMPTY_WIDGET,
98+
};
99+
},
100+
),
101+
),
102+
],
103+
),
104+
body: Container(
105+
width: double.infinity,
106+
padding: const EdgeInsetsDirectional.all(LARGE_SPACE),
107+
child: _FolksonomyCardBody(
108+
onEmptyPageTag: () => _openFolksonomyPage(
109+
context,
110+
context.read<Product>(),
111+
),
64112
),
65113
),
66114
),
67115
);
68116
}
117+
118+
Future<void> _openFolksonomyPage(
119+
BuildContext context,
120+
Product product,
121+
) async {
122+
if (!await ProductRefresher().checkIfLoggedIn(
123+
context,
124+
isLoggedInMandatory: true,
125+
)) {
126+
return;
127+
}
128+
if (!context.mounted) {
129+
return;
130+
}
131+
132+
await Navigator.of(context).push(
133+
MaterialPageRoute<void>(
134+
builder: (BuildContext lContext) => FolksonomyPage(
135+
product: product,
136+
provider: context.read<FolksonomyProvider>(),
137+
),
138+
),
139+
);
140+
if (context.mounted) {
141+
await context.read<FolksonomyProvider>().fetchProductTags();
142+
}
143+
}
69144
}
70145

71-
class _FolksonomyCardList extends StatelessWidget {
146+
class _FolksonomyCardBody extends StatelessWidget {
147+
const _FolksonomyCardBody({
148+
required this.onEmptyPageTag,
149+
});
150+
151+
final VoidCallback onEmptyPageTag;
152+
72153
@override
73154
Widget build(BuildContext context) {
74155
final AppLocalizations appLocalizations = AppLocalizations.of(context);
75-
final FolksonomyProvider provider = context.watch<FolksonomyProvider>();
76156

77-
if (provider.isLoading) {
78-
return const Center(child: CircularProgressIndicator.adaptive());
79-
} else if (!provider.tagsExist) {
80-
return Center(
81-
child: Row(
82-
mainAxisAlignment: MainAxisAlignment.center,
83-
children: <Widget>[
84-
Expanded(
157+
return Consumer<FolksonomyProvider>(
158+
builder: (
159+
BuildContext context,
160+
FolksonomyProvider provider,
161+
_,
162+
) {
163+
if (provider.value is FolksonomyStateLoading) {
164+
return const Center(
165+
child: CircularProgressIndicator.adaptive(),
166+
);
167+
} else if (provider.value.tags?.isNotEmpty != true) {
168+
return InkWell(
169+
onTap: onEmptyPageTag,
170+
child: Center(
85171
child: Text(
86172
appLocalizations.no_product_tags_found_message,
87173
textAlign: TextAlign.center,
88174
),
89175
),
90-
Icon(ConstantIcons.forwardIcon),
91-
],
92-
),
93-
);
94-
} else {
95-
final Map<String, ProductTag> tags = provider.productTags!;
96-
final Iterable<MapEntry<String, ProductTag>> displayTags =
97-
tags.entries.toList(growable: false).take(5);
176+
);
177+
} else {
178+
final Iterable<ProductTag> displayTags = provider.value.tags!.take(5);
98179

99-
return Padding(
100-
padding: const EdgeInsetsDirectional.only(
101-
top: VERY_SMALL_SPACE,
102-
bottom: VERY_SMALL_SPACE,
103-
),
104-
child: Row(
105-
children: <Widget>[
106-
Expanded(
107-
child: Padding(
108-
padding: const EdgeInsets.all(SMALL_SPACE),
109-
child: Column(
110-
crossAxisAlignment: CrossAxisAlignment.start,
111-
children:
112-
displayTags.map((MapEntry<String, ProductTag> entry) {
113-
return Tag(
114-
text:
115-
'${entry.key}${appLocalizations.sep}: ${entry.value.value}');
116-
}).toList(growable: false),
180+
return Padding(
181+
padding: const EdgeInsetsDirectional.only(
182+
top: VERY_SMALL_SPACE,
183+
bottom: VERY_SMALL_SPACE,
184+
),
185+
child: Column(
186+
crossAxisAlignment: CrossAxisAlignment.start,
187+
children: <Widget>[
188+
Padding(
189+
padding: const EdgeInsets.all(SMALL_SPACE),
190+
child: Column(
191+
crossAxisAlignment: CrossAxisAlignment.start,
192+
children: displayTags.map((ProductTag tag) {
193+
return Tag(
194+
text:
195+
'${tag.key}${appLocalizations.sep}: ${tag.value}');
196+
}).toList(growable: false),
197+
),
117198
),
118-
),
199+
],
119200
),
120-
Icon(ConstantIcons.forwardIcon),
121-
],
122-
),
123-
);
124-
}
201+
);
202+
}
203+
},
204+
);
125205
}
126206
}

0 commit comments

Comments
 (0)