remove item&product confirmation dialog

Because there currently is no way to auto-refresh the lists it will
appear as if nothing changed.
Switching to a different tab and back again will update the list though
This commit is contained in:
Jakob Meier 2024-02-23 10:16:57 +01:00
parent 384fbb0573
commit 13c071b8ca
No known key found for this signature in database
GPG key ID: 66BDC7E6A01A6152
8 changed files with 872 additions and 611 deletions

View file

@ -429,5 +429,12 @@
"newItemQueryEmpty": "Type to show quick access buttons",
"newItemQueryHint": "Type item or product name",
"newItemQuickAccessPrefix": "Create:",
"newItemQuickProduct": "product: {text}"
"newItemQuickProduct": "product: {text}",
"deleteItem": "Delete Item",
"deleteItemConfirm": "Do you really want to remove the item named {item}",
"deleteProductTitle": "Delete Product",
"deleteProductSubtitle": "Remove the product from list of known products",
"deleteProduct": "Delete Product",
"deleteProductConfirm": "DO you really want to remove the product named {product}"
}

View file

@ -123,38 +123,48 @@ class _HomePageState extends State<HomePage> {
)
],
),
body: ListView.builder(
itemCount: rooms.length,
itemBuilder: (ctx, i) {
final room = rooms[i];
return Card(
margin: const EdgeInsets.all(8.0),
clipBehavior: Clip.antiAliasWithSaveLayer,
semanticContainer: true,
child: InkWell(
onTap: () {
// open room
context.goNamed('room',
params: {'server': room.serverTag, 'id': room.id});
},
onLongPress: () {
// open bottom sheet
// NOTE: feature yet to be confirmed
},
child: Container(
padding: const EdgeInsets.fromLTRB(10, 5, 5, 10),
child: ListTile(
title: Text(room.name),
visualDensity: const VisualDensity(vertical: 3),
subtitle: Text(room.description),
leading: AspectRatio(
aspectRatio: 1 / 1,
child: SvgPicture.asset("${room.icon?.img}"),
),
hoverColor: Colors.transparent,
))));
},
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(14),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600),
child: ListView.builder(
itemCount: rooms.length,
itemBuilder: (ctx, i) {
final room = rooms[i];
return Card(
margin: const EdgeInsets.all(8.0),
clipBehavior: Clip.antiAliasWithSaveLayer,
semanticContainer: true,
child: InkWell(
onTap: () {
// open room
context.goNamed('room', params: {
'server': room.serverTag,
'id': room.id
});
},
onLongPress: () {
// open bottom sheet
// NOTE: feature yet to be confirmed
},
child: Container(
padding:
const EdgeInsets.fromLTRB(10, 5, 5, 10),
child: ListTile(
title: Text(room.name),
visualDensity:
const VisualDensity(vertical: 3),
subtitle: Text(room.description),
leading: AspectRatio(
aspectRatio: 1 / 1,
child:
SvgPicture.asset("${room.icon?.img}"),
),
hoverColor: Colors.transparent,
))));
},
)))),
floatingActionButton: FloatingActionButton.extended(
label: Text(AppLocalizations.of(context)!.addRoom),
icon: const Icon(Icons.add),

View file

@ -138,7 +138,7 @@ class _EditItemPageState extends State<EditItemPage> {
child: Padding(
padding: const EdgeInsets.all(14),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 400),
constraints: const BoxConstraints(maxWidth: 600),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,

View file

@ -33,303 +33,383 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
return SingleChildScrollView(
child: Center(
child: Column(children: [
// room meta display
...(widget.room != null)
? [
Padding(
child: Padding(
padding: const EdgeInsets.all(14),
child: Column(
children: [
SvgPicture.asset(
(widget.room?.icon?.img)!,
width: smallest * 0.2,
height: smallest * 0.2,
),
Text(
widget.room?.name ?? '',
style: textTheme.displayMedium,
),
Text(
'${widget.room?.id}@${widget.room?.serverTag}',
style: textTheme.bodySmall,
),
Text(
widget.room?.description ?? '',
style: textTheme.bodyMedium,
textAlign: TextAlign.center,
),
Padding(
padding: const EdgeInsets.all(8),
child: SegmentedButton<int>(
showSelectedIcon: true,
multiSelectionEnabled: false,
emptySelectionAllowed: false,
segments: RoomVisibility.list().map((vis) {
return ButtonSegment<int>(
value: vis.type,
label: Text(vis.text(context)),
icon: Icon(vis.icon));
}).toList(),
onSelectionChanged: ((vset) {
// check permission
// only show confirm dialog when user
// is admin, owner or has CHANGE_ADMIN permission
if (widget.info == null ||
(!(widget.info?.isAdmin ?? false) &&
!(widget.info?.isOwner ?? false) &&
((widget.info?.permissions)! &
RoomPermission.ota ==
0))) {
// action not permitted
// NOTE: no error dialog should be shown
// because the action is supposed to be hidden
return;
}
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600),
child: Column(children: [
// room meta display
...(widget.room != null)
? [
Padding(
padding: const EdgeInsets.all(14),
child: Column(
children: [
SvgPicture.asset(
(widget.room?.icon?.img)!,
width: smallest * 0.2,
height: smallest * 0.2,
),
Text(
widget.room?.name ?? '',
style: textTheme.displayMedium,
),
Text(
'${widget.room?.id}@${widget.room?.serverTag}',
style: textTheme.bodySmall,
),
Text(
widget.room?.description ?? '',
style: textTheme.bodyMedium,
textAlign: TextAlign.center,
),
Padding(
padding: const EdgeInsets.all(8),
child: SegmentedButton<int>(
showSelectedIcon: true,
multiSelectionEnabled: false,
emptySelectionAllowed: false,
segments:
RoomVisibility.list().map((vis) {
return ButtonSegment<int>(
value: vis.type,
label: Text(vis.text(context)),
icon: Icon(vis.icon));
}).toList(),
onSelectionChanged: ((vset) {
// check permission
// only show confirm dialog when user
// is admin, owner or has CHANGE_ADMIN permission
if (widget.info == null ||
(!(widget.info?.isAdmin ??
false) &&
!(widget.info?.isOwner ??
false) &&
((widget.info
?.permissions)! &
RoomPermission
.ota ==
0))) {
// action not permitted
// NOTE: no error dialog should be shown
// because the action is supposed to be hidden
return;
}
final vis = RoomVisibility(vset.first);
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Text(AppLocalizations.of(context)!
.changeRoomVisibilityTitle),
content: Text(
AppLocalizations.of(context)!
.changeRoomVisibilitySubtitle(
vis.text(context))),
actions: [
TextButton(
onPressed: () {
context.pop();
final vis =
RoomVisibility(vset.first);
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Text(AppLocalizations
.of(context)!
.changeRoomVisibilityTitle),
content: Text(AppLocalizations
.of(context)!
.changeRoomVisibilitySubtitle(
vis.text(
context))),
actions: [
TextButton(
onPressed: () {
context.pop();
},
child: Text(
AppLocalizations.of(
context)!
.cancel),
),
FilledButton(
onPressed: () async {
final scaffMgr =
ScaffoldMessenger
.of(context);
final nav =
Navigator.of(
context);
final user = context
.read<User>();
doNetworkRequest(
scaffMgr,
req: () =>
postWithCreadentials(
path:
'setVisibility',
target: user
.server,
body: {
'room': widget
.room
?.id,
'server': (widget
.room
?.serverTag)!,
'visibility':
vset.first
},
credentials:
user),
onOK: (_) {
Room r = widget
.room!;
r.visibility =
vis;
r.toDisk();
},
after: () {
nav.pop();
});
},
child: Text(
AppLocalizations.of(
context)!
.ok),
)
],
));
}),
selected: {
(widget.room?.visibility?.type)!
},
child: Text(
AppLocalizations.of(context)!
.cancel),
),
FilledButton(
onPressed: () async {
final scaffMgr =
ScaffoldMessenger.of(context);
final nav = Navigator.of(context);
final user = context.read<User>();
selectedIcon: Icon(
(widget.room?.visibility?.icon)!),
)),
],
),
)
]
: [],
doNetworkRequest(scaffMgr,
req: () => postWithCreadentials(
path: 'setVisibility',
target: user.server,
body: {
'room': widget.room?.id,
'server': (widget
.room?.serverTag)!,
'visibility': vset.first
},
credentials: user),
onOK: (_) {
Room r = widget.room!;
r.visibility = vis;
r.toDisk();
},
after: () {
nav.pop();
});
},
child: Text(
AppLocalizations.of(context)!.ok),
)
],
));
}),
selected: {(widget.room?.visibility?.type)!},
selectedIcon: Icon((widget.room?.visibility?.icon)!),
)),
],
),
)
]
: [],
Padding(
padding: const EdgeInsets.all(14),
child: Column(
children: [
// edit room meta button
...(widget.info != null &&
((widget.info?.isAdmin ?? false) ||
(widget.info?.isOwner ?? false) ||
((widget.info?.permissions)! &
RoomPermission.changeMeta !=
0)))
? [
ListTile(
trailing: const Icon(Icons.chevron_right),
title: Text(
AppLocalizations.of(context)!.editRoomMetadata),
subtitle: Text(AppLocalizations.of(context)!
.editRoomMetadataSubtitle),
onTap: () {
// show edit room screen
context.goNamed('edit-room', params: {
'server': (widget.room?.serverTag)!,
'id': (widget.room?.id)!
});
},
),
]
: [],
// open members view
ListTile(
trailing: const Icon(Icons.chevron_right),
title: Text(AppLocalizations.of(context)!.showRoomMembers),
subtitle:
Text(AppLocalizations.of(context)!.showRoomMembersSubtitle),
onTap: () {
// open member view screen
context.goNamed('room-members', params: {
'server': (widget.room?.serverTag)!,
'id': (widget.room?.id)!
});
},
),
// edit default member permission
...(widget.info != null &&
((widget.info?.isAdmin ?? false) ||
(widget.info?.isOwner ?? false) ||
((widget.info?.permissions)! &
RoomPermission.changeAdmin !=
0)))
? [
ListTile(
trailing: const Icon(Icons.chevron_right),
title: Text(
AppLocalizations.of(context)!.editRoomPermissions),
subtitle: Text(AppLocalizations.of(context)!
.editRoomPermissionsSubtitle),
onTap: () {
// show checkbox screen
context.goNamed('room-permissions', params: {
'server': (widget.room?.serverTag)!,
'id': (widget.room?.id)!
});
},
),
]
: [],
...(widget.info != null &&
((widget.info?.isAdmin ?? false) ||
(widget.info?.isOwner ?? false) ||
((widget.info?.permissions)! & RoomPermission.ota !=
0)))
? [
ListTile(
trailing: const Icon(Icons.chevron_right),
title:
Text(AppLocalizations.of(context)!.manageRoomOTA),
subtitle: Text(AppLocalizations.of(context)!
.manageRoomOTASubtitle),
onTap: () {
// show manage ota screen
context.goNamed('room-ota', params: {
'server': (widget.room?.serverTag)!,
'id': (widget.room?.id)!
});
},
),
ListTile(
trailing: const Icon(Icons.chevron_right),
title: Text(
AppLocalizations.of(context)!.manageRoomInvites),
subtitle: Text(AppLocalizations.of(context)!
.manageRoomInvitesSubtitle),
onTap: () {
// show manage ota screen
context.goNamed('room-invite', params: {
'server': (widget.room?.serverTag)!,
'id': (widget.room?.id)!
});
},
),
]
: [],
],
)),
...(widget.info != null)
? [
Padding(
padding: const EdgeInsets.all(8),
child: FilledButton.tonal(
child: Text(((widget.info?.isOwner)!)
? AppLocalizations.of(context)!.deleteRoom
: AppLocalizations.of(context)!.leaveRoom),
onPressed: () {
// show confirm dialog
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Text(((widget.info?.isOwner)!)
? AppLocalizations.of(context)!.deleteRoom
: AppLocalizations.of(context)!.leaveRoom),
content: Text(((widget.info?.isOwner)!)
? AppLocalizations.of(context)!
.deleteRoomConfirm
: AppLocalizations.of(context)!
.leaveRoomConfirm),
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);
final nav = Navigator.of(ctx);
final router = GoRouter.of(context);
final user = context.read<User>();
doNetworkRequest(scaffMgr,
req: () => postWithCreadentials(
path: ((widget.info?.isOwner)!)
? 'deleteRoom'
: 'leaveRoom',
target: user.server,
body: {
'room': widget.room?.id,
Padding(
padding: const EdgeInsets.all(14),
child: Column(
children: [
// edit room meta button
...(widget.info != null &&
((widget.info?.isAdmin ?? false) ||
(widget.info?.isOwner ?? false) ||
((widget.info?.permissions)! &
RoomPermission.changeMeta !=
0)))
? [
ListTile(
trailing:
const Icon(Icons.chevron_right),
title: Text(
AppLocalizations.of(context)!
.editRoomMetadata),
subtitle: Text(
AppLocalizations.of(context)!
.editRoomMetadataSubtitle),
onTap: () {
// show edit room screen
context.goNamed('edit-room', params: {
'server': (widget.room?.serverTag)!,
'id': (widget.room?.id)!
});
},
),
]
: [],
// open members view
ListTile(
trailing: const Icon(Icons.chevron_right),
title: Text(AppLocalizations.of(context)!
.showRoomMembers),
subtitle: Text(AppLocalizations.of(context)!
.showRoomMembersSubtitle),
onTap: () {
// open member view screen
context.goNamed('room-members', params: {
'server': (widget.room?.serverTag)!,
'id': (widget.room?.id)!
});
},
),
// edit default member permission
...(widget.info != null &&
((widget.info?.isAdmin ?? false) ||
(widget.info?.isOwner ?? false) ||
((widget.info?.permissions)! &
RoomPermission.changeAdmin !=
0)))
? [
ListTile(
trailing:
const Icon(Icons.chevron_right),
title: Text(
AppLocalizations.of(context)!
.editRoomPermissions),
subtitle: Text(
AppLocalizations.of(context)!
.editRoomPermissionsSubtitle),
onTap: () {
// show checkbox screen
context.goNamed('room-permissions',
params: {
'server':
(widget.room?.serverTag)!,
},
credentials: user),
onOK: (_) async {
// try delete room from disk
try {
await widget.room?.removeDisk();
} catch (_) {}
// go back home
router.pushReplacementNamed('home');
},
after: () {
// close popup
nav.pop();
'id': (widget.room?.id)!
});
},
),
]
: [],
...(widget.info != null &&
((widget.info?.isAdmin ?? false) ||
(widget.info?.isOwner ?? false) ||
((widget.info?.permissions)! &
RoomPermission.ota !=
0)))
? [
ListTile(
trailing:
const Icon(Icons.chevron_right),
title: Text(
AppLocalizations.of(context)!
.manageRoomOTA),
subtitle: Text(
AppLocalizations.of(context)!
.manageRoomOTASubtitle),
onTap: () {
// show manage ota screen
context.goNamed('room-ota', params: {
'server': (widget.room?.serverTag)!,
'id': (widget.room?.id)!
});
},
},
),
ListTile(
trailing:
const Icon(Icons.chevron_right),
title: Text(
AppLocalizations.of(context)!
.manageRoomInvites),
subtitle: Text(
AppLocalizations.of(context)!
.manageRoomInvitesSubtitle),
onTap: () {
// show manage ota screen
context
.goNamed('room-invite', params: {
'server': (widget.room?.serverTag)!,
'id': (widget.room?.id)!
});
},
),
]
: [],
],
)),
...(widget.info != null)
? [
Padding(
padding: const EdgeInsets.all(8),
child: FilledButton.tonal(
child: Text(((widget.info?.isOwner)!)
? AppLocalizations.of(context)!
.deleteRoomShort
.deleteRoom
: AppLocalizations.of(context)!
.leaveRoomShort),
)
],
));
},
))
]
: [],
])));
.leaveRoom),
onPressed: () {
// show confirm dialog
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Text(
((widget.info?.isOwner)!)
? AppLocalizations.of(
context)!
.deleteRoom
: AppLocalizations.of(
context)!
.leaveRoom),
content: Text(
((widget.info?.isOwner)!)
? AppLocalizations.of(
context)!
.deleteRoomConfirm
: AppLocalizations.of(
context)!
.leaveRoomConfirm),
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);
final nav =
Navigator.of(ctx);
final router =
GoRouter.of(context);
final user =
context.read<User>();
doNetworkRequest(scaffMgr,
req: () =>
postWithCreadentials(
path: ((widget
.info
?.isOwner)!)
? 'deleteRoom'
: 'leaveRoom',
target: user
.server,
body: {
'room': widget
.room
?.id,
'server': (widget
.room
?.serverTag)!,
},
credentials:
user),
onOK: (_) async {
// try delete room from disk
try {
await widget.room
?.removeDisk();
} catch (_) {}
// go back home
router
.pushReplacementNamed(
'home');
},
after: () {
// close popup
nav.pop();
});
},
child: Text(((widget
.info?.isOwner)!)
? AppLocalizations.of(
context)!
.deleteRoomShort
: AppLocalizations.of(
context)!
.leaveRoomShort),
)
],
));
},
))
]
: [],
])))));
}
}

