cache itemss for items list and edit item screen

BUG: when deleting an item the item list won't update
This commit is contained in:
Jakob Meier 2024-02-23 20:44:42 +01:00
parent 9ff6d97c90
commit 7174a03cf2
No known key found for this signature in database
GPG key ID: 66BDC7E6A01A6152
5 changed files with 327 additions and 74 deletions

View file

@ -666,7 +666,7 @@ class RoomProduct {
} }
class RoomItem { class RoomItem {
int id; final int id;
int state; int state;
String name; String name;
String description; String description;
@ -679,9 +679,14 @@ class RoomItem {
// may link to a product // may link to a product
int? link; int? link;
final String server;
final String room;
RoomItem( RoomItem(
{required this.id, {required this.id,
required this.name, required this.name,
required this.server,
required this.room,
this.description = '', this.description = '',
this.state = 0, this.state = 0,
this.category = -1, this.category = -1,
@ -689,10 +694,12 @@ class RoomItem {
this.value = '', this.value = '',
this.link}); this.link});
factory RoomItem.fromJSON(dynamic json) { factory RoomItem.fromJSON(String server, String room, dynamic json) {
return RoomItem( return RoomItem(
id: json['listItemID'], id: json['listItemID'],
name: json['title'], name: json['title'],
server: server,
room: room,
description: json['description'], description: json['description'],
category: json['listCatID'], category: json['listCatID'],
state: json['state'], state: json['state'],
@ -705,10 +712,84 @@ class RoomItem {
return RoomItem( return RoomItem(
id: id, id: id,
name: name, name: name,
server: server,
room: room,
description: description, description: description,
category: category, category: category,
unit: unit, unit: unit,
value: value, value: value,
link: link); link: link);
} }
// get list of all categories in a given room
static Future<List<RoomItem>> list(String server, String room) async {
final db = Localstore.instance;
final rooms = (await db.collection('items:$room@$server').get()) ?? {};
List<RoomItem> builder = [];
for (MapEntry entry in rooms.entries) {
try {
builder.add(RoomItem.fromMap(entry.value));
} catch (e) {
// skip invalid entries
// NOTE: might want to autodelete them in the future
// although keeping them might be ok,
// in case we ever get a new dataset to fix the current state
}
}
return builder;
}
// listen to room change
static listen(
String server, String room, Function(Map<String, dynamic>) cb) async {
final db = Localstore.instance;
final stream = db.collection('items:$room@$server').stream;
stream.listen(cb);
}
factory RoomItem.fromMap(Map<String, dynamic> map) {
return RoomItem(
server: map['server'],
room: map['room'],
id: map['id'],
name: map['name'],
description: map['description'],
category: map['category'],
state: map['state'],
unit: map['unit'],
value: map['value'],
link: map['product']);
}
Map<String, dynamic> toMap() {
return {
'server': server,
'room': room,
'id': id,
'name': name,
'description': description,
'category': category,
'state': state,
'unit': unit,
'value': value,
'product': link
};
}
Future<void> toDisk() async {
final db = Localstore.instance;
await db.collection('items:$room@$server').doc('$id').set(toMap());
}
Future<void> removeDisk() async {
final db = Localstore.instance;
await db.collection('items:$room@$server').doc('$id').delete();
}
static Future<RoomProduct> fromDisk(
{required int id, required String server, required String room}) async {
final db = Localstore.instance;
final raw = await db.collection('items:$room@$server').doc('$id').get();
return RoomProduct.fromMap(raw!);
}
} }

View file

@ -41,12 +41,19 @@ class _EditItemPageState extends State<EditItemPage> {
List<RoomProduct> products = []; List<RoomProduct> products = [];
RoomItem? item; RoomItem? item;
void fetchCategories() { void fetchCategories() async {
final user = context.read<User>(); final user = context.read<User>();
final scaffmgr = ScaffoldMessenger.of(context);
// TODO: load cached categories first // load cached categories
final cache = await RoomCategory.list(widget.server, widget.room);
if (mounted) {
setState(() {
categories = cache;
});
}
doNetworkRequest(ScaffoldMessenger.of(context), doNetworkRequest(scaffmgr,
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
@ -64,12 +71,19 @@ class _EditItemPageState extends State<EditItemPage> {
}); });
} }
void fetchProducts() { void fetchProducts() async {
final user = context.read<User>(); final user = context.read<User>();
final scaffmgr = ScaffoldMessenger.of(context);
// TODO: load cached products first // load cached products first
final cache = await RoomProduct.list(widget.server, widget.room);
if (mounted) {
setState(() {
products = cache;
});
}
doNetworkRequest(ScaffoldMessenger.of(context), doNetworkRequest(scaffmgr,
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
@ -87,12 +101,19 @@ class _EditItemPageState extends State<EditItemPage> {
}); });
} }
void fetchItem() { void fetchItem() async {
final user = context.read<User>(); final user = context.read<User>();
final scaffmgr = ScaffoldMessenger.of(context);
// TODO: load cached item first // load cached item first
try {
await RoomItem.fromDisk(
id: widget.item, server: widget.server, room: widget.room);
} catch (_) {
// cache miss
}
doNetworkRequest(ScaffoldMessenger.of(context), doNetworkRequest(scaffmgr,
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
@ -103,7 +124,8 @@ class _EditItemPageState extends State<EditItemPage> {
'listItemID': widget.item 'listItemID': widget.item
}), }),
onOK: (body) async { onOK: (body) async {
final resp = RoomItem.fromJSON(body['data']); final resp =
RoomItem.fromJSON(widget.server, widget.room, body['data']);
setState(() { setState(() {
item = resp; item = resp;
_ctrName.text = resp.name; _ctrName.text = resp.name;
@ -121,6 +143,26 @@ class _EditItemPageState extends State<EditItemPage> {
void initState() { void initState() {
super.initState(); super.initState();
// wait for background room product changes
RoomProduct.listen(widget.server, widget.room, (_) async {
try {
final updated = await RoomProduct.list(widget.server, widget.room);
setState(() {
products = updated;
});
} catch (_) {}
});
// wait for background room category changes
RoomCategory.listen(widget.server, widget.room, (_) async {
try {
final updated = await RoomCategory.list(widget.server, widget.room);
setState(() {
categories = updated;
});
} catch (_) {}
});
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
fetchCategories(); fetchCategories();
fetchProducts(); fetchProducts();
@ -256,6 +298,20 @@ class _EditItemPageState extends State<EditItemPage> {
'listProdID': _ctrLink 'listProdID': _ctrLink
}), }),
onOK: (_) async { onOK: (_) async {
// cache item
final item = RoomItem(
id: widget.item,
server: widget.server,
room: widget.room,
name: _ctrName.text,
state: _ctrState,
description: _ctrDescription.text,
category: _ctrCategory,
unit: _ctrUnit,
value: _ctrValue,
link: _ctrLink);
await item.toDisk();
nav.pop(); nav.pop();
}); });
}, },

View file

@ -28,14 +28,20 @@ class _NewItemPageState extends State<NewItemPage> {
// data cache // data cache
List<RoomCategory> categories = []; List<RoomCategory> categories = [];
List<RoomProduct> products = []; List<RoomProduct> products = [];
RoomItem? item;
void fetchCategories() { void fetchCategories() async {
final user = context.read<User>(); final user = context.read<User>();
final scaffmgr = ScaffoldMessenger.of(context);
// TODO: load cached categories first // load cached categories
final cache = await RoomCategory.list(widget.server, widget.room);
if (mounted) {
setState(() {
categories = cache;
});
}
doNetworkRequest(ScaffoldMessenger.of(context), doNetworkRequest(scaffmgr,
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
@ -53,12 +59,19 @@ class _NewItemPageState extends State<NewItemPage> {
}); });
} }
void fetchProducts() { void fetchProducts() async {
final user = context.read<User>(); final user = context.read<User>();
final scaffmgr = ScaffoldMessenger.of(context);
// TODO: load cached products first // load cached products first
final cache = await RoomProduct.list(widget.server, widget.room);
if (mounted) {
setState(() {
products = cache;
});
}
doNetworkRequest(ScaffoldMessenger.of(context), doNetworkRequest(scaffmgr,
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
@ -76,40 +89,33 @@ class _NewItemPageState extends State<NewItemPage> {
}); });
} }
void fetchItem() {
final user = context.read<User>();
// TODO: load cached item first
doNetworkRequest(ScaffoldMessenger.of(context),
req: () => postWithCreadentials(
credentials: user,
target: user.server,
path: 'getItem',
body: {
'room': widget.room,
'server': widget.server,
'listItemID': widget.item
}),
onOK: (body) async {
final resp = RoomItem.fromJSON(body['data']);
setState(() {
item = resp;
});
});
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
// wait for background room product changes
RoomProduct.listen(widget.server, widget.room, (_) async {
try {
final updated = await RoomProduct.list(widget.server, widget.room);
setState(() {
products = updated;
});
} catch (_) {}
});
// wait for background room category changes
RoomCategory.listen(widget.server, widget.room, (_) async {
try {
final updated = await RoomCategory.list(widget.server, widget.room);
setState(() {
categories = updated;
});
} catch (_) {}
});
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
fetchCategories(); fetchCategories();
fetchProducts(); fetchProducts();
if (widget.item != null) {
fetchItem();
}
}); });
} }
@ -138,7 +144,19 @@ class _NewItemPageState extends State<NewItemPage> {
}), }),
onOK: (body) async { onOK: (body) async {
final id = body["data"]["listItemID"]; final id = body["data"]["listItemID"];
// TODO cache item // cache item
final item = RoomItem(
id: id,
room: widget.room,
server: widget.server,
state: 0,
name: name,
description: '',
category: null,
unit: 0,
value: '',
link: productID);
await item.toDisk();
// launch edit item screen // launch edit item screen
router.pushReplacementNamed('edit-item', params: { router.pushReplacementNamed('edit-item', params: {
'server': widget.server, 'server': widget.server,
@ -170,7 +188,20 @@ class _NewItemPageState extends State<NewItemPage> {
}), }),
onOK: (body) async { onOK: (body) async {
final id = body["data"]["listProdID"]; final id = body["data"]["listProdID"];
// TODO: cache product // cache product
final prod = RoomProduct(
id: id,
name: name,
server: widget.server,
room: widget.room,
description: '',
category: null,
defaultUnit: 0,
defaultValue: '',
ean: '',
parent: null);
await prod.toDisk();
cb(id); cb(id);
}); });
} }

