import 'dart:async'; import 'dart:convert'; import 'package:hive/hive.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:smooth_app/database/abstract_dao.dart'; /// Transient operation: a minimalist [product] with a [key]. /// /// The [key] has nothing to do with the barcode, it's an operation key. /// Typically, the [key] may include: /// * a type ("it's a detail change") /// * a sequential id (in order to sort operations) class TransientOperation { const TransientOperation(this.key, this.product); final String key; final Product product; @override String toString() => 'TransientOperation($key})'; } /// Hive type adapter for [Product] class _ProductAdapter extends TypeAdapter { @override final int typeId = 2; @override Product read(BinaryReader reader) => Product.fromJson(jsonDecode(reader.readString()) as Map); @override void write(BinaryWriter writer, Product obj) => writer.writeString(jsonEncode(obj.toJson())); } /// Where we store products for transient operations. /// /// Use [DaoProduct] instead if you want to enrich the local product database. /// /// This class is only for transient operations. /// For instance, when you submit a change, we store the changes here, /// while async'ly saving them on the server. This way we can take into account /// those changes in the app before they are actually saved on the server. And /// typically, when we got an ACK from the server, we can remove that change /// from the pending changes. /// /// It is open to different uses, therefore the key is just a stupid String. /// Of course, to make those different uses compatible, the key should be /// properly designed, e.g. with all keys starting with `'USECASEx;'` /// /// This is not a Lazy database, because: /// * it's supposed to be little anyway, so we can load it at startup /// * the way we use it may sometimes require instant access (no `await`) class DaoTransientOperation extends AbstractDao { DaoTransientOperation(super.localDatabase); static const String _hiveBoxName = 'transientOperations'; @override Future init() async => Hive.openBox(_hiveBoxName); @override void registerAdapter() => Hive.registerAdapter(_ProductAdapter()); Box _getBox() => Hive.box(_hiveBoxName); Product? get(final String key) => _getBox().get(key); Future put(final String key, final Product product) => _getBox().put(key, product); Future delete(final String key) async => _getBox().delete(key); List getAllKeys() { final Box box = _getBox(); final List result = []; for (final dynamic key in box.keys) { result.add(key.toString()); } return result; } Iterable getAll(final String barcode) { final Box box = _getBox(); final List result = []; for (final dynamic key in box.keys) { final Product? product = box.get(key); if (product == null) { // very unlikely continue; } if (product.barcode != barcode) { continue; } result.add(TransientOperation(key.toString(), product)); } return result; } }