Files
smooth-app/packages/smooth_app/lib/data_models/continuous_scan_model.dart
monsieurtanuki 9e253a774f feat: #2339 - added language and country to query product list keys (#2465)
What:
* added language and country to query product list keys
* that means that if I run a query for "pizza", not only the results will be different depending on the language and country (that was already the case), but the results will be cached accordingly - if I change language or country, I will see the cached results for those new language/country, not the results of the same query but with different language/country
* we needed that improvement in order to display correctly the "world" results; that was the first step
* also moved all the "query" classes to a specific folder
* fun fact: there are more "pizza"s in France than in Italy

Impacted files:
* `abstract_onboarding_data.dart`: minor refactoring
* `barcode_product_query.dart`: moved; minor refactoring
* `category_product_query.dart`: moved; added language and country to product list key
* `continuous_scan_model.dart`: minor refactoring
* `country_selector.dart`: minor refactoring
* `database_product_list_supplier.dart`: minor refactoring
* `keywords_product_query.dart`: moved; added language and country to product list key
* `main.dart`: minor refactoring
* `new_product_page.dart`: minor refactoring
* `nutrition_page_loaded.dart`: minor refactoring
* `ocr_helper.dart`: minor refactoring
* `onboarding_data_product.dart`: minor refactoring
* `ordered_nutrients_cache.dart`: minor refactoring
* `paged_product_query.dart`: moved; added language and country to product list key; refactored around `getProducts`
* `paged_search_product_query.dart`: moved; refactored
* `paged_to_be_completed_product_query.dart`: moved; added language and country to product list key; refactored
* `paged_user_product_query.dart`: moved; added language to product list key; refactored
* `picture_capture_helper.dart`: minor refactoring
* `product_dialog_helper.dart`: minor refactoring
* `product_list.dart`: added optional language and country to constructor parameters
* `product_list_import_export.dart`: minor refactoring
* `product_list_page.dart`: minor refactoring
* `product_list_supplier.dart`: minor refactoring
* `product_query.dart`: moved; not an interface anymore
* `product_query_model.dart`: minor refactoring
* `product_query_page_helper.dart`: minor refactoring
* `product_refresher.dart`: minor refactoring
* `query_product_list_supplier.dart`: minor refactoring
* `robotoff_insight_helper.dart`: minor refactoring
* `robotoff_questions_query.dart`: moved; minor refactoring
* `search_page.dart`: minor refactoring
* `simple_input_page_helpers.dart`: minor refactoring
* `summary_card.dart`: minor refactoring
* `user_preferences_account.dart`: minor refactoring
* `user_preferences_dev_mode.dart`: minor refactoring
2022-07-02 15:44:56 +02:00

269 lines
7.6 KiB
Dart