View file

@ -28,12 +28,37 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
Map<int?, RoomCategory> categories = {}; Map<int?, RoomCategory> categories = {};
List<RoomProduct> products = []; List<RoomProduct> products = [];
void fetchItems() { void fetchItems() async {
final user = context.read<User>(); final user = context.read<User>();
final scaffmgr = ScaffoldMessenger.of(context);
// TODO: load cached items first // load cached items first
final cache = await RoomItem.list(
widget.room?.serverTag ?? "", widget.room?.id ?? "");
doNetworkRequest(ScaffoldMessenger.of(context), final List<RoomItem> l = [];
final List<RoomItem> c = [];
for (RoomItem item in cache) {
if (item.state == 0) {
l.add(item);
} else {
c.add(item);
}
// cache items
await item.toDisk();
}
if (mounted) {
setState(() {
list = l;
cart = c;
sortAll();
});
}
doNetworkRequest(scaffmgr,
req: () => postWithCreadentials( req: () => postWithCreadentials(
credentials: user, credentials: user,
target: user.server, target: user.server,
@ -41,7 +66,8 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}), body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
onOK: (body) async { onOK: (body) async {
final resp = body['data'] final resp = body['data']
.map<RoomItem>((raw) => RoomItem.fromJSON(raw)) .map<RoomItem>((raw) => RoomItem.fromJSON(
widget.room?.serverTag ?? "", widget.room?.id ?? "", raw))
.toList(); .toList();
final List<RoomItem> l = []; final List<RoomItem> l = [];
@ -53,10 +79,10 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
} else { } else {
c.add(item); c.add(item);
} }
// cache items
await item.toDisk();
} }
// TODO: cache items
if (mounted) { if (mounted) {
setState(() { setState(() {
list = l; list = l;
@ -111,17 +137,23 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
final scaffmgr = ScaffoldMessenger.of(context); final scaffmgr = ScaffoldMessenger.of(context);
// load cached categories // load cached categories
final cache = await RoomCategory.list( final resp = await RoomCategory.list(
widget.room?.serverTag ?? "", widget.room?.id ?? ""); widget.room?.serverTag ?? "", widget.room?.id ?? "");
if (mounted) { if (mounted) {
Map<int?, RoomCategory> map = {}; Map<int, int> map = {};
Map<int?, RoomCategory> cat = {};
for (RoomCategory cat in cache) { for (int i = 0; i < resp.length; i++) {
map[cat.id] = cat; map[resp[i].id ?? 0] = i;
cat[resp[i].id ?? 0] = resp[i];
}
if (mounted) {
setState(() {
weights = map;
categories = cat;
sortAll();
});
} }
setState(() {
categories = map;
});
} }
doNetworkRequest(scaffmgr, doNetworkRequest(scaffmgr,
@ -190,6 +222,34 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
void initState() { void initState() {
super.initState(); super.initState();
// wait for background room item changes
RoomItem.listen(widget.room?.serverTag ?? "", widget.room?.id ?? "",
(_) async {
try {
final updated = await RoomItem.list(
widget.room?.serverTag ?? "", widget.room?.id ?? "");
final List<RoomItem> l = [];
final List<RoomItem> c = [];
for (RoomItem item in updated) {
if (item.state == 0) {
l.add(item);
} else {
c.add(item);
}
}
if (mounted) {
setState(() {
list = l;
cart = c;
sortAll();
});
}
} catch (_) {}
});
// wait for background room product changes // wait for background room product changes
RoomProduct.listen(widget.room?.serverTag ?? "", widget.room?.id ?? "", RoomProduct.listen(widget.room?.serverTag ?? "", widget.room?.id ?? "",
(_) async { (_) async {
@ -205,16 +265,22 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
RoomCategory.listen(widget.room?.serverTag ?? "", widget.room?.id ?? "", RoomCategory.listen(widget.room?.serverTag ?? "", widget.room?.id ?? "",
(_) async { (_) async {
try { try {
final updated = await RoomCategory.list( final resp = await RoomCategory.list(
widget.room?.serverTag ?? "", widget.room?.id ?? ""); widget.room?.serverTag ?? "", widget.room?.id ?? "");
Map<int?, RoomCategory> map = {}; Map<int, int> map = {};
Map<int?, RoomCategory> cat = {};
for (RoomCategory cat in updated) { for (int i = 0; i < resp.length; i++) {
map[cat.id] = cat; map[resp[i].id ?? 0] = i;
cat[resp[i].id ?? 0] = resp[i];
}
if (mounted) {
setState(() {
weights = map;
categories = cat;
sortAll();
});
} }
setState(() {
categories = map;
});
} catch (_) {} } catch (_) {}
}); });
@ -518,6 +584,9 @@ class ShoppingListItemInfo extends StatelessWidget {
'id': room, 'id': room,
'item': item.id.toString() 'item': item.id.toString()
}); });
final navInner = Navigator.of(context);
navInner.pop();
}, },
), ),
ListTile( ListTile(
@ -567,7 +636,8 @@ class ShoppingListItemInfo extends StatelessWidget {
}, },
credentials: user), credentials: user),
onOK: (_) async { onOK: (_) async {
// TODO: remove cached item // remove cached item
await item.removeDisk();
}, },
after: () { after: () {
// close popup // close popup
@ -587,9 +657,9 @@ class ShoppingListItemInfo extends StatelessWidget {
ListTile( ListTile(
title: Text(item.state == 0 title: Text(item.state == 0
? AppLocalizations.of(context)!.moveItemToCartTitle ? AppLocalizations.of(context)!.moveItemToCartTitle
: AppLocalizations.of(context)!.moveItemToCartSubtitle), : AppLocalizations.of(context)!.removeItemFromCartTitle),
subtitle: Text(item.state == 0 subtitle: Text(item.state == 0
? AppLocalizations.of(context)!.removeItemFromCartTitle ? AppLocalizations.of(context)!.moveItemToCartSubtitle
: AppLocalizations.of(context)!.removeItemFromCartSubtitle), : AppLocalizations.of(context)!.removeItemFromCartSubtitle),
onTap: () { onTap: () {
// flip state // flip state
@ -605,7 +675,12 @@ class ShoppingListItemInfo extends StatelessWidget {
'server': server, 'server': server,
'listItemID': item.id, 'listItemID': item.id,
'state': item.state 'state': item.state
})); }),
onOK: (_) async {
final navInner = Navigator.of(context);
await item.toDisk();
navInner.pop();
});
}) })
], ],
), ),

View file

@ -118,6 +118,16 @@ class _EditProductPageState extends State<EditProductPage> {
void initState() { void initState() {
super.initState(); super.initState();
// wait for background room category changes
RoomCategory.listen(widget.server, widget.room, (_) async {
try {
final updated = await RoomCategory.list(widget.server, widget.room);
setState(() {
categories = updated;
});
} catch (_) {}
});
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
fetchCategories(); fetchCategories();
fetchProducts(); fetchProducts();