Skip to content

Commit

Permalink
feat: An indicator if the photo may be locked by the producer (#5974)
Browse files Browse the repository at this point in the history
* An indicator if the photo may be locked by the producer

* Implement suggestions
  • Loading branch information
g123k authored Dec 1, 2024
1 parent 001e1ee commit e53b0f3
Show file tree
Hide file tree
Showing 7 changed files with 620 additions and 216 deletions.
249 changes: 196 additions & 53 deletions packages/smooth_app/lib/cards/product_cards/smooth_product_image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:provider/provider.dart';
import 'package:smooth_app/database/transient_file.dart';
import 'package:smooth_app/generic_lib/design_constants.dart';
import 'package:smooth_app/helpers/image_field_extension.dart';
import 'package:smooth_app/pages/image/product_image_helper.dart';
import 'package:smooth_app/pages/product/owner_field_info.dart';
import 'package:smooth_app/pages/product/product_page/new_product_page.dart';
import 'package:smooth_app/query/product_query.dart';
import 'package:smooth_app/resources/app_icons.dart' as icons;
Expand All @@ -26,10 +28,11 @@ class ProductPicture extends StatefulWidget {
String? fallbackUrl,
VoidCallback? onTap,
String? heroTag,
bool? showObsoleteIcon,
bool showObsoleteIcon = false,
bool showOwnerIcon = false,
BorderRadius? borderRadius,
double? imageFoundBorder,
double? imageNotFoundBorder,
double imageFoundBorder = 0.0,
double imageNotFoundBorder = 0.0,
TextStyle? errorTextStyle,
}) : this._(
transientFile: null,
Expand All @@ -41,37 +44,43 @@ class ProductPicture extends StatefulWidget {
heroTag: heroTag,
onTap: onTap,
borderRadius: borderRadius,
imageFoundBorder: imageFoundBorder ?? 0.0,
imageNotFoundBorder: imageNotFoundBorder ?? 0.0,
imageFoundBorder: imageFoundBorder,
imageNotFoundBorder: imageNotFoundBorder,
errorTextStyle: errorTextStyle,
showObsoleteIcon: showObsoleteIcon ?? false,
showObsoleteIcon: showObsoleteIcon,
showOwnerIcon: showOwnerIcon,
);

ProductPicture.fromTransientFile({
required TransientFile transientFile,
required Size size,
OpenFoodFactsLanguage? language,
Product? product,
ImageField? imageField,
String? fallbackUrl,
VoidCallback? onTap,
String? heroTag,
bool? showObsoleteIcon,
bool showObsoleteIcon = false,
bool showOwnerIcon = false,
BorderRadius? borderRadius,
double? imageFoundBorder,
double? imageNotFoundBorder,
double imageFoundBorder = 0.0,
double imageNotFoundBorder = 0.0,
TextStyle? errorTextStyle,
}) : this._(
transientFile: transientFile,
product: null,
imageField: null,
language: null,
product: product,
imageField: imageField,
language: language,
size: size,
fallbackUrl: fallbackUrl,
heroTag: heroTag,
onTap: onTap,
borderRadius: borderRadius,
imageFoundBorder: imageFoundBorder ?? 0.0,
imageNotFoundBorder: imageNotFoundBorder ?? 0.0,
imageFoundBorder: imageFoundBorder,
imageNotFoundBorder: imageNotFoundBorder,
errorTextStyle: errorTextStyle,
showObsoleteIcon: showObsoleteIcon ?? false,
showObsoleteIcon: showObsoleteIcon,
showOwnerIcon: showOwnerIcon,
);

ProductPicture._({
Expand All @@ -88,6 +97,7 @@ class ProductPicture extends StatefulWidget {
this.imageNotFoundBorder = 0.0,
this.errorTextStyle,
this.showObsoleteIcon = false,
this.showOwnerIcon = false,
super.key,
}) : assert(imageFoundBorder >= 0.0),
assert(imageNotFoundBorder >= 0.0),
Expand All @@ -108,6 +118,9 @@ class ProductPicture extends StatefulWidget {
/// Show the obsolete icon on top of the image
final bool showObsoleteIcon;

/// Show the owner icon on top of the image
final bool showOwnerIcon;

/// Rounded borders around the image
final BorderRadius? borderRadius;
final double imageFoundBorder;
Expand Down Expand Up @@ -147,8 +160,11 @@ class _ProductPictureState extends State<ProductPicture> {
child = _ProductPictureAssetsSvg(
asset: 'assets/product/product_error.svg',
semanticsLabel:
appLocalizations.product_page_image_error_accessibility_label,
text: appLocalizations.product_page_image_error,
appLocalizations.product_image_error_accessibility_label(
widget.imageField?.getPictureAccessibilityLabel(appLocalizations) ??
appLocalizations.product_image_front_accessibility_label,
),
text: appLocalizations.product_image_error,
textStyle: TextStyle(
color: context.extension<SmoothColorsThemeExtension>().red,
).merge(widget.errorTextStyle ?? const TextStyle()),
Expand All @@ -160,10 +176,18 @@ class _ProductPictureState extends State<ProductPicture> {
} else if (imageProvider?.$1 != null) {
child = _ProductPictureWithImageProvider(
imageProvider: imageProvider!.$1!,
imageField: widget.imageField,
outdated: imageProvider.$2,
locked: widget.imageField != null &&
widget.product?.isImageLocked(
widget.imageField!,
widget.language ?? ProductQuery.getLanguage(),
) ==
true,
heroTag: widget.heroTag,
size: widget.size,
showOutdated: widget.showObsoleteIcon,
showOwner: widget.showOwnerIcon,
borderRadius: widget.borderRadius,
border: widget.imageFoundBorder,
onError: () {
Expand Down Expand Up @@ -243,21 +267,27 @@ class _ProductPictureWithImageProvider extends StatelessWidget {
const _ProductPictureWithImageProvider({
required this.imageProvider,
required this.outdated,
required this.locked,
required this.size,
required this.child,
required this.onError,
required this.showOutdated,
required this.showOwner,
required this.border,
this.imageField,
this.borderRadius,
this.heroTag,
});

final ImageProvider imageProvider;
final ImageField? imageField;
final bool outdated;
final bool locked;
final Size size;
final Widget? child;
final VoidCallback onError;
final bool showOutdated;
final bool showOwner;
final BorderRadius? borderRadius;
final double border;
final String? heroTag;
Expand All @@ -268,7 +298,8 @@ class _ProductPictureWithImageProvider extends StatelessWidget {
final bool lightTheme = context.lightTheme();

final Widget image = Semantics(
label: appLocalizations.product_page_image_front_accessibility_label,
label: imageField?.getPictureAccessibilityLabel(appLocalizations) ??
appLocalizations.product_image_front_accessibility_label,
image: true,
excludeSemantics: true,
child: SizedBox.fromSize(
Expand Down Expand Up @@ -318,43 +349,54 @@ class _ProductPictureWithImageProvider extends StatelessWidget {
),
);

if (showOutdated && outdated) {
return Semantics(
label: appLocalizations
.product_page_image_front_outdated_message_accessibility_label,
image: true,
excludeSemantics: true,
child: Tooltip(
message: appLocalizations.product_page_image_front_outdated_message,
child: Stack(
children: <Widget>[
image,
Positioned.directional(
bottom: 2.0,
end: 2.0,
textDirection: Directionality.of(context),
child: DecoratedBox(
decoration: BoxDecoration(
color: Colors.white54,
borderRadius: borderRadius,
),
child: const Padding(
padding: EdgeInsetsDirectional.only(
top: 4.5,
bottom: 5.5,
start: 5.0,
end: 5.0,
),
child: icons.Outdated(
size: 15.0,
color: Color(0xFF616161),
),
),
),
final Widget? iconOutdated = showOutdated && outdated
? _OutdatedProductPictureIcon(
appLocalizations: appLocalizations,
borderRadius: borderRadius,
imageField: imageField,
)
: null;

final Widget? iconLocked = showOwner && locked
? _LockedProductPictureIcon(
appLocalizations: appLocalizations,
borderRadius: borderRadius,
imageField: imageField,
)
: null;

Widget? icons;
if (iconOutdated == null) {
icons = iconLocked;
} else if (iconLocked == null) {
icons = iconOutdated;
} else {
icons = Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
iconOutdated,
const SizedBox(height: SMALL_SPACE),
iconLocked,
],
);
}

if (icons != null) {
return Stack(
children: <Widget>[
image,
Positioned.directional(
bottom: 2.0,
end: 2.0,
textDirection: Directionality.of(context),
child: IconTheme(
data: const IconThemeData(
color: Color(0xFF616161),
),
],
child: icons,
),
),
),
],
);
}

Expand Down Expand Up @@ -393,6 +435,107 @@ class _ProductPictureWithImageProvider extends StatelessWidget {
}
}

class _OutdatedProductPictureIcon extends StatelessWidget {
const _OutdatedProductPictureIcon({
required this.appLocalizations,
required this.borderRadius,
this.imageField,
});

final ImageField? imageField;
final AppLocalizations appLocalizations;
final BorderRadius? borderRadius;

@override
Widget build(BuildContext context) {
return _ProductPictureIcon(
semanticsLabel:
appLocalizations.product_image_outdated_message_accessibility_label(
imageField?.getPictureAccessibilityLabel(appLocalizations) ??
appLocalizations.product_image_front_accessibility_label,
),
icon: const icons.Outdated(size: 15.0),
padding: const EdgeInsetsDirectional.only(
top: 4.5,
bottom: 5.5,
start: 5.0,
end: 5.0,
),
borderRadius: borderRadius,
);
}
}

class _LockedProductPictureIcon extends StatelessWidget {
const _LockedProductPictureIcon({
required this.appLocalizations,
required this.borderRadius,
this.imageField,
});

final ImageField? imageField;
final AppLocalizations appLocalizations;
final BorderRadius? borderRadius;

@override
Widget build(BuildContext context) {
return _ProductPictureIcon(
semanticsLabel:
appLocalizations.product_image_locked_message_accessibility_label(
imageField?.getPictureAccessibilityLabel(appLocalizations) ??
appLocalizations.product_image_front_accessibility_label,
),
icon: IconTheme.merge(
data: const IconThemeData(size: 16.0),
child: const OwnerFieldIcon(),
),
padding: const EdgeInsetsDirectional.only(
top: 4.5,
bottom: 5.5,
start: 5.0,
end: 5.0,
),
borderRadius: borderRadius,
);
}
}

class _ProductPictureIcon extends StatelessWidget {
const _ProductPictureIcon({
required this.semanticsLabel,
required this.icon,
required this.padding,
this.borderRadius,
});

final String semanticsLabel;
final Widget icon;
final EdgeInsetsGeometry padding;
final BorderRadius? borderRadius;

@override
Widget build(BuildContext context) {
return Semantics(
label: semanticsLabel,
image: true,
excludeSemantics: true,
child: Tooltip(
message: semanticsLabel,
child: DecoratedBox(
decoration: BoxDecoration(
color: Colors.white54,
borderRadius: borderRadius,
),
child: Padding(
padding: padding,
child: icon,
),
),
),
);
}
}

class _ProductPictureAssetsSvg extends StatelessWidget {
_ProductPictureAssetsSvg({
required this.asset,
Expand Down
16 changes: 16 additions & 0 deletions packages/smooth_app/lib/helpers/image_field_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,22 @@ extension ImageFieldSmoothieExtension on ImageField {
ImageField.OTHER => appLocalizations.take_more_photo_button_label,
};

String getPictureAccessibilityLabel(
final AppLocalizations appLocalizations,
) =>
switch (this) {
ImageField.FRONT =>
appLocalizations.product_image_front_accessibility_label,
ImageField.INGREDIENTS =>
appLocalizations.product_image_ingredients_accessibility_label,
ImageField.NUTRITION =>
appLocalizations.product_image_nutrition_accessibility_label,
ImageField.PACKAGING =>
appLocalizations.product_image_packaging_accessibility_label,
ImageField.OTHER =>
appLocalizations.product_image_other_accessibility_label,
};

Widget getPhotoButton(
final BuildContext context,
final Product product,
Expand Down
Loading

0 comments on commit e53b0f3

Please sign in to comment.