Skip to content

Commit

Permalink
feature/#21 - scan now uses local database, for product look up and f…
Browse files Browse the repository at this point in the history
…or its history

New file:
* `scan_page.dart`: helper class to be used on top of `AlternativeContinuousScanPage` and `ContinuousScanPage`

Impacted files:
* `alternative_continuous_scan_page.dart`: `ContinuousScanModel` is now initialized upstream by `ScanPage`
* `continuous_scan_model.dart`: now a `ChangeNotifier`, uses a `ProductList` and database cache
* `continuous_scan_page.dart`: `ContinuousScanModel` is now initialized upstream by `ScanPage`
* `contribution_page.dart`: uses the new `ScanPage` widget instead of the 2 scan pages
* `local_database.dart`: removed the dummy notify listener method
* `main.dart`: uses the new `ScanPage` widget instead of the 2 scan pages
* `personalized_ranking_page.dart`: now we use a `ProductList` as input
* `product_list.dart`: added a `getProduct` method and a "scan list" identifier
* `product_page.dart`: unrelated UI fixes
* `product_query_page.dart`: slight refactoring
* `smooth_it_model.dart`: now we use `ProductList`
* `smooth_product_carousel.dart`: added the new "CACHED" product possibility; fixed an init bug
  • Loading branch information
monsieurtanuki committed Jan 16, 2021
1 parent cb7d803 commit 69c3dbf
Show file tree
Hide file tree
Showing 13 changed files with 413 additions and 354 deletions.
121 changes: 81 additions & 40 deletions packages/smooth_app/lib/data_models/continuous_scan_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,103 +5,144 @@ import 'package:qr_code_scanner/qr_code_scanner.dart';
import 'package:smooth_app/database/barcode_product_query.dart';
import 'package:smooth_app/database/dao_product.dart';
import 'package:smooth_app/database/local_database.dart';
import 'package:smooth_app/data_models/product_list.dart';
import 'package:smooth_app/database/dao_product_list.dart';

enum ScannedProductState {
FOUND,
NOT_FOUND,
LOADING,
THANKS,
CACHED,
}

