fixes before build
This commit is contained in:
@@ -84,6 +84,8 @@ class SettingsScope extends InheritedNotifier<AppSettings> {
|
||||
}
|
||||
|
||||
static AppSettings? maybeOf(BuildContext context) {
|
||||
return context.dependOnInheritedWidgetOfExactType<SettingsScope>()?.notifier;
|
||||
return context
|
||||
.dependOnInheritedWidgetOfExactType<SettingsScope>()
|
||||
?.notifier;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +126,7 @@ class BackupService {
|
||||
if (copy['normalized_name'] == null && copy['name'] is String) {
|
||||
copy['normalized_name'] = ProductRepository.normalizeName(
|
||||
copy['name']! as String,
|
||||
supermarket: copy['supermarket'] as String?,
|
||||
);
|
||||
}
|
||||
products.add(copy);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/// Local SQLite store of products and shopping trips.
|
||||
library;
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
@@ -16,6 +17,7 @@ import '../models/shop.dart';
|
||||
import '../models/shopping_list.dart';
|
||||
import '../models/supermarket.dart';
|
||||
import '../utils/money.dart';
|
||||
import '../utils/product_names.dart';
|
||||
import '../utils/supermarkets.dart';
|
||||
|
||||
class ProductRepository extends ChangeNotifier {
|
||||
@@ -35,7 +37,7 @@ class ProductRepository extends ChangeNotifier {
|
||||
p.join((await getApplicationDocumentsDirectory()).path, 'receipity.db');
|
||||
return openDatabase(
|
||||
path,
|
||||
version: 7,
|
||||
version: 9,
|
||||
onConfigure: (db) async {
|
||||
await db.execute('PRAGMA foreign_keys = ON');
|
||||
},
|
||||
@@ -72,6 +74,14 @@ class ProductRepository extends ChangeNotifier {
|
||||
if (oldVersion < 7) {
|
||||
await _upgradeShopItemsProductFk(db);
|
||||
}
|
||||
if (oldVersion < 8) {
|
||||
await db.execute(
|
||||
'ALTER TABLE supermarkets ADD COLUMN bonus_image_path TEXT',
|
||||
);
|
||||
}
|
||||
if (oldVersion < 9) {
|
||||
await _upgradeProductNormalizedNamesV9(db);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -107,7 +117,8 @@ class ProductRepository extends ChangeNotifier {
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
color INTEGER NOT NULL,
|
||||
sort_order INTEGER NOT NULL
|
||||
sort_order INTEGER NOT NULL,
|
||||
bonus_image_path TEXT
|
||||
)
|
||||
''');
|
||||
}
|
||||
@@ -222,8 +233,28 @@ class ProductRepository extends ChangeNotifier {
|
||||
''');
|
||||
}
|
||||
|
||||
static String normalizeName(String name) =>
|
||||
name.trim().toUpperCase().replaceAll(RegExp(r'\s+'), ' ');
|
||||
static String normalizeName(String name, {String? supermarket}) =>
|
||||
productConflictKey(name: name, supermarket: supermarket);
|
||||
|
||||
Future<void> _upgradeProductNormalizedNamesV9(DatabaseExecutor db) async {
|
||||
final rows = await db.query('products', columns: ['id', 'name', 'supermarket']);
|
||||
final used = <String>{};
|
||||
for (final row in rows) {
|
||||
final id = row['id']! as int;
|
||||
final name = row['name']! as String;
|
||||
final supermarket = row['supermarket'] as String?;
|
||||
var key = normalizeName(name, supermarket: supermarket);
|
||||
if (!used.add(key)) {
|
||||
key = '$key#$id';
|
||||
}
|
||||
await db.update(
|
||||
'products',
|
||||
{'normalized_name': key},
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> _persistImage(String sourcePath, String relativePath) async {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
@@ -308,7 +339,7 @@ class ProductRepository extends ChangeNotifier {
|
||||
required String supermarket,
|
||||
required String now,
|
||||
}) async {
|
||||
final key = normalizeName(item.name);
|
||||
final key = normalizeName(item.name, supermarket: supermarket);
|
||||
final existing = await txn.query(
|
||||
'products',
|
||||
where: 'normalized_name = ?',
|
||||
@@ -359,12 +390,12 @@ class ProductRepository extends ChangeNotifier {
|
||||
|
||||
Future<List<Product>> getAll({
|
||||
ProductSort sort = ProductSort.name,
|
||||
bool favouritesOnly = false,
|
||||
}) async {
|
||||
final db = await _database;
|
||||
final orderBy = switch (sort) {
|
||||
ProductSort.name => 'name COLLATE NOCASE ASC',
|
||||
ProductSort.preferred => 'times_seen DESC, name COLLATE NOCASE ASC',
|
||||
ProductSort.start => 'favourite DESC, name COLLATE NOCASE ASC',
|
||||
};
|
||||
final rows = await db.rawQuery('''
|
||||
SELECT
|
||||
@@ -378,12 +409,132 @@ class ProductRepository extends ChangeNotifier {
|
||||
LIMIT 1
|
||||
) AS display_price
|
||||
FROM products
|
||||
${favouritesOnly ? 'WHERE favourite = 1' : ''}
|
||||
ORDER BY $orderBy
|
||||
''');
|
||||
return rows.map(Product.fromMap).toList();
|
||||
}
|
||||
|
||||
Future<List<({int productId, String name, String supermarket})>>
|
||||
_shopLineNames() async {
|
||||
final db = await _database;
|
||||
final rows = await db.rawQuery('''
|
||||
SELECT shop_items.product_id, shop_items.name, shops.supermarket
|
||||
FROM shop_items
|
||||
JOIN shops ON shops.id = shop_items.shop_id
|
||||
WHERE shop_items.product_id IS NOT NULL
|
||||
''');
|
||||
return [
|
||||
for (final row in rows)
|
||||
if (_readProductId(row['product_id']) case final int id
|
||||
when row['name'] is String && row['supermarket'] is String)
|
||||
(
|
||||
productId: id,
|
||||
name: row['name']! as String,
|
||||
supermarket: row['supermarket']! as String,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
int? _readProductId(Object? value) {
|
||||
if (value is int) return value;
|
||||
if (value is num) return value.toInt();
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<ProductNameIssues> getProductNameIssues() async {
|
||||
final products = await getAll();
|
||||
return buildProductNameIssues(
|
||||
products: products,
|
||||
shopLines: await _shopLineNames(),
|
||||
dismissedKeys: await getDismissedNameIssues(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<Set<String>> getDismissedNameIssues() async {
|
||||
final db = await _database;
|
||||
final rows = await db.query(
|
||||
'app_settings',
|
||||
where: 'key = ?',
|
||||
whereArgs: ['dismissed_name_issues'],
|
||||
limit: 1,
|
||||
);
|
||||
if (rows.isEmpty) return {};
|
||||
final raw = rows.first['value'] as String?;
|
||||
if (raw == null || raw.isEmpty) return {};
|
||||
try {
|
||||
final decoded = jsonDecode(raw) as List<dynamic>;
|
||||
return decoded.map((entry) => entry as String).toSet();
|
||||
} catch (_) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> dismissNameIssue(String key) async {
|
||||
final dismissed = await getDismissedNameIssues()
|
||||
..add(key);
|
||||
final db = await _database;
|
||||
await db.insert('app_settings', {
|
||||
'key': 'dismissed_name_issues',
|
||||
'value': jsonEncode(dismissed.toList()..sort()),
|
||||
}, conflictAlgorithm: ConflictAlgorithm.replace);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<List<List<Product>>> getNameConflictGroups() async {
|
||||
return [
|
||||
for (final group in (await getProductNameIssues()).mergeGroups)
|
||||
group.products,
|
||||
];
|
||||
}
|
||||
|
||||
Future<List<ProductSpellingVariant>> getProductSpellingVariants() async {
|
||||
return (await getProductNameIssues()).spellingVariants;
|
||||
}
|
||||
|
||||
/// Picks one spelling for a catalog product at a store.
|
||||
Future<void> unifyProductSpelling({
|
||||
required int productId,
|
||||
required String name,
|
||||
String? supermarket,
|
||||
}) async {
|
||||
final trimmed = name.trim();
|
||||
final key = normalizeName(trimmed, supermarket: supermarket);
|
||||
final db = await _database;
|
||||
final now = DateTime.now().toIso8601String();
|
||||
final storeKey = supermarket == null
|
||||
? null
|
||||
: normalizeSupermarket(supermarket);
|
||||
|
||||
await db.transaction((txn) async {
|
||||
await txn.update(
|
||||
'products',
|
||||
{'name': trimmed, 'normalized_name': key, 'updated_at': now},
|
||||
where: 'id = ?',
|
||||
whereArgs: [productId],
|
||||
);
|
||||
final rows = await txn.rawQuery('''
|
||||
SELECT shop_items.id, shop_items.name
|
||||
FROM shop_items
|
||||
JOIN shops ON shops.id = shop_items.shop_id
|
||||
WHERE shop_items.product_id = ?
|
||||
${storeKey == null ? '' : 'AND upper(trim(shops.supermarket)) = ?'}
|
||||
''', storeKey == null ? [productId] : [productId, storeKey]);
|
||||
for (final row in rows) {
|
||||
final lineName = row['name'] as String?;
|
||||
final lineId = row['id'];
|
||||
if (lineName == null || lineId == null) continue;
|
||||
if (normalizeName(lineName, supermarket: supermarket) != key) continue;
|
||||
await txn.update(
|
||||
'shop_items',
|
||||
{'name': trimmed},
|
||||
where: 'id = ?',
|
||||
whereArgs: [lineId],
|
||||
);
|
||||
}
|
||||
});
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<AppOverview> getOverview() async {
|
||||
final db = await _database;
|
||||
final productCount =
|
||||
@@ -399,7 +550,9 @@ class ProductRepository extends ChangeNotifier {
|
||||
) ??
|
||||
0;
|
||||
final tripCount =
|
||||
Sqflite.firstIntValue(await db.rawQuery('SELECT COUNT(*) FROM shops')) ??
|
||||
Sqflite.firstIntValue(
|
||||
await db.rawQuery('SELECT COUNT(*) FROM shops'),
|
||||
) ??
|
||||
0;
|
||||
final listCount =
|
||||
Sqflite.firstIntValue(
|
||||
@@ -480,13 +633,28 @@ class ProductRepository extends ChangeNotifier {
|
||||
bool clearCategory = false,
|
||||
}) async {
|
||||
final db = await _database;
|
||||
final existing = await db.query(
|
||||
'products',
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
limit: 1,
|
||||
);
|
||||
if (existing.isEmpty) return;
|
||||
final row = existing.first;
|
||||
|
||||
final nextName = name?.trim() ?? row['name']! as String;
|
||||
final nextSupermarket = clearSupermarket
|
||||
? null
|
||||
: (supermarket ?? row['supermarket'] as String?);
|
||||
|
||||
final values = <String, Object?>{
|
||||
'updated_at': DateTime.now().toIso8601String(),
|
||||
'normalized_name': normalizeName(
|
||||
nextName,
|
||||
supermarket: nextSupermarket,
|
||||
),
|
||||
};
|
||||
if (name != null) {
|
||||
values['name'] = name.trim();
|
||||
values['normalized_name'] = normalizeName(name);
|
||||
}
|
||||
if (name != null) values['name'] = nextName;
|
||||
if (favourite != null) values['favourite'] = favourite ? 1 : 0;
|
||||
if (clearSupermarket) {
|
||||
values['supermarket'] = null;
|
||||
@@ -565,6 +733,145 @@ class ProductRepository extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Moves trip lines and list items onto [keepId], then deletes the rest.
|
||||
Future<void> mergeProducts({
|
||||
required int keepId,
|
||||
required List<int> sourceIds,
|
||||
}) async {
|
||||
final mergeIds = [
|
||||
for (final id in sourceIds)
|
||||
if (id != keepId) id,
|
||||
];
|
||||
if (mergeIds.isEmpty) return;
|
||||
|
||||
final db = await _database;
|
||||
final keepRows = await db.query(
|
||||
'products',
|
||||
where: 'id = ?',
|
||||
whereArgs: [keepId],
|
||||
limit: 1,
|
||||
);
|
||||
if (keepRows.isEmpty) return;
|
||||
final keep = Product.fromMap(keepRows.first);
|
||||
final losers = <Product>[];
|
||||
for (final id in mergeIds) {
|
||||
final rows = await db.query(
|
||||
'products',
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
limit: 1,
|
||||
);
|
||||
if (rows.isNotEmpty) losers.add(Product.fromMap(rows.first));
|
||||
}
|
||||
if (losers.isEmpty) return;
|
||||
|
||||
var imagePath = keep.imagePath;
|
||||
final unusedImages = <String?>[];
|
||||
for (final loser in losers) {
|
||||
final path = loser.imagePath;
|
||||
if ((imagePath == null || imagePath.isEmpty) &&
|
||||
path != null &&
|
||||
path.isNotEmpty) {
|
||||
imagePath = path;
|
||||
} else {
|
||||
unusedImages.add(path);
|
||||
}
|
||||
}
|
||||
|
||||
var notes = keep.notes?.trim();
|
||||
for (final loser in losers) {
|
||||
final extra = loser.notes?.trim();
|
||||
if (extra == null || extra.isEmpty) continue;
|
||||
if (notes == null || notes.isEmpty) {
|
||||
notes = extra;
|
||||
} else if (notes != extra) {
|
||||
notes = '$notes\n$extra';
|
||||
}
|
||||
}
|
||||
|
||||
final favourite = keep.favourite || losers.any((item) => item.favourite);
|
||||
final supermarket = (keep.supermarket == null || keep.supermarket!.isEmpty)
|
||||
? losers
|
||||
.map((item) => item.supermarket)
|
||||
.firstWhere(
|
||||
(value) => value != null && value.isNotEmpty,
|
||||
orElse: () => keep.supermarket,
|
||||
)
|
||||
: keep.supermarket;
|
||||
final category = (keep.category == null || keep.category!.isEmpty)
|
||||
? losers
|
||||
.map((item) => item.category)
|
||||
.firstWhere(
|
||||
(value) => value != null && value.isNotEmpty,
|
||||
orElse: () => keep.category,
|
||||
)
|
||||
: keep.category;
|
||||
|
||||
await db.transaction((txn) async {
|
||||
for (final id in mergeIds) {
|
||||
await txn.update(
|
||||
'shop_items',
|
||||
{'product_id': keepId},
|
||||
where: 'product_id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
await txn.update(
|
||||
'shopping_list_items',
|
||||
{'product_id': keepId},
|
||||
where: 'product_id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
await txn.delete('products', where: 'id = ?', whereArgs: [id]);
|
||||
}
|
||||
|
||||
final seen = Sqflite.firstIntValue(
|
||||
await txn.rawQuery(
|
||||
'SELECT COUNT(*) FROM shop_items WHERE product_id = ?',
|
||||
[keepId],
|
||||
),
|
||||
);
|
||||
final priceRows = await txn.rawQuery(
|
||||
'''
|
||||
SELECT COALESCE(shop_items.unit_price, shop_items.price) AS price
|
||||
FROM shop_items
|
||||
JOIN shops ON shops.id = shop_items.shop_id
|
||||
WHERE shop_items.product_id = ?
|
||||
ORDER BY shops.shopped_at DESC, shop_items.id DESC
|
||||
LIMIT 1
|
||||
''',
|
||||
[keepId],
|
||||
);
|
||||
final lastPrice = priceRows.isEmpty
|
||||
? keep.lastPrice
|
||||
: (priceRows.first['price'] as num).toDouble();
|
||||
|
||||
await txn.update(
|
||||
'products',
|
||||
{
|
||||
'normalized_name': normalizeName(
|
||||
keep.name,
|
||||
supermarket: keep.supermarket,
|
||||
),
|
||||
'times_seen': seen ?? keep.timesSeen,
|
||||
'last_price': lastPrice,
|
||||
'favourite': favourite ? 1 : 0,
|
||||
'image_path': imagePath,
|
||||
'supermarket': supermarket,
|
||||
'notes': (notes == null || notes.isEmpty) ? null : notes,
|
||||
'category': category,
|
||||
'updated_at': DateTime.now().toIso8601String(),
|
||||
},
|
||||
where: 'id = ?',
|
||||
whereArgs: [keepId],
|
||||
);
|
||||
});
|
||||
|
||||
for (final path in unusedImages) {
|
||||
await _tryDeleteFile(path);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> _tryDeleteFile(String? path) async {
|
||||
if (path == null || path.isEmpty) return;
|
||||
try {
|
||||
@@ -691,17 +998,20 @@ class ProductRepository extends ChangeNotifier {
|
||||
required int id,
|
||||
String? name,
|
||||
bool clearName = false,
|
||||
DateTime? shoppedAt,
|
||||
List<String>? tags,
|
||||
}) async {
|
||||
final db = await _database;
|
||||
await db.transaction((txn) async {
|
||||
final values = <String, Object?>{};
|
||||
if (clearName || name != null) {
|
||||
await txn.update(
|
||||
'shops',
|
||||
{'name': clearName ? null : _optionalText(name)},
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
values['name'] = clearName ? null : _optionalText(name);
|
||||
}
|
||||
if (shoppedAt != null) {
|
||||
values['shopped_at'] = shoppedAt.toIso8601String();
|
||||
}
|
||||
if (values.isNotEmpty) {
|
||||
await txn.update('shops', values, where: 'id = ?', whereArgs: [id]);
|
||||
}
|
||||
if (tags != null) {
|
||||
await _replaceShopTags(txn, shopId: id, tags: tags);
|
||||
@@ -760,6 +1070,51 @@ class ProductRepository extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Adds a line to an existing trip and upserts the product catalog entry.
|
||||
Future<ShopItem> addShopItem({
|
||||
required int shopId,
|
||||
required LineItem item,
|
||||
}) async {
|
||||
final db = await _database;
|
||||
final shopRows = await db.query(
|
||||
'shops',
|
||||
where: 'id = ?',
|
||||
whereArgs: [shopId],
|
||||
limit: 1,
|
||||
);
|
||||
if (shopRows.isEmpty) throw StateError('Shop not found');
|
||||
final supermarket = shopRows.first['supermarket']! as String;
|
||||
final now = DateTime.now().toIso8601String();
|
||||
|
||||
final itemId = await db.transaction((txn) async {
|
||||
final productId = await _upsertProduct(
|
||||
txn,
|
||||
item: item,
|
||||
supermarket: supermarket,
|
||||
now: now,
|
||||
);
|
||||
return txn.insert('shop_items', {
|
||||
'shop_id': shopId,
|
||||
'product_id': productId,
|
||||
'name': item.name.trim(),
|
||||
'price': item.price,
|
||||
'quantity': item.quantity,
|
||||
'unit_price': item.unitPrice,
|
||||
});
|
||||
});
|
||||
|
||||
await _recalculateShopTotal(shopId);
|
||||
notifyListeners();
|
||||
|
||||
final rows = await db.query(
|
||||
'shop_items',
|
||||
where: 'id = ?',
|
||||
whereArgs: [itemId],
|
||||
limit: 1,
|
||||
);
|
||||
return ShopItem.fromMap(rows.first);
|
||||
}
|
||||
|
||||
/// Removes one trip line. Catalog products are left unchanged.
|
||||
Future<void> deleteShopItem(int id) async {
|
||||
final db = await _database;
|
||||
@@ -825,6 +1180,7 @@ class ProductRepository extends ChangeNotifier {
|
||||
SELECT
|
||||
shopping_lists.id,
|
||||
shopping_lists.name,
|
||||
shopping_lists.created_at,
|
||||
shopping_lists.updated_at,
|
||||
COUNT(shopping_list_items.id) AS item_count,
|
||||
COALESCE(SUM(shopping_list_items.checked), 0) AS checked_count
|
||||
@@ -844,6 +1200,7 @@ class ProductRepository extends ChangeNotifier {
|
||||
SELECT
|
||||
shopping_lists.id,
|
||||
shopping_lists.name,
|
||||
shopping_lists.created_at,
|
||||
shopping_lists.updated_at,
|
||||
COUNT(shopping_list_items.id) AS item_count,
|
||||
COALESCE(SUM(shopping_list_items.checked), 0) AS checked_count
|
||||
@@ -882,6 +1239,18 @@ class ProductRepository extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> resetShoppingListChecks(int listId) async {
|
||||
final db = await _database;
|
||||
await db.update(
|
||||
'shopping_list_items',
|
||||
{'checked': 0},
|
||||
where: 'list_id = ? AND checked = 1',
|
||||
whereArgs: [listId],
|
||||
);
|
||||
await _touchShoppingList(listId);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> deleteShoppingList(int id) async {
|
||||
final db = await _database;
|
||||
await db.transaction((txn) async {
|
||||
@@ -911,23 +1280,72 @@ class ProductRepository extends ChangeNotifier {
|
||||
required String name,
|
||||
int? productId,
|
||||
}) async {
|
||||
final db = await _database;
|
||||
final max = Sqflite.firstIntValue(
|
||||
await db.rawQuery(
|
||||
'SELECT MAX(sort_order) FROM shopping_list_items WHERE list_id = ?',
|
||||
[listId],
|
||||
),
|
||||
final added = await addShoppingListItems(
|
||||
listId: listId,
|
||||
items: [(name: name, productId: productId)],
|
||||
);
|
||||
final id = await db.insert('shopping_list_items', {
|
||||
'list_id': listId,
|
||||
'product_id': productId,
|
||||
'name': name.trim(),
|
||||
'checked': 0,
|
||||
'sort_order': (max ?? -1) + 1,
|
||||
return added.single.id;
|
||||
}
|
||||
|
||||
Future<List<ShoppingListItem>> addShoppingListItems({
|
||||
required int listId,
|
||||
required List<({String name, int? productId})> items,
|
||||
}) async {
|
||||
if (items.isEmpty) return const [];
|
||||
|
||||
final db = await _database;
|
||||
final added = <ShoppingListItem>[];
|
||||
await db.transaction((txn) async {
|
||||
var max = Sqflite.firstIntValue(
|
||||
await txn.rawQuery(
|
||||
'SELECT MAX(sort_order) FROM shopping_list_items WHERE list_id = ?',
|
||||
[listId],
|
||||
),
|
||||
);
|
||||
var sortOrder = (max ?? -1) + 1;
|
||||
for (final item in items) {
|
||||
final name = item.name.trim();
|
||||
if (name.isEmpty) continue;
|
||||
final id = await txn.insert('shopping_list_items', {
|
||||
'list_id': listId,
|
||||
'product_id': item.productId,
|
||||
'name': name,
|
||||
'checked': 0,
|
||||
'sort_order': sortOrder++,
|
||||
});
|
||||
added.add(
|
||||
ShoppingListItem(
|
||||
id: id,
|
||||
listId: listId,
|
||||
name: name,
|
||||
checked: false,
|
||||
sortOrder: sortOrder - 1,
|
||||
productId: item.productId,
|
||||
),
|
||||
);
|
||||
}
|
||||
await txn.update(
|
||||
'shopping_lists',
|
||||
{'updated_at': DateTime.now().toIso8601String()},
|
||||
where: 'id = ?',
|
||||
whereArgs: [listId],
|
||||
);
|
||||
});
|
||||
await _touchShoppingList(listId);
|
||||
notifyListeners();
|
||||
return id;
|
||||
return added;
|
||||
}
|
||||
|
||||
Future<String?> setSupermarketBonusCard(int id, String sourcePath) async {
|
||||
final stored = await _persistImage(sourcePath, 'bonus_cards/store_$id.jpg');
|
||||
final db = await _database;
|
||||
await db.update(
|
||||
'supermarkets',
|
||||
{'bonus_image_path': stored},
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
notifyListeners();
|
||||
return stored;
|
||||
}
|
||||
|
||||
Future<void> updateShoppingListItem({
|
||||
@@ -1057,6 +1475,7 @@ class ProductRepository extends ChangeNotifier {
|
||||
'name': store.name.trim(),
|
||||
'color': store.colorValue,
|
||||
'sort_order': store.sortOrder,
|
||||
'bonus_image_path': store.bonusImagePath,
|
||||
},
|
||||
where: 'id = ?',
|
||||
whereArgs: [id],
|
||||
@@ -1166,7 +1585,13 @@ class ProductRepository extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
static const _supermarketColumns = {'id', 'name', 'color', 'sort_order'};
|
||||
static const _supermarketColumns = {
|
||||
'id',
|
||||
'name',
|
||||
'color',
|
||||
'sort_order',
|
||||
'bonus_image_path',
|
||||
};
|
||||
static const _productColumns = {
|
||||
'id',
|
||||
'name',
|
||||
|
||||
Reference in New Issue
Block a user