diff --git a/packages/smooth_app/lib/background/background_task_download_products.dart b/packages/smooth_app/lib/background/background_task_download_products.dart index 4366496be90..79c2171585d 100644 --- a/packages/smooth_app/lib/background/background_task_download_products.dart +++ b/packages/smooth_app/lib/background/background_task_download_products.dart @@ -112,6 +112,7 @@ class BackgroundTaskDownloadProducts extends BackgroundTaskProgressing { if (downloadFlag & flagMaskExcludeKP != 0) { fields.remove(ProductField.KNOWLEDGE_PANELS); } + final OpenFoodFactsLanguage language = ProductQuery.getLanguage(); final SearchResult searchResult = await OpenFoodAPIClient.searchProducts( getUser(), ProductSearchQueryConfiguration( @@ -121,7 +122,7 @@ class BackgroundTaskDownloadProducts extends BackgroundTaskProgressing { const PageNumber(page: 1), BarcodeParameter.list(barcodes), ], - language: ProductQuery.getLanguage(), + language: language, country: ProductQuery.getCountry(), version: ProductQuery.productQueryVersion, ), @@ -134,7 +135,7 @@ class BackgroundTaskDownloadProducts extends BackgroundTaskProgressing { final DaoProduct daoProduct = DaoProduct(localDatabase); for (final Product product in downloadedProducts) { if (await _shouldBeUpdated(daoProduct, product.barcode!)) { - await daoProduct.put(product); + await daoProduct.put(product, language); } } final int deleted = await daoWorkBarcode.deleteBarcodes(work, barcodes); diff --git a/packages/smooth_app/lib/data_models/onboarding_data_product.dart b/packages/smooth_app/lib/data_models/onboarding_data_product.dart index 417be1025a9..4deb889c166 100644 --- a/packages/smooth_app/lib/data_models/onboarding_data_product.dart +++ b/packages/smooth_app/lib/data_models/onboarding_data_product.dart @@ -38,6 +38,7 @@ class OnboardingDataProduct extends AbstractOnboardingData { OpenFoodAPIClient.getProductString( ProductRefresher().getBarcodeQueryConfiguration( AbstractOnboardingData.barcode, + ProductQuery.getLanguage(), ), uriHelper: ProductQuery.uriProductHelper, ).timeout(SnackBarDuration.long); diff --git a/packages/smooth_app/lib/data_models/query_product_list_supplier.dart b/packages/smooth_app/lib/data_models/query_product_list_supplier.dart index e762a032f88..8d624b2058e 100644 --- a/packages/smooth_app/lib/data_models/query_product_list_supplier.dart +++ b/packages/smooth_app/lib/data_models/query_product_list_supplier.dart @@ -23,7 +23,10 @@ class QueryProductListSupplier extends ProductListSupplier { productList.setAll(searchResult.products!); productList.totalSize = searchResult.count ?? 0; partialProductList.add(productList); - await DaoProduct(localDatabase).putAll(searchResult.products!); + await DaoProduct(localDatabase).putAll( + searchResult.products!, + productQuery.language, + ); } await DaoProductList(localDatabase).put(productList); return null; diff --git a/packages/smooth_app/lib/database/dao_hive_product.dart b/packages/smooth_app/lib/database/dao_hive_product.dart index 8f29bfc0757..40aee8a6515 100644 --- a/packages/smooth_app/lib/database/dao_hive_product.dart +++ b/packages/smooth_app/lib/database/dao_hive_product.dart @@ -3,7 +3,6 @@ import 'dart:convert'; import 'package:hive/hive.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:smooth_app/database/abstract_dao.dart'; -import 'package:smooth_app/database/dao_product_migration.dart'; import 'package:smooth_app/database/local_database.dart'; /// Hive type adapter for [Product] @@ -24,7 +23,7 @@ class _ProductAdapter extends TypeAdapter { /// But Hive needs it - it doesn't like data to be removed... /// Where we store the products as "barcode => product". @Deprecated('use [DaoProduct] instead') -class DaoHiveProduct extends AbstractDao implements DaoProductMigrationSource { +class DaoHiveProduct extends AbstractDao { @Deprecated('use [DaoProduct] instead') DaoHiveProduct(final LocalDatabase localDatabase) : super(localDatabase); @@ -35,48 +34,4 @@ class DaoHiveProduct extends AbstractDao implements DaoProductMigrationSource { @override void registerAdapter() => Hive.registerAdapter(_ProductAdapter()); - - LazyBox _getBox() => Hive.lazyBox(_hiveBoxName); - - Future get(final String barcode) async => _getBox().get(barcode); - - @override - Future> getAll(final List barcodes) async { - final LazyBox box = _getBox(); - final Map result = {}; - for (final String barcode in barcodes) { - final Product? product = await box.get(barcode); - if (product != null) { - result[barcode] = product; - } - } - return result; - } - - Future put(final Product product) async => putAll([product]); - - Future putAll(final Iterable products) async { - final Map upserts = {}; - for (final Product product in products) { - upserts[product.barcode!] = product; - } - await _getBox().putAll(upserts); - } - - @override - Future> getAllKeys() async { - final LazyBox box = _getBox(); - final List result = []; - for (final dynamic key in box.keys) { - result.add(key.toString()); - } - return result; - } - - // Just for the migration - @override - Future deleteAll(final List barcodes) async { - final LazyBox box = _getBox(); - await box.deleteAll(barcodes); - } } diff --git a/packages/smooth_app/lib/database/dao_product.dart b/packages/smooth_app/lib/database/dao_product.dart index 8b34ef144f0..b346f05d4f5 100644 --- a/packages/smooth_app/lib/database/dao_product.dart +++ b/packages/smooth_app/lib/database/dao_product.dart @@ -8,12 +8,10 @@ import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:smooth_app/database/abstract_sql_dao.dart'; import 'package:smooth_app/database/bulk_deletable.dart'; import 'package:smooth_app/database/bulk_manager.dart'; -import 'package:smooth_app/database/dao_product_migration.dart'; import 'package:smooth_app/database/local_database.dart'; import 'package:sqflite/sqflite.dart'; -class DaoProduct extends AbstractSqlDao - implements BulkDeletable, DaoProductMigrationDestination { +class DaoProduct extends AbstractSqlDao implements BulkDeletable { DaoProduct(super.localDatabase); static const String _TABLE_PRODUCT = 'gzipped_product'; @@ -21,11 +19,13 @@ class DaoProduct extends AbstractSqlDao static const String _TABLE_PRODUCT_COLUMN_GZIPPED_JSON = 'encoded_gzipped_json'; static const String _TABLE_PRODUCT_COLUMN_LAST_UPDATE = 'last_update'; + static const String _TABLE_PRODUCT_COLUMN_LANGUAGE = 'lc'; static const List _columns = [ _TABLE_PRODUCT_COLUMN_BARCODE, _TABLE_PRODUCT_COLUMN_GZIPPED_JSON, _TABLE_PRODUCT_COLUMN_LAST_UPDATE, + _TABLE_PRODUCT_COLUMN_LANGUAGE, ]; static FutureOr onUpgrade( @@ -41,6 +41,10 @@ class DaoProduct extends AbstractSqlDao ',$_TABLE_PRODUCT_COLUMN_LAST_UPDATE INT NOT NULL' ')'); } + if (oldVersion < 4) { + await db.execute('alter table $_TABLE_PRODUCT add column ' + '$_TABLE_PRODUCT_COLUMN_LANGUAGE TEXT'); + } } /// Returns the [Product] that matches the [barcode], or null. @@ -87,17 +91,28 @@ class DaoProduct extends AbstractSqlDao return result; } - Future put(final Product product) async => putAll([product]); + Future put( + final Product product, + final OpenFoodFactsLanguage language, + ) async => + putAll( + [product], + language, + ); /// Replaces products in database - @override - Future putAll(final Iterable products) async => + Future putAll( + final Iterable products, + final OpenFoodFactsLanguage language, + ) async => localDatabase.database.transaction( - (final Transaction transaction) async => - _bulkReplaceLoop(transaction, products), + (final Transaction transaction) async => _bulkReplaceLoop( + transaction, + products, + language, + ), ); - @override Future> getAllKeys() async { final List result = []; final List> queryResults = @@ -126,6 +141,7 @@ class DaoProduct extends AbstractSqlDao Future _bulkReplaceLoop( final DatabaseExecutor databaseExecutor, final Iterable products, + final OpenFoodFactsLanguage language, ) async { final int lastUpdate = LocalDatabase.nowInMillis(); final BulkManager bulkManager = BulkManager(); @@ -138,6 +154,7 @@ class DaoProduct extends AbstractSqlDao ), ); insertParameters.add(lastUpdate); + insertParameters.add(language.offTag); } await bulkManager.insert( bulkInsertable: this, @@ -223,6 +240,7 @@ class DaoProduct extends AbstractSqlDao await localDatabase.database.rawQuery(''' select sum(length($_TABLE_PRODUCT_COLUMN_BARCODE)) + sum(length($_TABLE_PRODUCT_COLUMN_LAST_UPDATE)) + + sum(length($_TABLE_PRODUCT_COLUMN_LANGUAGE)) + sum(length($_TABLE_PRODUCT_COLUMN_GZIPPED_JSON)) from $_TABLE_PRODUCT '''), diff --git a/packages/smooth_app/lib/database/dao_product_migration.dart b/packages/smooth_app/lib/database/dao_product_migration.dart deleted file mode 100644 index cc504fca682..00000000000 --- a/packages/smooth_app/lib/database/dao_product_migration.dart +++ /dev/null @@ -1,55 +0,0 @@ -import 'package:openfoodfacts/openfoodfacts.dart'; - -/// Helper around product data migration. -abstract class DaoProductMigration { - Future> getAllKeys(); - - /// Migrates product data from a [source] to a [destination]. - /// - /// Will empty the source in the end if successful. - static Future migrate({ - required final DaoProductMigrationSource source, - required final DaoProductMigrationDestination destination, - }) async { - final List barcodesFrom = await source.getAllKeys(); - if (barcodesFrom.isEmpty) { - // nothing to migrate, or already migrated and cleaned. - return; - } - - final List barcodesAlreadyThere = await destination.getAllKeys(); - - final List barcodesToBeCopied = List.from(barcodesFrom); - barcodesToBeCopied.removeWhere( - (final String barcode) => barcodesAlreadyThere.contains(barcode)); - - if (barcodesToBeCopied.isNotEmpty) { - final Map copiedProducts = - await source.getAll(barcodesToBeCopied); - await destination.putAll(copiedProducts.values); - final List barcodesFinallyThere = await destination.getAllKeys(); - if (barcodesFinallyThere.length != - barcodesAlreadyThere.length + barcodesToBeCopied.length) { - throw Exception('Unexpected difference between counts'); - } - } - - // cleaning the old product table - await source.deleteAll(barcodesFrom); - final List barcodesNoMore = await source.getAllKeys(); - if (barcodesNoMore.isNotEmpty) { - throw Exception('Unexpected not empty source'); - } - } -} - -/// Source of a dao product migration. -abstract class DaoProductMigrationSource implements DaoProductMigration { - Future> getAll(final List barcodes); - Future deleteAll(final List barcodes); -} - -/// Destination of a dao product migration. -abstract class DaoProductMigrationDestination implements DaoProductMigration { - Future putAll(final Iterable products); -} diff --git a/packages/smooth_app/lib/database/local_database.dart b/packages/smooth_app/lib/database/local_database.dart index f6a2d4336cf..a1a282929fd 100644 --- a/packages/smooth_app/lib/database/local_database.dart +++ b/packages/smooth_app/lib/database/local_database.dart @@ -62,7 +62,7 @@ class LocalDatabase extends ChangeNotifier { final String databasePath = join(databasesRootPath, 'smoothie.db'); final Database database = await openDatabase( databasePath, - version: 3, + version: 4, singleInstance: true, onUpgrade: _onUpgrade, ); diff --git a/packages/smooth_app/lib/pages/product/common/product_list_page.dart b/packages/smooth_app/lib/pages/product/common/product_list_page.dart index b91b04a3222..de5ffe7a3d7 100644 --- a/packages/smooth_app/lib/pages/product/common/product_list_page.dart +++ b/packages/smooth_app/lib/pages/product/common/product_list_page.dart @@ -478,16 +478,20 @@ class _ProductListPageState extends State final LocalDatabase localDatabase, ) async { try { + final OpenFoodFactsLanguage language = ProductQuery.getLanguage(); final SearchResult searchResult = await OpenFoodAPIClient.searchProducts( ProductQuery.getUser(), - ProductRefresher().getBarcodeListQueryConfiguration(barcodes), + ProductRefresher().getBarcodeListQueryConfiguration( + barcodes, + language, + ), uriHelper: ProductQuery.uriProductHelper, ); final List? freshProducts = searchResult.products; if (freshProducts == null) { return false; } - await DaoProduct(localDatabase).putAll(freshProducts); + await DaoProduct(localDatabase).putAll(freshProducts, language); localDatabase.upToDate.setLatestDownloadedProducts(freshProducts); final RobotoffInsightHelper robotoffInsightHelper = RobotoffInsightHelper(localDatabase); diff --git a/packages/smooth_app/lib/pages/product/common/product_refresher.dart b/packages/smooth_app/lib/pages/product/common/product_refresher.dart index 5b5a815cce6..f9d2a8f2e99 100644 --- a/packages/smooth_app/lib/pages/product/common/product_refresher.dart +++ b/packages/smooth_app/lib/pages/product/common/product_refresher.dart @@ -74,23 +74,25 @@ class ProductRefresher { /// Returns the standard configuration for barcode product query. ProductQueryConfiguration getBarcodeQueryConfiguration( final String barcode, + final OpenFoodFactsLanguage language, ) => ProductQueryConfiguration( barcode, fields: ProductQuery.fields, - language: ProductQuery.getLanguage(), + language: language, country: ProductQuery.getCountry(), version: ProductQuery.productQueryVersion, ); /// Returns the standard configuration for several barcodes product query. ProductSearchQueryConfiguration getBarcodeListQueryConfiguration( - final List barcodes, { + final List barcodes, + final OpenFoodFactsLanguage language, { final List? fields, }) => ProductSearchQueryConfiguration( fields: fields ?? ProductQuery.fields, - language: ProductQuery.getLanguage(), + language: language, country: ProductQuery.getCountry(), parametersList: [ BarcodeParameter.list(barcodes), @@ -159,12 +161,16 @@ class ProductRefresher { required final String barcode, }) async { try { + final OpenFoodFactsLanguage language = ProductQuery.getLanguage(); final ProductResultV3 result = await OpenFoodAPIClient.getProductV3( - getBarcodeQueryConfiguration(barcode), + getBarcodeQueryConfiguration( + barcode, + language, + ), uriHelper: ProductQuery.uriProductHelper, ); if (result.product != null) { - await DaoProduct(localDatabase).put(result.product!); + await DaoProduct(localDatabase).put(result.product!, language); localDatabase.upToDate.setLatestDownloadedProduct(result.product!); localDatabase.notifyListeners(); return FetchedProduct.found(result.product!); @@ -198,15 +204,16 @@ class ProductRefresher { final List barcodes, ) async { try { + final OpenFoodFactsLanguage language = ProductQuery.getLanguage(); final SearchResult searchResult = await OpenFoodAPIClient.searchProducts( ProductQuery.getUser(), - getBarcodeListQueryConfiguration(barcodes), + getBarcodeListQueryConfiguration(barcodes, language), uriHelper: ProductQuery.uriProductHelper, ); if (searchResult.products == null) { return null; } - await DaoProduct(localDatabase).putAll(searchResult.products!); + await DaoProduct(localDatabase).putAll(searchResult.products!, language); localDatabase.upToDate .setLatestDownloadedProducts(searchResult.products!); localDatabase.notifyListeners();