From c0ff1ffa27ee9c5fd8c377bfa05aca4296be8a21 Mon Sep 17 00:00:00 2001 From: Alexander Pahn Date: Sat, 18 May 2024 10:04:37 +0200 Subject: [PATCH 01/12] created and connect basket overview page --- .../basket/overview/basket_overview_page.dart | 27 +++++++++++++++++++ .../structural/drawer/drawer_entries.dart | 6 +++++ lib/generated/intl/messages_de_DE.dart | 1 + lib/generated/intl/messages_en.dart | 1 + lib/generated/l10n.dart | 10 +++++++ lib/l10n/intl_de_DE.arb | 1 + lib/l10n/intl_en.arb | 1 + lib/routing/router_config.dart | 7 +++++ lib/routing/routes.dart | 3 ++- 9 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 lib/features/basket/overview/basket_overview_page.dart diff --git a/lib/features/basket/overview/basket_overview_page.dart b/lib/features/basket/overview/basket_overview_page.dart new file mode 100644 index 0000000..2983e3c --- /dev/null +++ b/lib/features/basket/overview/basket_overview_page.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:rabenkorb/features/core/structural/core_scaffold.dart'; +import 'package:rabenkorb/features/core/structural/drawer/core_drawer.dart'; +import 'package:rabenkorb/generated/l10n.dart'; + +class BasketOverviewPage extends StatelessWidget { + const BasketOverviewPage({super.key}); + + @override + Widget build(BuildContext context) { + return CoreScaffold( + body: Center( + child: Padding( + padding: EdgeInsets.all(8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [Text(S.of(context).BasketOverview)], + ), + ), + ), + drawer: const CoreDrawer(), + appBar: AppBar( + title: Text(S.of(context).BasketOverview), + ), + ); + } +} diff --git a/lib/features/core/structural/drawer/drawer_entries.dart b/lib/features/core/structural/drawer/drawer_entries.dart index 820f09f..bfc1c92 100644 --- a/lib/features/core/structural/drawer/drawer_entries.dart +++ b/lib/features/core/structural/drawer/drawer_entries.dart @@ -14,6 +14,12 @@ List drawerEntriesBuilder(BuildContext context) => [ onTap: (BuildContext context) => context.go(Routes.home), leading: const Icon(Icons.home), ), + DrawerEntry( + title: S.of(context).BasketOverview, + position: 1, + onTap: (BuildContext context) => context.go(Routes.basketOverview), + leading: const Icon(Icons.list), + ), DrawerEntry( title: S.of(context).Settings, position: 1, diff --git a/lib/generated/intl/messages_de_DE.dart b/lib/generated/intl/messages_de_DE.dart index 6e7c2c9..6a9e13a 100644 --- a/lib/generated/intl/messages_de_DE.dart +++ b/lib/generated/intl/messages_de_DE.dart @@ -44,6 +44,7 @@ class MessageLookup extends MessageLookupByLibrary { "BackupOverwriteWarning": MessageLookupByLibrary.simpleMessage( "Wenn du ein Backup importierst wird dein derzeitiger Stand überschrieben! Wähle oben die Option zum Überschreiben alter Data ab, wenn du die importierten Daten zu deiner Bibliothek hinzufügen willst."), "Basket": MessageLookupByLibrary.simpleMessage("Korb"), + "BasketOverview": MessageLookupByLibrary.simpleMessage("Korb Übersicht"), "Cancel": MessageLookupByLibrary.simpleMessage("Abbrechen"), "Category": MessageLookupByLibrary.simpleMessage("Kategorie"), "ChooseSource": MessageLookupByLibrary.simpleMessage("Quelle wählen"), diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index c33c06f..f255c84 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -44,6 +44,7 @@ class MessageLookup extends MessageLookupByLibrary { "BackupOverwriteWarning": MessageLookupByLibrary.simpleMessage( "By importing a backup your current state will be overwritten! Uncheck the option for overwriting data above, if you want to add the imported data to your library."), "Basket": MessageLookupByLibrary.simpleMessage("Basket"), + "BasketOverview": MessageLookupByLibrary.simpleMessage("Basket Overview"), "Cancel": MessageLookupByLibrary.simpleMessage("Cancel"), "Category": MessageLookupByLibrary.simpleMessage("Category"), "ChooseSource": MessageLookupByLibrary.simpleMessage("Choose the source"), diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 1e3beb0..7cc4ad4 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -167,6 +167,16 @@ class S { ); } + /// `Basket Overview` + String get BasketOverview { + return Intl.message( + 'Basket Overview', + name: 'BasketOverview', + desc: '', + args: [], + ); + } + /// `Library` String get Library { return Intl.message( diff --git a/lib/l10n/intl_de_DE.arb b/lib/l10n/intl_de_DE.arb index d00748f..667b23a 100644 --- a/lib/l10n/intl_de_DE.arb +++ b/lib/l10n/intl_de_DE.arb @@ -12,6 +12,7 @@ "Language": "Sprache", "EmptyMessage": "Nichts zu sehen", "Basket": "Korb", + "BasketOverview": "Korb Übersicht", "Library": "Bibliothek", "Item": "Artikel", "Template": "Template", diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 73620a6..04b1f11 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -12,6 +12,7 @@ "Language": "Language", "EmptyMessage": "Nothing to see", "Basket": "Basket", + "BasketOverview": "Basket Overview", "Library": "Library", "Item": "Item", "Template": "Template", diff --git a/lib/routing/router_config.dart b/lib/routing/router_config.dart index 51b17ee..1ca13dd 100644 --- a/lib/routing/router_config.dart +++ b/lib/routing/router_config.dart @@ -3,6 +3,7 @@ import 'package:go_router/go_router.dart'; import 'package:rabenkorb/features/backup/backup_page.dart'; import 'package:rabenkorb/features/backup/backup_restore_screen.dart'; import 'package:rabenkorb/features/basket/details/basket_item_details.dart'; +import 'package:rabenkorb/features/basket/overview/basket_overview_page.dart'; import 'package:rabenkorb/features/debug/debug_page.dart'; import 'package:rabenkorb/features/library/details/item_template_details.dart'; import 'package:rabenkorb/features/main/main_page.dart'; @@ -50,6 +51,12 @@ RouterConfig goRouterConfig({String initialLocation = Routes.home}) => G ); }, ), + GoRoute( + path: Routes.basketOverview, + builder: (context, state) { + return const BasketOverviewPage(); + }, + ), GoRoute( path: Routes.basket, redirect: (context, state) { diff --git a/lib/routing/routes.dart b/lib/routing/routes.dart index 5e2acc9..eb9a914 100644 --- a/lib/routing/routes.dart +++ b/lib/routing/routes.dart @@ -2,8 +2,9 @@ abstract class Routes { static const String home = "/"; static const String library = "/library"; static const String basket = "/basket"; + static const String basketItemDetails = "$basket/item"; + static const String basketOverview = "$basket/overview"; static const String libraryItemTemplateDetails = "$library/item-template"; - static const String basketItemDetails = "$library/item"; static const String debug = "/debug"; static const String backup = "/backup"; static const String backupRestore = "$backup/import"; From 0c0a3d40d7915dd3765db752f3985ebd430e13d4 Mon Sep 17 00:00:00 2001 From: Alexander Pahn Date: Sat, 18 May 2024 10:33:06 +0200 Subject: [PATCH 02/12] implemented basket overview --- lib/database/daos/basket_items_dao.dart | 6 ++ lib/features/basket/overview/basket_list.dart | 28 ++++++++ .../basket/overview/basket_overview_page.dart | 20 +++++- lib/features/basket/overview/basket_tile.dart | 68 +++++++++++++++++++ lib/generated/intl/messages_de_DE.dart | 1 + lib/generated/intl/messages_en.dart | 1 + lib/generated/l10n.dart | 10 +++ lib/l10n/intl_de_DE.arb | 1 + lib/l10n/intl_en.arb | 1 + lib/services/business/basket_service.dart | 4 ++ .../data_access/basket_item_service.dart | 4 ++ 11 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 lib/features/basket/overview/basket_list.dart create mode 100644 lib/features/basket/overview/basket_tile.dart diff --git a/lib/database/daos/basket_items_dao.dart b/lib/database/daos/basket_items_dao.dart index 4dc0011..2845fd7 100644 --- a/lib/database/daos/basket_items_dao.dart +++ b/lib/database/daos/basket_items_dao.dart @@ -174,6 +174,12 @@ class BasketItemsDao extends DatabaseAccessor with _$BasketItemsDao return _rowsToViewModels(rows); } + Future countItemsInBasket(int basketId) async { + final itemsInBasket = basketItems.basket.count(filter: basketItems.basket.equals(basketId)); + final itemsInBasketQuery = selectOnly(basketItems)..addColumns([itemsInBasket]); + return await itemsInBasketQuery.map((row) => row.read(itemsInBasket)).getSingle() ?? 0; + } + List _getOrderingTerms( SortMode sortMode, ) { diff --git a/lib/features/basket/overview/basket_list.dart b/lib/features/basket/overview/basket_list.dart new file mode 100644 index 0000000..f5112a1 --- /dev/null +++ b/lib/features/basket/overview/basket_list.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:rabenkorb/features/basket/overview/basket_tile.dart'; +import 'package:rabenkorb/models/shopping_basket_view_model.dart'; +import 'package:rabenkorb/services/business/basket_service.dart'; +import 'package:watch_it/watch_it.dart'; + +class BasketList extends StatelessWidget with WatchItMixin { + const BasketList({super.key}); + + @override + Widget build(BuildContext context) { + final baskets = watchStream((BasketService p0) => p0.baskets, initialValue: []); + final basketList = baskets.data ?? []; + return Expanded( + child: ListView.builder( + prototypeItem: BasketTile( + basket: ShoppingBasketViewModel(-1, "Prototyp"), + ), + itemCount: basketList.length, + itemBuilder: (BuildContext context, int index) { + return BasketTile( + basket: basketList[index], + ); + }, + ), + ); + } +} diff --git a/lib/features/basket/overview/basket_overview_page.dart b/lib/features/basket/overview/basket_overview_page.dart index 2983e3c..e31fcea 100644 --- a/lib/features/basket/overview/basket_overview_page.dart +++ b/lib/features/basket/overview/basket_overview_page.dart @@ -1,7 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:rabenkorb/features/basket/overview/basket_list.dart'; import 'package:rabenkorb/features/core/structural/core_scaffold.dart'; import 'package:rabenkorb/features/core/structural/drawer/core_drawer.dart'; import 'package:rabenkorb/generated/l10n.dart'; +import 'package:rabenkorb/services/business/basket_service.dart'; +import 'package:rabenkorb/shared/helper_functions.dart'; +import 'package:watch_it/watch_it.dart'; class BasketOverviewPage extends StatelessWidget { const BasketOverviewPage({super.key}); @@ -9,12 +13,12 @@ class BasketOverviewPage extends StatelessWidget { @override Widget build(BuildContext context) { return CoreScaffold( - body: Center( + body: const Center( child: Padding( padding: EdgeInsets.all(8.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, - children: [Text(S.of(context).BasketOverview)], + children: [BasketList()], ), ), ), @@ -22,6 +26,18 @@ class BasketOverviewPage extends StatelessWidget { appBar: AppBar( title: Text(S.of(context).BasketOverview), ), + floatingActionButton: FloatingActionButton( + key: const Key("basket-overview-page-fab"), + onPressed: () async { + await showRenameDialog( + context, + onConfirm: (String? newName, bool _) async { + await di().createShoppingBasket(newName); + }, + ); + }, + child: const Icon(Icons.add), + ), ); } } diff --git a/lib/features/basket/overview/basket_tile.dart b/lib/features/basket/overview/basket_tile.dart new file mode 100644 index 0000000..4b9ba9d --- /dev/null +++ b/lib/features/basket/overview/basket_tile.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:rabenkorb/generated/l10n.dart'; +import 'package:rabenkorb/models/shopping_basket_view_model.dart'; +import 'package:rabenkorb/services/business/basket_service.dart'; +import 'package:rabenkorb/shared/helper_functions.dart'; +import 'package:rabenkorb/shared/widgets/inputs/core_icon_button.dart'; +import 'package:watch_it/watch_it.dart'; + +class BasketTile extends StatelessWidget { + final ShoppingBasketViewModel basket; + + const BasketTile({super.key, required this.basket}); + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: di().countItemsInBasket(basket.id), + initialData: 0, + builder: (BuildContext context, AsyncSnapshot itemsInBasketSnapshot) { + final itemsInBasketCount = itemsInBasketSnapshot.data ?? 0; + return Card( + child: ListTile( + title: Text(basket.name), + subtitle: Text("${S.of(context).Items}: $itemsInBasketCount"), + trailing: Row( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + CoreIconButton( + icon: const Icon(Icons.edit), + onPressed: () async { + await showRenameDialog( + context, + initialName: basket.name, + onConfirm: (String? newName, bool nameChanged) async { + if (!nameChanged || newName == null) { + return; + } + await di().updateShoppingBasket(basket.id, newName); + }, + ); + }, + ), + CoreIconButton( + icon: const Icon( + Icons.delete_forever, + color: Colors.red, + ), + onPressed: () async { + await doWithConfirmation( + context, + text: S.of(context).ConfirmDeleteBasket, + title: S.of(context).Confirm, + onConfirm: () async { + await di().deleteShoppingBasketById(basket.id); + di().setFirstShoppingBasketActive(); + }, + ); + }, + ) + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/generated/intl/messages_de_DE.dart b/lib/generated/intl/messages_de_DE.dart index 6a9e13a..329708f 100644 --- a/lib/generated/intl/messages_de_DE.dart +++ b/lib/generated/intl/messages_de_DE.dart @@ -66,6 +66,7 @@ class MessageLookup extends MessageLookupByLibrary { "Image": MessageLookupByLibrary.simpleMessage("Bild"), "Item": MessageLookupByLibrary.simpleMessage("Artikel"), "ItemAddedToCard": m0, + "Items": MessageLookupByLibrary.simpleMessage("Artikel"), "Language": MessageLookupByLibrary.simpleMessage("Sprache"), "LanguageDisplayName": MessageLookupByLibrary.simpleMessage("Deutsch"), "Library": MessageLookupByLibrary.simpleMessage("Bibliothek"), diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index f255c84..ee13820 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -66,6 +66,7 @@ class MessageLookup extends MessageLookupByLibrary { "Image": MessageLookupByLibrary.simpleMessage("Image"), "Item": MessageLookupByLibrary.simpleMessage("Item"), "ItemAddedToCard": m0, + "Items": MessageLookupByLibrary.simpleMessage("Items"), "Language": MessageLookupByLibrary.simpleMessage("Language"), "LanguageDisplayName": MessageLookupByLibrary.simpleMessage("English"), "Library": MessageLookupByLibrary.simpleMessage("Library"), diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 7cc4ad4..9a01ee7 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -197,6 +197,16 @@ class S { ); } + /// `Items` + String get Items { + return Intl.message( + 'Items', + name: 'Items', + desc: '', + args: [], + ); + } + /// `Template` String get Template { return Intl.message( diff --git a/lib/l10n/intl_de_DE.arb b/lib/l10n/intl_de_DE.arb index 667b23a..5dd554f 100644 --- a/lib/l10n/intl_de_DE.arb +++ b/lib/l10n/intl_de_DE.arb @@ -15,6 +15,7 @@ "BasketOverview": "Korb Übersicht", "Library": "Bibliothek", "Item": "Artikel", + "Items": "Artikel", "Template": "Template", "Unnamed": "Unbenannt", "MissingString": "", diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 04b1f11..8e609ed 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -15,6 +15,7 @@ "BasketOverview": "Basket Overview", "Library": "Library", "Item": "Item", + "Items": "Items", "Template": "Template", "Unnamed": "Unnamed", "MissingString": "", diff --git a/lib/services/business/basket_service.dart b/lib/services/business/basket_service.dart index 812227a..36e2ba5 100644 --- a/lib/services/business/basket_service.dart +++ b/lib/services/business/basket_service.dart @@ -238,6 +238,10 @@ class BasketService implements Disposable { return deletedItems; } + Future countItemsInBasket(int basketId) { + return _basketItemService.countItemsInBasket(basketId); + } + Future _ensureExistingBasket(int? basketId) async { if (basketId == null) { basketId = await _shoppingBasketService.getFirstShoppingBasketId(); diff --git a/lib/services/data_access/basket_item_service.dart b/lib/services/data_access/basket_item_service.dart index d8e7c27..082c8ee 100644 --- a/lib/services/data_access/basket_item_service.dart +++ b/lib/services/data_access/basket_item_service.dart @@ -131,6 +131,10 @@ class BasketItemService implements Disposable { return (await _db.countImagePathUsages(imagePath)) ?? 0; } + Future countItemsInBasket(int basketId) { + return _db.basketItemsDao.countItemsInBasket(basketId); + } + Stream>> _watchBasketItemsInOrder({ required int? basketId, required SortMode sortMode, From 1e7e0d5a464b9e6c273834c11cd4319c99201bf8 Mon Sep 17 00:00:00 2001 From: Alexander Pahn Date: Sat, 18 May 2024 11:00:12 +0200 Subject: [PATCH 03/12] created and connected data management page --- lib/abstracts/navigation_state_service.dart | 9 ++++ lib/di/di_setup.dart | 6 ++- lib/features/basket/basket_main_action.dart | 2 +- .../structural/drawer/drawer_entries.dart | 6 +++ .../data_management/data_management_page.dart | 46 +++++++++++++++++ .../navigation/destinations.dart | 30 +++++++++++ lib/features/library/library_main_action.dart | 2 +- lib/features/main/main_page.dart | 14 +++-- .../main/navigation/destinations.dart | 2 +- lib/generated/intl/messages_de_DE.dart | 3 ++ lib/generated/intl/messages_en.dart | 3 ++ lib/generated/l10n.dart | 30 +++++++++++ lib/l10n/intl_de_DE.arb | 3 ++ lib/l10n/intl_en.arb | 3 ++ lib/models/main_page_details.dart | 6 +-- lib/routing/router_config.dart | 26 ++++++++-- lib/routing/routes.dart | 3 ++ ...a_management_navigation_state_service.dart | 51 +++++++++++++++++++ ...art => main_navigation_state_service.dart} | 13 ++--- .../destination_details.dart | 0 .../widgets}/core_navigation.dart | 8 ++- 21 files changed, 239 insertions(+), 27 deletions(-) create mode 100644 lib/abstracts/navigation_state_service.dart create mode 100644 lib/features/data_management/data_management_page.dart create mode 100644 lib/features/data_management/navigation/destinations.dart create mode 100644 lib/services/state/data_management_navigation_state_service.dart rename lib/services/state/{navigation_state_service.dart => main_navigation_state_service.dart} (71%) rename lib/{features/main/navigation => shared}/destination_details.dart (100%) rename lib/{features/main/navigation => shared/widgets}/core_navigation.dart (63%) diff --git a/lib/abstracts/navigation_state_service.dart b/lib/abstracts/navigation_state_service.dart new file mode 100644 index 0000000..4c709b3 --- /dev/null +++ b/lib/abstracts/navigation_state_service.dart @@ -0,0 +1,9 @@ +import 'package:flutter/material.dart'; + +abstract class NavigationStateService { + void setCurrentPageIndex(int index); + + int get currentPageIndexSync; + + List get destinations; +} diff --git a/lib/di/di_setup.dart b/lib/di/di_setup.dart index af76692..223a3b5 100644 --- a/lib/di/di_setup.dart +++ b/lib/di/di_setup.dart @@ -27,10 +27,11 @@ import 'package:rabenkorb/services/data_access/sort_rule_service.dart'; import 'package:rabenkorb/services/data_access/template_library_service.dart'; import 'package:rabenkorb/services/data_access/variant_key_service.dart'; import 'package:rabenkorb/services/state/basket_state_service.dart'; +import 'package:rabenkorb/services/state/data_management_navigation_state_service.dart'; import 'package:rabenkorb/services/state/intl_state_service.dart'; import 'package:rabenkorb/services/state/library_state_service.dart'; import 'package:rabenkorb/services/state/loading_state.dart'; -import 'package:rabenkorb/services/state/navigation_state_service.dart'; +import 'package:rabenkorb/services/state/main_navigation_state_service.dart'; import 'package:rabenkorb/services/state/shared_preference_service.dart'; import 'package:rabenkorb/services/utility/backup_service.dart'; import 'package:rabenkorb/services/utility/image_service.dart'; @@ -122,7 +123,8 @@ Future _registerStateServices() async { await intlService.init(); return intlService; }, dependsOn: [PreferenceService]); - di.registerSingletonWithDependencies(() => NavigationStateService(), dependsOn: [IntlStateService]); + di.registerSingletonWithDependencies(() => MainNavigationStateService(), dependsOn: [IntlStateService]); + di.registerSingletonWithDependencies(() => DataManagementNavigationStateService(), dependsOn: [IntlStateService]); } void _registerUtilityServices() { diff --git a/lib/features/basket/basket_main_action.dart b/lib/features/basket/basket_main_action.dart index 0d22c18..7077ed9 100644 --- a/lib/features/basket/basket_main_action.dart +++ b/lib/features/basket/basket_main_action.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import 'package:rabenkorb/features/main/navigation/destination_details.dart'; import 'package:rabenkorb/routing/routes.dart'; +import 'package:rabenkorb/shared/destination_details.dart'; final basketMainAction = MainAction( onPressed: (BuildContext context) { diff --git a/lib/features/core/structural/drawer/drawer_entries.dart b/lib/features/core/structural/drawer/drawer_entries.dart index bfc1c92..677f4ac 100644 --- a/lib/features/core/structural/drawer/drawer_entries.dart +++ b/lib/features/core/structural/drawer/drawer_entries.dart @@ -20,6 +20,12 @@ List drawerEntriesBuilder(BuildContext context) => [ onTap: (BuildContext context) => context.go(Routes.basketOverview), leading: const Icon(Icons.list), ), + DrawerEntry( + title: S.of(context).DataManagement, + position: 1, + onTap: (BuildContext context) => context.go(Routes.dataManagement), + leading: const Icon(Icons.dataset), + ), DrawerEntry( title: S.of(context).Settings, position: 1, diff --git a/lib/features/data_management/data_management_page.dart b/lib/features/data_management/data_management_page.dart new file mode 100644 index 0000000..00c71d4 --- /dev/null +++ b/lib/features/data_management/data_management_page.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:rabenkorb/features/core/structural/core_scaffold.dart'; +import 'package:rabenkorb/features/core/structural/drawer/core_drawer.dart'; +import 'package:rabenkorb/services/state/data_management_navigation_state_service.dart'; +import 'package:rabenkorb/shared/destination_details.dart'; +import 'package:rabenkorb/shared/widgets/core_navigation.dart'; +import 'package:watch_it/watch_it.dart'; + +class DataManagementPage extends StatelessWidget with WatchItMixin { + const DataManagementPage({super.key}); + + @override + Widget build(BuildContext context) { + final details = watchStream((DataManagementNavigationStateService p0) => p0.dataManagementPageDetails); + + final pageIndex = details.data?.pageIndex ?? 0; + final body = details.data?.body; + final mainAction = details.data?.mainAction; + final appBar = details.data?.appBar; + final state = di(); + + return CoreScaffold( + body: body, + bottomNavigationBar: CoreNavigation( + pageIndex: pageIndex, + state: state, + ), + floatingActionButton: toFloatingActionButton(context, mainAction), + drawer: const CoreDrawer(), + appBar: appBar, + ); + } + + Widget? toFloatingActionButton(BuildContext context, MainAction? mainAction) { + if (mainAction?.onPressed == null) { + return null; + } + return FloatingActionButton( + key: const Key("data-management-page-fab"), + onPressed: () { + mainAction.onPressed!(context); + }, + child: mainAction!.icon ?? const Icon(Icons.add), + ); + } +} diff --git a/lib/features/data_management/navigation/destinations.dart b/lib/features/data_management/navigation/destinations.dart new file mode 100644 index 0000000..7959361 --- /dev/null +++ b/lib/features/data_management/navigation/destinations.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:rabenkorb/generated/l10n.dart'; +import 'package:rabenkorb/shared/destination_details.dart'; + +final List dataManagementDestinations = [ + DestinationDetails( + destination: NavigationDestination( + icon: const Icon(Icons.category), + label: S.current.Categories, + ), + body: const Text("A"), + mainAction: null, + appBar: AppBar( + title: null, + ), + index: 0, + ), + DestinationDetails( + destination: NavigationDestination( + icon: const Icon(Icons.square_foot), + label: S.current.Units, + ), + body: const Text("B"), + index: 1, + mainAction: null, + appBar: AppBar( + title: null, + ), + ) +]; diff --git a/lib/features/library/library_main_action.dart b/lib/features/library/library_main_action.dart index 626e0da..5b3e5d6 100644 --- a/lib/features/library/library_main_action.dart +++ b/lib/features/library/library_main_action.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import 'package:rabenkorb/features/main/navigation/destination_details.dart'; import 'package:rabenkorb/routing/routes.dart'; +import 'package:rabenkorb/shared/destination_details.dart'; final libraryMainAction = MainAction( onPressed: (BuildContext context) { diff --git a/lib/features/main/main_page.dart b/lib/features/main/main_page.dart index bcfe67f..f59737b 100644 --- a/lib/features/main/main_page.dart +++ b/lib/features/main/main_page.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:rabenkorb/features/core/structural/core_scaffold.dart'; import 'package:rabenkorb/features/core/structural/drawer/core_drawer.dart'; -import 'package:rabenkorb/features/main/navigation/core_navigation.dart'; -import 'package:rabenkorb/features/main/navigation/destination_details.dart'; import 'package:rabenkorb/services/state/basket_state_service.dart'; -import 'package:rabenkorb/services/state/navigation_state_service.dart'; +import 'package:rabenkorb/services/state/main_navigation_state_service.dart'; +import 'package:rabenkorb/shared/destination_details.dart'; +import 'package:rabenkorb/shared/widgets/core_navigation.dart'; import 'package:watch_it/watch_it.dart'; class MainPage extends StatelessWidget with WatchItMixin { @@ -12,7 +12,7 @@ class MainPage extends StatelessWidget with WatchItMixin { @override Widget build(BuildContext context) { - final details = watchStream((NavigationStateService p0) => p0.mainPageDetails); + final details = watchStream((MainNavigationStateService p0) => p0.mainPageDetails); final isShoppingModeStream = watchStream((BasketStateService p0) => p0.isShoppingMode, initialValue: false); final isShoppingMode = isShoppingModeStream.data ?? false; @@ -22,10 +22,14 @@ class MainPage extends StatelessWidget with WatchItMixin { final body = details.data?.body; final mainAction = hideFab ? null : details.data?.mainAction; final appBar = details.data?.appBar; + final state = di(); return CoreScaffold( body: body, - bottomNavigationBar: CoreNavigation(pageIndex: pageIndex), + bottomNavigationBar: CoreNavigation( + pageIndex: pageIndex, + state: state, + ), floatingActionButton: toFloatingActionButton(context, mainAction), drawer: const CoreDrawer(), appBar: appBar, diff --git a/lib/features/main/navigation/destinations.dart b/lib/features/main/navigation/destinations.dart index 47f7395..ebbcf29 100644 --- a/lib/features/main/navigation/destinations.dart +++ b/lib/features/main/navigation/destinations.dart @@ -5,8 +5,8 @@ import 'package:rabenkorb/features/basket/basket_view.dart'; import 'package:rabenkorb/features/library/library_main_action.dart'; import 'package:rabenkorb/features/library/library_search.dart'; import 'package:rabenkorb/features/library/library_view.dart'; -import 'package:rabenkorb/features/main/navigation/destination_details.dart'; import 'package:rabenkorb/generated/l10n.dart'; +import 'package:rabenkorb/shared/destination_details.dart'; final List mainDestinations = [ DestinationDetails( diff --git a/lib/generated/intl/messages_de_DE.dart b/lib/generated/intl/messages_de_DE.dart index 329708f..28557c5 100644 --- a/lib/generated/intl/messages_de_DE.dart +++ b/lib/generated/intl/messages_de_DE.dart @@ -46,12 +46,14 @@ class MessageLookup extends MessageLookupByLibrary { "Basket": MessageLookupByLibrary.simpleMessage("Korb"), "BasketOverview": MessageLookupByLibrary.simpleMessage("Korb Übersicht"), "Cancel": MessageLookupByLibrary.simpleMessage("Abbrechen"), + "Categories": MessageLookupByLibrary.simpleMessage("Kategorien"), "Category": MessageLookupByLibrary.simpleMessage("Kategorie"), "ChooseSource": MessageLookupByLibrary.simpleMessage("Quelle wählen"), "ClearImage": MessageLookupByLibrary.simpleMessage("Bild zurücksetzen"), "Confirm": MessageLookupByLibrary.simpleMessage("Bestätigen"), "ConfirmDeleteAllItems": MessageLookupByLibrary.simpleMessage("Bist du sicher, dass du alle Artikel löschen willst?"), "ConfirmDeleteBasket": MessageLookupByLibrary.simpleMessage("Bist du sicher, dass du diesen Korb löschen willst?"), + "DataManagement": MessageLookupByLibrary.simpleMessage("Datenverwaltung"), "Debug": MessageLookupByLibrary.simpleMessage("Debug"), "Delete": MessageLookupByLibrary.simpleMessage("Löschen"), "DeleteAll": MessageLookupByLibrary.simpleMessage("Alle löschen"), @@ -99,6 +101,7 @@ class MessageLookup extends MessageLookupByLibrary { "TakePicture": MessageLookupByLibrary.simpleMessage("Bild aufnehmen"), "Template": MessageLookupByLibrary.simpleMessage("Template"), "Unit": MessageLookupByLibrary.simpleMessage("Einheit"), + "Units": MessageLookupByLibrary.simpleMessage("Einheiten"), "Unnamed": MessageLookupByLibrary.simpleMessage("Unbenannt"), "Variant": MessageLookupByLibrary.simpleMessage("Variante") }; diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index ee13820..83d844a 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -46,12 +46,14 @@ class MessageLookup extends MessageLookupByLibrary { "Basket": MessageLookupByLibrary.simpleMessage("Basket"), "BasketOverview": MessageLookupByLibrary.simpleMessage("Basket Overview"), "Cancel": MessageLookupByLibrary.simpleMessage("Cancel"), + "Categories": MessageLookupByLibrary.simpleMessage("Categories"), "Category": MessageLookupByLibrary.simpleMessage("Category"), "ChooseSource": MessageLookupByLibrary.simpleMessage("Choose the source"), "ClearImage": MessageLookupByLibrary.simpleMessage("Clear image"), "Confirm": MessageLookupByLibrary.simpleMessage("Confirm"), "ConfirmDeleteAllItems": MessageLookupByLibrary.simpleMessage("Are you sure you want to delete all items?"), "ConfirmDeleteBasket": MessageLookupByLibrary.simpleMessage("Are you sure you want to delete this basket?"), + "DataManagement": MessageLookupByLibrary.simpleMessage("Data Management"), "Debug": MessageLookupByLibrary.simpleMessage("Debug"), "Delete": MessageLookupByLibrary.simpleMessage("Delete"), "DeleteAll": MessageLookupByLibrary.simpleMessage("Delete all"), @@ -99,6 +101,7 @@ class MessageLookup extends MessageLookupByLibrary { "TakePicture": MessageLookupByLibrary.simpleMessage("Take Picture"), "Template": MessageLookupByLibrary.simpleMessage("Template"), "Unit": MessageLookupByLibrary.simpleMessage("Unit"), + "Units": MessageLookupByLibrary.simpleMessage("Units"), "Unnamed": MessageLookupByLibrary.simpleMessage("Unnamed"), "Variant": MessageLookupByLibrary.simpleMessage("Variant") }; diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 9a01ee7..988c652 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -187,6 +187,16 @@ class S { ); } + /// `Data Management` + String get DataManagement { + return Intl.message( + 'Data Management', + name: 'DataManagement', + desc: '', + args: [], + ); + } + /// `Item` String get Item { return Intl.message( @@ -441,6 +451,16 @@ class S { ); } + /// `Categories` + String get Categories { + return Intl.message( + 'Categories', + name: 'Categories', + desc: '', + args: [], + ); + } + /// `Variant` String get Variant { return Intl.message( @@ -481,6 +501,16 @@ class S { ); } + /// `Units` + String get Units { + return Intl.message( + 'Units', + name: 'Units', + desc: '', + args: [], + ); + } + /// `New Item` String get NewItem { return Intl.message( diff --git a/lib/l10n/intl_de_DE.arb b/lib/l10n/intl_de_DE.arb index 5dd554f..1286704 100644 --- a/lib/l10n/intl_de_DE.arb +++ b/lib/l10n/intl_de_DE.arb @@ -14,6 +14,7 @@ "Basket": "Korb", "BasketOverview": "Korb Übersicht", "Library": "Bibliothek", + "DataManagement": "Datenverwaltung", "Item": "Artikel", "Items": "Artikel", "Template": "Template", @@ -47,10 +48,12 @@ "SortByDatabaseOrder": "Erstellung", "Name": "Name", "Category": "Kategorie", + "Categories": "Kategorien", "Variant": "Variante", "Image": "Bild", "Amount": "Menge", "Unit": "Einheit", + "Units": "Einheiten", "NewItem": "Neuer Artikel", "NoSearchResult": "Keine Ergebnisse", "NameMustNotBeEmpty": "Name darf nicht leer sein", diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 8e609ed..6fa2a66 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -14,6 +14,7 @@ "Basket": "Basket", "BasketOverview": "Basket Overview", "Library": "Library", + "DataManagement": "Data Management", "Item": "Item", "Items": "Items", "Template": "Template", @@ -47,10 +48,12 @@ "SortByDatabaseOrder": "Creation", "Name": "Name", "Category": "Category", + "Categories": "Categories", "Variant": "Variant", "Image": "Image", "Amount": "Amount", "Unit": "Unit", + "Units": "Units", "NewItem": "New Item", "NoSearchResult": "No results", "NameMustNotBeEmpty": "Name must not be empty", diff --git a/lib/models/main_page_details.dart b/lib/models/main_page_details.dart index b0c6a67..e6ee8cb 100644 --- a/lib/models/main_page_details.dart +++ b/lib/models/main_page_details.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; -import 'package:rabenkorb/features/main/navigation/destination_details.dart'; +import 'package:rabenkorb/shared/destination_details.dart'; -class MainPageDetails { +class NavigationPageDetails { final int pageIndex; final Widget? body; final MainAction? mainAction; final PreferredSizeWidget? appBar; final bool hideFabInShoppingMode; - MainPageDetails({required this.pageIndex, this.body, this.mainAction, this.appBar, this.hideFabInShoppingMode = false}); + NavigationPageDetails({required this.pageIndex, this.body, this.mainAction, this.appBar, this.hideFabInShoppingMode = false}); } diff --git a/lib/routing/router_config.dart b/lib/routing/router_config.dart index 1ca13dd..611d550 100644 --- a/lib/routing/router_config.dart +++ b/lib/routing/router_config.dart @@ -4,6 +4,7 @@ import 'package:rabenkorb/features/backup/backup_page.dart'; import 'package:rabenkorb/features/backup/backup_restore_screen.dart'; import 'package:rabenkorb/features/basket/details/basket_item_details.dart'; import 'package:rabenkorb/features/basket/overview/basket_overview_page.dart'; +import 'package:rabenkorb/features/data_management/data_management_page.dart'; import 'package:rabenkorb/features/debug/debug_page.dart'; import 'package:rabenkorb/features/library/details/item_template_details.dart'; import 'package:rabenkorb/features/main/main_page.dart'; @@ -11,7 +12,8 @@ import 'package:rabenkorb/features/settings/settings_page.dart'; import 'package:rabenkorb/models/basket_item_view_model.dart'; import 'package:rabenkorb/models/item_template_view_model.dart'; import 'package:rabenkorb/routing/routes.dart'; -import 'package:rabenkorb/services/state/navigation_state_service.dart'; +import 'package:rabenkorb/services/state/data_management_navigation_state_service.dart'; +import 'package:rabenkorb/services/state/main_navigation_state_service.dart'; import 'package:watch_it/watch_it.dart'; // GoRouter configuration @@ -36,7 +38,7 @@ RouterConfig goRouterConfig({String initialLocation = Routes.home}) => G GoRoute( path: Routes.library, redirect: (context, state) { - di().setCurrentPageIndex(1); + di().setCurrentPageIndex(1); return Routes.home; }, ), @@ -60,7 +62,7 @@ RouterConfig goRouterConfig({String initialLocation = Routes.home}) => G GoRoute( path: Routes.basket, redirect: (context, state) { - di().setCurrentPageIndex(0); + di().setCurrentPageIndex(0); return Routes.home; }, ), @@ -80,5 +82,23 @@ RouterConfig goRouterConfig({String initialLocation = Routes.home}) => G path: Routes.debug, builder: (context, state) => const DebugPage(), ), + GoRoute( + path: Routes.dataManagementCategory, + redirect: (context, state) { + di().setCurrentPageIndex(0); + return Routes.dataManagement; + }, + ), + GoRoute( + path: Routes.dataManagementUnit, + redirect: (context, state) { + di().setCurrentPageIndex(1); + return Routes.dataManagement; + }, + ), + GoRoute( + path: Routes.dataManagement, + builder: (context, state) => const DataManagementPage(), + ), ], ); diff --git a/lib/routing/routes.dart b/lib/routing/routes.dart index eb9a914..b622a5e 100644 --- a/lib/routing/routes.dart +++ b/lib/routing/routes.dart @@ -9,4 +9,7 @@ abstract class Routes { static const String backup = "/backup"; static const String backupRestore = "$backup/import"; static const String settings = "/settings"; + static const String dataManagement = "/data-management"; + static const String dataManagementCategory = "/data-management/category"; + static const String dataManagementUnit = "/data-management/unit"; } diff --git a/lib/services/state/data_management_navigation_state_service.dart b/lib/services/state/data_management_navigation_state_service.dart new file mode 100644 index 0000000..c48e457 --- /dev/null +++ b/lib/services/state/data_management_navigation_state_service.dart @@ -0,0 +1,51 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:rabenkorb/abstracts/navigation_state_service.dart'; +import 'package:rabenkorb/features/data_management/navigation/destinations.dart'; +import 'package:rabenkorb/shared/destination_details.dart'; +import 'package:rxdart/rxdart.dart'; +import 'package:watch_it/watch_it.dart'; + +import '../../models/main_page_details.dart'; + +class DataManagementNavigationStateService implements Disposable, NavigationStateService { + final BehaviorSubject _currentPageIndex = BehaviorSubject.seeded(0); + final BehaviorSubject _dataManagementPageDetails = BehaviorSubject.seeded(null); + + late StreamSubscription _dataManagementPageDetailsSub; + + int get currentPageIndexSync => _currentPageIndex.value; + + Stream get dataManagementPageDetails => _dataManagementPageDetails.stream; + + void setCurrentPageIndex(int index) { + _currentPageIndex.add(index); + } + + final List _destinations = dataManagementDestinations; + + List get destinations => _destinations.map((e) => e.destination).toList(); + + DataManagementNavigationStateService() { + _destinations.sort((a, b) => a.index.compareTo(b.index)); + + _dataManagementPageDetailsSub = _currentPageIndex.distinct().listen((index) { + final destination = _destinations[index]; + _dataManagementPageDetails.add(NavigationPageDetails( + pageIndex: index, + body: destination.body, + mainAction: destination.mainAction, + appBar: destination.appBar, + hideFabInShoppingMode: destination.hideFabInShoppingMode, + )); + }); + } + + @override + FutureOr onDispose() { + _dataManagementPageDetailsSub.cancel(); + _currentPageIndex.close(); + _dataManagementPageDetails.close(); + } +} diff --git a/lib/services/state/navigation_state_service.dart b/lib/services/state/main_navigation_state_service.dart similarity index 71% rename from lib/services/state/navigation_state_service.dart rename to lib/services/state/main_navigation_state_service.dart index b415871..42874e6 100644 --- a/lib/services/state/navigation_state_service.dart +++ b/lib/services/state/main_navigation_state_service.dart @@ -1,22 +1,23 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:rabenkorb/features/main/navigation/destination_details.dart'; +import 'package:rabenkorb/abstracts/navigation_state_service.dart'; import 'package:rabenkorb/features/main/navigation/destinations.dart'; +import 'package:rabenkorb/shared/destination_details.dart'; import 'package:rxdart/rxdart.dart'; import 'package:watch_it/watch_it.dart'; import '../../models/main_page_details.dart'; -class NavigationStateService implements Disposable { +class MainNavigationStateService implements Disposable, NavigationStateService { final BehaviorSubject _currentPageIndex = BehaviorSubject.seeded(0); - final BehaviorSubject _mainPageDetails = BehaviorSubject.seeded(null); + final BehaviorSubject _mainPageDetails = BehaviorSubject.seeded(null); late StreamSubscription _mainPageDetailsSub; int get currentPageIndexSync => _currentPageIndex.value; - Stream get mainPageDetails => _mainPageDetails.stream; + Stream get mainPageDetails => _mainPageDetails.stream; void setCurrentPageIndex(int index) { _currentPageIndex.add(index); @@ -26,12 +27,12 @@ class NavigationStateService implements Disposable { List get destinations => _destinations.map((e) => e.destination).toList(); - NavigationStateService() { + MainNavigationStateService() { _destinations.sort((a, b) => a.index.compareTo(b.index)); _mainPageDetailsSub = _currentPageIndex.distinct().listen((index) { final destination = _destinations[index]; - _mainPageDetails.add(MainPageDetails( + _mainPageDetails.add(NavigationPageDetails( pageIndex: index, body: destination.body, mainAction: destination.mainAction, diff --git a/lib/features/main/navigation/destination_details.dart b/lib/shared/destination_details.dart similarity index 100% rename from lib/features/main/navigation/destination_details.dart rename to lib/shared/destination_details.dart diff --git a/lib/features/main/navigation/core_navigation.dart b/lib/shared/widgets/core_navigation.dart similarity index 63% rename from lib/features/main/navigation/core_navigation.dart rename to lib/shared/widgets/core_navigation.dart index 4b50f40..b5132bc 100644 --- a/lib/features/main/navigation/core_navigation.dart +++ b/lib/shared/widgets/core_navigation.dart @@ -1,16 +1,14 @@ import 'package:flutter/material.dart'; -import 'package:rabenkorb/services/state/navigation_state_service.dart'; -import 'package:watch_it/watch_it.dart'; +import 'package:rabenkorb/abstracts/navigation_state_service.dart'; class CoreNavigation extends StatelessWidget { final int pageIndex; + final NavigationStateService state; - const CoreNavigation({super.key, required this.pageIndex}); + const CoreNavigation({super.key, required this.pageIndex, required this.state}); @override Widget build(BuildContext context) { - final state = di(); - return NavigationBar( onDestinationSelected: (int index) { state.setCurrentPageIndex(index); From db2e8cdbe067b33ccc3d370696cef4ee9cf6ee31 Mon Sep 17 00:00:00 2001 From: Alexander Pahn Date: Sun, 19 May 2024 10:25:53 +0200 Subject: [PATCH 04/12] created data management service to allow for state dependent access to categories --- lib/di/di_setup.dart | 4 ++ .../business/data_management_service.dart | 42 +++++++++++++++++++ lib/services/business/metadata_service.dart | 6 +++ lib/shared/filter_details.dart | 12 ++++++ 4 files changed, 64 insertions(+) create mode 100644 lib/services/business/data_management_service.dart diff --git a/lib/di/di_setup.dart b/lib/di/di_setup.dart index 223a3b5..6c7c198 100644 --- a/lib/di/di_setup.dart +++ b/lib/di/di_setup.dart @@ -9,6 +9,7 @@ import 'package:rabenkorb/features/core/logging/core_logger.dart'; import 'package:rabenkorb/features/core/logging/sinks/mongo_db_sink.dart'; import 'package:rabenkorb/features/core/logging/sinks/void_sink.dart'; import 'package:rabenkorb/services/business/basket_service.dart'; +import 'package:rabenkorb/services/business/data_management_service.dart'; import 'package:rabenkorb/services/business/library_service.dart'; import 'package:rabenkorb/services/business/metadata_service.dart'; import 'package:rabenkorb/services/business/sort_service.dart'; @@ -28,6 +29,7 @@ import 'package:rabenkorb/services/data_access/template_library_service.dart'; import 'package:rabenkorb/services/data_access/variant_key_service.dart'; import 'package:rabenkorb/services/state/basket_state_service.dart'; import 'package:rabenkorb/services/state/data_management_navigation_state_service.dart'; +import 'package:rabenkorb/services/state/data_management_state_service.dart'; import 'package:rabenkorb/services/state/intl_state_service.dart'; import 'package:rabenkorb/services/state/library_state_service.dart'; import 'package:rabenkorb/services/state/loading_state.dart'; @@ -112,6 +114,7 @@ void _registerBusinessServices() { di.registerSingletonWithDependencies(() => LibraryService(), dependsOn: [ItemTemplateService, MetadataService]); di.registerSingletonWithDependencies(() => BasketService(), dependsOn: [BasketItemService, MetadataService]); + di.registerSingletonWithDependencies(() => DataManagementService(), dependsOn: [MetadataService]); } Future _registerStateServices() async { @@ -125,6 +128,7 @@ Future _registerStateServices() async { }, dependsOn: [PreferenceService]); di.registerSingletonWithDependencies(() => MainNavigationStateService(), dependsOn: [IntlStateService]); di.registerSingletonWithDependencies(() => DataManagementNavigationStateService(), dependsOn: [IntlStateService]); + di.registerLazySingleton(() => DataManagementStateService()); } void _registerUtilityServices() { diff --git a/lib/services/business/data_management_service.dart b/lib/services/business/data_management_service.dart new file mode 100644 index 0000000..b8e33df --- /dev/null +++ b/lib/services/business/data_management_service.dart @@ -0,0 +1,42 @@ +import 'dart:async'; + +import 'package:rabenkorb/models/item_category_view_model.dart'; +import 'package:rabenkorb/services/business/metadata_service.dart'; +import 'package:rabenkorb/services/state/data_management_state_service.dart'; +import 'package:rabenkorb/shared/filter_details.dart'; +import 'package:rabenkorb/shared/sort_direction.dart'; +import 'package:rabenkorb/shared/sort_mode.dart'; +import 'package:rxdart/rxdart.dart'; +import 'package:watch_it/watch_it.dart'; + +class DataManagementService implements Disposable { + final _dataManagementStateService = di(); + final _metadataService = di(); + + late StreamSubscription _templateSub; + final _itemCategories = BehaviorSubject>.seeded([]); + + Stream> get itemCategories => _itemCategories.stream; + + DataManagementService() { + _templateSub = Rx.combineLatest3( + _dataManagementStateService.sortMode, + _dataManagementStateService.sortDirection, + _dataManagementStateService.sortRuleId, + (SortMode sortMode, SortDirection sortDirection, int? sortRuleId) => + ItemCategoryFilterDetails(sortMode: sortMode, sortDirection: sortDirection, sortRuleId: sortRuleId)) + .switchMap((details) => _metadataService.watchItemCategoriesInOrder( + details.sortMode, + details.sortDirection, + sortRuleId: details.sortRuleId, + )) + .listen((categories) { + _itemCategories.add(categories); + }); + } + + @override + FutureOr onDispose() { + _templateSub.cancel(); + } +} diff --git a/lib/services/business/metadata_service.dart b/lib/services/business/metadata_service.dart index 01c03dd..c94eacc 100644 --- a/lib/services/business/metadata_service.dart +++ b/lib/services/business/metadata_service.dart @@ -10,6 +10,8 @@ import 'package:rabenkorb/services/data_access/item_unit_service.dart'; import 'package:rabenkorb/services/data_access/variant_key_service.dart'; import 'package:rabenkorb/services/state/basket_state_service.dart'; import 'package:rabenkorb/services/state/library_state_service.dart'; +import 'package:rabenkorb/shared/sort_direction.dart'; +import 'package:rabenkorb/shared/sort_mode.dart'; import 'package:rxdart/rxdart.dart'; import 'package:watch_it/watch_it.dart'; @@ -87,6 +89,10 @@ class MetadataService implements Disposable { return _itemCategoryService.watchItemCategories(); } + Stream> watchItemCategoriesInOrder(SortMode sortMode, SortDirection sortDirection, {int? sortRuleId}) { + return _itemCategoryService.watchItemCategoriesInOrder(sortMode, sortDirection, sortRuleId: sortRuleId); + } + Future createVariantKey(String name) { return _variantKeyService.createVariantKey(name); } diff --git a/lib/shared/filter_details.dart b/lib/shared/filter_details.dart index 72bc0b8..3380a1c 100644 --- a/lib/shared/filter_details.dart +++ b/lib/shared/filter_details.dart @@ -30,3 +30,15 @@ class BasketItemFilterDetails { this.basketId, }); } + +class ItemCategoryFilterDetails { + SortMode sortMode; + SortDirection sortDirection; + int? sortRuleId; + + ItemCategoryFilterDetails({ + required this.sortMode, + required this.sortDirection, + this.sortRuleId, + }); +} From f2b726c1f6635a2f79c3118f4636ffe9e695bb63 Mon Sep 17 00:00:00 2001 From: Alexander Pahn Date: Sun, 19 May 2024 10:31:25 +0200 Subject: [PATCH 05/12] created reorderCategories helper function in advance --- lib/shared/helper_functions.dart | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lib/shared/helper_functions.dart b/lib/shared/helper_functions.dart index a0e33c4..6a32774 100644 --- a/lib/shared/helper_functions.dart +++ b/lib/shared/helper_functions.dart @@ -4,6 +4,7 @@ import 'package:permission_handler/permission_handler.dart'; import 'package:rabenkorb/abstracts/data_item.dart'; import 'package:rabenkorb/generated/l10n.dart'; import 'package:rabenkorb/models/grouped_items.dart'; +import 'package:rabenkorb/models/item_category_view_model.dart'; import 'package:rabenkorb/services/business/sort_service.dart'; import 'package:rabenkorb/services/core/dialog_service.dart'; import 'package:rabenkorb/services/state/library_state_service.dart'; @@ -153,3 +154,26 @@ void reorderGroupedItems(int oldIndex, int newIndex, List().updateOrderSingle(activeSortRuleId, targetId, placeBeforeId: placeBeforeId, placeAfterId: placeAfterId); await di().setSortRuleId(activeSortRuleId); } + +void reorderCategories(int oldIndex, int newIndex, List list, int? activeSortRuleId) async { + if (activeSortRuleId == null || newIndex == oldIndex) { + return; + } + // New index would be relative to the list without the item being reordered + newIndex--; + + final reorderedItem = list[oldIndex]; + final placeAfterItem = newIndex < list.length && newIndex >= 0 + ? list[newIndex] + : newIndex >= list.length + ? list.last + : null; + final placeBeforeItem = newIndex < 0 ? list.first : null; + + final targetId = reorderedItem.id; + final placeAfterId = placeAfterItem?.id; + final placeBeforeId = placeBeforeItem?.id; + + await di().updateOrderSingle(activeSortRuleId, targetId, placeBeforeId: placeBeforeId, placeAfterId: placeAfterId); + await di().setSortRuleId(activeSortRuleId); +} From d57a7cc70c7d0179d7709af921e9d8a5be39fc6b Mon Sep 17 00:00:00 2001 From: Alexander Pahn Date: Sun, 19 May 2024 10:32:20 +0200 Subject: [PATCH 06/12] added management capabilities for categories --- lib/database/daos/item_categories_dao.dart | 45 ++++++++++++ .../categories/category_tile.dart | 59 +++++++++++++++ .../categories/catgory_list.dart | 27 +++++++ .../categories/catgory_management_view.dart | 16 +++++ .../data_management_sort_control.dart | 72 +++++++++++++++++++ .../navigation/destinations.dart | 5 +- lib/generated/intl/messages_de_DE.dart | 1 + lib/generated/intl/messages_en.dart | 1 + lib/generated/l10n.dart | 10 +++ lib/l10n/intl_de_DE.arb | 1 + lib/l10n/intl_en.arb | 1 + .../data_access/item_category_service.dart | 6 ++ .../state/data_management_state_service.dart | 38 ++++++++++ 13 files changed, 280 insertions(+), 2 deletions(-) create mode 100644 lib/features/data_management/categories/category_tile.dart create mode 100644 lib/features/data_management/categories/catgory_list.dart create mode 100644 lib/features/data_management/categories/catgory_management_view.dart create mode 100644 lib/features/data_management/data_management_sort_control.dart create mode 100644 lib/services/state/data_management_state_service.dart diff --git a/lib/database/daos/item_categories_dao.dart b/lib/database/daos/item_categories_dao.dart index 2420ccd..a3d4f7a 100644 --- a/lib/database/daos/item_categories_dao.dart +++ b/lib/database/daos/item_categories_dao.dart @@ -3,6 +3,8 @@ import 'package:rabenkorb/database/database.dart'; import 'package:rabenkorb/database/tables/item_categories.dart'; import 'package:rabenkorb/mappers/to_view_model.dart'; import 'package:rabenkorb/models/item_category_view_model.dart'; +import 'package:rabenkorb/shared/sort_direction.dart'; +import 'package:rabenkorb/shared/sort_mode.dart'; part 'item_categories_dao.g.dart'; @@ -34,4 +36,47 @@ class ItemCategoriesDao extends DatabaseAccessor with _$ItemCategor Stream> watchItemCategories() { return (select(itemCategories)).watch().map((categories) => categories.map((category) => toItemCategoryViewModel(category)!).toList()); } + + Stream> watchItemCategoriesInOrder(SortMode sortMode, SortDirection sortDirection, {int? sortRuleId}) { + final sourceQuery = select(itemCategories); + + final query = sourceQuery.join( + [ + leftOuterJoin(attachedDatabase.sortOrders, + itemCategories.id.equalsExp(attachedDatabase.sortOrders.categoryId) & attachedDatabase.sortOrders.ruleId.equalsNullable(sortRuleId)), + ], + ); + query.orderBy([ + OrderingTerm(expression: itemCategories.id.isNull(), mode: toOrderingMode(sortDirection)), + OrderingTerm(expression: attachedDatabase.sortOrders.sortOrder.isNull(), mode: toOrderingMode(sortDirection)), + ..._getOrderingTerms(sortMode), + ]); + + return query.watch().map((rows) => rows.map((row) => toItemCategoryViewModel(row.readTable(itemCategories))!).toList()); + } + + List _getOrderingTerms( + SortMode sortMode, + ) { + switch (sortMode) { + case SortMode.databaseOrder: + return [_byId()]; + case SortMode.name: + return [_byName()]; + case SortMode.custom: + return [_bySortOrder(), _byName()]; + } + } + + OrderingTerm _byId() { + return OrderingTerm(expression: itemCategories.id); + } + + OrderingTerm _byName() { + return OrderingTerm(expression: itemCategories.name); + } + + OrderingTerm _bySortOrder() { + return OrderingTerm(expression: attachedDatabase.sortOrders.sortOrder); + } } diff --git a/lib/features/data_management/categories/category_tile.dart b/lib/features/data_management/categories/category_tile.dart new file mode 100644 index 0000000..fed93cb --- /dev/null +++ b/lib/features/data_management/categories/category_tile.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:rabenkorb/generated/l10n.dart'; +import 'package:rabenkorb/models/item_category_view_model.dart'; +import 'package:rabenkorb/services/business/metadata_service.dart'; +import 'package:rabenkorb/shared/helper_functions.dart'; +import 'package:rabenkorb/shared/widgets/inputs/core_icon_button.dart'; +import 'package:watch_it/watch_it.dart'; + +class CategoryTile extends StatelessWidget { + final ItemCategoryViewModel category; + + const CategoryTile({super.key, required this.category}); + + @override + Widget build(BuildContext context) { + return Card( + child: ListTile( + title: Text(category.name), + trailing: Row( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + CoreIconButton( + icon: const Icon(Icons.edit), + onPressed: () async { + await showRenameDialog( + context, + initialName: category.name, + onConfirm: (String? newName, bool nameChanged) async { + if (!nameChanged || newName == null) { + return; + } + await di().updateItemCategory(category.id, newName); + }, + ); + }, + ), + CoreIconButton( + icon: const Icon( + Icons.delete_forever, + color: Colors.red, + ), + onPressed: () async { + await doWithConfirmation( + context, + text: S.of(context).ConfirmDeleteCategory, + title: S.of(context).Confirm, + onConfirm: () async { + await di().deleteItemCategoryById(category.id); + }, + ); + }, + ) + ], + ), + ), + ); + } +} diff --git a/lib/features/data_management/categories/catgory_list.dart b/lib/features/data_management/categories/catgory_list.dart new file mode 100644 index 0000000..56ff4fa --- /dev/null +++ b/lib/features/data_management/categories/catgory_list.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:rabenkorb/features/data_management/categories/category_tile.dart'; +import 'package:rabenkorb/models/item_category_view_model.dart'; +import 'package:rabenkorb/services/business/metadata_service.dart'; +import 'package:watch_it/watch_it.dart'; + +class CategoryList extends StatelessWidget with WatchItMixin { + const CategoryList({super.key}); + + @override + Widget build(BuildContext context) { + final AsyncSnapshot> categories = watchStream((MetadataService p0) => p0.categories, initialValue: []); + final categoryList = categories.data ?? []; + + return Expanded( + child: ListView.builder( + itemCount: categoryList.length, + prototypeItem: CategoryTile( + category: ItemCategoryViewModel(-1, "Category Prototype"), + ), + itemBuilder: (BuildContext context, int index) { + return CategoryTile(category: categoryList[index]); + }, + ), + ); + } +} diff --git a/lib/features/data_management/categories/catgory_management_view.dart b/lib/features/data_management/categories/catgory_management_view.dart new file mode 100644 index 0000000..6a81107 --- /dev/null +++ b/lib/features/data_management/categories/catgory_management_view.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + +import 'catgory_list.dart'; + +class CategoryManagementView extends StatelessWidget { + const CategoryManagementView({super.key}); + + @override + Widget build(BuildContext context) { + return const Column( + children: [ + CategoryList(), + ], + ); + } +} diff --git a/lib/features/data_management/data_management_sort_control.dart b/lib/features/data_management/data_management_sort_control.dart new file mode 100644 index 0000000..e1a876c --- /dev/null +++ b/lib/features/data_management/data_management_sort_control.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:rabenkorb/generated/l10n.dart'; +import 'package:rabenkorb/models/sort_rule_view_model.dart'; +import 'package:rabenkorb/services/data_access/sort_rule_service.dart'; +import 'package:rabenkorb/services/state/data_management_state_service.dart'; +import 'package:rabenkorb/shared/default_sort_rules.dart'; +import 'package:rabenkorb/shared/sort_mode.dart'; +import 'package:rabenkorb/shared/widgets/inputs/core_icon_text_button.dart'; +import 'package:rabenkorb/shared/widgets/sort_rule_dropdown.dart'; +import 'package:watch_it/watch_it.dart'; + +class DataManagementSortControl extends StatelessWidget with WatchItMixin { + const DataManagementSortControl({ + super.key, + }); + + @override + Widget build(BuildContext context) { + final dataManagementStateService = di(); + final AsyncSnapshot> availableSortRules = watchStream((SortRuleService p0) => p0.sortRules, initialValue: []); + final sortRuleId = watchStream((DataManagementStateService p0) => p0.sortRuleId, initialValue: dataManagementStateService.sortRuleIdSync); + final sortMode = watchStream((DataManagementStateService p0) => p0.sortMode, initialValue: dataManagementStateService.sortModeSync); + final sortDirection = watchStream((DataManagementStateService p0) => p0.sortDirection, initialValue: dataManagementStateService.sortDirectionSync); + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: SortRuleDropdown( + sortMode: sortMode.data, + sortRuleId: sortRuleId.data, + availableSortRules: availableSortRules.data ?? [], + updateSortRuleDetails: _updateSortDetails, + onNewSortRule: (int newId) async { + await di().setSortRuleId(newId); + }, + )), + CoreIconTextButton( + icon: const Icon(Icons.sort), + label: Text(S.of(context).SortDirection(sortDirection.data?.name ?? "")), + onPressed: () async { + await dataManagementStateService.switchSortDirection(); + }, + ), + ], + ), + ); + } + + Future _updateSortDetails(SortRuleViewModel? rule) async { + final ruleId = rule?.id; + if (ruleId == null) { + return; + } + final dataManagementStateService = di(); + if (ruleId == sortByNamePseudoId) { + await dataManagementStateService.setSortMode(SortMode.name); + await dataManagementStateService.setSortRuleId(null); + return; + } + if (ruleId == sortByDatabasePseudoId) { + await dataManagementStateService.setSortMode(SortMode.databaseOrder); + await dataManagementStateService.setSortRuleId(null); + return; + } + + await dataManagementStateService.setSortMode(SortMode.custom); + await dataManagementStateService.setSortRuleId(ruleId); + } +} diff --git a/lib/features/data_management/navigation/destinations.dart b/lib/features/data_management/navigation/destinations.dart index 7959361..d12d0ba 100644 --- a/lib/features/data_management/navigation/destinations.dart +++ b/lib/features/data_management/navigation/destinations.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:rabenkorb/features/data_management/categories/catgory_management_view.dart'; import 'package:rabenkorb/generated/l10n.dart'; import 'package:rabenkorb/shared/destination_details.dart'; @@ -8,10 +9,10 @@ final List dataManagementDestinations = [ icon: const Icon(Icons.category), label: S.current.Categories, ), - body: const Text("A"), + body: const CategoryManagementView(), mainAction: null, appBar: AppBar( - title: null, + title: Text(S.current.Categories), ), index: 0, ), diff --git a/lib/generated/intl/messages_de_DE.dart b/lib/generated/intl/messages_de_DE.dart index 28557c5..eaaecd8 100644 --- a/lib/generated/intl/messages_de_DE.dart +++ b/lib/generated/intl/messages_de_DE.dart @@ -53,6 +53,7 @@ class MessageLookup extends MessageLookupByLibrary { "Confirm": MessageLookupByLibrary.simpleMessage("Bestätigen"), "ConfirmDeleteAllItems": MessageLookupByLibrary.simpleMessage("Bist du sicher, dass du alle Artikel löschen willst?"), "ConfirmDeleteBasket": MessageLookupByLibrary.simpleMessage("Bist du sicher, dass du diesen Korb löschen willst?"), + "ConfirmDeleteCategory": MessageLookupByLibrary.simpleMessage("Bist du sicher, dass du diese Kategorie löschen willst?"), "DataManagement": MessageLookupByLibrary.simpleMessage("Datenverwaltung"), "Debug": MessageLookupByLibrary.simpleMessage("Debug"), "Delete": MessageLookupByLibrary.simpleMessage("Löschen"), diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index 83d844a..a904153 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -53,6 +53,7 @@ class MessageLookup extends MessageLookupByLibrary { "Confirm": MessageLookupByLibrary.simpleMessage("Confirm"), "ConfirmDeleteAllItems": MessageLookupByLibrary.simpleMessage("Are you sure you want to delete all items?"), "ConfirmDeleteBasket": MessageLookupByLibrary.simpleMessage("Are you sure you want to delete this basket?"), + "ConfirmDeleteCategory": MessageLookupByLibrary.simpleMessage("Are you sure you want to delete this category?"), "DataManagement": MessageLookupByLibrary.simpleMessage("Data Management"), "Debug": MessageLookupByLibrary.simpleMessage("Debug"), "Delete": MessageLookupByLibrary.simpleMessage("Delete"), diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 988c652..345d7b3 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -691,6 +691,16 @@ class S { ); } + /// `Are you sure you want to delete this category?` + String get ConfirmDeleteCategory { + return Intl.message( + 'Are you sure you want to delete this category?', + name: 'ConfirmDeleteCategory', + desc: '', + args: [], + ); + } + /// `Rename` String get Rename { return Intl.message( diff --git a/lib/l10n/intl_de_DE.arb b/lib/l10n/intl_de_DE.arb index 1286704..4a08e4c 100644 --- a/lib/l10n/intl_de_DE.arb +++ b/lib/l10n/intl_de_DE.arb @@ -80,6 +80,7 @@ "DeleteAll": "Alle löschen", "ConfirmDeleteAllItems": "Bist du sicher, dass du alle Artikel löschen willst?", "ConfirmDeleteBasket": "Bist du sicher, dass du diesen Korb löschen willst?", + "ConfirmDeleteCategory": "Bist du sicher, dass du diese Kategorie löschen willst?", "Rename": "Umbenennen", "NewBasket": "Neuer Korb", "New": "Neu", diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 6fa2a66..b74437a 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -80,6 +80,7 @@ "DeleteAll": "Delete all", "ConfirmDeleteAllItems": "Are you sure you want to delete all items?", "ConfirmDeleteBasket": "Are you sure you want to delete this basket?", + "ConfirmDeleteCategory": "Are you sure you want to delete this category?", "Rename": "Rename", "NewBasket": "New Basket", "New": "New", diff --git a/lib/services/data_access/item_category_service.dart b/lib/services/data_access/item_category_service.dart index 5bf4f9f..60be498 100644 --- a/lib/services/data_access/item_category_service.dart +++ b/lib/services/data_access/item_category_service.dart @@ -1,6 +1,8 @@ import 'package:rabenkorb/database/database.dart'; import 'package:rabenkorb/models/item_category_view_model.dart'; import 'package:rabenkorb/models/save_delete_result.dart'; +import 'package:rabenkorb/shared/sort_direction.dart'; +import 'package:rabenkorb/shared/sort_mode.dart'; import 'package:watch_it/watch_it.dart'; class ItemCategoryService { @@ -30,4 +32,8 @@ class ItemCategoryService { Stream> watchItemCategories() { return _db.itemCategoriesDao.watchItemCategories(); } + + Stream> watchItemCategoriesInOrder(SortMode sortMode, SortDirection sortDirection, {int? sortRuleId}) { + return _db.itemCategoriesDao.watchItemCategoriesInOrder(sortMode, sortDirection, sortRuleId: sortRuleId); + } } diff --git a/lib/services/state/data_management_state_service.dart b/lib/services/state/data_management_state_service.dart new file mode 100644 index 0000000..14e67ed --- /dev/null +++ b/lib/services/state/data_management_state_service.dart @@ -0,0 +1,38 @@ +import 'package:rabenkorb/shared/sort_direction.dart'; +import 'package:rxdart/rxdart.dart'; + +import '../../shared/sort_mode.dart'; + +class DataManagementStateService { + final BehaviorSubject _sortRuleIdSubject = BehaviorSubject.seeded(null); + final BehaviorSubject _sortModeSubject = BehaviorSubject.seeded(SortMode.name); + final BehaviorSubject _sortDirectionSubject = BehaviorSubject.seeded(SortDirection.asc); + + Stream get sortRuleId => _sortRuleIdSubject.stream; + + int? get sortRuleIdSync => _sortRuleIdSubject.value; + + Stream get sortMode => _sortModeSubject.stream; + + SortMode get sortModeSync => _sortModeSubject.value; + + Stream get sortDirection => _sortDirectionSubject.stream; + + SortDirection get sortDirectionSync => _sortDirectionSubject.value; + + Future setSortMode(SortMode? sortMode) async { + if (sortMode == null) { + return; + } + _sortModeSubject.add(sortMode); + } + + Future setSortRuleId(int? sortRuleId) async { + _sortRuleIdSubject.add(sortRuleId); + } + + Future switchSortDirection() async { + final newDirection = flipSortDirection(sortDirectionSync); + _sortDirectionSubject.add(newDirection); + } +} From 5a0aa5e82ae9592f8c671f01aadb742e92675e5a Mon Sep 17 00:00:00 2001 From: Alexander Pahn Date: Sun, 19 May 2024 10:35:45 +0200 Subject: [PATCH 07/12] item sort rules, categories and units are ordered by name by default --- lib/database/daos/item_categories_dao.dart | 4 +++- lib/database/daos/item_units_dao.dart | 4 +++- lib/database/daos/sort_rules_dao.dart | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/database/daos/item_categories_dao.dart b/lib/database/daos/item_categories_dao.dart index a3d4f7a..26bf06b 100644 --- a/lib/database/daos/item_categories_dao.dart +++ b/lib/database/daos/item_categories_dao.dart @@ -34,7 +34,9 @@ class ItemCategoriesDao extends DatabaseAccessor with _$ItemCategor } Stream> watchItemCategories() { - return (select(itemCategories)).watch().map((categories) => categories.map((category) => toItemCategoryViewModel(category)!).toList()); + return (select(itemCategories)..orderBy([(tbl) => OrderingTerm(expression: tbl.name)])) + .watch() + .map((categories) => categories.map((category) => toItemCategoryViewModel(category)!).toList()); } Stream> watchItemCategoriesInOrder(SortMode sortMode, SortDirection sortDirection, {int? sortRuleId}) { diff --git a/lib/database/daos/item_units_dao.dart b/lib/database/daos/item_units_dao.dart index 694c701..ea901ff 100644 --- a/lib/database/daos/item_units_dao.dart +++ b/lib/database/daos/item_units_dao.dart @@ -32,6 +32,8 @@ class ItemUnitsDao extends DatabaseAccessor with _$ItemUnitsDaoMixi } Stream> watchItemUnits() { - return (select(itemUnits)).watch().map((units) => units.map((unit) => toItemUnitViewModel(unit)!).toList()); + return (select(itemUnits)..orderBy([(tbl) => OrderingTerm(expression: tbl.name)])) + .watch() + .map((units) => units.map((unit) => toItemUnitViewModel(unit)!).toList()); } } diff --git a/lib/database/daos/sort_rules_dao.dart b/lib/database/daos/sort_rules_dao.dart index 2747ac0..6416e0a 100644 --- a/lib/database/daos/sort_rules_dao.dart +++ b/lib/database/daos/sort_rules_dao.dart @@ -32,6 +32,8 @@ class SortRulesDao extends DatabaseAccessor with _$SortRulesDaoMixi } Stream> watchSortRules() { - return (select(sortRules)).watch().map((rules) => rules.map((rule) => toSortRuleViewModel(rule)!).toList()); + return (select(sortRules)..orderBy([(tbl) => OrderingTerm(expression: tbl.name)])) + .watch() + .map((rules) => rules.map((rule) => toSortRuleViewModel(rule)!).toList()); } } From 1a618ff4090601d9c47b96089330d433d01f033c Mon Sep 17 00:00:00 2001 From: Alexander Pahn Date: Sun, 19 May 2024 10:43:02 +0200 Subject: [PATCH 08/12] added main page action for categories management --- .../navigation/destinations.dart | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/features/data_management/navigation/destinations.dart b/lib/features/data_management/navigation/destinations.dart index d12d0ba..7eb1926 100644 --- a/lib/features/data_management/navigation/destinations.dart +++ b/lib/features/data_management/navigation/destinations.dart @@ -1,7 +1,10 @@ import 'package:flutter/material.dart'; import 'package:rabenkorb/features/data_management/categories/catgory_management_view.dart'; import 'package:rabenkorb/generated/l10n.dart'; +import 'package:rabenkorb/services/business/metadata_service.dart'; import 'package:rabenkorb/shared/destination_details.dart'; +import 'package:rabenkorb/shared/helper_functions.dart'; +import 'package:watch_it/watch_it.dart'; final List dataManagementDestinations = [ DestinationDetails( @@ -10,7 +13,19 @@ final List dataManagementDestinations = [ label: S.current.Categories, ), body: const CategoryManagementView(), - mainAction: null, + mainAction: MainAction( + onPressed: (BuildContext context) async { + await showRenameDialog( + context, + onConfirm: (String? newName, bool _) async { + if (newName == null) { + return; + } + await di().createItemCategory(newName); + }, + ); + }, + ), appBar: AppBar( title: Text(S.current.Categories), ), From b253cc1154e5d82383f862895262e96113fb8ddc Mon Sep 17 00:00:00 2001 From: Alexander Pahn Date: Sun, 19 May 2024 10:44:20 +0200 Subject: [PATCH 09/12] implemented unit management --- .../navigation/destinations.dart | 19 +++++- .../data_management/units/unit_list.dart | 27 +++++++++ .../units/unit_management_view.dart | 16 +++++ .../data_management/units/unit_tile.dart | 59 +++++++++++++++++++ lib/generated/intl/messages_de_DE.dart | 1 + lib/generated/intl/messages_en.dart | 1 + lib/generated/l10n.dart | 10 ++++ lib/l10n/intl_de_DE.arb | 1 + lib/l10n/intl_en.arb | 1 + 9 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 lib/features/data_management/units/unit_list.dart create mode 100644 lib/features/data_management/units/unit_management_view.dart create mode 100644 lib/features/data_management/units/unit_tile.dart diff --git a/lib/features/data_management/navigation/destinations.dart b/lib/features/data_management/navigation/destinations.dart index 7eb1926..2a03f45 100644 --- a/lib/features/data_management/navigation/destinations.dart +++ b/lib/features/data_management/navigation/destinations.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:rabenkorb/features/data_management/categories/catgory_management_view.dart'; +import 'package:rabenkorb/features/data_management/units/unit_management_view.dart'; import 'package:rabenkorb/generated/l10n.dart'; import 'package:rabenkorb/services/business/metadata_service.dart'; import 'package:rabenkorb/shared/destination_details.dart'; @@ -36,11 +37,23 @@ final List dataManagementDestinations = [ icon: const Icon(Icons.square_foot), label: S.current.Units, ), - body: const Text("B"), + body: const UnitManagementView(), index: 1, - mainAction: null, + mainAction: MainAction( + onPressed: (BuildContext context) async { + await showRenameDialog( + context, + onConfirm: (String? newName, bool _) async { + if (newName == null) { + return; + } + await di().createItemUnit(newName); + }, + ); + }, + ), appBar: AppBar( - title: null, + title: Text(S.current.Units), ), ) ]; diff --git a/lib/features/data_management/units/unit_list.dart b/lib/features/data_management/units/unit_list.dart new file mode 100644 index 0000000..f341373 --- /dev/null +++ b/lib/features/data_management/units/unit_list.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:rabenkorb/features/data_management/units/unit_tile.dart'; +import 'package:rabenkorb/models/item_unit_view_model.dart'; +import 'package:rabenkorb/services/business/metadata_service.dart'; +import 'package:watch_it/watch_it.dart'; + +class UnitList extends StatelessWidget with WatchItMixin { + const UnitList({super.key}); + + @override + Widget build(BuildContext context) { + final AsyncSnapshot> units = watchStream((MetadataService p0) => p0.units, initialValue: []); + final unitList = units.data ?? []; + + return Expanded( + child: ListView.builder( + itemCount: unitList.length, + prototypeItem: UnitTile( + unit: ItemUnitViewModel(-1, "Unit Prototype"), + ), + itemBuilder: (BuildContext context, int index) { + return UnitTile(unit: unitList[index]); + }, + ), + ); + } +} diff --git a/lib/features/data_management/units/unit_management_view.dart b/lib/features/data_management/units/unit_management_view.dart new file mode 100644 index 0000000..6484f36 --- /dev/null +++ b/lib/features/data_management/units/unit_management_view.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + +import 'unit_list.dart'; + +class UnitManagementView extends StatelessWidget { + const UnitManagementView({super.key}); + + @override + Widget build(BuildContext context) { + return const Column( + children: [ + UnitList(), + ], + ); + } +} diff --git a/lib/features/data_management/units/unit_tile.dart b/lib/features/data_management/units/unit_tile.dart new file mode 100644 index 0000000..0d9a2e2 --- /dev/null +++ b/lib/features/data_management/units/unit_tile.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:rabenkorb/generated/l10n.dart'; +import 'package:rabenkorb/models/item_unit_view_model.dart'; +import 'package:rabenkorb/services/business/metadata_service.dart'; +import 'package:rabenkorb/shared/helper_functions.dart'; +import 'package:rabenkorb/shared/widgets/inputs/core_icon_button.dart'; +import 'package:watch_it/watch_it.dart'; + +class UnitTile extends StatelessWidget { + final ItemUnitViewModel unit; + + const UnitTile({super.key, required this.unit}); + + @override + Widget build(BuildContext context) { + return Card( + child: ListTile( + title: Text(unit.name), + trailing: Row( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + CoreIconButton( + icon: const Icon(Icons.edit), + onPressed: () async { + await showRenameDialog( + context, + initialName: unit.name, + onConfirm: (String? newName, bool nameChanged) async { + if (!nameChanged || newName == null) { + return; + } + await di().updateItemUnit(unit.id, newName); + }, + ); + }, + ), + CoreIconButton( + icon: const Icon( + Icons.delete_forever, + color: Colors.red, + ), + onPressed: () async { + await doWithConfirmation( + context, + text: S.of(context).ConfirmDeleteUnit, + title: S.of(context).Confirm, + onConfirm: () async { + await di().deleteItemUnitById(unit.id); + }, + ); + }, + ) + ], + ), + ), + ); + } +} diff --git a/lib/generated/intl/messages_de_DE.dart b/lib/generated/intl/messages_de_DE.dart index eaaecd8..8659d50 100644 --- a/lib/generated/intl/messages_de_DE.dart +++ b/lib/generated/intl/messages_de_DE.dart @@ -54,6 +54,7 @@ class MessageLookup extends MessageLookupByLibrary { "ConfirmDeleteAllItems": MessageLookupByLibrary.simpleMessage("Bist du sicher, dass du alle Artikel löschen willst?"), "ConfirmDeleteBasket": MessageLookupByLibrary.simpleMessage("Bist du sicher, dass du diesen Korb löschen willst?"), "ConfirmDeleteCategory": MessageLookupByLibrary.simpleMessage("Bist du sicher, dass du diese Kategorie löschen willst?"), + "ConfirmDeleteUnit": MessageLookupByLibrary.simpleMessage("Bist du sicher, dass du diese Einheit löschen willst?"), "DataManagement": MessageLookupByLibrary.simpleMessage("Datenverwaltung"), "Debug": MessageLookupByLibrary.simpleMessage("Debug"), "Delete": MessageLookupByLibrary.simpleMessage("Löschen"), diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index a904153..e9f3950 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -54,6 +54,7 @@ class MessageLookup extends MessageLookupByLibrary { "ConfirmDeleteAllItems": MessageLookupByLibrary.simpleMessage("Are you sure you want to delete all items?"), "ConfirmDeleteBasket": MessageLookupByLibrary.simpleMessage("Are you sure you want to delete this basket?"), "ConfirmDeleteCategory": MessageLookupByLibrary.simpleMessage("Are you sure you want to delete this category?"), + "ConfirmDeleteUnit": MessageLookupByLibrary.simpleMessage("Are you sure you want to delete this unit?"), "DataManagement": MessageLookupByLibrary.simpleMessage("Data Management"), "Debug": MessageLookupByLibrary.simpleMessage("Debug"), "Delete": MessageLookupByLibrary.simpleMessage("Delete"), diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 345d7b3..017d2fb 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -701,6 +701,16 @@ class S { ); } + /// `Are you sure you want to delete this unit?` + String get ConfirmDeleteUnit { + return Intl.message( + 'Are you sure you want to delete this unit?', + name: 'ConfirmDeleteUnit', + desc: '', + args: [], + ); + } + /// `Rename` String get Rename { return Intl.message( diff --git a/lib/l10n/intl_de_DE.arb b/lib/l10n/intl_de_DE.arb index 4a08e4c..2e6f606 100644 --- a/lib/l10n/intl_de_DE.arb +++ b/lib/l10n/intl_de_DE.arb @@ -81,6 +81,7 @@ "ConfirmDeleteAllItems": "Bist du sicher, dass du alle Artikel löschen willst?", "ConfirmDeleteBasket": "Bist du sicher, dass du diesen Korb löschen willst?", "ConfirmDeleteCategory": "Bist du sicher, dass du diese Kategorie löschen willst?", + "ConfirmDeleteUnit": "Bist du sicher, dass du diese Einheit löschen willst?", "Rename": "Umbenennen", "NewBasket": "Neuer Korb", "New": "Neu", diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index b74437a..b376829 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -81,6 +81,7 @@ "ConfirmDeleteAllItems": "Are you sure you want to delete all items?", "ConfirmDeleteBasket": "Are you sure you want to delete this basket?", "ConfirmDeleteCategory": "Are you sure you want to delete this category?", + "ConfirmDeleteUnit": "Are you sure you want to delete this unit?", "Rename": "Rename", "NewBasket": "New Basket", "New": "New", From ebb588a975ed7465ce2d447ad5250e78adc6699b Mon Sep 17 00:00:00 2001 From: Alexander Pahn Date: Sun, 19 May 2024 11:31:54 +0200 Subject: [PATCH 10/12] added sort rule management --- .../data_management_sort_control.dart | 23 +++++--- .../navigation/destinations.dart | 33 ++++++++++- .../sort_rules/sort_rule_category_tile.dart | 17 ++++++ .../sort_rules/sort_rule_catgory_list.dart | 45 ++++++++++++++ .../sort_rule_management_title.dart | 58 +++++++++++++++++++ .../sort_rules/sort_rule_management_view.dart | 20 +++++++ lib/generated/intl/messages_de_DE.dart | 5 ++ lib/generated/intl/messages_en.dart | 5 ++ lib/generated/l10n.dart | 50 ++++++++++++++++ lib/l10n/intl_de_DE.arb | 5 ++ lib/l10n/intl_en.arb | 5 ++ lib/routing/router_config.dart | 7 +++ lib/routing/routes.dart | 1 + .../business/data_management_service.dart | 41 ++++++++----- .../data_access/sort_rule_service.dart | 5 +- .../state/data_management_state_service.dart | 2 +- lib/shared/widgets/sort_rule_dropdown.dart | 25 ++++++-- 17 files changed, 316 insertions(+), 31 deletions(-) create mode 100644 lib/features/data_management/sort_rules/sort_rule_category_tile.dart create mode 100644 lib/features/data_management/sort_rules/sort_rule_catgory_list.dart create mode 100644 lib/features/data_management/sort_rules/sort_rule_management_title.dart create mode 100644 lib/features/data_management/sort_rules/sort_rule_management_view.dart diff --git a/lib/features/data_management/data_management_sort_control.dart b/lib/features/data_management/data_management_sort_control.dart index e1a876c..ed857d8 100644 --- a/lib/features/data_management/data_management_sort_control.dart +++ b/lib/features/data_management/data_management_sort_control.dart @@ -10,8 +10,11 @@ import 'package:rabenkorb/shared/widgets/sort_rule_dropdown.dart'; import 'package:watch_it/watch_it.dart'; class DataManagementSortControl extends StatelessWidget with WatchItMixin { + final bool customRulesOnly; + const DataManagementSortControl({ super.key, + this.customRulesOnly = false, }); @override @@ -28,15 +31,17 @@ class DataManagementSortControl extends StatelessWidget with WatchItMixin { mainAxisSize: MainAxisSize.max, children: [ Expanded( - child: SortRuleDropdown( - sortMode: sortMode.data, - sortRuleId: sortRuleId.data, - availableSortRules: availableSortRules.data ?? [], - updateSortRuleDetails: _updateSortDetails, - onNewSortRule: (int newId) async { - await di().setSortRuleId(newId); - }, - )), + child: SortRuleDropdown( + customRulesOnly: customRulesOnly, + sortMode: sortMode.data, + sortRuleId: sortRuleId.data, + availableSortRules: availableSortRules.data ?? [], + updateSortRuleDetails: _updateSortDetails, + onNewSortRule: (int newId) async { + await di().setSortRuleId(newId); + }, + ), + ), CoreIconTextButton( icon: const Icon(Icons.sort), label: Text(S.of(context).SortDirection(sortDirection.data?.name ?? "")), diff --git a/lib/features/data_management/navigation/destinations.dart b/lib/features/data_management/navigation/destinations.dart index 2a03f45..c86cdfb 100644 --- a/lib/features/data_management/navigation/destinations.dart +++ b/lib/features/data_management/navigation/destinations.dart @@ -1,10 +1,15 @@ import 'package:flutter/material.dart'; import 'package:rabenkorb/features/data_management/categories/catgory_management_view.dart'; +import 'package:rabenkorb/features/data_management/sort_rules/sort_rule_management_title.dart'; +import 'package:rabenkorb/features/data_management/sort_rules/sort_rule_management_view.dart'; import 'package:rabenkorb/features/data_management/units/unit_management_view.dart'; import 'package:rabenkorb/generated/l10n.dart'; import 'package:rabenkorb/services/business/metadata_service.dart'; +import 'package:rabenkorb/services/data_access/sort_rule_service.dart'; +import 'package:rabenkorb/services/state/data_management_state_service.dart'; import 'package:rabenkorb/shared/destination_details.dart'; import 'package:rabenkorb/shared/helper_functions.dart'; +import 'package:rabenkorb/shared/sort_mode.dart'; import 'package:watch_it/watch_it.dart'; final List dataManagementDestinations = [ @@ -55,5 +60,31 @@ final List dataManagementDestinations = [ appBar: AppBar( title: Text(S.current.Units), ), - ) + ), + DestinationDetails( + destination: NavigationDestination( + icon: const Icon(Icons.sort), + label: S.current.SortRules, + ), + body: const SortRuleManagementView(), + mainAction: MainAction( + onPressed: (BuildContext context) async { + await showRenameDialog( + context, + onConfirm: (String? newName, bool _) async { + if (newName == null) { + return; + } + final newId = await di().createSortRule(newName); + await di().setSortMode(SortMode.custom); + await di().setSortRuleId(newId); + }, + ); + }, + ), + appBar: AppBar( + title: const SortRuleManagementTitle(), + ), + index: 2, + ), ]; diff --git a/lib/features/data_management/sort_rules/sort_rule_category_tile.dart b/lib/features/data_management/sort_rules/sort_rule_category_tile.dart new file mode 100644 index 0000000..2da66ff --- /dev/null +++ b/lib/features/data_management/sort_rules/sort_rule_category_tile.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:rabenkorb/models/item_category_view_model.dart'; + +class SortRuleCategoryTile extends StatelessWidget { + final ItemCategoryViewModel category; + + const SortRuleCategoryTile({super.key, required this.category}); + + @override + Widget build(BuildContext context) { + return Card( + child: ListTile( + title: Text(category.name), + ), + ); + } +} diff --git a/lib/features/data_management/sort_rules/sort_rule_catgory_list.dart b/lib/features/data_management/sort_rules/sort_rule_catgory_list.dart new file mode 100644 index 0000000..2d8b00c --- /dev/null +++ b/lib/features/data_management/sort_rules/sort_rule_catgory_list.dart @@ -0,0 +1,45 @@ +import 'package:drag_and_drop_lists/drag_and_drop_lists.dart'; +import 'package:flutter/material.dart'; +import 'package:rabenkorb/features/data_management/sort_rules/sort_rule_category_tile.dart'; +import 'package:rabenkorb/generated/l10n.dart'; +import 'package:rabenkorb/models/item_category_view_model.dart'; +import 'package:rabenkorb/services/business/data_management_service.dart'; +import 'package:rabenkorb/services/state/data_management_state_service.dart'; +import 'package:rabenkorb/shared/helper_functions.dart'; +import 'package:watch_it/watch_it.dart'; + +class SortRuleCategoryList extends StatelessWidget with WatchItMixin { + const SortRuleCategoryList({super.key}); + + @override + Widget build(BuildContext context) { + final AsyncSnapshot> categories = watchStream((DataManagementService p0) => p0.categories, initialValue: []); + final categoryList = categories.data ?? []; + final activeSortRule = di().sortRuleIdSync; + + return Expanded( + child: DragAndDropLists( + onItemReorder: (int oldItemIndex, int oldListIndex, int newItemIndex, int newListIndex) { + reorderCategories(oldItemIndex, newItemIndex, categoryList, activeSortRule); + }, + onListReorder: (int oldListIndex, int newListIndex) {}, + contentsWhenEmpty: Text(S.of(context).SelectSortRule), + children: [ + if (categoryList.isNotEmpty) + DragAndDropList( + children: _toDragAndDropItem(categoryList, activeSortRule), + ) + ], + ), + ); + } + + List _toDragAndDropItem(List categories, int? activeSortRule) { + return categories.map((c) { + return DragAndDropItem( + child: SortRuleCategoryTile(category: c), + canDrag: activeSortRule != null, + ); + }).toList(); + } +} diff --git a/lib/features/data_management/sort_rules/sort_rule_management_title.dart b/lib/features/data_management/sort_rules/sort_rule_management_title.dart new file mode 100644 index 0000000..dabb04d --- /dev/null +++ b/lib/features/data_management/sort_rules/sort_rule_management_title.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:rabenkorb/generated/l10n.dart'; +import 'package:rabenkorb/services/business/data_management_service.dart'; +import 'package:rabenkorb/services/data_access/sort_rule_service.dart'; +import 'package:rabenkorb/shared/helper_functions.dart'; +import 'package:rabenkorb/shared/widgets/inputs/core_icon_button.dart'; +import 'package:watch_it/watch_it.dart'; + +class SortRuleManagementTitle extends StatelessWidget with WatchItMixin { + const SortRuleManagementTitle({super.key}); + + @override + Widget build(BuildContext context) { + final activeSortRule = watchStream((DataManagementService p0) => p0.activeSortRule, initialValue: null); + final activeSortRuleData = activeSortRule.data; + return Row( + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: Text(activeSortRuleData?.name ?? S.current.SortRules), + ), + if (activeSortRuleData != null) + CoreIconButton( + icon: const Icon(Icons.edit), + onPressed: () async { + await showRenameDialog( + context, + initialName: activeSortRuleData.name, + onConfirm: (String? newName, bool nameChanged) async { + if (!nameChanged || newName == null) { + return; + } + await di().updateSortRule(activeSortRuleData.id, newName); + }, + ); + }, + ), + if (activeSortRuleData != null) + CoreIconButton( + icon: const Icon( + Icons.delete_forever, + color: Colors.red, + ), + onPressed: () async { + await doWithConfirmation( + context, + text: S.of(context).ConfirmDeleteSortRule, + title: S.of(context).Confirm, + onConfirm: () async { + await di().deleteSortRuleById(activeSortRuleData.id); + }, + ); + }, + ), + ], + ); + } +} diff --git a/lib/features/data_management/sort_rules/sort_rule_management_view.dart b/lib/features/data_management/sort_rules/sort_rule_management_view.dart new file mode 100644 index 0000000..8f05794 --- /dev/null +++ b/lib/features/data_management/sort_rules/sort_rule_management_view.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:rabenkorb/features/data_management/data_management_sort_control.dart'; + +import 'sort_rule_catgory_list.dart'; + +class SortRuleManagementView extends StatelessWidget { + const SortRuleManagementView({super.key}); + + @override + Widget build(BuildContext context) { + return const Column( + children: [ + DataManagementSortControl( + customRulesOnly: true, + ), + SortRuleCategoryList(), + ], + ); + } +} diff --git a/lib/generated/intl/messages_de_DE.dart b/lib/generated/intl/messages_de_DE.dart index 8659d50..e1645da 100644 --- a/lib/generated/intl/messages_de_DE.dart +++ b/lib/generated/intl/messages_de_DE.dart @@ -54,6 +54,7 @@ class MessageLookup extends MessageLookupByLibrary { "ConfirmDeleteAllItems": MessageLookupByLibrary.simpleMessage("Bist du sicher, dass du alle Artikel löschen willst?"), "ConfirmDeleteBasket": MessageLookupByLibrary.simpleMessage("Bist du sicher, dass du diesen Korb löschen willst?"), "ConfirmDeleteCategory": MessageLookupByLibrary.simpleMessage("Bist du sicher, dass du diese Kategorie löschen willst?"), + "ConfirmDeleteSortRule": MessageLookupByLibrary.simpleMessage("Bist du sicher, dass du diese Sortierregel löschen willst?"), "ConfirmDeleteUnit": MessageLookupByLibrary.simpleMessage("Bist du sicher, dass du diese Einheit löschen willst?"), "DataManagement": MessageLookupByLibrary.simpleMessage("Datenverwaltung"), "Debug": MessageLookupByLibrary.simpleMessage("Debug"), @@ -84,6 +85,7 @@ class MessageLookup extends MessageLookupByLibrary { "NewBasket": MessageLookupByLibrary.simpleMessage("Neuer Korb"), "NewItem": MessageLookupByLibrary.simpleMessage("Neuer Artikel"), "NoSearchResult": MessageLookupByLibrary.simpleMessage("Keine Ergebnisse"), + "NoSelection": MessageLookupByLibrary.simpleMessage("Keine Auswahl"), "NoShoppingBasketSelected": MessageLookupByLibrary.simpleMessage("Keinen Korb ausgewählt"), "PermissionsRequired": MessageLookupByLibrary.simpleMessage("Berechtigungen erforderlich"), "PickImage": MessageLookupByLibrary.simpleMessage("Bild auswählen"), @@ -92,6 +94,7 @@ class MessageLookup extends MessageLookupByLibrary { "SearchLabel": MessageLookupByLibrary.simpleMessage("Suchen..."), "SelectAll": MessageLookupByLibrary.simpleMessage("Alle auswählen"), "SelectBackup": MessageLookupByLibrary.simpleMessage("Backup-Ort auswählen"), + "SelectSortRule": MessageLookupByLibrary.simpleMessage("Wähle eine Sortierregel aus"), "SetPermission": MessageLookupByLibrary.simpleMessage("Berechtigung erteilen"), "Settings": MessageLookupByLibrary.simpleMessage("Einstellungen"), "SettingsBasket": MessageLookupByLibrary.simpleMessage("Einkaufskorb Einstellungen"), @@ -100,6 +103,8 @@ class MessageLookup extends MessageLookupByLibrary { "SortByDatabaseOrder": MessageLookupByLibrary.simpleMessage("Erstellung"), "SortByName": MessageLookupByLibrary.simpleMessage("Name"), "SortDirection": m1, + "SortRule": MessageLookupByLibrary.simpleMessage("Sortierregel"), + "SortRules": MessageLookupByLibrary.simpleMessage("Sortierregeln"), "TakePicture": MessageLookupByLibrary.simpleMessage("Bild aufnehmen"), "Template": MessageLookupByLibrary.simpleMessage("Template"), "Unit": MessageLookupByLibrary.simpleMessage("Einheit"), diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index e9f3950..a9de35c 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -54,6 +54,7 @@ class MessageLookup extends MessageLookupByLibrary { "ConfirmDeleteAllItems": MessageLookupByLibrary.simpleMessage("Are you sure you want to delete all items?"), "ConfirmDeleteBasket": MessageLookupByLibrary.simpleMessage("Are you sure you want to delete this basket?"), "ConfirmDeleteCategory": MessageLookupByLibrary.simpleMessage("Are you sure you want to delete this category?"), + "ConfirmDeleteSortRule": MessageLookupByLibrary.simpleMessage("Are you sure you want to delete this sort rule?"), "ConfirmDeleteUnit": MessageLookupByLibrary.simpleMessage("Are you sure you want to delete this unit?"), "DataManagement": MessageLookupByLibrary.simpleMessage("Data Management"), "Debug": MessageLookupByLibrary.simpleMessage("Debug"), @@ -84,6 +85,7 @@ class MessageLookup extends MessageLookupByLibrary { "NewBasket": MessageLookupByLibrary.simpleMessage("New Basket"), "NewItem": MessageLookupByLibrary.simpleMessage("New Item"), "NoSearchResult": MessageLookupByLibrary.simpleMessage("No results"), + "NoSelection": MessageLookupByLibrary.simpleMessage("No Selection"), "NoShoppingBasketSelected": MessageLookupByLibrary.simpleMessage("No basket selected"), "PermissionsRequired": MessageLookupByLibrary.simpleMessage("Permissions required"), "PickImage": MessageLookupByLibrary.simpleMessage("Pick an image"), @@ -92,6 +94,7 @@ class MessageLookup extends MessageLookupByLibrary { "SearchLabel": MessageLookupByLibrary.simpleMessage("Search..."), "SelectAll": MessageLookupByLibrary.simpleMessage("Select all"), "SelectBackup": MessageLookupByLibrary.simpleMessage("Select backup location"), + "SelectSortRule": MessageLookupByLibrary.simpleMessage("Select a Sort Rule"), "SetPermission": MessageLookupByLibrary.simpleMessage("Set Permission"), "Settings": MessageLookupByLibrary.simpleMessage("Settings"), "SettingsBasket": MessageLookupByLibrary.simpleMessage("Basket Settings"), @@ -100,6 +103,8 @@ class MessageLookup extends MessageLookupByLibrary { "SortByDatabaseOrder": MessageLookupByLibrary.simpleMessage("Creation"), "SortByName": MessageLookupByLibrary.simpleMessage("Name"), "SortDirection": m1, + "SortRule": MessageLookupByLibrary.simpleMessage("Sort Rule"), + "SortRules": MessageLookupByLibrary.simpleMessage("Sort Rules"), "TakePicture": MessageLookupByLibrary.simpleMessage("Take Picture"), "Template": MessageLookupByLibrary.simpleMessage("Template"), "Unit": MessageLookupByLibrary.simpleMessage("Unit"), diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 017d2fb..47300bb 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -511,6 +511,46 @@ class S { ); } + /// `Sort Rule` + String get SortRule { + return Intl.message( + 'Sort Rule', + name: 'SortRule', + desc: '', + args: [], + ); + } + + /// `Sort Rules` + String get SortRules { + return Intl.message( + 'Sort Rules', + name: 'SortRules', + desc: '', + args: [], + ); + } + + /// `Select a Sort Rule` + String get SelectSortRule { + return Intl.message( + 'Select a Sort Rule', + name: 'SelectSortRule', + desc: '', + args: [], + ); + } + + /// `No Selection` + String get NoSelection { + return Intl.message( + 'No Selection', + name: 'NoSelection', + desc: '', + args: [], + ); + } + /// `New Item` String get NewItem { return Intl.message( @@ -711,6 +751,16 @@ class S { ); } + /// `Are you sure you want to delete this sort rule?` + String get ConfirmDeleteSortRule { + return Intl.message( + 'Are you sure you want to delete this sort rule?', + name: 'ConfirmDeleteSortRule', + desc: '', + args: [], + ); + } + /// `Rename` String get Rename { return Intl.message( diff --git a/lib/l10n/intl_de_DE.arb b/lib/l10n/intl_de_DE.arb index 2e6f606..f8022a5 100644 --- a/lib/l10n/intl_de_DE.arb +++ b/lib/l10n/intl_de_DE.arb @@ -54,6 +54,10 @@ "Amount": "Menge", "Unit": "Einheit", "Units": "Einheiten", + "SortRule": "Sortierregel", + "SortRules": "Sortierregeln", + "SelectSortRule": "Wähle eine Sortierregel aus", + "NoSelection": "Keine Auswahl", "NewItem": "Neuer Artikel", "NoSearchResult": "Keine Ergebnisse", "NameMustNotBeEmpty": "Name darf nicht leer sein", @@ -82,6 +86,7 @@ "ConfirmDeleteBasket": "Bist du sicher, dass du diesen Korb löschen willst?", "ConfirmDeleteCategory": "Bist du sicher, dass du diese Kategorie löschen willst?", "ConfirmDeleteUnit": "Bist du sicher, dass du diese Einheit löschen willst?", + "ConfirmDeleteSortRule": "Bist du sicher, dass du diese Sortierregel löschen willst?", "Rename": "Umbenennen", "NewBasket": "Neuer Korb", "New": "Neu", diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index b376829..f19575e 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -54,6 +54,10 @@ "Amount": "Amount", "Unit": "Unit", "Units": "Units", + "SortRule": "Sort Rule", + "SortRules": "Sort Rules", + "SelectSortRule": "Select a Sort Rule", + "NoSelection": "No Selection", "NewItem": "New Item", "NoSearchResult": "No results", "NameMustNotBeEmpty": "Name must not be empty", @@ -82,6 +86,7 @@ "ConfirmDeleteBasket": "Are you sure you want to delete this basket?", "ConfirmDeleteCategory": "Are you sure you want to delete this category?", "ConfirmDeleteUnit": "Are you sure you want to delete this unit?", + "ConfirmDeleteSortRule": "Are you sure you want to delete this sort rule?", "Rename": "Rename", "NewBasket": "New Basket", "New": "New", diff --git a/lib/routing/router_config.dart b/lib/routing/router_config.dart index 611d550..d8dfafe 100644 --- a/lib/routing/router_config.dart +++ b/lib/routing/router_config.dart @@ -96,6 +96,13 @@ RouterConfig goRouterConfig({String initialLocation = Routes.home}) => G return Routes.dataManagement; }, ), + GoRoute( + path: Routes.dataManagementSortRule, + redirect: (context, state) { + di().setCurrentPageIndex(2); + return Routes.dataManagement; + }, + ), GoRoute( path: Routes.dataManagement, builder: (context, state) => const DataManagementPage(), diff --git a/lib/routing/routes.dart b/lib/routing/routes.dart index b622a5e..9292296 100644 --- a/lib/routing/routes.dart +++ b/lib/routing/routes.dart @@ -12,4 +12,5 @@ abstract class Routes { static const String dataManagement = "/data-management"; static const String dataManagementCategory = "/data-management/category"; static const String dataManagementUnit = "/data-management/unit"; + static const String dataManagementSortRule = "/data-management/sort-rule"; } diff --git a/lib/services/business/data_management_service.dart b/lib/services/business/data_management_service.dart index b8e33df..f6ff299 100644 --- a/lib/services/business/data_management_service.dart +++ b/lib/services/business/data_management_service.dart @@ -1,7 +1,9 @@ import 'dart:async'; import 'package:rabenkorb/models/item_category_view_model.dart'; +import 'package:rabenkorb/models/sort_rule_view_model.dart'; import 'package:rabenkorb/services/business/metadata_service.dart'; +import 'package:rabenkorb/services/data_access/sort_rule_service.dart'; import 'package:rabenkorb/services/state/data_management_state_service.dart'; import 'package:rabenkorb/shared/filter_details.dart'; import 'package:rabenkorb/shared/sort_direction.dart'; @@ -12,31 +14,44 @@ import 'package:watch_it/watch_it.dart'; class DataManagementService implements Disposable { final _dataManagementStateService = di(); final _metadataService = di(); + final _sortRuleService = di(); late StreamSubscription _templateSub; - final _itemCategories = BehaviorSubject>.seeded([]); + final _itemCategories = BehaviorSubject>.seeded(List.empty()); - Stream> get itemCategories => _itemCategories.stream; + late StreamSubscription _activeSortRuleSub; + final BehaviorSubject _activeSortRule = BehaviorSubject.seeded(null); + + Stream get activeSortRule => _activeSortRule.stream; + + Stream> get categories => _itemCategories.stream; DataManagementService() { _templateSub = Rx.combineLatest3( - _dataManagementStateService.sortMode, - _dataManagementStateService.sortDirection, - _dataManagementStateService.sortRuleId, - (SortMode sortMode, SortDirection sortDirection, int? sortRuleId) => - ItemCategoryFilterDetails(sortMode: sortMode, sortDirection: sortDirection, sortRuleId: sortRuleId)) - .switchMap((details) => _metadataService.watchItemCategoriesInOrder( - details.sortMode, - details.sortDirection, - sortRuleId: details.sortRuleId, - )) - .listen((categories) { + _dataManagementStateService.sortMode, + _dataManagementStateService.sortDirection, + _dataManagementStateService.sortRuleId, + (SortMode sortMode, SortDirection sortDirection, int? sortRuleId) => + ItemCategoryFilterDetails(sortMode: sortMode, sortDirection: sortDirection, sortRuleId: sortRuleId)).switchMap((details) { + if (details.sortRuleId == null) { + return Stream.value(List.empty()); + } + return _metadataService.watchItemCategoriesInOrder( + details.sortMode, + details.sortDirection, + sortRuleId: details.sortRuleId, + ); + }).listen((categories) { _itemCategories.add(categories); }); + _activeSortRuleSub = _dataManagementStateService.sortRuleId.switchMap((sortRuleId) => _sortRuleService.watchSortRuleWithId(sortRuleId)).listen((sortRule) { + _activeSortRule.add(sortRule); + }); } @override FutureOr onDispose() { _templateSub.cancel(); + _activeSortRuleSub.cancel(); } } diff --git a/lib/services/data_access/sort_rule_service.dart b/lib/services/data_access/sort_rule_service.dart index a5ba1f2..032f719 100644 --- a/lib/services/data_access/sort_rule_service.dart +++ b/lib/services/data_access/sort_rule_service.dart @@ -39,7 +39,10 @@ class SortRuleService implements Disposable { return _db.sortRulesDao.watchSortRules(); } - Stream watchSortRuleWithId(int id) { + Stream watchSortRuleWithId(int? id) { + if (id == null || id < 0) { + return Stream.value(null); + } return _db.sortRulesDao.watchSortRuleWithId(id); } diff --git a/lib/services/state/data_management_state_service.dart b/lib/services/state/data_management_state_service.dart index 14e67ed..6302653 100644 --- a/lib/services/state/data_management_state_service.dart +++ b/lib/services/state/data_management_state_service.dart @@ -5,7 +5,7 @@ import '../../shared/sort_mode.dart'; class DataManagementStateService { final BehaviorSubject _sortRuleIdSubject = BehaviorSubject.seeded(null); - final BehaviorSubject _sortModeSubject = BehaviorSubject.seeded(SortMode.name); + final BehaviorSubject _sortModeSubject = BehaviorSubject.seeded(SortMode.custom); final BehaviorSubject _sortDirectionSubject = BehaviorSubject.seeded(SortDirection.asc); Stream get sortRuleId => _sortRuleIdSubject.stream; diff --git a/lib/shared/widgets/sort_rule_dropdown.dart b/lib/shared/widgets/sort_rule_dropdown.dart index 0bd0b28..76c7363 100644 --- a/lib/shared/widgets/sort_rule_dropdown.dart +++ b/lib/shared/widgets/sort_rule_dropdown.dart @@ -3,6 +3,7 @@ import 'package:rabenkorb/generated/l10n.dart'; import 'package:rabenkorb/models/sort_rule_view_model.dart'; import 'package:rabenkorb/services/data_access/sort_rule_service.dart'; import 'package:rabenkorb/shared/default_sort_rules.dart'; +import 'package:rabenkorb/shared/extensions.dart'; import 'package:rabenkorb/shared/sort_mode.dart'; import 'package:rabenkorb/shared/widgets/form/core_searchable_dropdown.dart'; import 'package:watch_it/watch_it.dart'; @@ -13,6 +14,7 @@ class SortRuleDropdown extends StatelessWidget { final Function(int newId) onNewSortRule; final Function(SortRuleViewModel? rule) updateSortRuleDetails; final List availableSortRules; + final bool customRulesOnly; const SortRuleDropdown({ super.key, @@ -21,15 +23,14 @@ class SortRuleDropdown extends StatelessWidget { required this.onNewSortRule, required this.updateSortRuleDetails, required this.availableSortRules, + this.customRulesOnly = false, }); @override Widget build(BuildContext context) { - final sortRules = defaultSortRules(); - final selectedItem = sortRuleId != null && (availableSortRules.length ?? 0) > 0 - ? availableSortRules.firstWhere((e) => e.id == sortRuleId, orElse: () => _getDefaultSelectedItem(sortMode, sortRules)) - : _getDefaultSelectedItem(sortMode, sortRules); + final List sortRules = customRulesOnly ? [] : defaultSortRules(); sortRules.addAll(availableSortRules); + final selectedItem = _getSelectedItem(sortMode, sortRules, sortRuleId, customRulesOnly); return CoreSearchableDropdown( selectedItem: selectedItem, @@ -50,10 +51,22 @@ class SortRuleDropdown extends StatelessWidget { } String _getSortRuleName(BuildContext context, SortRuleViewModel? rule) { - return rule?.name ?? S.of(context).Unnamed; + return rule?.name ?? S.of(context).NoSelection; } - SortRuleViewModel _getDefaultSelectedItem(SortMode? sortMode, List sortRules) { + SortRuleViewModel? _getSelectedItem(SortMode? sortMode, List sortRules, int? sortRuleId, bool customRulesOnly) { + if (sortRules.isEmpty) { + return null; + } + + if (customRulesOnly && sortRuleId == null) { + return null; + } + + if (sortMode == SortMode.custom) { + return sortRules.firstWhereOrNull((e) => e.id == sortRuleId); + } + if (sortMode == SortMode.databaseOrder) { return sortRules.firstWhere((e) => e.id == sortByDatabasePseudoId); } From 597e2dd9633229a321a00ac4138e004985e655f6 Mon Sep 17 00:00:00 2001 From: Alexander Pahn Date: Sun, 19 May 2024 11:46:03 +0200 Subject: [PATCH 11/12] refactored how sort rules for managing are selected --- .../data_management_sort_control.dart | 77 ------------------- .../sort_rule_management_title.dart | 44 ++++++++++- .../sort_rules/sort_rule_management_view.dart | 4 - 3 files changed, 43 insertions(+), 82 deletions(-) delete mode 100644 lib/features/data_management/data_management_sort_control.dart diff --git a/lib/features/data_management/data_management_sort_control.dart b/lib/features/data_management/data_management_sort_control.dart deleted file mode 100644 index ed857d8..0000000 --- a/lib/features/data_management/data_management_sort_control.dart +++ /dev/null @@ -1,77 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:rabenkorb/generated/l10n.dart'; -import 'package:rabenkorb/models/sort_rule_view_model.dart'; -import 'package:rabenkorb/services/data_access/sort_rule_service.dart'; -import 'package:rabenkorb/services/state/data_management_state_service.dart'; -import 'package:rabenkorb/shared/default_sort_rules.dart'; -import 'package:rabenkorb/shared/sort_mode.dart'; -import 'package:rabenkorb/shared/widgets/inputs/core_icon_text_button.dart'; -import 'package:rabenkorb/shared/widgets/sort_rule_dropdown.dart'; -import 'package:watch_it/watch_it.dart'; - -class DataManagementSortControl extends StatelessWidget with WatchItMixin { - final bool customRulesOnly; - - const DataManagementSortControl({ - super.key, - this.customRulesOnly = false, - }); - - @override - Widget build(BuildContext context) { - final dataManagementStateService = di(); - final AsyncSnapshot> availableSortRules = watchStream((SortRuleService p0) => p0.sortRules, initialValue: []); - final sortRuleId = watchStream((DataManagementStateService p0) => p0.sortRuleId, initialValue: dataManagementStateService.sortRuleIdSync); - final sortMode = watchStream((DataManagementStateService p0) => p0.sortMode, initialValue: dataManagementStateService.sortModeSync); - final sortDirection = watchStream((DataManagementStateService p0) => p0.sortDirection, initialValue: dataManagementStateService.sortDirectionSync); - - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Row( - mainAxisSize: MainAxisSize.max, - children: [ - Expanded( - child: SortRuleDropdown( - customRulesOnly: customRulesOnly, - sortMode: sortMode.data, - sortRuleId: sortRuleId.data, - availableSortRules: availableSortRules.data ?? [], - updateSortRuleDetails: _updateSortDetails, - onNewSortRule: (int newId) async { - await di().setSortRuleId(newId); - }, - ), - ), - CoreIconTextButton( - icon: const Icon(Icons.sort), - label: Text(S.of(context).SortDirection(sortDirection.data?.name ?? "")), - onPressed: () async { - await dataManagementStateService.switchSortDirection(); - }, - ), - ], - ), - ); - } - - Future _updateSortDetails(SortRuleViewModel? rule) async { - final ruleId = rule?.id; - if (ruleId == null) { - return; - } - final dataManagementStateService = di(); - if (ruleId == sortByNamePseudoId) { - await dataManagementStateService.setSortMode(SortMode.name); - await dataManagementStateService.setSortRuleId(null); - return; - } - if (ruleId == sortByDatabasePseudoId) { - await dataManagementStateService.setSortMode(SortMode.databaseOrder); - await dataManagementStateService.setSortRuleId(null); - return; - } - - await dataManagementStateService.setSortMode(SortMode.custom); - await dataManagementStateService.setSortRuleId(ruleId); - } -} diff --git a/lib/features/data_management/sort_rules/sort_rule_management_title.dart b/lib/features/data_management/sort_rules/sort_rule_management_title.dart index dabb04d..ca111c6 100644 --- a/lib/features/data_management/sort_rules/sort_rule_management_title.dart +++ b/lib/features/data_management/sort_rules/sort_rule_management_title.dart @@ -1,9 +1,14 @@ import 'package:flutter/material.dart'; import 'package:rabenkorb/generated/l10n.dart'; +import 'package:rabenkorb/models/sort_rule_view_model.dart'; import 'package:rabenkorb/services/business/data_management_service.dart'; import 'package:rabenkorb/services/data_access/sort_rule_service.dart'; +import 'package:rabenkorb/services/state/data_management_state_service.dart'; +import 'package:rabenkorb/shared/default_sort_rules.dart'; import 'package:rabenkorb/shared/helper_functions.dart'; +import 'package:rabenkorb/shared/sort_mode.dart'; import 'package:rabenkorb/shared/widgets/inputs/core_icon_button.dart'; +import 'package:rabenkorb/shared/widgets/sort_rule_dropdown.dart'; import 'package:watch_it/watch_it.dart'; class SortRuleManagementTitle extends StatelessWidget with WatchItMixin { @@ -13,11 +18,26 @@ class SortRuleManagementTitle extends StatelessWidget with WatchItMixin { Widget build(BuildContext context) { final activeSortRule = watchStream((DataManagementService p0) => p0.activeSortRule, initialValue: null); final activeSortRuleData = activeSortRule.data; + + final dataManagementStateService = di(); + final AsyncSnapshot> availableSortRules = watchStream((SortRuleService p0) => p0.sortRules, initialValue: []); + final sortRuleId = watchStream((DataManagementStateService p0) => p0.sortRuleId, initialValue: dataManagementStateService.sortRuleIdSync); + final sortMode = watchStream((DataManagementStateService p0) => p0.sortMode, initialValue: dataManagementStateService.sortModeSync); + return Row( mainAxisSize: MainAxisSize.max, children: [ Expanded( - child: Text(activeSortRuleData?.name ?? S.current.SortRules), + child: SortRuleDropdown( + customRulesOnly: true, + sortMode: sortMode.data, + sortRuleId: sortRuleId.data, + availableSortRules: availableSortRules.data ?? [], + updateSortRuleDetails: _updateSortDetails, + onNewSortRule: (int newId) async { + await di().setSortRuleId(newId); + }, + ), ), if (activeSortRuleData != null) CoreIconButton( @@ -48,6 +68,7 @@ class SortRuleManagementTitle extends StatelessWidget with WatchItMixin { title: S.of(context).Confirm, onConfirm: () async { await di().deleteSortRuleById(activeSortRuleData.id); + await di().setSortRuleId(null); }, ); }, @@ -55,4 +76,25 @@ class SortRuleManagementTitle extends StatelessWidget with WatchItMixin { ], ); } + + Future _updateSortDetails(SortRuleViewModel? rule) async { + final ruleId = rule?.id; + if (ruleId == null) { + return; + } + final dataManagementStateService = di(); + if (ruleId == sortByNamePseudoId) { + await dataManagementStateService.setSortMode(SortMode.name); + await dataManagementStateService.setSortRuleId(null); + return; + } + if (ruleId == sortByDatabasePseudoId) { + await dataManagementStateService.setSortMode(SortMode.databaseOrder); + await dataManagementStateService.setSortRuleId(null); + return; + } + + await dataManagementStateService.setSortMode(SortMode.custom); + await dataManagementStateService.setSortRuleId(ruleId); + } } diff --git a/lib/features/data_management/sort_rules/sort_rule_management_view.dart b/lib/features/data_management/sort_rules/sort_rule_management_view.dart index 8f05794..9723424 100644 --- a/lib/features/data_management/sort_rules/sort_rule_management_view.dart +++ b/lib/features/data_management/sort_rules/sort_rule_management_view.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:rabenkorb/features/data_management/data_management_sort_control.dart'; import 'sort_rule_catgory_list.dart'; @@ -10,9 +9,6 @@ class SortRuleManagementView extends StatelessWidget { Widget build(BuildContext context) { return const Column( children: [ - DataManagementSortControl( - customRulesOnly: true, - ), SortRuleCategoryList(), ], ); From d4df24a33a0670885a38efacc75919e4d34a1f70 Mon Sep 17 00:00:00 2001 From: Alexander Pahn Date: Sun, 19 May 2024 12:25:34 +0200 Subject: [PATCH 12/12] improved how sort rules are updated --- lib/database/daos/sort_orders_dao.dart | 72 +++++++++++-------- lib/services/business/sort_service.dart | 5 +- .../data_access/sort_order_service.dart | 5 +- lib/shared/helper_functions.dart | 40 ++--------- 4 files changed, 54 insertions(+), 68 deletions(-) diff --git a/lib/database/daos/sort_orders_dao.dart b/lib/database/daos/sort_orders_dao.dart index 99c83ce..461eba7 100644 --- a/lib/database/daos/sort_orders_dao.dart +++ b/lib/database/daos/sort_orders_dao.dart @@ -1,6 +1,8 @@ import 'package:drift/drift.dart'; import 'package:rabenkorb/database/database.dart'; import 'package:rabenkorb/database/tables/sort_orders.dart'; +import 'package:rabenkorb/models/item_category_view_model.dart'; +import 'package:rabenkorb/shared/default_sort_rules.dart'; part 'sort_orders_dao.g.dart'; @@ -28,51 +30,63 @@ class SortOrdersDao extends DatabaseAccessor with _$SortOrdersDaoMi }); } - Future updateOrderSingle(int sortRuleId, int targetId, {int? placeBeforeId, int? placeAfterId}) async { + Future updateOrderSingle(int sortRuleId, List visibleCategories, int oldIndex, int newIndex) async { await transaction(() async { + // Fetch current sort orders for the given sort rule final allOrders = await (select(sortOrders) ..where((tbl) => tbl.ruleId.equals(sortRuleId)) ..orderBy([(tbl) => OrderingTerm(expression: tbl.sortOrder)])) .get(); - // Find target, placeBefore, and placeAfter indices - final targetIndex = allOrders.indexWhere((order) => order.categoryId == targetId); - final placeBeforeIndex = placeBeforeId != null ? allOrders.indexWhere((order) => order.categoryId == placeBeforeId) : -1; - final placeAfterIndex = placeAfterId != null ? allOrders.indexWhere((order) => order.categoryId == placeAfterId) : -1; + // Create a map of categoryId to its sort order + final orderMap = {for (var order in allOrders) order.categoryId: order}; - if (targetIndex == -1) { - throw ArgumentError("Target ID $targetId not found."); - } + // Filter out the pseudo-category with id -1 from visibleCategories + visibleCategories = visibleCategories.where((category) => category.id != withoutCategoryId).toList(); - if (placeBeforeId == null && placeAfterId == null) { - // No reordering needed - return; + // Validate indices + if (oldIndex < 0 || oldIndex >= visibleCategories.length || newIndex < 0 || newIndex >= visibleCategories.length) { + throw ArgumentError("Invalid indices provided."); } - final targetOrder = allOrders.removeAt(targetIndex); + // Reorder the visible categories based on the indices provided + final targetCategory = visibleCategories.removeAt(oldIndex); + visibleCategories.insert(newIndex, targetCategory); - // Determine the new position for targetOrder - int newIndex; - if (placeBeforeIndex != -1) { - newIndex = placeBeforeIndex > targetIndex ? placeBeforeIndex - 1 : placeBeforeIndex; - } else if (placeAfterIndex != -1) { - newIndex = placeAfterIndex > targetIndex ? placeAfterIndex : placeAfterIndex + 1; - } else { - // If both are null, it means we shouldn't be here due to the previous check - return; + // Ensure all categories within the range have sort orders + for (int i = 0; i < visibleCategories.length; i++) { + final category = visibleCategories[i]; + if (orderMap.containsKey(category.id)) { + // Update existing sort order + orderMap[category.id] = orderMap[category.id]!.copyWith(sortOrder: i + 1); + } else { + // Create a new sort order + orderMap[category.id] = SortOrder( + categoryId: category.id, + ruleId: sortRuleId, + sortOrder: i + 1, + ); + } } - // Insert the target order at the new position - allOrders.insert(newIndex, targetOrder); + // Handle unsorted categories not visible but should be ordered sequentially after visible ones + final sortedOrderCategories = orderMap.values.toList(); + sortedOrderCategories.sort((a, b) => a.sortOrder.compareTo(b.sortOrder)); + int nextSortOrder = sortedOrderCategories.length + 1; + + final unsortedCategories = await (select(itemCategories)..where((tbl) => (tbl.id.isIn(orderMap.keys.toList()) & tbl.id.isNotValue(-1)).not())).get(); - // Update sortOrder to ensure no gaps - for (int i = 0; i < allOrders.length; i++) { - allOrders[i] = allOrders[i].copyWith(sortOrder: i + 1); + for (var category in unsortedCategories) { + orderMap[category.id] = SortOrder( + categoryId: category.id, + ruleId: sortRuleId, + sortOrder: nextSortOrder++, + ); } - // Update the database - for (var order in allOrders) { - await (update(sortOrders)..where((tbl) => tbl.categoryId.equals(order.categoryId) & tbl.ruleId.equals(sortRuleId))).write(order); + // Update the database with new and updated sort orders + for (var order in orderMap.values) { + await (into(sortOrders).insertOnConflictUpdate(order)); } }); } diff --git a/lib/services/business/sort_service.dart b/lib/services/business/sort_service.dart index f12964e..0c3c332 100644 --- a/lib/services/business/sort_service.dart +++ b/lib/services/business/sort_service.dart @@ -1,3 +1,4 @@ +import 'package:rabenkorb/models/item_category_view_model.dart'; import 'package:rabenkorb/models/sort_rule_view_model.dart'; import 'package:rabenkorb/services/data_access/sort_order_service.dart'; import 'package:rabenkorb/services/data_access/sort_rule_service.dart'; @@ -39,7 +40,7 @@ class SortService { return _sortOrderService.setOrder(id, categoryIds); } - Future updateOrderSingle(int sortRuleId, int targetId, {int? placeBeforeId, int? placeAfterId}) { - return _sortOrderService.updateOrderSingle(sortRuleId, targetId, placeBeforeId: placeBeforeId, placeAfterId: placeAfterId); + Future updateOrderSingle(int sortRuleId, List visibleCategories, int oldIndex, int newIndex) { + return _sortOrderService.updateOrderSingle(sortRuleId, visibleCategories, oldIndex, newIndex); } } diff --git a/lib/services/data_access/sort_order_service.dart b/lib/services/data_access/sort_order_service.dart index 9a234b4..72cbfe8 100644 --- a/lib/services/data_access/sort_order_service.dart +++ b/lib/services/data_access/sort_order_service.dart @@ -1,4 +1,5 @@ import 'package:rabenkorb/database/database.dart'; +import 'package:rabenkorb/models/item_category_view_model.dart'; import 'package:watch_it/watch_it.dart'; class SortOrderService { @@ -12,7 +13,7 @@ class SortOrderService { return _db.sortOrdersDao.setOrder(id, categoryIds); } - Future updateOrderSingle(int sortRuleId, int targetId, {int? placeBeforeId, int? placeAfterId}) { - return _db.sortOrdersDao.updateOrderSingle(sortRuleId, targetId, placeBeforeId: placeBeforeId, placeAfterId: placeAfterId); + Future updateOrderSingle(int sortRuleId, List visibleCategories, int oldIndex, int newIndex) { + return _db.sortOrdersDao.updateOrderSingle(sortRuleId, visibleCategories, oldIndex, newIndex); } } diff --git a/lib/shared/helper_functions.dart b/lib/shared/helper_functions.dart index 6a32774..58c8957 100644 --- a/lib/shared/helper_functions.dart +++ b/lib/shared/helper_functions.dart @@ -9,7 +9,6 @@ import 'package:rabenkorb/services/business/sort_service.dart'; import 'package:rabenkorb/services/core/dialog_service.dart'; import 'package:rabenkorb/services/state/library_state_service.dart'; import 'package:rabenkorb/services/state/loading_state.dart'; -import 'package:rabenkorb/shared/default_sort_rules.dart'; import 'package:rabenkorb/shared/state_types.dart'; import 'package:rabenkorb/shared/widgets/rename_dialog.dart'; import 'package:watch_it/watch_it.dart'; @@ -132,48 +131,19 @@ Future pickImage(ImageSource source) async { return image; } -void reorderGroupedItems(int oldIndex, int newIndex, List> list, int? activeSortRuleId) async { +Future reorderGroupedItems(int oldIndex, int newIndex, List> list, int? activeSortRuleId) async { if (activeSortRuleId == null || newIndex == oldIndex) { return; } - // New index would be relative to the list without the item being reordered - newIndex--; - final filteredList = list.where((e) => e.category.id != withoutCategoryId).toList(); - final reorderedItem = filteredList[oldIndex]; - final placeAfterItem = newIndex < filteredList.length && newIndex >= 0 - ? filteredList[newIndex] - : newIndex >= filteredList.length - ? filteredList.last - : null; - final placeBeforeItem = newIndex < 0 ? filteredList.first : null; + final visibleCategories = list.map((e) => e.category).toList(); - final targetId = reorderedItem.category.id; - final placeAfterId = placeAfterItem?.category.id; - final placeBeforeId = placeBeforeItem?.category.id; - - await di().updateOrderSingle(activeSortRuleId, targetId, placeBeforeId: placeBeforeId, placeAfterId: placeAfterId); - await di().setSortRuleId(activeSortRuleId); + await reorderCategories(oldIndex, newIndex, visibleCategories, activeSortRuleId); } -void reorderCategories(int oldIndex, int newIndex, List list, int? activeSortRuleId) async { +Future reorderCategories(int oldIndex, int newIndex, List list, int? activeSortRuleId) async { if (activeSortRuleId == null || newIndex == oldIndex) { return; } - // New index would be relative to the list without the item being reordered - newIndex--; - - final reorderedItem = list[oldIndex]; - final placeAfterItem = newIndex < list.length && newIndex >= 0 - ? list[newIndex] - : newIndex >= list.length - ? list.last - : null; - final placeBeforeItem = newIndex < 0 ? list.first : null; - - final targetId = reorderedItem.id; - final placeAfterId = placeAfterItem?.id; - final placeBeforeId = placeBeforeItem?.id; - - await di().updateOrderSingle(activeSortRuleId, targetId, placeBeforeId: placeBeforeId, placeAfterId: placeAfterId); + await di().updateOrderSingle(activeSortRuleId, list, oldIndex, newIndex); await di().setSortRuleId(activeSortRuleId); }