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 {
int id;
final int id;
int state;
String name;
String description;
@ -679,9 +679,14 @@ class RoomItem {
// may link to a product
int? link;
final String server;
final String room;
RoomItem(
{required this.id,
required this.name,
required this.server,
required this.room,
this.description = '',
this.state = 0,
this.category = -1,
@ -689,10 +694,12 @@ class RoomItem {
this.value = '',
this.link});
factory RoomItem.fromJSON(dynamic json) {
factory RoomItem.fromJSON(String server, String room, dynamic json) {
return RoomItem(
id: json['listItemID'],
name: json['title'],
server: server,
room: room,
description: json['description'],
category: json['listCatID'],
state: json['state'],
@ -705,10 +712,84 @@ class RoomItem {
return RoomItem(
id: id,
name: name,
server: server,
room: room,
description: description,
category: category,
unit: unit,
value: value,
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 = [];
RoomItem? item;
void fetchCategories() {
void fetchCategories() async {
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(
credentials: user,
target: user.server,
@ -64,12 +71,19 @@ class _EditItemPageState extends State<EditItemPage> {
});
}
void fetchProducts() {
void fetchProducts() async {
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(
credentials: user,
target: user.server,
@ -87,12 +101,19 @@ class _EditItemPageState extends State<EditItemPage> {
});
}
void fetchItem() {
void fetchItem() async {
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(
credentials: user,
target: user.server,
@ -103,7 +124,8 @@ class _EditItemPageState extends State<EditItemPage> {
'listItemID': widget.item
}),
onOK: (body) async {
final resp = RoomItem.fromJSON(body['data']);
final resp =
RoomItem.fromJSON(widget.server, widget.room, body['data']);
setState(() {
item = resp;
_ctrName.text = resp.name;
@ -121,6 +143,26 @@ class _EditItemPageState extends State<EditItemPage> {
void 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((_) {
fetchCategories();
fetchProducts();
@ -256,6 +298,20 @@ class _EditItemPageState extends State<EditItemPage> {
'listProdID': _ctrLink
}),
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();
});
},

View file

@ -28,14 +28,20 @@ class _NewItemPageState extends State<NewItemPage> {
// data cache
List<RoomCategory> categories = [];
List<RoomProduct> products = [];
RoomItem? item;
void fetchCategories() {
void fetchCategories() async {
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(
credentials: user,
target: user.server,
@ -53,12 +59,19 @@ class _NewItemPageState extends State<NewItemPage> {
});
}
void fetchProducts() {
void fetchProducts() async {
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(
credentials: user,
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
void 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((_) {
fetchCategories();
fetchProducts();
if (widget.item != null) {
fetchItem();
}
});
}
@ -138,7 +144,19 @@ class _NewItemPageState extends State<NewItemPage> {
}),
onOK: (body) async {
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
router.pushReplacementNamed('edit-item', params: {
'server': widget.server,
@ -170,7 +188,20 @@ class _NewItemPageState extends State<NewItemPage> {
}),
onOK: (body) async {
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);
});
}

View file

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