class ContinuousScanModel {
class ContinuousScanModel with ChangeNotifier {
ContinuousScanModel({@required bool contributionMode})
: _contributionMode = contributionMode;

final Map<String, ScannedProductState> _states =
<String, ScannedProductState>{};
final Map<String, Product> _products = <String, Product>{};
final List<String> _barcodes = <String>[];
final ProductList _productList =
ProductList(listType: ProductList.LIST_TYPE_SCAN, parameters: '');

bool _contributionMode;
QRViewController _scannerController;
String _barcodeLatest;
String _barcodeTrustCheck;
LocalDatabase _localDatabase;
DaoProduct _daoProduct;
DaoProductList _daoProductList;

bool get isNotEmpty => getBarcodes().isNotEmpty;
bool get contributionMode => _contributionMode;
ProductList get productList => _productList;

List<String> getBarcodes() => _barcodes;

Future<ContinuousScanModel> load(final LocalDatabase localDatabase) async {
try {
_daoProduct = DaoProduct(localDatabase);
_daoProductList = DaoProductList(localDatabase);
await _daoProductList.get(_productList);
for (final String barcode in _productList.barcodes) {
_barcodes.add(barcode);
_states[barcode] = ScannedProductState.CACHED;
_barcodeLatest = barcode;
}
return this;
} catch (e) {
print('exception: $e');
}
return null;
}

void setBarcodeState(
final String barcode,
final ScannedProductState state,
) {
_states[barcode] = state;
_localDatabase.dummyNotifyListeners();
notifyListeners();
}

ScannedProductState getBarcodeState(final String barcode) => _states[barcode];

Product getProduct(final String barcode) => _products[barcode];
Product getProduct(final String barcode) => _productList.getProduct(barcode);

void setLocalDatabase(final LocalDatabase localDatabase) =>
_localDatabase = localDatabase;
void setupScanner(QRViewController controller) =>
controller.scannedDataStream.listen((String barcode) => onScan(barcode));

void setupScanner(QRViewController controller) {
_scannerController = controller;
_scannerController.scannedDataStream.listen(
(String barcode) => onScan(barcode),
);
}
List<Product> getProducts() => _productList.getList();

List<Product> getFoundProducts() {
final List<Product> result = <Product>[];
for (final String barcode in _barcodes) {
if (getBarcodeState(barcode) == ScannedProductState.FOUND) {
result.add(_products[barcode]);
}
}
return result;
}

Future<void> onScan(String code) async {
print('Barcode detected : $code');
Future<void> onScan(final String code) async {
if (_barcodeTrustCheck != code) {
_barcodeTrustCheck = code;
return;
}
_addBarcode(code);
}

Future<void> onScanAlt(String code, List<Offset> offsets) async {
print('Barcode detected : $code');
if (_barcodeLatest == code) {
return;
}
_barcodeLatest = code;
_addBarcode(code);
}

void contributionModeSwitch(bool value) {
if (_contributionMode != value) {
_contributionMode = value;
_localDatabase.dummyNotifyListeners();
notifyListeners();
}
}

bool _addBarcode(final String barcode) {
if (getBarcodeState(barcode) == null) {
Future<bool> _addBarcode(final String barcode) async {
final ScannedProductState state = getBarcodeState(barcode);
if (state == null) {
_barcodes.add(barcode);
setBarcodeState(barcode, ScannedProductState.LOADING);
_loadBarcode(barcode);
_cacheOrLoadBarcode(barcode);
return true;
}
if (state == ScannedProductState.FOUND ||
state == ScannedProductState.CACHED) {
final Product product = getProduct(barcode);
_barcodes.add(barcode);
_addProduct(product, state);
return true;
}
return false;
}

Future<void> _loadBarcode(final String barcode) async {
Future<void> _cacheOrLoadBarcode(final String barcode) async {
final bool cached = await _cachedBarcode(barcode);
if (!cached) {
_loadBarcode(barcode, ScannedProductState.NOT_FOUND);
}
}

Future<bool> _cachedBarcode(final String barcode) async {
final int latestUpdate = await _daoProduct.getLastUpdate(barcode);
if (latestUpdate != null) {
_addProduct(await _daoProduct.get(barcode), ScannedProductState.CACHED);
return true;
}
return false;
}

Future<void> _loadBarcode(
final String barcode,
final ScannedProductState notFound,
) async {
final Product product = await BarcodeProductQuery(barcode).getProduct();
if (product != null) {
await DaoProduct(_localDatabase).put(product);
_products[barcode] = product;
setBarcodeState(barcode, ScannedProductState.FOUND);
_addProduct(product, ScannedProductState.FOUND);
} else {
setBarcodeState(barcode, ScannedProductState.NOT_FOUND);
setBarcodeState(barcode, notFound);
}
}

Future<void> _addProduct(
final Product product,
final ScannedProductState state,
) async {
_productList.add(product);
_daoProductList.put(_productList);
setBarcodeState(product.barcode, state);
}
}
22 changes: 22 additions & 0 deletions packages/smooth_app/lib/data_models/product_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class ProductList {

static const String LIST_TYPE_HTTP_SEARCH_GROUP = 'http/search/group';
static const String LIST_TYPE_HTTP_SEARCH_KEYWORDS = 'http/search/keywords';
static const String LIST_TYPE_SCAN = 'scan';

List<String> get barcodes => _barcodes;

Expand All @@ -25,6 +26,8 @@ class ProductList {
_products.clear();
}

Product getProduct(final String barcode) => _products[barcode];

void add(final Product product) {
if (product == null) {
throw Exception('null product');
Expand Down Expand Up @@ -59,4 +62,23 @@ class ProductList {
}
return result;
}

List<Product> getUniqueList({final bool ascending = true}) {
final List<Product> result = <Product>[];
final Set<String> done = <String>{};
final Iterable<String> orderedBarcodes =
ascending ? _barcodes : _barcodes.reversed;
for (final String barcode in orderedBarcodes) {
if (done.contains(barcode)) {
continue;
}
done.add(barcode);
final Product product = _products[barcode];
if (product == null) {
throw Exception('no product for barcode $barcode');
}
result.add(product);
}
return result;
}
}
4 changes: 3 additions & 1 deletion packages/smooth_app/lib/data_models/smooth_it_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:smooth_app/structures/ranked_product.dart';
import 'package:smooth_app/data_models/user_preferences_model.dart';
import 'package:smooth_app/data_models/match.dart';
import 'package:smooth_app/temp/user_preferences.dart';
import 'package:smooth_app/data_models/product_list.dart';

class SmoothItModel {
static const int MATCH_INDEX_YES = 0;
Expand All @@ -16,14 +17,15 @@ class SmoothItModel {
bool _nextRefreshIsJustChangingTabs = false;

void refresh(
final List<Product> unprocessedProducts,
final ProductList productList,
final UserPreferences userPreferences,
final UserPreferencesModel userPreferencesModel,
) {
if (_nextRefreshIsJustChangingTabs) {
_nextRefreshIsJustChangingTabs = false;
return;
}
final List<Product> unprocessedProducts = productList.getUniqueList();
_allProducts =
Match.sort(unprocessedProducts, userPreferences, userPreferencesModel);
_categorizedProducts.clear();
Expand Down
4 changes: 0 additions & 4 deletions packages/smooth_app/lib/database/local_database.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,6 @@ class LocalDatabase extends ChangeNotifier {
}

static int nowInMillis() => DateTime.now().millisecondsSinceEpoch;

void dummyNotifyListeners() {
notifyListeners(); // TODO(monsieurtanuki): create a ScanNotifier instead
}
}

class TableStats {
Expand Down
6 changes: 5 additions & 1 deletion packages/smooth_app/lib/lists/smooth_product_carousel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ class _SmoothProductCarouselState extends State<SmoothProductCarousel> {
if (_length != barcodesLength) {
_length = barcodesLength;
if (_length > 1) {
_controller.animateToPage(_length - 1);
Future<void>.delayed(
const Duration(seconds: 0),
() => _controller.animateToPage(_length - 1),
);
}
}
return CarouselSlider.builder(
Expand All @@ -55,6 +58,7 @@ class _SmoothProductCarouselState extends State<SmoothProductCarousel> {
final Product product = widget.continuousScanModel.getProduct(barcode);
switch (widget.continuousScanModel.getBarcodeState(barcode)) {
case ScannedProductState.FOUND:
case ScannedProductState.CACHED:
if (widget.continuousScanModel.contributionMode) {
return SmoothProductCardEdit(heroTag: barcode, product: product);
}
Expand Down
22 changes: 10 additions & 12 deletions packages/smooth_app/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ import 'package:sentry/sentry.dart';
import 'package:provider/provider.dart';

import 'package:smooth_app/data_models/user_preferences_model.dart';
import 'package:smooth_app/pages/alternative_continuous_scan_page.dart';
import 'package:smooth_app/pages/scan_page.dart';
import 'package:smooth_app/pages/choose_page.dart';
import 'package:smooth_app/pages/contribution_page.dart';
import 'package:smooth_app/pages/continuous_scan_page.dart';
import 'package:smooth_app/pages/profile_page.dart';
import 'package:smooth_app/pages/tracking_page.dart';
import 'package:smooth_app/themes/smooth_theme.dart';
Expand Down Expand Up @@ -222,16 +221,15 @@ class SmoothApp extends StatelessWidget {
icon: 'assets/actions/scanner_alt_2.svg',
iconPadding: _navigationIconPadding,
iconSize: _navigationIconSize,
onTap: () {
final Widget newPage = mlKitState
? const ContinuousScanPage()
: const AlternativeContinuousScanPage();
Navigator.push<Widget>(
context,
MaterialPageRoute<Widget>(
builder: (BuildContext context) => newPage),
);
},
onTap: () => Navigator.push<Widget>(
context,
MaterialPageRoute<Widget>(
builder: (BuildContext context) => ScanPage(
contributionMode: false,
mlKit: mlKitState,
),
),
),
),
);
}
Loading

0 comments on commit 69c3dbf

Please sign in to comment.