Skip to content

Commit

Permalink
feat: Product loading card (#4318)
Browse files Browse the repository at this point in the history
  • Loading branch information
g123k authored Jul 22, 2023
1 parent 899cea6 commit 41e9191
Show file tree
Hide file tree
Showing 9 changed files with 289 additions and 41 deletions.
Binary file added packages/smooth_app/assets/animations/off.riv
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -68,19 +68,22 @@ class ProductCardCloseButton extends StatelessWidget {
Widget build(BuildContext context) {
final AppLocalizations appLocalizations = AppLocalizations.of(context);

return InkWell(
customBorder: const CircleBorder(),
onTap: () {
onRemove?.call(context);
SmoothHapticFeedback.lightNotification();
},
child: Tooltip(
message: appLocalizations.product_card_remove_product_tooltip,
child: Padding(
padding: const EdgeInsets.all(SMALL_SPACE),
child: Icon(
iconData,
size: DEFAULT_ICON_SIZE,
return Material(
type: MaterialType.transparency,
child: InkWell(
customBorder: const CircleBorder(),
onTap: () {
onRemove?.call(context);
SmoothHapticFeedback.lightNotification();
},
child: Tooltip(
message: appLocalizations.product_card_remove_product_tooltip,
child: Padding(
padding: const EdgeInsets.all(SMALL_SPACE),
child: Icon(
iconData,
size: DEFAULT_ICON_SIZE,
),
),
),
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,224 @@
import 'dart:async';
import 'dart:ui';

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
import 'package:rive/rive.dart';
import 'package:smooth_app/cards/product_cards/smooth_product_base_card.dart';
import 'package:smooth_app/data_models/continuous_scan_model.dart';
import 'package:smooth_app/generic_lib/buttons/smooth_simple_button.dart';
import 'package:smooth_app/generic_lib/design_constants.dart';
import 'package:smooth_app/generic_lib/duration_constants.dart';
import 'package:smooth_app/helpers/analytics_helper.dart';

class SmoothProductCardLoading extends StatelessWidget {
const SmoothProductCardLoading({required this.barcode});
class SmoothProductCardLoading extends StatefulWidget {
const SmoothProductCardLoading({
required this.barcode,
this.onRemoveProduct,
});

final String barcode;
final OnRemoveCallback? onRemoveProduct;

@override
State<SmoothProductCardLoading> createState() =>
_SmoothProductCardLoadingState();
}

class _SmoothProductCardLoadingState extends State<SmoothProductCardLoading> {
late Timer _timer;
_SmoothProductCardLoadingProgress _progress =
_SmoothProductCardLoadingProgress.initial;

@override
void initState() {
super.initState();
_timer = Timer(const Duration(seconds: 7), _onLongRequest);
}

@override
Widget build(BuildContext context) {
final AppLocalizations appLocalizations = AppLocalizations.of(context);
final ThemeData themeData = Theme.of(context);

return Container(
decoration: BoxDecoration(
color: themeData.brightness == Brightness.light
? Colors.white
: Colors.black,
borderRadius: ROUNDED_BORDER_RADIUS,
),
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(barcode, style: Theme.of(context).textTheme.titleMedium),
],
),
const SizedBox(
height: 12.0,
),
const CircularProgressIndicator.adaptive()
],
),
return DefaultTextStyle.merge(
textAlign: TextAlign.center,
style: const TextStyle(height: 1.4),
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return Stack(
children: <Widget>[
Positioned.fill(
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
vertical: SMALL_SPACE,
horizontal: MEDIUM_SPACE,
),
decoration: BoxDecoration(
color: themeData.brightness == Brightness.light
? Colors.white
: Colors.black,
borderRadius: ROUNDED_BORDER_RADIUS,
),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
const Spacer(),
Text(
appLocalizations.scan_product_loading,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18.0,
),
),
const Spacer(flex: 2),
Container(
padding: const EdgeInsets.symmetric(
horizontal: SMALL_SPACE,
vertical: SMALL_SPACE,
),
color: Colors.grey.withOpacity(0.2),
child: Text(
'<${widget.barcode}>',
style: const TextStyle(
letterSpacing: 6.0,
fontFeatures: <FontFeature>[
FontFeature.tabularFigures(),
],
),
),
),
const Spacer(flex: 2),
AnimatedSwitcher(
duration: SmoothAnimationsDuration.long,
child: Text(_description(appLocalizations)),
transitionBuilder:
(Widget child, Animation<double> animation) {
return FadeTransition(
opacity: Tween<double>(
begin: 0.0,
end: 1.0,
).animate(animation),
child: child,
);
},
),
const Spacer(),
Expanded(
flex: 10,
child: ConstrainedBox(
constraints: const BoxConstraints(
maxHeight: 300,
),
child: _progress ==
_SmoothProductCardLoadingProgress.unresponsive
? Center(
child: SmoothSimpleButton(
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Icon(Icons.restart_alt),
const SizedBox(
width: SMALL_SPACE,
),
Text(appLocalizations
.scan_product_loading_restart_button)
],
),
onPressed: () {
AnalyticsHelper.trackEvent(
AnalyticsEvent.restartProductLoading,
barcode: widget.barcode,
);

final ContinuousScanModel model =
context.read<ContinuousScanModel>();

model.retryBarcodeFetch(widget.barcode);
},
),
)
: const RiveAnimation.asset(
'assets/animations/off.riv',
artboard: 'Loading',
alignment: Alignment.topCenter,
fit: BoxFit.fitHeight,
),
),
),
const Spacer(),
],
),
),
),
if (_progress != _SmoothProductCardLoadingProgress.initial)
Positioned.directional(
top: 0.0,
end: 0.0,
textDirection: Directionality.of(context),
child: Padding(
padding: EdgeInsetsDirectional.only(
top: constraints.maxHeight * 0.05,
end: constraints.maxWidth * 0.05,
),
child: ProductCardCloseButton(
onRemove: (BuildContext context) {
AnalyticsHelper.trackEvent(
AnalyticsEvent.ignoreProductLoading,
barcode: widget.barcode,
);

widget.onRemoveProduct?.call(context);
},
iconData: CupertinoIcons.clear_circled,
),
),
),
],
);
}),
);
}