import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:openfoodfacts/model/Product.dart';
import 'package:smooth_app/data_models/fetched_product.dart';
import 'package:smooth_app/data_models/product_list.dart';
import 'package:smooth_app/database/dao_product.dart';
import 'package:smooth_app/database/dao_product_list.dart';
import 'package:smooth_app/database/local_database.dart';
import 'package:smooth_app/helpers/analytics_helper.dart';
import 'package:smooth_app/query/barcode_product_query.dart';
import 'package:smooth_app/services/smooth_services.dart';
enum ScannedProductState {
FOUND,
NOT_FOUND,
LOADING,
THANKS,
CACHED,
ERROR_INTERNET,
ERROR_INVALID_CODE,
}
class ContinuousScanModel with ChangeNotifier {
ContinuousScanModel();
final Map<String, ScannedProductState> _states =
<String, ScannedProductState>{};
final List<String> _barcodes = <String>[];
final ProductList _productList = ProductList.scanSession();
final ProductList _history = ProductList.history();
String? _latestScannedBarcode;
String? _latestFoundBarcode;
String? _latestConsultedBarcode;
String? _barcodeTrustCheck; // TODO(monsieurtanuki): could probably be removed
late DaoProduct _daoProduct;
late DaoProductList _daoProductList;
ProductList get productList => _productList;
List<String> getBarcodes() => _barcodes;
String? get latestConsultedBarcode => _latestConsultedBarcode;
set lastConsultedBarcode(String? barcode) {
_latestConsultedBarcode = barcode;
if (barcode != null) {
notifyListeners();
}
}
Future<ContinuousScanModel?> load(final LocalDatabase localDatabase) async {
try {
_daoProduct = DaoProduct(localDatabase);
_daoProductList = DaoProductList(localDatabase);
if (!await _refresh()) {
return null;
}
return this;
} catch (e) {
Logs.e('Load database error', ex: e);
}
return null;
}
Future<bool> _refresh() async {
try {
_latestScannedBarcode = null;
_latestFoundBarcode = null;
_barcodeTrustCheck = null;
_barcodes.clear();
_states.clear();
_latestScannedBarcode = null;
await refreshProductList();
for (final String barcode in _productList.barcodes) {
_barcodes.add(barcode);
_states[barcode] = ScannedProductState.CACHED;
_latestScannedBarcode = barcode;
}
return true;
} catch (e) {
Logs.e('Refresh database error', ex: e);
}
return false;
}
Future<void> refreshProductList() async => _daoProductList.get(_productList);
void _setBarcodeState(
final String barcode,
final ScannedProductState state,
) {
_states[barcode] = state;
notifyListeners();
}
ScannedProductState? getBarcodeState(final String barcode) =>
_states[barcode];
Product getProduct(final String barcode) => _productList.getProduct(barcode);
/// Adds a barcode
/// Will return [true] if this barcode is successfully added
Future<bool> onScan(String? code) async {
if (code == null) {
return false;
}
if (_barcodeTrustCheck != code) {
_barcodeTrustCheck = code;
return false;
}
if (_latestScannedBarcode == code || _barcodes.contains(code)) {
lastConsultedBarcode = code;
return false;
}
AnalyticsHelper.trackScannedProduct(barcode: code);
_latestScannedBarcode = code;
return _addBarcode(code);
}
Future<bool> onCreateProduct(String? barcode) async {
if (barcode == null) {
return false;
}
return _addBarcode(barcode);
}
Future<void> retryBarcodeFetch(String barcode) async {
_setBarcodeState(barcode, ScannedProductState.LOADING);
await _updateBarcode(barcode);
}
Future<bool> _addBarcode(final String barcode) async {
final ScannedProductState? state = getBarcodeState(barcode);
if (state == null || state == ScannedProductState.NOT_FOUND) {
if (!_barcodes.contains(barcode)) {
_barcodes.add(barcode);
}
_setBarcodeState(barcode, ScannedProductState.LOADING);
_cacheOrLoadBarcode(barcode);
lastConsultedBarcode = barcode;
return true;
}
if (state == ScannedProductState.FOUND ||
state == ScannedProductState.CACHED) {
final Product product = getProduct(barcode);
_barcodes.remove(barcode);
_barcodes.add(barcode);
_addProduct(product, state);
if (state == ScannedProductState.CACHED) {
_updateBarcode(barcode);
}
lastConsultedBarcode = barcode;
return true;
}
return false;
}
Future<void> _cacheOrLoadBarcode(final String barcode) async {
final bool cached = await _cachedBarcode(barcode);
if (!cached) {
_loadBarcode(barcode);
}
}
Future<bool> _cachedBarcode(final String barcode) async {
final Product? product = await _daoProduct.get(barcode);
if (product != null) {
_addProduct(product, ScannedProductState.CACHED);
return true;
}
return false;
}
Future<FetchedProduct> _queryBarcode(
final String barcode,
) async =>
BarcodeProductQuery(
barcode: barcode,
daoProduct: _daoProduct,
isScanned: true,
).getFetchedProduct();
Future<void> _loadBarcode(
final String barcode,
) async {
final FetchedProduct fetchedProduct = await _queryBarcode(barcode);
switch (fetchedProduct.status) {
case FetchedProductStatus.ok:
_addProduct(fetchedProduct.product!, ScannedProductState.FOUND);
return;
case FetchedProductStatus.internetNotFound:
_setBarcodeState(barcode, ScannedProductState.NOT_FOUND);
return;
case FetchedProductStatus.internetError:
_setBarcodeState(barcode, ScannedProductState.ERROR_INTERNET);
return;
case FetchedProductStatus.codeInvalid:
_setBarcodeState(barcode, ScannedProductState.ERROR_INVALID_CODE);
return;
case FetchedProductStatus.userCancelled:
// we do nothing
return;
}
}
Future<void> _updateBarcode(
final String barcode,
) async {
final FetchedProduct fetchedProduct = await _queryBarcode(barcode);
switch (fetchedProduct.status) {
case FetchedProductStatus.ok:
_addProduct(fetchedProduct.product!, ScannedProductState.FOUND);
return;
case FetchedProductStatus.internetNotFound:
_setBarcodeState(barcode, ScannedProductState.NOT_FOUND);
return;
case FetchedProductStatus.internetError:
_setBarcodeState(barcode, ScannedProductState.ERROR_INTERNET);
return;
case FetchedProductStatus.codeInvalid:
_setBarcodeState(barcode, ScannedProductState.ERROR_INVALID_CODE);
return;
case FetchedProductStatus.userCancelled:
// we do nothing
return;
}
}
Future<void> _addProduct(
final Product product,
final ScannedProductState state,
) async {
_productList.refresh(product);
if (_latestFoundBarcode != product.barcode!) {
_latestFoundBarcode = product.barcode;
_daoProductList.push(productList, _latestFoundBarcode!);
_daoProductList.push(_history, _latestFoundBarcode!);
_daoProductList.localDatabase.notifyListeners();
}
_setBarcodeState(product.barcode!, state);
}
Future<void> clearScanSession() async {
_daoProductList.clear(productList);
await refresh();
}
Future<void> removeBarcode(
final String barcode,
) async {
_daoProductList.set(
productList,
barcode,
false,
);
_barcodes.remove(barcode);
notifyListeners();
}
Future<void> refresh() async {
await _refresh();
notifyListeners();
}
}