View file

@ -61,178 +61,209 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
return Scaffold(
body: ReorderableListView.builder(
buildDefaultDragHandles: false,
itemBuilder: (context, index) {
final item = list[index];
body: Center(
child: Padding(
padding: const EdgeInsets.all(14),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600),
child: ReorderableListView.builder(
buildDefaultDragHandles: false,
itemBuilder: (context, index) {
final item = list[index];
return ListTile(
key: Key('cat-${item.id}'),
leading: Icon(Icons.square_rounded, color: item.color),
trailing: ((widget.info?.isAdmin ?? false) ||
(widget.info?.isOwner ?? false) ||
((widget.info?.permissions)! &
RoomPermission.editRoomContent !=
0))
? ReorderableDragStartListener(
index: index, child: const Icon(Icons.drag_handle))
: null,
title: Text(item.name),
onTap: () {
// TODO show edit category popup
// NOTE: maybe use ModalBottomSheet
// and show delete button in there
return ListTile(
key: Key('cat-${item.id}'),
leading: Icon(Icons.square_rounded, color: item.color),
trailing: ((widget.info?.isAdmin ?? false) ||
(widget.info?.isOwner ?? false) ||
((widget.info?.permissions)! &
RoomPermission.editRoomContent !=
0))
? ReorderableDragStartListener(
index: index,
child: const Icon(Icons.drag_handle))
: null,
title: Text(item.name),
onTap: () {
// TODO show edit category popup
// NOTE: maybe use ModalBottomSheet
// and show delete button in there
if (!((widget.info?.isAdmin ?? false) ||
(widget.info?.isOwner ?? false) ||
((widget.info?.permissions)! &
RoomPermission.editRoomContent !=
0))) {
// user is not allowed to edit or delete categories
return;
}
if (!((widget.info?.isAdmin ?? false) ||
(widget.info?.isOwner ?? false) ||
((widget.info?.permissions)! &
RoomPermission.editRoomContent !=
0))) {
// user is not allowed to edit or delete categories
return;
}
showModalBottomSheet(
context: context,
builder: (context) => BottomSheet(
builder: (context) => Column(children: [
Padding(
padding: const EdgeInsets.all(8),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Icons.square_rounded,
size: 48.0, color: item.color),
Text(item.name, style: textTheme.titleLarge)
],
)),
// edit category
ListTile(
leading: const Icon(Icons.edit),
title: Text(AppLocalizations.of(context)!.editCategory),
subtitle:
Text(AppLocalizations.of(context)!.editCategoryLong),
trailing: const Icon(Icons.chevron_right),
onTap: () {
// close the modal bottom sheet
// so the user returns to the list,
// when leaving the category editor
Navigator.of(context).pop();
// launch category editor
context.pushNamed('edit-category', params: {
'server': widget.room!.serverTag,
'id': widget.room!.id,
'category': item.id.toString()
});
},
),
// delete category
ListTile(
leading: const Icon(Icons.delete),
title: Text(AppLocalizations.of(context)!.deleteCategory),
subtitle: Text(
AppLocalizations.of(context)!.deleteCategoryLong),
trailing: const Icon(Icons.chevron_right),
onTap: () {
// show popup
showDialog(
showModalBottomSheet(
context: context,
builder: (ctx) => AlertDialog(
icon: const Icon(Icons.delete),
builder: (context) => BottomSheet(
builder: (context) => Column(children: [
Padding(
padding: const EdgeInsets.all(8),
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.center,
children: [
Icon(Icons.square_rounded,
size: 48.0, color: item.color),
Text(item.name,
style: textTheme.titleLarge)
],
)),
// edit category
ListTile(
leading: const Icon(Icons.edit),
title: Text(AppLocalizations.of(context)!
.editCategory),
subtitle: Text(AppLocalizations.of(context)!
.editCategoryLong),
trailing: const Icon(Icons.chevron_right),
onTap: () {
// close the modal bottom sheet
// so the user returns to the list,
// when leaving the category editor
Navigator.of(context).pop();
// launch category editor
context.pushNamed('edit-category', params: {
'server': widget.room!.serverTag,
'id': widget.room!.id,
'category': item.id.toString()
});
},
),
// delete category
ListTile(
leading: const Icon(Icons.delete),
title: Text(AppLocalizations.of(context)!
.deleteCategory),
content: Text(AppLocalizations.of(context)!
.deleteCategoryConfirm(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>();
subtitle: Text(AppLocalizations.of(context)!
.deleteCategoryLong),
trailing: const Icon(Icons.chevron_right),
onTap: () {
// show popup
showDialog(
context: context,
builder: (ctx) => AlertDialog(
icon: const Icon(Icons.delete),
title: Text(
AppLocalizations.of(context)!
.deleteCategory),
content: Text(
AppLocalizations.of(context)!
.deleteCategoryConfirm(
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: 'deleteCategory',
target: user.server,
body: {
'room': widget.room?.id,
'server':
widget.room?.serverTag,
'listCatID': item.id
},
credentials: user),
onOK: (_) async {
// TODO: remove cached category
fetchCategories();
},
after: () {
// close popup
navInner.pop();
// close modal bottom sheet
nav.pop();
});
},
child: Text(AppLocalizations.of(context)!
.deleteCategory),
)
],
));
},
),
]),
onClosing: () {},
),
);
},
);
},
itemCount: list.length,
onReorder: (int oldIndex, int newIndex) {
if (!((widget.info?.isAdmin ?? false) ||
(widget.info?.isOwner ?? false) ||
((widget.info?.permissions)! & RoomPermission.editRoomContent !=
0))) {
// user is not allowed to edit or delete categories
return;
}
doNetworkRequest(scaffMgr,
req: () =>
postWithCreadentials(
path:
'deleteCategory',
target:
user.server,
body: {
'room': widget
.room?.id,
'server': widget
.room
?.serverTag,
'listCatID':
item.id
},
credentials:
user),
onOK: (_) async {
// TODO: remove cached category
fetchCategories();
},
after: () {
// close popup
navInner.pop();
// close modal bottom sheet
nav.pop();
});
},
child: Text(
AppLocalizations.of(
context)!
.deleteCategory),
)
],
));
},
),
]),
onClosing: () {},
),
);
},
);
},
itemCount: list.length,
onReorder: (int oldIndex, int newIndex) {
if (!((widget.info?.isAdmin ?? false) ||
(widget.info?.isOwner ?? false) ||
((widget.info?.permissions)! &
RoomPermission.editRoomContent !=
0))) {
// user is not allowed to edit or delete categories
return;
}
setState(() {
if (oldIndex < newIndex) {
newIndex -= 1;
}
final item = list.removeAt(oldIndex);
list.insert(newIndex, item);
setState(() {
if (oldIndex < newIndex) {
newIndex -= 1;
}
final item = list.removeAt(oldIndex);
list.insert(newIndex, item);
// network request
final user = context.read<User>();
doNetworkRequest(ScaffoldMessenger.of(context),
req: () => postWithCreadentials(
credentials: user,
target: user.server,
path: 'changeCategoriesOrder',
body: {
'room': widget.room?.id,
'server': widget.room?.serverTag,
'listCatIDs': list.map((item) => item.id).toList()
}));
});
},
),
// network request
final user = context.read<User>();
doNetworkRequest(ScaffoldMessenger.of(context),
req: () => postWithCreadentials(
credentials: user,
target: user.server,
path: 'changeCategoriesOrder',
body: {
'room': widget.room?.id,
'server': widget.room?.serverTag,
'listCatIDs':
list.map((item) => item.id).toList()
}));
});
},
)))),
floatingActionButton: (widget.info != null &&
((widget.info?.isAdmin ?? false) ||
(widget.info?.isOwner ?? false) ||

View file

@ -194,110 +194,115 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: 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] ?? RoomCategory.other(context);
return ShoppingListItem(
name: item.name,
description: item.description,
category: cat,
key: Key(item.id.toString()),
inCart: item.state != 0,
onDismiss: () {
// move to cart
item.state = 1;
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] ??
RoomCategory.other(context);
return ShoppingListItem(
name: item.name,
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();
});
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] ?? RoomCategory.other(context);
// 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] ??
RoomCategory.other(context);
return ShoppingListItem(
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();
});
return ShoppingListItem(
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));
});
},
)
]),
// 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));
});
},
)
])))),
floatingActionButton: (widget.info != null &&
((widget.info?.isAdmin ?? false) ||
(widget.info?.isOwner ?? false) ||
@ -459,7 +464,60 @@ class ShoppingListItemInfo extends StatelessWidget {
AppLocalizations.of(context)!.deleteItemSubtitle),
trailing: const Icon(Icons.chevron_right),
onTap: () {
// TODO: show confirm dialog
// 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 {
// TODO: remove cached item
},
after: () {
// close popup
navInner.pop();
// close modal bottom sheet
nav.pop();
});
},
child: Text(AppLocalizations.of(context)!
.deleteItem),
)
],
));
}),
]
: [],

