Skip to content

Commit

Permalink
fix: #3018 - new "interesting barcode" and "latest download" features (
Browse files Browse the repository at this point in the history
…#3227)

Impacted files:
* `add_basic_details_page.dart`: new `addTask` parameter
* `add_new_product_page.dart`: added "interesting barcode" feature
* `background_task_details.dart`: new `addTask` parameter in order to embed snackbar
* `confirm_and_upload_picture.dart`: new `uploadCapturedPicture` parameter
* `edit_ingredients_page.dart`: new `uploadCapturedPicture` parameter; new `addTask` parameter; added "interesting barcode" feature
* `edit_product_page.dart`: added "interesting barcode" feature; new `fetchAndRefresh` parameters
* `image_upload_card.dart`: new `uploadCapturedPicture` parameter
* `knowledge_panel_page.dart`: added "interesting barcode" feature; new `fetchAndRefresh` parameters
* `new_product_page.dart`: added "interesting barcode" feature; new `fetchAndRefresh` parameters
* `nutrition_page_loaded.dart`: new `addTask` parameter
* `personalized_ranking_model.dart`: added "interesting barcode" feature; refactored around the new `LocalDatabase` field
* `personalized_ranking_page.dart`: refactored around the new `PersonalizedRankingModel` `LocalDatabase` field
* `picture_capture_helper.dart`: new `uploadCapturedPicture` parameter in order to embed snackbar
* `product_image_gallery_view.dart`: added "interesting barcode" feature; new `fetchAndRefresh` parameters; new `uploadCapturedPicture` parameter
* `product_list_item_simple.dart`: added "interesting barcode" feature
* `product_list_page.dart`: added "interesting barcode" / downloaded feature
* `product_model.dart`: refactored around the new `LocalDatabase` field; added "interesting barcode" / downloaded feature
* `product_refresher.dart`: new `fetchAndRefresh` parameters, in order to embed a snackbar
* `simple_input_page.dart`: new `addTask` parameter
* `summary_card.dart`: added "interesting barcode" feature; new `fetchAndRefresh` parameters
* `up_to_date_product_provider.dart`: added "interesting barcode" and "latest downloaded" features
  • Loading branch information
monsieurtanuki committed Oct 28, 2022
1 parent 26293c3 commit 24c1579
Show file tree
Hide file tree
Showing 21 changed files with 257 additions and 166 deletions.
17 changes: 16 additions & 1 deletion packages/smooth_app/lib/background/background_task_details.dart
@@ -1,8 +1,10 @@
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:openfoodfacts/utils/CountryHelper.dart';
import 'package:smooth_app/background/abstract_background_task.dart';
import 'package:smooth_app/generic_lib/duration_constants.dart';
import 'package:smooth_app/query/product_query.dart';
import 'package:task_manager/task_manager.dart';

Expand Down Expand Up @@ -68,6 +70,7 @@ class BackgroundTaskDetails extends AbstractBackgroundTask {
final Product minimalistProduct, {
final List<ProductEditTask>? productEditTasks,
final ProductEditTask? productEditTask,
required final State<StatefulWidget> widget,
}) async {
final String code;
if (productEditTask != null) {
Expand Down Expand Up @@ -104,6 +107,18 @@ class BackgroundTaskDetails extends AbstractBackgroundTask {
uniqueId: uniqueId,
),
);
if (!widget.mounted) {
return;
}
final BuildContext context = widget.context;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
AppLocalizations.of(context).product_task_background_schedule,
),
duration: SnackBarDuration.medium,
),
);
}

/// Uploads the product changes.
Expand Down
Expand Up @@ -46,15 +46,11 @@ class _ImageUploadCardState extends State<ImageUploadCard> {
return;
}
await uploadCapturedPicture(
context,
widget: this,
barcode: widget.product.barcode!,
imageField: widget.productImageData.imageField,
imageUri: croppedImageFile.uri,
);

if (!mounted) {
return;
}
}
}

