add cache to categories list & add autoupdate after editing / creating a
category
This commit is contained in:
parent
13c071b8ca
commit
6cdfcdf85c
10 changed files with 171 additions and 29 deletions
|
@ -363,20 +363,30 @@ class RoomInfo {
|
|||
|
||||
class RoomCategory {
|
||||
final int? id;
|
||||
final String name;
|
||||
final ColorSwatch<int> color;
|
||||
String name;
|
||||
ColorSwatch<int> color;
|
||||
final String room;
|
||||
final String server;
|
||||
|
||||
const RoomCategory(
|
||||
{required this.id, required this.name, required this.color});
|
||||
RoomCategory(
|
||||
{required this.id,
|
||||
required this.name,
|
||||
required this.color,
|
||||
required this.server,
|
||||
required this.room});
|
||||
|
||||
factory RoomCategory.fromJSON(dynamic json) {
|
||||
factory RoomCategory.fromJSON(String server, String room, dynamic json) {
|
||||
return RoomCategory(
|
||||
server: server,
|
||||
room: room,
|
||||
id: json['id'],
|
||||
name: json['title'],
|
||||
color: colorFromString(json['color']));
|
||||
}
|
||||
factory RoomCategory.other(BuildContext context) {
|
||||
factory RoomCategory.other(String server, String room, BuildContext context) {
|
||||
return RoomCategory(
|
||||
server: server,
|
||||
room: room,
|
||||
id: null,
|
||||
name: AppLocalizations.of(context)!.categoryNameOther,
|
||||
color: Colors.grey);
|
||||
|
@ -398,6 +408,69 @@ class RoomCategory {
|
|||
"purple-acc",
|
||||
].map((txt) => colorFromString(txt)).toList();
|
||||
}
|
||||
|
||||
// get list of all categories in a given room
|
||||
static Future<List<RoomCategory>> list(String server, String room) async {
|
||||
final db = Localstore.instance;
|
||||
final rooms = (await db.collection('categories:$room@$server').get()) ?? {};
|
||||
List<RoomCategory> builder = [];
|
||||
for (MapEntry entry in rooms.entries) {
|
||||
try {
|
||||
builder.add(RoomCategory.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('categories:$room@$server').stream;
|
||||
stream.listen(cb);
|
||||
}
|
||||
|
||||
factory RoomCategory.fromMap(Map<String, dynamic> map) {
|
||||
return RoomCategory(
|
||||
server: map['server'],
|
||||
room: map['room'],
|
||||
id: map['id'],
|
||||
name: map['name'],
|
||||
color: colorFromString(map['color']));
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'server': server,
|
||||
'room': room,
|
||||
'id': id,
|
||||
'name': name,
|
||||
'color': colorIdFromColor(color)
|
||||
};
|
||||
}
|
||||
|
||||
Future<void> toDisk() async {
|
||||
final db = Localstore.instance;
|
||||
await db.collection('categories:$room@$server').doc('$id').set(toMap());
|
||||
}
|
||||
|
||||
Future<void> removeDisk() async {
|
||||
final db = Localstore.instance;
|
||||
await db.collection('categories:$room@$server').doc('$id').delete();
|
||||
}
|
||||
|
||||
static Future<RoomCategory> fromDisk(
|
||||
{required int id, required String server, required String room}) async {
|
||||
final db = Localstore.instance;
|
||||
final raw =
|
||||
await db.collection('categories:$room@$server').doc('$id').get();
|
||||
return RoomCategory.fromMap(raw!);
|
||||
}
|
||||
}
|
||||
|
||||
ColorSwatch<int> colorFromString(String text) {
|
||||
|
|
|
@ -3,15 +3,20 @@ import 'package:outbag_app/backend/room.dart';
|
|||
|
||||
class CategoryChip extends StatelessWidget {
|
||||
final RoomCategory? category;
|
||||
final String server;
|
||||
final String room;
|
||||
|
||||
const CategoryChip({super.key, this.category});
|
||||
const CategoryChip(
|
||||
{super.key, required this.server, required this.room, this.category});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ActionChip(
|
||||
avatar: Icon(Icons.square_rounded,
|
||||
color: category?.color ?? RoomCategory.other(context).color),
|
||||
label: Text(category?.name ?? RoomCategory.other(context).name),
|
||||
color: category?.color ??
|
||||
RoomCategory.other(server, room, context).color),
|
||||
label: Text(
|
||||
category?.name ?? RoomCategory.other(server, room, context).name),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,9 +11,14 @@ class CategoryPicker extends StatelessWidget {
|
|||
final String? hint;
|
||||
final String? label;
|
||||
|
||||
final String server;
|
||||
final String room;
|
||||
|
||||
const CategoryPicker(
|
||||
{super.key,
|
||||
required this.categories,
|
||||
required this.server,
|
||||
required this.room,
|
||||
this.selected,
|
||||
this.onSelect,
|
||||
this.hint,
|
||||
|
@ -31,7 +36,7 @@ class CategoryPicker extends StatelessWidget {
|
|||
border: const OutlineInputBorder(),
|
||||
prefixIcon: const Icon(Icons.category)),
|
||||
value: selected,
|
||||
items: [...categories, RoomCategory.other(context)]
|
||||
items: [...categories, RoomCategory.other(server, room, context)]
|
||||
.map((category) => DropdownMenuItem<int?>(
|
||||
value: category.id,
|
||||
child: Row(
|
||||
|
|
|
@ -202,8 +202,13 @@ class _EditCategoryPageState extends State<EditCategoryPage> {
|
|||
final id = body['data']['catID'];
|
||||
|
||||
final cat = RoomCategory(
|
||||
id: id, name: _ctrName.text, color: _ctrColor);
|
||||
// TODO: cache category
|
||||
server: widget.server,
|
||||
room: widget.tag,
|
||||
id: id,
|
||||
name: _ctrName.text,
|
||||
color: _ctrColor);
|
||||
// cache category
|
||||
await cat.toDisk();
|
||||
|
||||
// go back
|
||||
router.pop();
|
||||
|
@ -229,11 +234,13 @@ class _EditCategoryPageState extends State<EditCategoryPage> {
|
|||
}),
|
||||
onOK: (body) async {
|
||||
final cat = RoomCategory(
|
||||
server: widget.server,
|
||||
room: widget.tag,
|
||||
id: widget.id!,
|
||||
name: _ctrName.text,
|
||||
color: _ctrColor);
|
||||
// TODO: cache category
|
||||
|
||||
// cache category
|
||||
await cat.toDisk();
|
||||
// go back
|
||||
router.pop();
|
||||
return;
|
||||
|
|
|
@ -54,7 +54,8 @@ class _EditItemPageState extends State<EditItemPage> {
|
|||
body: {'room': widget.room, 'server': widget.server}),
|
||||
onOK: (body) async {
|
||||
final resp = body['data']
|
||||
.map<RoomCategory>((raw) => RoomCategory.fromJSON(raw))
|
||||
.map<RoomCategory>((raw) =>
|
||||
RoomCategory.fromJSON(widget.server, widget.room, raw))
|
||||
.toList();
|
||||
|
||||
setState(() {
|
||||
|
@ -205,6 +206,8 @@ class _EditItemPageState extends State<EditItemPage> {
|
|||
},
|
||||
),
|
||||
CategoryPicker(
|
||||
server: widget.server,
|
||||
room: widget.room,
|
||||
label: AppLocalizations.of(context)!
|
||||
.selectCategoryLabel,
|
||||
hint: AppLocalizations.of(context)!
|
||||
|
|
|
@ -43,7 +43,8 @@ class _NewItemPageState extends State<NewItemPage> {
|
|||
body: {'room': widget.room, 'server': widget.server}),
|
||||
onOK: (body) async {
|
||||
final resp = body['data']
|
||||
.map<RoomCategory>((raw) => RoomCategory.fromJSON(raw))
|
||||
.map<RoomCategory>((raw) =>
|
||||
RoomCategory.fromJSON(widget.server, widget.room, raw))
|
||||
.toList();
|
||||
|
||||
setState(() {
|
||||
|
@ -262,11 +263,14 @@ class _NewItemPageState extends State<NewItemPage> {
|
|||
title: Text(e.name),
|
||||
subtitle: Text(e.description),
|
||||
trailing: CategoryChip(
|
||||
server: widget.server,
|
||||
room: widget.room,
|
||||
category: categories
|
||||
.where((element) =>
|
||||
element.id == e.category)
|
||||
.firstOrNull ??
|
||||
RoomCategory.other(context),
|
||||
RoomCategory.other(widget.server,
|
||||
widget.room, context),
|
||||
),
|
||||
onTap: () {
|
||||
// create new item and link it to the product
|
||||
|
|
|
@ -25,6 +25,18 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
|
|||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// wait for background room category changes
|
||||
RoomCategory.listen(widget.room?.serverTag ?? "", widget.room?.id ?? "",
|
||||
(_) async {
|
||||
try {
|
||||
final updated = await RoomCategory.list(
|
||||
widget.room?.serverTag ?? "", widget.room?.id ?? "");
|
||||
setState(() {
|
||||
list = updated;
|
||||
});
|
||||
} catch (_) {}
|
||||
});
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
fetchCategories();
|
||||
});
|
||||
|
@ -32,10 +44,18 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
|
|||
|
||||
void fetchCategories() async {
|
||||
final user = context.read<User>();
|
||||
final scaffmgr = ScaffoldMessenger.of(context);
|
||||
|
||||
// TODO: load cached rooms
|
||||
// load cached categories
|
||||
final cache = await RoomCategory.list(
|
||||
widget.room?.serverTag ?? "", widget.room?.id ?? "");
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
list = cache;
|
||||
});
|
||||
}
|
||||
|
||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
||||
doNetworkRequest(scaffmgr,
|
||||
req: () => postWithCreadentials(
|
||||
credentials: user,
|
||||
target: user.server,
|
||||
|
@ -43,8 +63,12 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
|
|||
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
|
||||
onOK: (json) {
|
||||
final resp = json['data']
|
||||
.map<RoomCategory>((raw) => RoomCategory.fromJSON(raw))
|
||||
.map<RoomCategory>((raw) => RoomCategory.fromJSON(
|
||||
widget.room?.serverTag ?? "", widget.room?.id ?? "", raw))
|
||||
.toList();
|
||||
for (RoomCategory ce in resp) {
|
||||
ce.toDisk();
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
|
@ -204,7 +228,8 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
|
|||
credentials:
|
||||
user),
|
||||
onOK: (_) async {
|
||||
// TODO: remove cached category
|
||||
// remove cached category
|
||||
item.removeDisk();
|
||||
fetchCategories();
|
||||
},
|
||||
after: () {
|
||||
|
|
|
@ -5,9 +5,7 @@ import 'package:outbag_app/backend/request.dart';
|
|||
import 'package:outbag_app/backend/room.dart';
|
||||
import 'package:outbag_app/backend/user.dart';
|
||||
import 'package:outbag_app/components/category_chip.dart';
|
||||
import 'package:outbag_app/components/category_picker.dart';
|
||||
import 'package:outbag_app/components/labeled_divider.dart';
|
||||
import 'package:outbag_app/components/product_picker.dart';
|
||||
import 'package:outbag_app/components/value_unit_input.dart';
|
||||
import 'package:outbag_app/tools/fetch_wrapper.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
@ -121,7 +119,8 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
|
|||
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
|
||||
onOK: (body) async {
|
||||
final resp = body['data']
|
||||
.map<RoomCategory>((raw) => RoomCategory.fromJSON(raw))
|
||||
.map<RoomCategory>((raw) => RoomCategory.fromJSON(
|
||||
widget.room?.serverTag ?? "", widget.room?.id ?? "", raw))
|
||||
.toList();
|
||||
|
||||
Map<int, int> map = {};
|
||||
|
@ -208,9 +207,12 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
|
|||
itemBuilder: (context, index) {
|
||||
final item = list[index];
|
||||
final cat = categories[item.category] ??
|
||||
RoomCategory.other(context);
|
||||
RoomCategory.other(widget.room?.serverTag ?? "",
|
||||
widget.room?.id ?? "", context);
|
||||
return ShoppingListItem(
|
||||
name: item.name,
|
||||
server: widget.room!.serverTag,
|
||||
room: widget.room!.id,
|
||||
description: item.description,
|
||||
category: cat,
|
||||
key: Key(item.id.toString()),
|
||||
|
@ -259,9 +261,12 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
|
|||
itemBuilder: (context, index) {
|
||||
final item = cart[index];
|
||||
final cat = categories[item.category] ??
|
||||
RoomCategory.other(context);
|
||||
RoomCategory.other(widget.room!.serverTag,
|
||||
widget.room!.id, context);
|
||||
|
||||
return ShoppingListItem(
|
||||
server: widget.room!.serverTag,
|
||||
room: widget.room!.id,
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
category: cat,
|
||||
|
@ -334,12 +339,16 @@ class ShoppingListItem extends StatelessWidget {
|
|||
final Key _key;
|
||||
final Function()? onDismiss;
|
||||
final Function()? onTap;
|
||||
final String server;
|
||||
final String room;
|
||||
|
||||
const ShoppingListItem(
|
||||
{required this.name,
|
||||
required this.category,
|
||||
required this.inCart,
|
||||
required this.description,
|
||||
required this.server,
|
||||
required this.room,
|
||||
required key,
|
||||
this.onDismiss,
|
||||
this.onTap})
|
||||
|
@ -372,6 +381,8 @@ class ShoppingListItem extends StatelessWidget {
|
|||
title: Text(name),
|
||||
subtitle: Text(description),
|
||||
trailing: CategoryChip(
|
||||
server: server,
|
||||
room: room,
|
||||
category: category,
|
||||
),
|
||||
onTap: () {
|
||||
|
@ -415,6 +426,8 @@ class ShoppingListItemInfo extends StatelessWidget {
|
|||
Text(item.name, style: textTheme.headlineLarge),
|
||||
Text(item.description, style: textTheme.titleMedium),
|
||||
CategoryChip(
|
||||
server: server,
|
||||
room: room,
|
||||
category: category,
|
||||
),
|
||||
Text(Unit.fromId(item.unit).display(context, item.value))
|
||||
|
|
|
@ -49,7 +49,8 @@ class _EditProductPageState extends State<EditProductPage> {
|
|||
body: {'room': widget.room, 'server': widget.server}),
|
||||
onOK: (body) async {
|
||||
final resp = body['data']
|
||||
.map<RoomCategory>((raw) => RoomCategory.fromJSON(raw))
|
||||
.map<RoomCategory>((raw) =>
|
||||
RoomCategory.fromJSON(widget.server, widget.room, raw))
|
||||
.toList();
|
||||
|
||||
setState(() {
|
||||
|
@ -192,6 +193,8 @@ class _EditProductPageState extends State<EditProductPage> {
|
|||
},
|
||||
),
|
||||
CategoryPicker(
|
||||
server: widget.server,
|
||||
room: widget.room,
|
||||
label: AppLocalizations.of(context)!
|
||||
.selectCategoryLabel,
|
||||
hint: AppLocalizations.of(context)!
|
||||
|
|
|
@ -66,7 +66,8 @@ class _ViewProductPageState extends State<ViewProductPage> {
|
|||
body: {'room': widget.room, 'server': widget.server}),
|
||||
onOK: (body) async {
|
||||
final resp = body['data']
|
||||
.map<RoomCategory>((raw) => RoomCategory.fromJSON(raw))
|
||||
.map<RoomCategory>((raw) =>
|
||||
RoomCategory.fromJSON(widget.server, widget.room, raw))
|
||||
.toList();
|
||||
|
||||
Map<int?, RoomCategory> map = {};
|
||||
|
@ -144,7 +145,10 @@ class _ViewProductPageState extends State<ViewProductPage> {
|
|||
Text(product?.description ?? '',
|
||||
style: textTheme.titleMedium),
|
||||
Text(product?.ean ?? ''),
|
||||
CategoryChip(category: categories[product?.category]),
|
||||
CategoryChip(
|
||||
server: widget.server,
|
||||
room: widget.room,
|
||||
category: categories[product?.category]),
|
||||
Text(product != null
|
||||
? Unit.fromId(product!.defaultUnit)
|
||||
.display(context, product!.defaultValue)
|
||||
|
|
Loading…
Reference in a new issue