From 13c071b8ca94236532f882bbdf94541d4a196090 Mon Sep 17 00:00:00 2001 From: Jakob Meier Date: Fri, 23 Feb 2024 10:16:57 +0100 Subject: [PATCH] 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 --- lib/l10n/app_en.arb | 9 +- lib/screens/home.dart | 74 +-- lib/screens/room/items/edit.dart | 2 +- lib/screens/room/pages/about.dart | 656 ++++++++++++++----------- lib/screens/room/pages/categories.dart | 357 ++++++++------ lib/screens/room/pages/list.dart | 260 ++++++---- lib/screens/room/pages/products.dart | 55 ++- lib/screens/room/products/view.dart | 70 +++ 8 files changed, 872 insertions(+), 611 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 047540a..de5cb8f 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -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}" } diff --git a/lib/screens/home.dart b/lib/screens/home.dart index 5eae005..bd28d15 100644 --- a/lib/screens/home.dart +++ b/lib/screens/home.dart @@ -123,38 +123,48 @@ class _HomePageState extends State { ) ], ), - 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), diff --git a/lib/screens/room/items/edit.dart b/lib/screens/room/items/edit.dart index 79717dd..331b7c6 100644 --- a/lib/screens/room/items/edit.dart +++ b/lib/screens/room/items/edit.dart @@ -138,7 +138,7 @@ class _EditItemPageState extends State { 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, diff --git a/lib/screens/room/pages/about.dart b/lib/screens/room/pages/about.dart index fdea953..cf14040 100644 --- a/lib/screens/room/pages/about.dart +++ b/lib/screens/room/pages/about.dart @@ -33,303 +33,383 @@ class _AboutRoomPageState extends State { 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( - showSelectedIcon: true, - multiSelectionEnabled: false, - emptySelectionAllowed: false, - segments: RoomVisibility.list().map((vis) { - return ButtonSegment( - 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( + showSelectedIcon: true, + multiSelectionEnabled: false, + emptySelectionAllowed: false, + segments: + RoomVisibility.list().map((vis) { + return ButtonSegment( + 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(); + + 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(); + 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(); - - 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(); + + 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), + ) + ], + )); + }, + )) + ] + : [], + ]))))); } } diff --git a/lib/screens/room/pages/categories.dart b/lib/screens/room/pages/categories.dart index 89a8c43..325598f 100644 --- a/lib/screens/room/pages/categories.dart +++ b/lib/screens/room/pages/categories.dart @@ -61,178 +61,209 @@ class _RoomCategoriesPageState extends State { .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(); + 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(); - 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(); - 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(); + 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) || diff --git a/lib/screens/room/pages/list.dart b/lib/screens/room/pages/list.dart index 1acf73d..87a424f 100644 --- a/lib/screens/room/pages/list.dart +++ b/lib/screens/room/pages/list.dart @@ -194,110 +194,115 @@ class _ShoppingListPageState extends State { @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(); + + 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), + ) + ], + )); }), ] : [], diff --git a/lib/screens/room/pages/products.dart b/lib/screens/room/pages/products.dart index 6514940..7b29f60 100644 --- a/lib/screens/room/pages/products.dart +++ b/lib/screens/room/pages/products.dart @@ -55,32 +55,37 @@ class _RoomProductsPageState extends State { @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) || diff --git a/lib/screens/room/products/view.dart b/lib/screens/room/products/view.dart index f3476ce..5aff841 100644 --- a/lib/screens/room/products/view.dart +++ b/lib/screens/room/products/view.dart @@ -207,6 +207,76 @@ class _ViewProductPageState extends State { }, 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(); + + 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), + ), + ] + : [] ])), ); }