Skip to content

Commit 77569bf

Browse files
g123kmonsieurtanukiteolemon
authored
feat: Common layout for welcome / product not found / error cards (#2955)
* Common layout for welcome / product not found / error cards * Change the opacity for the main/welcome card * Update packages/smooth_app/lib/widgets/smooth_product_carousel.dart Co-authored-by: monsieurtanuki <fabrice_fontaine@hotmail.com> * SmoothBaseCard move to a better named file + some doc about this Widget * Fix build issue Co-authored-by: monsieurtanuki <fabrice_fontaine@hotmail.com> Co-authored-by: Pierre Slamich <pierre@openfoodfacts.org>
1 parent 337fad6 commit 77569bf

5 files changed

Lines changed: 168 additions & 134 deletions

File tree

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:smooth_app/generic_lib/widgets/smooth_card.dart';
3+
4+
/// A common Widget for carrousel item cards.
5+
/// It allows to have the correct width/height and also a scale down feature,
6+
/// in the case where the content is too big.
7+
///
8+
/// An optional [backgroundColorOpacity] can be used (mainly for the "main" card).
9+
class SmoothProductBaseCard extends StatelessWidget {
10+
const SmoothProductBaseCard({
11+
required this.child,
12+
this.backgroundColorOpacity,
13+
super.key,
14+
});
15+
16+
final Widget child;
17+
final double? backgroundColorOpacity;
18+
19+
@override
20+
Widget build(BuildContext context) {
21+
final ThemeData themeData = Theme.of(context);
22+
23+
return SizedBox.expand(
24+
child: LayoutBuilder(
25+
builder: (BuildContext context, BoxConstraints constraints) {
26+
final EdgeInsets padding = EdgeInsets.symmetric(
27+
vertical: constraints.maxHeight * 0.05,
28+
horizontal: constraints.maxWidth * 0.05,
29+
);
30+
31+
return SmoothCard(
32+
color: themeData.brightness == Brightness.light
33+
? Colors.white.withOpacity(backgroundColorOpacity ?? 1.0)
34+
: Colors.black.withOpacity(backgroundColorOpacity ?? 1.0),
35+
padding: padding,
36+
child: FittedBox(
37+
fit: BoxFit.scaleDown,
38+
child: SizedBox(
39+
width: constraints.maxWidth - padding.horizontal,
40+
height: constraints.maxHeight - padding.vertical,
41+
child: child,
42+
),
43+
),
44+
);
45+
},
46+
),
47+
);
48+
}
49+
}

packages/smooth_app/lib/cards/product_cards/smooth_product_card_error.dart

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
22
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
33
import 'package:flutter_svg/flutter_svg.dart';
44
import 'package:provider/provider.dart';
5+
import 'package:smooth_app/cards/product_cards/smooth_product_base_card.dart';
56
import 'package:smooth_app/data_models/continuous_scan_model.dart';
67
import 'package:smooth_app/generic_lib/design_constants.dart';
78
import 'package:smooth_app/pages/product/common/product_dialog_helper.dart';
@@ -19,32 +20,22 @@ class SmoothProductCardError extends StatelessWidget {
1920
@override
2021
Widget build(BuildContext context) {
2122
final AppLocalizations appLocalizations = AppLocalizations.of(context);
22-
final ThemeData themeData = Theme.of(context);
2323

24-
return Container(
25-
decoration: BoxDecoration(
26-
color: themeData.brightness == Brightness.light
27-
? Colors.white
28-
: Colors.black,
29-
borderRadius: ROUNDED_BORDER_RADIUS,
30-
),
24+
return SmoothProductBaseCard(
3125
child: Column(
3226
mainAxisSize: MainAxisSize.max,
3327
mainAxisAlignment: MainAxisAlignment.center,
3428
children: <Widget>[
35-
Padding(
36-
padding: const EdgeInsets.all(SMALL_SPACE),
37-
child: SvgPicture.asset(
38-
'assets/misc/error.svg',
39-
width: MINIMUM_TOUCH_SIZE * 2,
40-
),
29+
SvgPicture.asset(
30+
'assets/misc/error.svg',
31+
width: MINIMUM_TOUCH_SIZE * 2,
32+
),
33+
const SizedBox(
34+
height: SMALL_SPACE,
4135
),
42-
Row(
43-
mainAxisSize: MainAxisSize.max,
44-
mainAxisAlignment: MainAxisAlignment.center,
45-
children: <Widget>[
46-
Text(barcode, style: Theme.of(context).textTheme.subtitle1),
47-
],
36+
Text(
37+
barcode,
38+
style: Theme.of(context).textTheme.subtitle1,
4839
),
4940
const SizedBox(
5041
height: MEDIUM_SPACE,
Lines changed: 59 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,79 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
3+
import 'package:smooth_app/cards/product_cards/smooth_product_base_card.dart';
34
import 'package:smooth_app/generic_lib/buttons/smooth_large_button_with_icon.dart';
45
import 'package:smooth_app/generic_lib/design_constants.dart';
5-
import 'package:smooth_app/generic_lib/widgets/smooth_card.dart';
66
import 'package:smooth_app/pages/product/add_new_product_page.dart';
77

88
class SmoothProductCardNotFound extends StatelessWidget {
9-
const SmoothProductCardNotFound({
9+
SmoothProductCardNotFound({
1010
required this.barcode,
1111
this.callback,
12-
this.elevation = 0.0,
13-
});
12+
}) : assert(barcode.isNotEmpty);
1413

15-
final Function(String?)? callback;
16-
final double elevation;
14+
final Future<void> Function(String?)? callback;
1715
final String barcode;
1816

1917
@override
2018
Widget build(BuildContext context) {
2119
final AppLocalizations appLocalizations = AppLocalizations.of(context);
22-
final ThemeData themeData = Theme.of(context);
20+
final TextTheme textTheme = Theme.of(context).textTheme;
2321

24-
return LayoutBuilder(
25-
builder: (BuildContext context, BoxConstraints constraints) {
26-
return SmoothCard(
27-
elevation: elevation,
28-
color: themeData.brightness == Brightness.light
29-
? Colors.white
30-
: Colors.black,
31-
child: Padding(
32-
padding: EdgeInsets.symmetric(
33-
vertical: constraints.maxHeight * 0.10,
34-
horizontal: constraints.maxWidth * 0.05,
35-
),
36-
child: Column(
37-
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
38-
crossAxisAlignment: CrossAxisAlignment.center,
39-
children: <Widget>[
40-
Text(
41-
appLocalizations.missing_product,
42-
textAlign: TextAlign.center,
43-
style: Theme.of(context).textTheme.headline2,
44-
),
45-
Text(
46-
appLocalizations.add_product_take_photos,
47-
textAlign: TextAlign.center,
48-
style: Theme.of(context).textTheme.bodyText2,
49-
),
50-
Text(
51-
'(${appLocalizations.barcode_barcode(barcode)})',
52-
textAlign: TextAlign.center,
53-
style: Theme.of(context).textTheme.bodyText2,
54-
),
55-
Padding(
56-
padding: const EdgeInsetsDirectional.only(top: LARGE_SPACE),
57-
child: SmoothLargeButtonWithIcon(
58-
text: appLocalizations.add_product_information_button_label,
59-
icon: Icons.add,
60-
padding: const EdgeInsets.symmetric(vertical: LARGE_SPACE),
61-
onPressed: () async {
62-
// TODO(monsieurtanuki): careful, waiting for pop'ed value
63-
final String? result = await Navigator.push<String>(
64-
context,
65-
MaterialPageRoute<String>(
66-
builder: (BuildContext context) =>
67-
AddNewProductPage(barcode),
68-
),
69-
);
70-
if (callback != null) {
71-
await callback!(result);
72-
}
73-
},
22+
return SmoothProductBaseCard(
23+
child: Column(
24+
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
25+
crossAxisAlignment: CrossAxisAlignment.center,
26+
children: <Widget>[
27+
RichText(
28+
textAlign: TextAlign.center,
29+
text: TextSpan(
30+
style: textTheme.headline5,
31+
children: <InlineSpan>[
32+
TextSpan(
33+
text: appLocalizations.missing_product,
34+
style: textTheme.headline2,
35+
),
36+
const WidgetSpan(
37+
alignment: PlaceholderAlignment.belowBaseline,
38+
baseline: TextBaseline.alphabetic,
39+
child: SizedBox(
40+
height: LARGE_SPACE,
41+
),
42+
),
43+
TextSpan(
44+
text: '\n${appLocalizations.add_product_take_photos}\n',
45+
style: textTheme.bodyText2,
7446
),
75-
),
76-
],
47+
TextSpan(
48+
text: '(${appLocalizations.barcode_barcode(barcode)})',
49+
style: textTheme.bodyText2,
50+
),
51+
],
52+
),
53+
),
54+
Padding(
55+
padding: const EdgeInsetsDirectional.only(top: LARGE_SPACE),
56+
child: SmoothLargeButtonWithIcon(
57+
text: appLocalizations.add_product_information_button_label,
58+
icon: Icons.add,
59+
padding: const EdgeInsets.symmetric(vertical: LARGE_SPACE),
60+
onPressed: () async {
61+
// TODO(monsieurtanuki): careful, waiting for pop'ed value
62+
final String? result = await Navigator.push<String>(
63+
context,
64+
MaterialPageRoute<String>(
65+
builder: (BuildContext context) =>
66+
AddNewProductPage(barcode),
67+
),
68+
);
69+
if (callback != null) {
70+
await callback!(result);
71+
}
72+
},
73+
),
7774
),
78-
),
79-
);
80-
});
75+
],
76+
),
77+
);
8178
}
8279
}

packages/smooth_app/lib/pages/product/common/product_dialog_helper.dart

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:openfoodfacts/openfoodfacts.dart';
44
import 'package:smooth_app/data_models/fetched_product.dart';
55
import 'package:smooth_app/database/dao_product.dart';
66
import 'package:smooth_app/database/local_database.dart';
7+
import 'package:smooth_app/generic_lib/design_constants.dart';
78
import 'package:smooth_app/generic_lib/dialogs/smooth_alert_dialog.dart';
89
import 'package:smooth_app/generic_lib/loading_dialog.dart';
910
import 'package:smooth_app/pages/product/add_new_product_page.dart';
@@ -70,9 +71,12 @@ class ProductDialogHelper {
7071
},
7172
);
7273

73-
static Widget getErrorMessage(final String message) => ListTile(
74-
leading: const Icon(Icons.error_outline, color: Colors.red),
75-
title: Text(message),
74+
static Widget getErrorMessage(final String message) => Row(
75+
children: <Widget>[
76+
const Icon(Icons.error_outline, color: Colors.red),
77+
const SizedBox(width: SMALL_SPACE),
78+
Expanded(child: Text(message))
79+
],
7680
);
7781

7882
void _openErrorMessage(final String message) => showDialog<void>(

packages/smooth_app/lib/widgets/smooth_product_carousel.dart

Lines changed: 42 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
66
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
77
import 'package:flutter_svg/flutter_svg.dart';
88
import 'package:provider/provider.dart';
9+
import 'package:smooth_app/cards/product_cards/smooth_product_base_card.dart';
910
import 'package:smooth_app/cards/product_cards/smooth_product_card_error.dart';
1011
import 'package:smooth_app/cards/product_cards/smooth_product_card_loading.dart';
1112
import 'package:smooth_app/cards/product_cards/smooth_product_card_not_found.dart';
@@ -14,7 +15,6 @@ import 'package:smooth_app/data_models/continuous_scan_model.dart';
1415
import 'package:smooth_app/data_models/tagline.dart';
1516
import 'package:smooth_app/data_models/user_preferences.dart';
1617
import 'package:smooth_app/generic_lib/design_constants.dart';
17-
import 'package:smooth_app/generic_lib/widgets/smooth_card.dart';
1818
import 'package:smooth_app/pages/inherited_data_manager.dart';
1919
import 'package:smooth_app/pages/scan/scan_product_card_loader.dart';
2020
import 'package:smooth_app/pages/scan/search_page.dart';
@@ -141,7 +141,7 @@ class _SmoothProductCarouselState extends State<SmoothProductCarousel> {
141141
/// instead in the meanwhile.
142142
Widget _getWidget(final int index) {
143143
if (index >= barcodes.length) {
144-
return Container();
144+
return EMPTY_WIDGET;
145145
}
146146
final String barcode = barcodes[index];
147147
switch (_model.getBarcodeState(barcode)!) {
@@ -188,54 +188,47 @@ class SearchCard extends StatelessWidget {
188188
final AppLocalizations localizations = AppLocalizations.of(context);
189189
final ThemeData themeData = Theme.of(context);
190190
final bool isDarkmode = themeData.brightness == Brightness.dark;
191-
return SmoothCard(
192-
color: Theme.of(context).brightness == Brightness.light
193-
? Colors.white.withOpacity(OPACITY)
194-
: Colors.black.withOpacity(OPACITY),
195-
elevation: 0,
196-
padding: SmoothProductCarousel.carouselItemHorizontalPadding,
197-
child: SizedBox(
198-
height: height,
199-
child: Column(
200-
mainAxisAlignment: MainAxisAlignment.center,
201-
crossAxisAlignment: CrossAxisAlignment.stretch,
202-
children: <Widget>[
203-
SvgPicture.asset(
204-
Theme.of(context).brightness == Brightness.light
205-
? 'assets/app/release_icon_light_transparent_no_border.svg'
206-
: 'assets/app/release_icon_dark_transparent_no_border.svg',
207-
width: height * 0.2,
208-
height: height * 0.2,
209-
),
210-
AutoSizeText(
211-
localizations.welcomeToOpenFoodFacts,
212-
textAlign: TextAlign.center,
213-
style: const TextStyle(
214-
fontSize: 26.0,
215-
fontWeight: FontWeight.bold,
216-
height: 1.25,
217-
),
218-
maxLines: 2,
219-
),
220-
SizedBox(
221-
height: height * 0.05,
222-
),
223-
const Expanded(
224-
child: _SearchCardTagLine(),
225-
),
226-
SearchField(
227-
onFocus: () => _openSearchPage(context),
228-
readOnly: true,
229-
showClearButton: false,
230-
backgroundColor: isDarkmode
231-
? Colors.white10
232-
: const Color.fromARGB(255, 240, 240, 240)
233-
.withOpacity(OPACITY),
234-
foregroundColor:
235-
themeData.colorScheme.onSurface.withOpacity(OPACITY),
191+
192+
return SmoothProductBaseCard(
193+
backgroundColorOpacity: OPACITY,
194+
child: Column(
195+
mainAxisAlignment: MainAxisAlignment.center,
196+
crossAxisAlignment: CrossAxisAlignment.stretch,
197+
children: <Widget>[
198+
SvgPicture.asset(
199+
Theme.of(context).brightness == Brightness.light
200+
? 'assets/app/release_icon_light_transparent_no_border.svg'
201+
: 'assets/app/release_icon_dark_transparent_no_border.svg',
202+
width: height * 0.2,
203+
height: height * 0.2,
204+
),
205+
AutoSizeText(
206+
localizations.welcomeToOpenFoodFacts,
207+
textAlign: TextAlign.center,
208+
style: const TextStyle(
209+
fontSize: 26.0,
210+
fontWeight: FontWeight.bold,
211+
height: 1.25,
236212
),
237-
],
238-
),
213+
maxLines: 2,
214+
),
215+
SizedBox(
216+
height: height * 0.05,
217+
),
218+
const Expanded(
219+
child: _SearchCardTagLine(),
220+
),
221+
SearchField(
222+
onFocus: () => _openSearchPage(context),
223+
readOnly: true,
224+
showClearButton: false,
225+
backgroundColor: isDarkmode
226+
? Colors.white10
227+
: const Color.fromARGB(255, 240, 240, 240).withOpacity(OPACITY),
228+
foregroundColor:
229+
themeData.colorScheme.onSurface.withOpacity(OPACITY),
230+
),
231+
],
239232
),
240233
);
241234
}

0 commit comments

Comments
 (0)