View file

@ -55,32 +55,37 @@ class _RoomProductsPageState extends State<RoomProductsPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
final item = products[index];
body: Center(
child: Padding(
padding: const EdgeInsets.all(14),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600),
child: ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
final item = products[index];
return ListTile(
title: Text(item.name),
subtitle: Text(item.description),
onTap: () {
// NOTE: we could also show a bottom sheet here,
// but because we need a seperate page/route either way
// (for viewing item-links and exploring the product-tree)
// we might as well use the view-product page here
// NOTE: This might seem inconsistent,
// but you probably wont ever need to read a product description,
// where as reading the shopping item description,
// might be a good idea
context.pushNamed('view-product', params: {
'server': widget.room!.serverTag,
'id': widget.room!.id,
'product': item.id.toString()
});
},
);
},
),
return ListTile(
title: Text(item.name),
subtitle: Text(item.description),
onTap: () {
// NOTE: we could also show a bottom sheet here,
// but because we need a seperate page/route either way
// (for viewing item-links and exploring the product-tree)
// we might as well use the view-product page here
// NOTE: This might seem inconsistent,
// but you probably wont ever need to read a product description,
// where as reading the shopping item description,
// might be a good idea
context.pushNamed('view-product', params: {
'server': widget.room!.serverTag,
'id': widget.room!.id,
'product': item.id.toString()
});
},
);
},
)))),
floatingActionButton: (widget.info != null &&
((widget.info?.isAdmin ?? false) ||
(widget.info?.isOwner ?? false) ||

View file

@ -207,6 +207,76 @@ class _ViewProductPageState extends State<ViewProductPage> {
},
trailing: const Icon(Icons.chevron_right),
),
...(info != null &&
((info?.isAdmin ?? false) ||
(info?.isOwner ?? false) ||
((info?.permissions)! & RoomPermission.editRoomContent !=
0)))
? [
// delete product
ListTile(
title: Text(AppLocalizations.of(context)!.deleteProductTitle),
subtitle:
Text(AppLocalizations.of(context)!.deleteProductSubtitle),
onTap: () {
// show popup
showDialog(
context: context,
builder: (ctx) => AlertDialog(
icon: const Icon(Icons.delete),
title: Text(
AppLocalizations.of(context)!.deleteProduct),
content: Text(AppLocalizations.of(context)!
.deleteProductConfirm(product?.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: 'deleteProduct',
target: user.server,
body: {
'room': widget.room,
'server': widget.server,
'listProdID': product?.id ?? ""
},
credentials: user),
onOK: (_) async {
// TODO: remove cached product
},
after: () {
// close popup
navInner.pop();
// close modal bottom sheet
nav.pop();
});
},
child: Text(AppLocalizations.of(context)!
.deleteProduct),
)
],
));
},
trailing: const Icon(Icons.chevron_right),
),
]
: []
])),
);
}