String _description(AppLocalizations appLocalizations) {
return switch (_progress) {
_SmoothProductCardLoadingProgress.longRequest =>
appLocalizations.scan_product_loading_long_request,
_SmoothProductCardLoadingProgress.unresponsive =>
appLocalizations.scan_product_loading_unresponsive,
_ => appLocalizations.scan_product_loading_initial,
};
}

void _onLongRequest() {
if (!mounted) {
return;
}
setState(() => _progress = _SmoothProductCardLoadingProgress.longRequest);
_timer = Timer(const Duration(seconds: 5), _onUnresponsiveRequest);
}

void _onUnresponsiveRequest() {
if (!mounted) {
return;
}
setState(() => _progress = _SmoothProductCardLoadingProgress.unresponsive);
}

@override
void dispose() {
_timer.cancel();
super.dispose();
}
}

enum _SmoothProductCardLoadingProgress {
initial,
longRequest,
unresponsive,
}
9 changes: 9 additions & 0 deletions packages/smooth_app/lib/helpers/analytics_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ enum AnalyticsCategory {
userManagement(tag: 'user management'),
scanning(tag: 'scanning'),
share(tag: 'share'),
loadingProduct(tag: 'loading product'),
couldNotFindProduct(tag: 'could not find product'),
productEdit(tag: 'product edit'),
productFastTrackEdit(tag: 'product fast track edit'),
Expand Down Expand Up @@ -40,6 +41,14 @@ enum AnalyticsEvent {
tag: 'could not find product',
category: AnalyticsCategory.couldNotFindProduct,
),
ignoreProductLoading(
tag: 'ignore product',
category: AnalyticsCategory.loadingProduct,
),
restartProductLoading(
tag: 'restart request',
category: AnalyticsCategory.loadingProduct,
),
ignoreProductNotFound(
tag: 'ignore product',
category: AnalyticsCategory.couldNotFindProduct,
Expand Down
22 changes: 21 additions & 1 deletion packages/smooth_app/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -1955,7 +1955,27 @@
"@scan_header_compare_button_valid_state_tooltip": {
"description": "Tooltip (message visible with a long-press) on the Compare button on top of the scanner, when there is at least two prodiucts"
},
"portion_calculator_description": "Calculate nutrition facts for a specific quantity:",
"scan_product_loading": "You have scanned\nthe barcode:",
"@scan_product_loading": {
"description": "Title when a product is loading (carousel card). Please ensure to keep the line break."
},
"scan_product_loading_initial": "We're looking for this product!\nPlease wait a few seconds…",
"@scan_product_loading_initial": {
"description": "Message when a product is loading (carousel card). Please ensure to keep the line break."
},
"scan_product_loading_long_request": "We're still looking for this product!\nDo you find it takes a long time to load? So are we…",
"@scan_product_loading_long_request": {
"description": "Message when a product is long to load (carousel card). Please ensure to keep the line break."
},
"scan_product_loading_unresponsive": "We're still looking for this product.\nWould you like to restart the search?",
"@scan_product_loading_unresponsive": {
"description": "Message when a product is too long to load (carousel card). Please ensure to keep the line break."
},
"scan_product_loading_restart_button": "Restart search",
"@scan_product_loading_restart_button": {
"description": "Button to force restart a product search"
},
"portion_calculator_description": "Calculate nutrition facts for a specific quantity",
"@portion_calculator_description": {
"description": "Sort of title that describes the portion calculator."
},
Expand Down
20 changes: 20 additions & 0 deletions packages/smooth_app/lib/l10n/app_fr.arb
Original file line number Diff line number Diff line change
Expand Up @@ -1927,6 +1927,26 @@
"@scan_header_compare_button_valid_state_tooltip": {
"description": "Tooltip (message visible with a long-press) on the Compare button on top of the scanner, when there is at least two prodiucts"
},
"scan_product_loading": "Vous avez scanné\nle code-barres :",
"@scan_product_loading": {
"description": "Title when a product is loading (carousel card). Please ensure to keep the line break."
},
"scan_product_loading_initial": "Nous cherchons ce produit !\nMerci de patienter quelques instants…",
"@scan_product_loading_initial": {
"description": "Message when a product is loading (carousel card). Please ensure to keep the line break."
},
"scan_product_loading_long_request": "Nous cherchons toujours ce produit !\nLe chargement vous paraît long ? Nous aussi…",
"@scan_product_loading_long_request": {
"description": "Message when a product is long to load (carousel card). Please ensure to keep the line break."
},
"scan_product_loading_unresponsive": "Nous cherchons toujours ce produit !\nVoulez-vous relancer la recherche ?",
"@scan_product_loading_unresponsive": {
"description": "Message when a product is too long to load (carousel card). Please ensure to keep the line break."
},
"scan_product_loading_restart_button": "Relancer la recherche",
"@scan_product_loading_restart_button": {
"description": "Button to force restart a product search"
},
"portion_calculator_description": "Calculer les valeurs nutritionnelles pour une quantité spécifique",
"@portion_calculator_description": {
"description": "Sort of title that describes the portion calculator."
Expand Down
5 changes: 4 additions & 1 deletion packages/smooth_app/lib/widgets/smooth_product_carousel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,10 @@ class _SmoothProductCarouselState extends State<SmoothProductCarousel> {
case ScannedProductState.CACHED:
return ScanProductCardLoader(barcode);
case ScannedProductState.LOADING:
return SmoothProductCardLoading(barcode: barcode);
return SmoothProductCardLoading(
barcode: barcode,
onRemoveProduct: (_) => _model.removeBarcode(barcode),
);
case ScannedProductState.NOT_FOUND:
return SmoothProductCardNotFound(
barcode: barcode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import in_app_review
import mobile_scanner
import package_info_plus
import path_provider_foundation
import rive_common
import sentry_flutter
import share_plus
import shared_preferences_foundation
Expand All @@ -28,6 +29,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
RivePlugin.register(with: registry.registrar(forPlugin: "RivePlugin"))
SentryFlutterPlugin.register(with: registry.registrar(forPlugin: "SentryFlutterPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
Expand Down
Loading

0 comments on commit 41e9191

Please sign in to comment.