2023-03-22 21:16:00 +01:00
|
|
|
import 'package:flutter/material.dart';
|
2023-04-04 10:29:29 +02:00
|
|
|
import 'package:go_router/go_router.dart';
|
|
|
|
import 'package:outbag_app/backend/permissions.dart';
|
|
|
|
import 'package:outbag_app/backend/request.dart';
|
2023-03-22 21:16:00 +01:00
|
|
|
import 'package:outbag_app/backend/room.dart';
|
2023-04-04 10:29:29 +02:00
|
|
|
import 'package:outbag_app/backend/user.dart';
|
2023-04-04 20:28:26 +02:00
|
|
|
import 'package:outbag_app/components/category_chip.dart';
|
2023-04-04 10:29:29 +02:00
|
|
|
import 'package:outbag_app/components/labeled_divider.dart';
|
2023-04-05 10:22:43 +02:00
|
|
|
import 'package:outbag_app/components/value_unit_input.dart';
|
2023-04-04 10:29:29 +02:00
|
|
|
import 'package:outbag_app/tools/fetch_wrapper.dart';
|
|
|
|
import 'package:provider/provider.dart';
|
|
|
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
2023-03-22 21:16:00 +01:00
|
|
|
|
|
|
|
class ShoppingListPage extends StatefulWidget {
|
|
|
|
final RoomInfo? info;
|
|
|
|
final Room? room;
|
|
|
|
|
|
|
|
const ShoppingListPage(this.room, this.info, {super.key});
|
|
|
|
|
|
|
|
@override
|
|
|
|
State<StatefulWidget> createState() => _ShoppingListPageState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _ShoppingListPageState extends State<ShoppingListPage> {
|
2023-04-04 10:29:29 +02:00
|
|
|
List<RoomItem> list = [];
|
|
|
|
List<RoomItem> cart = [];
|
|
|
|
Map<int, int> weights = {};
|
2023-04-05 10:07:42 +02:00
|
|
|
Map<int?, RoomCategory> categories = {};
|
2023-04-04 10:29:29 +02:00
|
|
|
List<RoomProduct> products = [];
|
2023-03-22 21:16:00 +01:00
|
|
|
|
2024-02-23 20:44:42 +01:00
|
|
|
void fetchItems() async {
|
2023-04-04 10:29:29 +02:00
|
|
|
final user = context.read<User>();
|
2024-02-23 20:44:42 +01:00
|
|
|
final scaffmgr = ScaffoldMessenger.of(context);
|
2023-03-22 21:16:00 +01:00
|
|
|
|
2024-02-23 20:44:42 +01:00
|
|
|
// load cached items first
|
|
|
|
final cache = await RoomItem.list(
|
|
|
|
widget.room?.serverTag ?? "", widget.room?.id ?? "");
|
2023-03-22 21:16:00 +01:00
|
|
|
|
2024-02-23 20:44:42 +01:00
|
|
|
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,
|
2023-12-22 20:14:36 +01:00
|
|
|
req: () => postWithCreadentials(
|
|
|
|
credentials: user,
|
|
|
|
target: user.server,
|
|
|
|
path: 'getItems',
|
|
|
|
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
|
|
|
|
onOK: (body) async {
|
|
|
|
final resp = body['data']
|
2024-02-23 20:44:42 +01:00
|
|
|
.map<RoomItem>((raw) => RoomItem.fromJSON(
|
|
|
|
widget.room?.serverTag ?? "", widget.room?.id ?? "", raw))
|
2023-12-22 20:14:36 +01:00
|
|
|
.toList();
|
|
|
|
|
|
|
|
final List<RoomItem> l = [];
|
|
|
|
final List<RoomItem> c = [];
|
|
|
|
|
|
|
|
for (RoomItem item in resp) {
|
|
|
|
if (item.state == 0) {
|
|
|
|
l.add(item);
|
|
|
|
} else {
|
|
|
|
c.add(item);
|
|
|
|
}
|
2024-02-23 20:44:42 +01:00
|
|
|
// cache items
|
|
|
|
await item.toDisk();
|
2023-04-04 10:29:29 +02:00
|
|
|
}
|
|
|
|
|
2023-12-22 20:14:36 +01:00
|
|
|
if (mounted) {
|
|
|
|
setState(() {
|
2023-04-05 08:49:46 +02:00
|
|
|
list = l;
|
|
|
|
cart = c;
|
2023-04-04 10:29:29 +02:00
|
|
|
|
2023-04-05 08:49:46 +02:00
|
|
|
sortAll();
|
2023-12-22 20:14:36 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
2023-04-04 10:29:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void sortAll() {
|
|
|
|
for (List<RoomItem> input in [list, cart]) {
|
|
|
|
setState(() {
|
2023-12-22 20:14:36 +01:00
|
|
|
input.sort((a, b) {
|
|
|
|
if (a.category == b.category) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (a.category == null) {
|
|
|
|
// b should be below
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (b.category == null) {
|
|
|
|
// a should be below
|
|
|
|
return 1;
|
|
|
|
}
|
2023-04-04 10:29:29 +02:00
|
|
|
|
2023-12-22 20:14:36 +01:00
|
|
|
final weightA = weights[a.category];
|
|
|
|
final weightB = weights[b.category];
|
|
|
|
// both could be null now,
|
|
|
|
// so we have to check agein
|
|
|
|
if (weightA == weightB) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (weightA == null) {
|
|
|
|
// b should be below
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (weightB == null) {
|
|
|
|
// a should be below
|
|
|
|
return 1;
|
|
|
|
}
|
2023-04-04 10:29:29 +02:00
|
|
|
|
2023-12-22 20:14:36 +01:00
|
|
|
return weightA.compareTo(weightB);
|
|
|
|
});
|
2023-04-04 10:29:29 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-23 20:06:49 +01:00
|
|
|
void fetchCategories() async {
|
2023-04-04 10:29:29 +02:00
|
|
|
final user = context.read<User>();
|
2024-02-23 20:06:49 +01:00
|
|
|
final scaffmgr = ScaffoldMessenger.of(context);
|
2023-04-04 10:29:29 +02:00
|
|
|
|
2024-02-23 20:06:49 +01:00
|
|
|
// load cached categories
|
2024-02-23 20:44:42 +01:00
|
|
|
final resp = await RoomCategory.list(
|
2024-02-23 20:06:49 +01:00
|
|
|
widget.room?.serverTag ?? "", widget.room?.id ?? "");
|
|
|
|
if (mounted) {
|
2024-02-23 20:44:42 +01:00
|
|
|
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];
|
|
|
|
}
|
2023-04-04 10:29:29 +02:00
|
|
|
|
2024-02-23 20:44:42 +01:00
|
|
|
if (mounted) {
|
|
|
|
setState(() {
|
|
|
|
weights = map;
|
|
|
|
categories = cat;
|
|
|
|
sortAll();
|
|
|
|
});
|
2024-02-23 20:06:49 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
doNetworkRequest(scaffmgr,
|
2023-12-22 20:14:36 +01:00
|
|
|
req: () => postWithCreadentials(
|
|
|
|
credentials: user,
|
|
|
|
target: user.server,
|
|
|
|
path: 'getCategories',
|
|
|
|
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
|
|
|
|
onOK: (body) async {
|
|
|
|
final resp = body['data']
|
2024-02-23 16:13:15 +01:00
|
|
|
.map<RoomCategory>((raw) => RoomCategory.fromJSON(
|
|
|
|
widget.room?.serverTag ?? "", widget.room?.id ?? "", raw))
|
2023-12-22 20:14:36 +01:00
|
|
|
.toList();
|
|
|
|
|
|
|
|
Map<int, int> map = {};
|
|
|
|
Map<int?, RoomCategory> cat = {};
|
|
|
|
for (int i = 0; i < resp.length; i++) {
|
|
|
|
map[resp[i].id] = i;
|
|
|
|
cat[resp[i].id] = resp[i];
|
|
|
|
}
|
2023-04-05 08:49:46 +02:00
|
|
|
|
2023-12-22 20:14:36 +01:00
|
|
|
if (mounted) {
|
|
|
|
setState(() {
|
2023-04-05 08:49:46 +02:00
|
|
|
weights = map;
|
2023-04-05 10:07:42 +02:00
|
|
|
categories = cat;
|
2023-04-05 08:49:46 +02:00
|
|
|
sortAll();
|
2023-12-22 20:14:36 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
2023-04-04 10:29:29 +02:00
|
|
|
}
|
|
|
|
|
2024-02-23 20:06:49 +01:00
|
|
|
void fetchProducts() async {
|
2023-04-04 10:29:29 +02:00
|
|
|
final user = context.read<User>();
|
2024-02-23 20:06:49 +01:00
|
|
|
final scaffmgr = ScaffoldMessenger.of(context);
|
2023-04-04 10:29:29 +02:00
|
|
|
|
2024-02-23 20:06:49 +01:00
|
|
|
// load cached products first
|
|
|
|
final cache = await RoomProduct.list(
|
|
|
|
widget.room?.serverTag ?? "", widget.room?.id ?? "");
|
|
|
|
if (mounted) {
|
|
|
|
setState(() {
|
|
|
|
products = cache;
|
|
|
|
});
|
|
|
|
}
|
2023-04-04 10:29:29 +02:00
|
|
|
|
2024-02-23 20:06:49 +01:00
|
|
|
doNetworkRequest(scaffmgr,
|
2023-12-22 20:14:36 +01:00
|
|
|
req: () => postWithCreadentials(
|
|
|
|
credentials: user,
|
|
|
|
target: user.server,
|
|
|
|
path: 'getProducts',
|
|
|
|
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
|
|
|
|
onOK: (body) async {
|
|
|
|
final resp = body['data']
|
2024-02-23 20:06:49 +01:00
|
|
|
.map<RoomProduct>((raw) => RoomProduct.fromJSON(
|
|
|
|
widget.room!.serverTag, widget.room!.id, raw))
|
2023-12-22 20:14:36 +01:00
|
|
|
.toList();
|
|
|
|
|
|
|
|
if (mounted) {
|
|
|
|
setState(() {
|
2023-04-05 08:49:46 +02:00
|
|
|
products = resp;
|
2023-12-22 20:14:36 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
2023-03-22 21:16:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
|
|
|
|
2024-02-23 20:44:42 +01:00
|
|
|
// 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 (_) {}
|
|
|
|
});
|
2024-02-23 20:06:49 +01:00
|
|
|
// wait for background room product changes
|
|
|
|
RoomProduct.listen(widget.room?.serverTag ?? "", widget.room?.id ?? "",
|
|
|
|
(_) async {
|
|
|
|
try {
|
|
|
|
final updated = await RoomProduct.list(
|
|
|
|
widget.room?.serverTag ?? "", widget.room?.id ?? "");
|
|
|
|
setState(() {
|
|
|
|
products = updated;
|
|
|
|
});
|
|
|
|
} catch (_) {}
|
|
|
|
});
|
|
|
|
// wait for background room category changes
|
|
|
|
RoomCategory.listen(widget.room?.serverTag ?? "", widget.room?.id ?? "",
|
|
|
|
(_) async {
|
|
|
|
try {
|
2024-02-23 20:44:42 +01:00
|
|
|
final resp = await RoomCategory.list(
|
2024-02-23 20:06:49 +01:00
|
|
|
widget.room?.serverTag ?? "", widget.room?.id ?? "");
|
2024-02-23 20:44:42 +01:00
|
|
|
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];
|
|
|
|
}
|
2024-02-23 20:06:49 +01:00
|
|
|
|
2024-02-23 20:44:42 +01:00
|
|
|
if (mounted) {
|
|
|
|
setState(() {
|
|
|
|
weights = map;
|
|
|
|
categories = cat;
|
|
|
|
sortAll();
|
|
|
|
});
|
2024-02-23 20:06:49 +01:00
|
|
|
}
|
|
|
|
} catch (_) {}
|
|
|
|
});
|
|
|
|
|
2023-04-04 10:29:29 +02:00
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
2023-12-22 20:14:36 +01:00
|
|
|
fetchItems();
|
|
|
|
fetchCategories();
|
|
|
|
fetchProducts();
|
2023-04-04 10:29:29 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void changeItemState(RoomItem item) {
|
|
|
|
final user = context.read<User>();
|
|
|
|
doNetworkRequest(ScaffoldMessenger.of(context),
|
2023-12-22 20:14:36 +01:00
|
|
|
req: () => postWithCreadentials(
|
|
|
|
credentials: user,
|
|
|
|
target: user.server,
|
|
|
|
path: 'changeItemState',
|
|
|
|
body: {
|
|
|
|
'room': widget.room?.id,
|
|
|
|
'server': widget.room?.serverTag,
|
|
|
|
'listItemID': item.id,
|
|
|
|
'state': item.state
|
|
|
|
}));
|
2023-04-04 10:29:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return Scaffold(
|
2024-02-23 10:16:57 +01:00
|
|
|
body: Center(
|
|
|
|
child: Padding(
|
|
|
|
padding: const EdgeInsets.all(14),
|
|
|
|
child: ConstrainedBox(
|
|
|
|
constraints: const BoxConstraints(maxWidth: 600),
|
|
|
|
child: ListView(children: [
|
|
|
|
LabeledDivider(AppLocalizations.of(context)!.shoppingList),
|
|
|
|
ListView.builder(
|
|
|
|
shrinkWrap: true,
|
|
|
|
physics: const ClampingScrollPhysics(),
|
|
|
|
itemCount: list.length,
|
|
|
|
itemBuilder: (context, index) {
|
|
|
|
final item = list[index];
|
|
|
|
final cat = categories[item.category] ??
|
2024-02-23 16:13:15 +01:00
|
|
|
RoomCategory.other(widget.room?.serverTag ?? "",
|
|
|
|
widget.room?.id ?? "", context);
|
2024-02-23 10:16:57 +01:00
|
|
|
return ShoppingListItem(
|
|
|
|
name: item.name,
|
2024-02-23 16:13:15 +01:00
|
|
|
server: widget.room!.serverTag,
|
|
|
|
room: widget.room!.id,
|
2024-02-23 10:16:57 +01:00
|
|
|
description: item.description,
|
|
|
|
category: cat,
|
|
|
|
key: Key(item.id.toString()),
|
|
|
|
inCart: item.state != 0,
|
|
|
|
onDismiss: () {
|
|
|
|
// move to cart
|
|
|
|
item.state = 1;
|
|
|
|
|
|
|
|
setState(() {
|
|
|
|
list.removeAt(index);
|
|
|
|
cart.add(item);
|
|
|
|
sortAll();
|
|
|
|
});
|
|
|
|
|
|
|
|
// network request
|
|
|
|
// NOTE: for now we do not care if it is successfull,
|
|
|
|
// because the shopping cart is supposed to work even
|
|
|
|
// without network
|
|
|
|
changeItemState(item);
|
|
|
|
},
|
|
|
|
onTap: () {
|
|
|
|
// TODO: show modal bottom sheet
|
|
|
|
// containing
|
|
|
|
// - item info
|
|
|
|
// - option to view linked product (if available)
|
|
|
|
// - edit item (if allowed)
|
|
|
|
// - delete item (if allowed)
|
|
|
|
// - move to/from shopping cart?
|
|
|
|
showModalBottomSheet(
|
|
|
|
context: context,
|
|
|
|
builder: (context) => ShoppingListItemInfo(
|
|
|
|
products: products,
|
|
|
|
category: cat,
|
|
|
|
info: widget.info,
|
|
|
|
item: item,
|
|
|
|
room: widget.room!.id,
|
|
|
|
server: widget.room!.serverTag));
|
|
|
|
});
|
|
|
|
},
|
|
|
|
),
|
|
|
|
LabeledDivider(AppLocalizations.of(context)!.shoppingCart),
|
|
|
|
ListView.builder(
|
|
|
|
itemCount: cart.length,
|
|
|
|
shrinkWrap: true,
|
|
|
|
physics: const ClampingScrollPhysics(),
|
|
|
|
itemBuilder: (context, index) {
|
|
|
|
final item = cart[index];
|
|
|
|
final cat = categories[item.category] ??
|
2024-02-23 16:13:15 +01:00
|
|
|
RoomCategory.other(widget.room!.serverTag,
|
|
|
|
widget.room!.id, context);
|
2024-02-23 10:16:57 +01:00
|
|
|
|
|
|
|
return ShoppingListItem(
|
2024-02-23 16:13:15 +01:00
|
|
|
server: widget.room!.serverTag,
|
|
|
|
room: widget.room!.id,
|
2024-02-23 10:16:57 +01:00
|
|
|
name: item.name,
|
|
|
|
description: item.description,
|
|
|
|
category: cat,
|
|
|
|
key: Key(item.id.toString()),
|
|
|
|
inCart: item.state != 0,
|
|
|
|
onDismiss: () {
|
|
|
|
// move back to list
|
|
|
|
item.state = 0;
|
|
|
|
setState(() {
|
|
|
|
cart.removeAt(index);
|
|
|
|
list.add(item);
|
|
|
|
sortAll();
|
|
|
|
});
|
|
|
|
|
|
|
|
// network request
|
|
|
|
// NOTE: for now we do not care if it is successfull,
|
|
|
|
// because the shopping cart is supposed to work even
|
|
|
|
// without network
|
|
|
|
changeItemState(item);
|
|
|
|
},
|
|
|
|
onTap: () {
|
|
|
|
// show modal bottom sheet
|
|
|
|
// containing
|
|
|
|
// - item info
|
|
|
|
// - option to view linked product (if available)
|
|
|
|
// - edit item (if allowed)
|
|
|
|
// - delete item (if allowed)
|
|
|
|
// - move to/from shopping cart?
|
|
|
|
showModalBottomSheet(
|
|
|
|
context: context,
|
|
|
|
builder: (context) => ShoppingListItemInfo(
|
|
|
|
products: products,
|
|
|
|
category: cat,
|
|
|
|
item: item,
|
|
|
|
info: widget.info,
|
|
|
|
room: widget.room!.id,
|
|
|
|
server: widget.room!.serverTag));
|
|
|
|
});
|
|
|
|
},
|
|
|
|
)
|
|
|
|
])))),
|
2023-04-05 09:23:41 +02:00
|
|
|
floatingActionButton: (widget.info != null &&
|
2023-12-22 20:14:36 +01:00
|
|
|
((widget.info?.isAdmin ?? false) ||
|
|
|
|
(widget.info?.isOwner ?? false) ||
|
|
|
|
((widget.info?.permissions)! &
|
|
|
|
RoomPermission.addShoppingListItems !=
|
|
|
|
0)))
|
|
|
|
? FloatingActionButton.extended(
|
|
|
|
icon: const Icon(Icons.add),
|
|
|
|
label: Text(AppLocalizations.of(context)!.newItemShort),
|
|
|
|
tooltip: AppLocalizations.of(context)!.newItemLong,
|
|
|
|
onPressed: () {
|
|
|
|
// show new category popup
|
|
|
|
context.pushNamed('new-item', params: {
|
|
|
|
'server': widget.room!.serverTag,
|
|
|
|
'id': widget.room!.id,
|
|
|
|
});
|
|
|
|
},
|
|
|
|
)
|
|
|
|
: null,
|
2023-04-04 10:29:29 +02:00
|
|
|
);
|
2023-03-22 21:16:00 +01:00
|
|
|
}
|
2023-04-04 10:29:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
class ShoppingListItem extends StatelessWidget {
|
2023-04-05 10:22:43 +02:00
|
|
|
final String name;
|
|
|
|
final RoomCategory category;
|
|
|
|
final String description;
|
|
|
|
final bool inCart;
|
2023-04-04 10:29:29 +02:00
|
|
|
final Key _key;
|
2023-04-05 10:22:43 +02:00
|
|
|
final Function()? onDismiss;
|
|
|
|
final Function()? onTap;
|
2024-02-23 16:13:15 +01:00
|
|
|
final String server;
|
|
|
|
final String room;
|
2023-04-04 10:29:29 +02:00
|
|
|
|
2023-04-05 10:22:43 +02:00
|
|
|
const ShoppingListItem(
|
2023-12-22 20:14:36 +01:00
|
|
|
{required this.name,
|
2023-04-04 10:29:29 +02:00
|
|
|
required this.category,
|
|
|
|
required this.inCart,
|
|
|
|
required this.description,
|
2024-02-23 16:13:15 +01:00
|
|
|
required this.server,
|
|
|
|
required this.room,
|
2023-04-04 10:29:29 +02:00
|
|
|
required key,
|
|
|
|
this.onDismiss,
|
|
|
|
this.onTap})
|
2023-12-22 20:14:36 +01:00
|
|
|
: _key = key;
|
2023-03-22 21:16:00 +01:00
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2023-04-04 10:29:29 +02:00
|
|
|
return Dismissible(
|
|
|
|
key: Key('item-$_key'),
|
|
|
|
dismissThresholds: const {
|
|
|
|
// NOTE: value might need updating
|
|
|
|
// maybe we could calculate this using the screen width
|
|
|
|
DismissDirection.horizontal: 500.0,
|
|
|
|
},
|
|
|
|
confirmDismiss: (_) async {
|
|
|
|
if (onDismiss != null) {
|
|
|
|
onDismiss!();
|
|
|
|
}
|
2023-03-22 21:16:00 +01:00
|
|
|
|
2023-04-04 10:29:29 +02:00
|
|
|
// keep item in list
|
|
|
|
// NOTE: might want to set this to true/variable
|
|
|
|
// if in-shopping-cart items have a second screen
|
|
|
|
return true;
|
2023-03-22 21:16:00 +01:00
|
|
|
},
|
2023-04-04 10:29:29 +02:00
|
|
|
background:
|
2023-12-22 20:14:36 +01:00
|
|
|
Icon(!inCart ? Icons.shopping_cart : Icons.remove_shopping_cart),
|
2023-04-05 10:07:42 +02:00
|
|
|
child: Opacity(
|
2023-12-22 20:14:36 +01:00
|
|
|
opacity: inCart ? 0.5 : 1.0,
|
|
|
|
child: ListTile(
|
|
|
|
title: Text(name),
|
|
|
|
subtitle: Text(description),
|
|
|
|
trailing: CategoryChip(
|
2024-02-23 16:13:15 +01:00
|
|
|
server: server,
|
|
|
|
room: room,
|
2023-12-22 20:14:36 +01:00
|
|
|
category: category,
|
|
|
|
),
|
|
|
|
onTap: () {
|
|
|
|
if (onTap != null) {
|
|
|
|
onTap!();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
)),
|
2023-04-04 10:29:29 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class ShoppingListItemInfo extends StatelessWidget {
|
2023-04-05 10:07:42 +02:00
|
|
|
final RoomItem item;
|
|
|
|
final String server;
|
|
|
|
final String room;
|
2023-04-05 19:04:18 +02:00
|
|
|
final RoomInfo? info;
|
2023-04-05 10:07:42 +02:00
|
|
|
final RoomCategory category;
|
|
|
|
final List<RoomProduct> products;
|
|
|
|
|
|
|
|
const ShoppingListItemInfo(
|
2023-12-22 20:14:36 +01:00
|
|
|
{super.key,
|
2023-04-05 19:04:18 +02:00
|
|
|
this.info,
|
2023-04-05 10:07:42 +02:00
|
|
|
required this.item,
|
2023-04-04 10:29:29 +02:00
|
|
|
required this.server,
|
|
|
|
required this.room,
|
2023-04-05 10:07:42 +02:00
|
|
|
required this.category,
|
2023-04-04 10:29:29 +02:00
|
|
|
required this.products});
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
final textTheme = Theme.of(context).textTheme;
|
|
|
|
return BottomSheet(
|
|
|
|
onClosing: () {},
|
|
|
|
builder: (context) => Column(
|
|
|
|
children: [
|
|
|
|
Padding(
|
2023-12-22 20:14:36 +01:00
|
|
|
padding: const EdgeInsets.all(14),
|
|
|
|
child: Center(
|
|
|
|
child: Column(children: [
|
|
|
|
Text(item.name, style: textTheme.headlineLarge),
|
|
|
|
Text(item.description, style: textTheme.titleMedium),
|
|
|
|
CategoryChip(
|
2024-02-23 16:13:15 +01:00
|
|
|
server: server,
|
|
|
|
room: room,
|
2023-12-22 20:14:36 +01:00
|
|
|
category: category,
|
|
|
|
),
|
|
|
|
Text(Unit.fromId(item.unit).display(context, item.value))
|
|
|
|
]))),
|
2023-04-04 10:29:29 +02:00
|
|
|
...(item.link != null)
|
2023-12-22 20:14:36 +01:00
|
|
|
? [
|
|
|
|
ListTile(
|
|
|
|
title: Text(AppLocalizations.of(context)!
|
|
|
|
.itemShowLinkedProductTitle),
|
|
|
|
subtitle: Text(AppLocalizations.of(context)!
|
|
|
|
.itemShowLinkedProductSubtitle),
|
|
|
|
trailing: const Icon(Icons.chevron_right),
|
|
|
|
onTap: () {
|
|
|
|
// launch "view-product" page for specific product
|
|
|
|
context.pushNamed('view-product', params: {
|
|
|
|
'server': server,
|
|
|
|
'id': room,
|
|
|
|
'product': item.link.toString()
|
|
|
|
});
|
|
|
|
},
|
|
|
|
)
|
|
|
|
]
|
|
|
|
: [],
|
2023-04-05 19:04:18 +02:00
|
|
|
...(info != null &&
|
2023-12-22 20:14:36 +01:00
|
|
|
((info?.isAdmin ?? false) ||
|
|
|
|
(info?.isOwner ?? false) ||
|
|
|
|
((info?.permissions)! &
|
|
|
|
RoomPermission.addShoppingListItems !=
|
|
|
|
0)))
|
|
|
|
? [
|
|
|
|
ListTile(
|
|
|
|
title: Text(AppLocalizations.of(context)!.editItem),
|
|
|
|
subtitle: Text(AppLocalizations.of(context)!.editItemLong),
|
|
|
|
trailing: const Icon(Icons.chevron_right),
|
|
|
|
onTap: () {
|
2024-02-22 20:36:59 +01:00
|
|
|
context.pushNamed('edit-item', params: {
|
2023-12-22 20:14:36 +01:00
|
|
|
'server': server,
|
|
|
|
'id': room,
|
|
|
|
'item': item.id.toString()
|
|
|
|
});
|
2024-02-23 20:44:42 +01:00
|
|
|
|
|
|
|
final navInner = Navigator.of(context);
|
|
|
|
navInner.pop();
|
2023-12-22 20:14:36 +01:00
|
|
|
},
|
|
|
|
),
|
|
|
|
ListTile(
|
|
|
|
title:
|
|
|
|
Text(AppLocalizations.of(context)!.deleteItemTitle),
|
|
|
|
subtitle: Text(
|
|
|
|
AppLocalizations.of(context)!.deleteItemSubtitle),
|
|
|
|
trailing: const Icon(Icons.chevron_right),
|
|
|
|
onTap: () {
|
2024-02-23 10:16:57 +01:00
|
|
|
// show popup
|
|
|
|
showDialog(
|
|
|
|
context: context,
|
|
|
|
builder: (ctx) => AlertDialog(
|
|
|
|
icon: const Icon(Icons.delete),
|
|
|
|
title: Text(
|
|
|
|
AppLocalizations.of(context)!.deleteItem),
|
|
|
|
content: Text(AppLocalizations.of(context)!
|
|
|
|
.deleteItemConfirm(item.name)),
|
|
|
|
actions: [
|
|
|
|
TextButton(
|
|
|
|
onPressed: () {
|
|
|
|
// close popup
|
|
|
|
Navigator.of(ctx).pop();
|
|
|
|
},
|
|
|
|
child: Text(
|
|
|
|
AppLocalizations.of(context)!.cancel),
|
|
|
|
),
|
|
|
|
FilledButton(
|
|
|
|
onPressed: () async {
|
|
|
|
// send request
|
|
|
|
final scaffMgr =
|
|
|
|
ScaffoldMessenger.of(ctx);
|
|
|
|
// popup context
|
|
|
|
final navInner = Navigator.of(ctx);
|
|
|
|
// bottomsheet context
|
|
|
|
final nav = Navigator.of(context);
|
|
|
|
final user = context.read<User>();
|
|
|
|
|
|
|
|
doNetworkRequest(scaffMgr,
|
|
|
|
req: () => postWithCreadentials(
|
|
|
|
path: 'deleteItem',
|
|
|
|
target: user.server,
|
|
|
|
body: {
|
|
|
|
'room': room,
|
|
|
|
'server': server,
|
|
|
|
'listItemID': item.id
|
|
|
|
},
|
|
|
|
credentials: user),
|
|
|
|
onOK: (_) async {
|
2024-02-23 20:44:42 +01:00
|
|
|
// remove cached item
|
|
|
|
await item.removeDisk();
|
2024-02-23 10:16:57 +01:00
|
|
|
},
|
|
|
|
after: () {
|
|
|
|
// close popup
|
|
|
|
navInner.pop();
|
|
|
|
// close modal bottom sheet
|
|
|
|
nav.pop();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
child: Text(AppLocalizations.of(context)!
|
|
|
|
.deleteItem),
|
|
|
|
)
|
|
|
|
],
|
|
|
|
));
|
2023-12-22 20:14:36 +01:00
|
|
|
}),
|
|
|
|
]
|
|
|
|
: [],
|
2023-04-05 19:04:18 +02:00
|
|
|
ListTile(
|
2023-12-22 20:14:36 +01:00
|
|
|
title: Text(item.state == 0
|
|
|
|
? AppLocalizations.of(context)!.moveItemToCartTitle
|
2024-02-23 20:44:42 +01:00
|
|
|
: AppLocalizations.of(context)!.removeItemFromCartTitle),
|
2023-12-22 20:14:36 +01:00
|
|
|
subtitle: Text(item.state == 0
|
2024-02-23 20:44:42 +01:00
|
|
|
? AppLocalizations.of(context)!.moveItemToCartSubtitle
|
2023-12-22 20:14:36 +01:00
|
|
|
: AppLocalizations.of(context)!.removeItemFromCartSubtitle),
|
|
|
|
onTap: () {
|
|
|
|
// flip state
|
|
|
|
item.state = (item.state - 1).abs();
|
|
|
|
final user = context.read<User>();
|
|
|
|
doNetworkRequest(ScaffoldMessenger.of(context),
|
|
|
|
req: () => postWithCreadentials(
|
|
|
|
credentials: user,
|
|
|
|
target: user.server,
|
|
|
|
path: 'changeItemState',
|
|
|
|
body: {
|
|
|
|
'room': room,
|
|
|
|
'server': server,
|
|
|
|
'listItemID': item.id,
|
|
|
|
'state': item.state
|
2024-02-23 20:44:42 +01:00
|
|
|
}),
|
|
|
|
onOK: (_) async {
|
|
|
|
final navInner = Navigator.of(context);
|
|
|
|
await item.toDisk();
|
|
|
|
navInner.pop();
|
|
|
|
});
|
2023-12-22 20:14:36 +01:00
|
|
|
})
|
2023-04-04 10:29:29 +02:00
|
|
|
],
|
|
|
|
),
|
2023-03-22 21:16:00 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|