Expand Down
Expand Up @@ -13,9 +13,15 @@ enum LoadingStatus {

/// Model that computes the scores and sorts the barcodes accordingly.
class PersonalizedRankingModel with ChangeNotifier {
PersonalizedRankingModel(this.initialBarcodes);
PersonalizedRankingModel(
this.initialBarcodes,
this.localDatabase,
) {
initialBarcodes.forEach(localDatabase.upToDate.showInterest);
}

final List<String> initialBarcodes;
final LocalDatabase localDatabase;

late LoadingStatus _loadingStatus;
String? _loadingError;
Expand All @@ -27,9 +33,14 @@ class PersonalizedRankingModel with ChangeNotifier {

int? _timestamp;

@override
void dispose() {
initialBarcodes.forEach(localDatabase.upToDate.loseInterest);
super.dispose();
}

/// Refreshes the computations.
Future<void> refresh(
final LocalDatabase localDatabase,
final ProductPreferences productPreferences,
) async {
_clear();
Expand All @@ -56,6 +67,7 @@ class PersonalizedRankingModel with ChangeNotifier {

/// Removes a barcode from the barcodes and from the scores.
void dismiss(final String barcode) {
localDatabase.upToDate.loseInterest(barcode);
initialBarcodes.remove(barcode);
int? index;
int i = 0;
Expand Down Expand Up @@ -98,6 +110,6 @@ class PersonalizedRankingModel with ChangeNotifier {
return _loadingStatus == LoadingStatus.LOADED;
}

bool needsRefresh(final LocalDatabase localDatabase) =>
bool needsRefresh() =>
localDatabase.upToDate.needsRefresh(_timestamp, initialBarcodes);
}
Expand Up @@ -10,6 +10,16 @@ class UpToDateProductProvider {
final Map<String, Product> _map = <String, Product>{};
final Map<String, int> _timestamps = <String, int>{};

/// Latest downloaded product for a barcode.
final Map<String, Product> _latestDownloadedProducts = <String, Product>{};

/// Barcodes currently displayed in the app.
///
/// We need to know which barcodes are "interesting" because we need to cache
/// products in memory for instant access. And we should cache only them,
/// because we cannot cache all products in memory.
final Map<String, int> _interestingBarcodes = <String, int>{};

Product? get(final Product product) => _map[product.barcode!];

Product? getFromBarcode(final String barcode) => _map[barcode];
Expand All @@ -34,4 +44,58 @@ class UpToDateProductProvider {
}
return false;
}

/// Shows an interest for a barcode.
///
/// Typically, to be used by a widget in `initState`.
void showInterest(final String barcode) {
final int result = (_interestingBarcodes[barcode] ?? 0) + 1;
_interestingBarcodes[barcode] = result;
}

/// Loses interest for a barcode.
///
/// Typically, to be used by a widget in `dispose`.
void loseInterest(final String barcode) {
final int result = (_interestingBarcodes[barcode] ?? 0) - 1;
if (result <= 0) {
_interestingBarcodes.remove(barcode);
_latestDownloadedProducts.remove(barcode);
_timestamps.remove(barcode);
} else {
_interestingBarcodes[barcode] = result;
}
}

/// Typical use-case: a product page is refreshed through a pull-gesture.
void setLatestDownloadedProduct(
final Product product, {
final bool notify = true,
}) {
_latestDownloadedProducts[product.barcode!] = product;
_timestamps[product.barcode!] = LocalDatabase.nowInMillis();
if (notify) {
localDatabase.notifyListeners();
}
}

/// Typical use-case: a product list page is refreshed through a pull-gesture.
void setLatestDownloadedProducts(
final Iterable<Product> products, {
final bool notify = true,
}) {
if (_interestingBarcodes.isEmpty) {
return;
}
bool atLeastOne = false;
for (final Product product in products) {
if (_interestingBarcodes.containsKey(product.barcode)) {
atLeastOne = true;
setLatestDownloadedProduct(product, notify: false);
}
}
if (notify && atLeastOne) {
localDatabase.notifyListeners();
}
}
}
17 changes: 9 additions & 8 deletions packages/smooth_app/lib/helpers/picture_capture_helper.dart
Expand Up @@ -8,31 +8,32 @@ import 'package:smooth_app/data_models/continuous_scan_model.dart';
import 'package:smooth_app/database/local_database.dart';
import 'package:smooth_app/generic_lib/duration_constants.dart';

Future<bool> uploadCapturedPicture(
BuildContext context, {
Future<bool> uploadCapturedPicture({
required String barcode,
required ImageField imageField,
required Uri imageUri,
required State<StatefulWidget> widget,
}) async {
final AppLocalizations appLocalizations = AppLocalizations.of(context);
final LocalDatabase localDatabase = context.read<LocalDatabase>();
final AppLocalizations appLocalizations = AppLocalizations.of(widget.context);
final LocalDatabase localDatabase = widget.context.read<LocalDatabase>();
await BackgroundTaskImage.addTask(
barcode,
imageField: imageField,
imageFile: File(imageUri.path),
);
localDatabase.notifyListeners();
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context).showSnackBar(
if (!widget.mounted) {
return true;
}
ScaffoldMessenger.of(widget.context).showSnackBar(
SnackBar(
content: Text(
appLocalizations.image_upload_queued,
),
duration: SnackBarDuration.medium,
),
);
//ignore: use_build_context_synchronously
await _updateContinuousScanModel(context, barcode);
await _updateContinuousScanModel(widget.context, barcode);
return true;
}

Expand Down
@@ -1,13 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:matomo_tracker/matomo_tracker.dart';
import 'package:openfoodfacts/model/KnowledgePanel.dart';
import 'package:openfoodfacts/model/KnowledgePanelElement.dart';
import 'package:openfoodfacts/model/Product.dart';
import 'package:provider/provider.dart';
import 'package:smooth_app/database/local_database.dart';
import 'package:smooth_app/generic_lib/design_constants.dart';
import 'package:smooth_app/generic_lib/duration_constants.dart';
import 'package:smooth_app/generic_lib/widgets/smooth_card.dart';
import 'package:smooth_app/knowledge_panel/knowledge_panels/knowledge_panel_expanded_card.dart';
import 'package:smooth_app/knowledge_panel/knowledge_panels_builder.dart';
Expand Down Expand Up @@ -37,11 +35,20 @@ class _KnowledgePanelPageState extends State<KnowledgePanelPage>
String get traceName => 'Opened full knowledge panel page';

late Product _product;
late final LocalDatabase _localDatabase;

@override
void initState() {
super.initState();
_product = widget.product;
_localDatabase = context.read<LocalDatabase>();
_localDatabase.upToDate.showInterest(_product.barcode!);
}

@override
void dispose() {
_localDatabase.upToDate.loseInterest(_product.barcode!);
super.dispose();
}

static KnowledgePanelPanelGroupElement? _groupElementOf(
Expand Down Expand Up @@ -86,32 +93,18 @@ class _KnowledgePanelPageState extends State<KnowledgePanelPage>
);
}

Future<bool> _refreshProduct(BuildContext context) async {
Future<void> _refreshProduct(BuildContext context) async {
try {
if (InheritedDataManager.of(context).currentBarcode.isNotEmpty) {
final LocalDatabase localDatabase = context.read<LocalDatabase>();
final bool result = await ProductRefresher().fetchAndRefresh(
context: context,
localDatabase: localDatabase,
barcode: InheritedDataManager.of(context).currentBarcode,
);
if (mounted && result) {
final AppLocalizations appLocalizations =
AppLocalizations.of(context);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(appLocalizations.product_refreshed),
duration: SnackBarDuration.short,
),
);
}
return result;
} else {
return false;
final String barcode = InheritedDataManager.of(context).currentBarcode;
if (barcode.isEmpty) {
return;
}
await ProductRefresher().fetchAndRefresh(
barcode: barcode,
widget: this,
);
} catch (e) {
//no refreshing during onboarding
return false;
}
}

Expand Down
14 changes: 8 additions & 6 deletions packages/smooth_app/lib/pages/personalized_ranking_page.dart
Expand Up @@ -47,14 +47,17 @@ class _PersonalizedRankingPageState extends State<PersonalizedRankingPage>
@override
void initState() {
super.initState();
_model = PersonalizedRankingModel(widget.barcodes);
_model = PersonalizedRankingModel(
widget.barcodes,
context.read<LocalDatabase>(),
);
}

@override
Widget build(BuildContext context) {
final ProductPreferences productPreferences =
context.watch<ProductPreferences>();
final LocalDatabase localDatabase = context.watch<LocalDatabase>();
context.watch<LocalDatabase>();
final AppLocalizations appLocalizations = AppLocalizations.of(context);
return SmoothScaffold(
appBar: AppBar(
Expand All @@ -68,11 +71,11 @@ class _PersonalizedRankingPageState extends State<PersonalizedRankingPage>
productPreferences.getCompactView();
if (_compactPreferences == null) {
_compactPreferences = compactPreferences;
_model.refresh(context.read<LocalDatabase>(), productPreferences);
_model.refresh(productPreferences);
} else {
bool refresh = !_compactPreferences!.equals(compactPreferences);
if (!refresh) {
refresh = _model.needsRefresh(localDatabase);
refresh = _model.needsRefresh();
}
if (refresh) {
// TODO(monsieurtanuki): could maybe be automatic with VisibilityDetector
Expand All @@ -84,8 +87,7 @@ class _PersonalizedRankingPageState extends State<PersonalizedRankingPage>
text: appLocalizations.refresh_with_new_preferences,
onPressed: () {
_compactPreferences = compactPreferences;
_model.refresh(
context.read<LocalDatabase>(), productPreferences);
_model.refresh(productPreferences);
},
),
),
Expand Down
Expand Up @@ -156,6 +156,7 @@ class _AddBasicDetailsPageState extends State<AddBasicDetailsPage> {
await BackgroundTaskDetails.addTask(
inputProduct,
productEditTask: ProductEditTask.basic,
widget: this,
);
final Product upToDateProduct =
cachedProduct ?? inputProduct;
Expand Down
15 changes: 15 additions & 0 deletions packages/smooth_app/lib/pages/product/add_new_product_page.dart
Expand Up @@ -43,10 +43,25 @@ class _AddNewProductPageState extends State<AddNewProductPage> {
final Map<ImageField, List<File>> _uploadedImages =
<ImageField, List<File>>{};

late final LocalDatabase _localDatabase;

bool _nutritionFactsAdded = false;
bool _basicDetailsAdded = false;
bool _isProductLoaded = false;

@override
void initState() {
super.initState();
_localDatabase = context.read<LocalDatabase>();
_localDatabase.upToDate.showInterest(widget.barcode);
}

@override
void dispose() {
_localDatabase.upToDate.loseInterest(widget.barcode);
super.dispose();
}

@override
Widget build(BuildContext context) {
final AppLocalizations appLocalizations = AppLocalizations.of(context);
Expand Down

0 comments on commit 24c1579

Please sign in to comment.