From: Hamatoma Date: Fri, 8 Oct 2021 06:05:21 +0000 (+0200) Subject: Generator routes Structures X-Git-Url: https://gitweb.hamatoma.de/?a=commitdiff_plain;h=f08829441e8246e79bd4ba66c75b43e7be82f165;p=exhibition.git Generator routes Structures * new: application_name.dart * Routes: uses module name instead of lowercase module name * new: Structures * new: AttendedStateful as base class for all meta data driven pages (StateFulWidget). * new: Handling of multiple asynchronous db requests: DbDataState * WidgetForm: ** new: flexibleGridAttended(), ** parameters changed: flexibleGrid(): works on a widget list (instead of an AttendedWidget list) --- diff --git a/lib/base/application_name.dart b/lib/base/application_name.dart new file mode 100644 index 0000000..897d202 --- /dev/null +++ b/lib/base/application_name.dart @@ -0,0 +1,2 @@ +const theApplicationName = 'exhibition'; +const theVariantName = 'exhibition'; diff --git a/lib/base/defines.dart b/lib/base/defines.dart index 1496a80..69cd576 100644 --- a/lib/base/defines.dart +++ b/lib/base/defines.dart @@ -1,6 +1,6 @@ enum ServerEnvironment { productive, development } typedef JsonMap = Map; -typedef JsonList = List; +typedef JsonList = List; ///JsonMap or JsonList typedef JsonData = Object; diff --git a/lib/exhibition_app.dart b/lib/exhibition_app.dart index 148a4f2..bb0cca4 100644 --- a/lib/exhibition_app.dart +++ b/lib/exhibition_app.dart @@ -1,4 +1,4 @@ -import 'package:exhibition/page/page_collection.dart'; +import 'package:exhibition/page/page_manager.dart'; import 'package:flutter/material.dart'; import 'package:dart_bones/dart_bones.dart'; @@ -21,7 +21,7 @@ Route? _getRoute(RouteSettings settings) { page = LogPage(); break; default: - page = PageCollection().newPageByRoute(settings.name ?? ''); + page = PageManager().newPageByRoute(settings.name ?? ''); break; } if (page != null) { diff --git a/lib/meta/module_meta_data.dart b/lib/meta/module_meta_data.dart index b7c507c..b7a105d 100644 --- a/lib/meta/module_meta_data.dart +++ b/lib/meta/module_meta_data.dart @@ -76,7 +76,7 @@ class ModuleMetaData { String moduleNameSingular = '', String columnPrefix = '', bool shortModifiedLabel = false}) { - this.tableName = tableName.isEmpty ? moduleName : tableName; + this.tableName = tableName.isEmpty ? moduleName.toLowerCase() : tableName; this.shortModifiedLabel = shortModifiedLabel; if (moduleNameSingular.isEmpty) { if (!moduleName.endsWith('s')) { diff --git a/lib/meta/modules.dart b/lib/meta/modules.dart index 57e2fe3..2ff718c 100644 --- a/lib/meta/modules.dart +++ b/lib/meta/modules.dart @@ -1,7 +1,9 @@ // DO NOT CHANGE. This file is created by the meta_tool import 'module_meta_data.dart'; import 'roles_meta.dart'; +import 'structures_meta.dart'; import 'users_meta.dart'; + /// Returns the meta data of the module given by [name]. /// Returns null if not found. ModuleMetaData? moduleByName(String name) { @@ -10,6 +12,9 @@ ModuleMetaData? moduleByName(String name) { case 'Roles': rc = RoleMeta(); break; + case 'Structures': + rc = StructureMeta(); + break; case 'Users': rc = UserMeta(); break; @@ -18,10 +23,12 @@ ModuleMetaData? moduleByName(String name) { } return rc; } + /// Returns the module names as string list. -List moduleNames(){ +List moduleNames() { return [ 'Roles', + 'Structures', 'Users', ]; } diff --git a/lib/meta/structures_meta.dart b/lib/meta/structures_meta.dart new file mode 100644 index 0000000..8f0f5e7 --- /dev/null +++ b/lib/meta/structures_meta.dart @@ -0,0 +1,44 @@ +import '../base/defines.dart'; +import '../base/i18n.dart'; +import 'module_meta_data.dart'; + +final i18n = I18N(); +final M = i18n.module("Structures"); + +class StructureMeta extends ModuleMetaData { + static StructureMeta instance = StructureMeta.internal(); + factory StructureMeta() { + return instance; + } + StructureMeta.internal() + : super('Structures', [ + PropertyMetaData('id', i18n.tr('Id'), DataType.reference, ':primary:', + displayType: DisplayType.combobox), + PropertyMetaData( + 'scope', i18n.tr('Scope'), DataType.string, ':notnull:', + size: 64), + PropertyMetaData( + 'name', i18n.tr('Name', M), DataType.string, ':notnull:', + size: 64), + PropertyMetaData( + 'value', i18n.tr('Value', M), DataType.string, ':notnull:', + size: 32), + PropertyMetaData( + 'position', i18n.tr('Position', M), DataType.int, ''), + PropertyMetaData( + 'created', i18n.tr('Created'), DataType.datetime, ':hidden:'), + PropertyMetaData( + 'createdBy', i18n.tr('Created by'), DataType.string, ':hidden:', + size: 32), + PropertyMetaData( + 'changed', i18n.tr('Changed'), DataType.datetime, ':hidden:'), + PropertyMetaData( + 'changedBy', i18n.tr('Changed by'), DataType.string, ':hidden:', + size: 32), + ], [ + PageMetaData('New Structure', PageType.create), + PageMetaData('Change Structure', PageType.edit), + PageMetaData('Delete Structure', PageType.delete), + PageMetaData('Structures Overview', PageType.list), + ]); +} diff --git a/lib/meta/users_meta.dart b/lib/meta/users_meta.dart index 53b9a2b..0eba0ff 100644 --- a/lib/meta/users_meta.dart +++ b/lib/meta/users_meta.dart @@ -25,7 +25,8 @@ class UserMeta extends ModuleMetaData { size: 255), PropertyMetaData( 'role', i18n.tr('Role'), DataType.reference, ':notnull:', - displayType: DisplayType.combobox, foreignKey: 'roles.role_id;role_name;role'), + displayType: DisplayType.combobox, + foreignKey: 'roles.role_id;role_name;role'), PropertyMetaData( 'created', i18n.tr('Created'), DataType.datetime, ':hidden:'), PropertyMetaData( diff --git a/lib/page/page_collection.dart b/lib/page/page_collection.dart deleted file mode 100644 index eca445f..0000000 --- a/lib/page/page_collection.dart +++ /dev/null @@ -1,65 +0,0 @@ -// DO NOT CHANGE. This file is created by the meta_tool! -import 'package:flutter/material.dart'; -import 'page_collection_custom.dart'; - -import 'roles/create_role_page.dart'; -import 'roles/edit_role_page.dart'; -import 'roles/list_role_page.dart'; -import 'users/create_user_page.dart'; -import 'users/edit_user_page.dart'; -import 'users/delete_user_page.dart'; -import 'users/list_user_page.dart'; - - -/// Manages all meta data driven pages of the program. -class PageCollection { - static PageCollection? _instance; - PageCollection.internal(); - factory PageCollection() { - _instance ??= PageCollection.internal(); - return _instance!; - } - final map = {}; - - /// Creates a page defined by a [route]. - StatefulWidget? newPageByRoute(String route) { - StatefulWidget? rc; - final parts = route.split(';'); - final arg1 = parts.length < 2 ? 0 : int.parse(parts[1]); - switch (parts[0]) { - case '/roles/create': - rc = CreateRolePage(); - break; - case '/roles/edit': - rc = EditRolePage(arg1); - break; - case '/roles/list': - rc = ListRolePage(); - break; - case '/users/create': - rc = CreateUserPage(); - break; - case '/users/edit': - rc = EditUserPage(arg1); - break; - case '/users/delete': - rc = DeleteUserPage(arg1); - break; - case '/users/list': - rc = ListUserPage(); - break; - default: - rc = customPageByRoute(route); - break; - } - if (rc != null) { - map[route] = rc; - } - return rc; - } - - /// Returns the last generated page of a given [route]. - StatefulWidget? existingPageByRoute(String route) { - return map[route]; - } -} diff --git a/lib/page/page_collection_custom.dart b/lib/page/page_collection_custom.dart deleted file mode 100644 index d9c8dc9..0000000 --- a/lib/page/page_collection_custom.dart +++ /dev/null @@ -1,25 +0,0 @@ -// This file is created by the meta_tool. But it can be customized. -// It will never overridden by the meta_tool. -import 'package:flutter/material.dart'; -import 'info_page.dart'; -import 'log_page.dart'; - -/// Creates a page defined by a [route]. -/// -/// Note: only pages without meta data definitions should be handled here. -StatefulWidget? customPageByRoute(String route) { - StatefulWidget? rc; - final parts = route.split(';'); - //final arg1 = parts.length < 2 ? 0 : int.parse(parts[1]); - switch (parts[0]) { - case '/info': - rc = InfoPage(); - break; - case '/log': - rc = LogPage(); - break; - default: - break; - } - return rc; -} diff --git a/lib/page/page_manager.dart b/lib/page/page_manager.dart new file mode 100644 index 0000000..77d4168 --- /dev/null +++ b/lib/page/page_manager.dart @@ -0,0 +1,80 @@ +// DO NOT CHANGE. This file is created by the meta_tool! +import 'package:flutter/material.dart'; +import 'page_manager_custom.dart'; + +import 'roles/create_role_page.dart'; +import 'roles/edit_role_page.dart'; +import 'roles/list_role_page.dart'; +import 'structures/create_structure_page.dart'; +import 'structures/edit_structure_page.dart'; +import 'structures/delete_structure_page.dart'; +import 'structures/list_structure_page.dart'; +import 'users/create_user_page.dart'; +import 'users/edit_user_page.dart'; +import 'users/delete_user_page.dart'; +import 'users/list_user_page.dart'; + +/// Manages all meta data driven pages of the program. +class PageManager { + static PageManager? _instance; + PageManager.internal(); + factory PageManager() { + _instance ??= PageManager.internal(); + return _instance!; + } + final map = {}; + + /// Creates a page defined by a [route]. + StatefulWidget? newPageByRoute(String route) { + StatefulWidget? rc; + final parts = route.split(';'); + final arg1 = parts.length < 2 ? 0 : int.parse(parts[1]); + switch (parts[0]) { + case '/Roles/create': + rc = CreateRolePage(); + break; + case '/Roles/edit': + rc = EditRolePage(arg1); + break; + case '/Roles/list': + rc = ListRolePage(); + break; + case '/Structures/create': + rc = CreateStructurePage(); + break; + case '/Structures/edit': + rc = EditStructurePage(arg1); + break; + case '/Structures/delete': + rc = DeleteStructurePage(arg1); + break; + case '/Structures/list': + rc = ListStructurePage(); + break; + case '/Users/create': + rc = CreateUserPage(); + break; + case '/Users/edit': + rc = EditUserPage(arg1); + break; + case '/Users/delete': + rc = DeleteUserPage(arg1); + break; + case '/Users/list': + rc = ListUserPage(); + break; + default: + rc = customPageByRoute(route); + break; + } + if (rc != null) { + map[route] = rc; + } + return rc; + } + + /// Returns the last generated page of a given [route]. + StatefulWidget? existingPageByRoute(String route) { + return map[route]; + } +} diff --git a/lib/page/page_manager_custom.dart b/lib/page/page_manager_custom.dart new file mode 100644 index 0000000..d9c8dc9 --- /dev/null +++ b/lib/page/page_manager_custom.dart @@ -0,0 +1,25 @@ +// This file is created by the meta_tool. But it can be customized. +// It will never overridden by the meta_tool. +import 'package:flutter/material.dart'; +import 'info_page.dart'; +import 'log_page.dart'; + +/// Creates a page defined by a [route]. +/// +/// Note: only pages without meta data definitions should be handled here. +StatefulWidget? customPageByRoute(String route) { + StatefulWidget? rc; + final parts = route.split(';'); + //final arg1 = parts.length < 2 ? 0 : int.parse(parts[1]); + switch (parts[0]) { + case '/info': + rc = InfoPage(); + break; + case '/log': + rc = LogPage(); + break; + default: + break; + } + return rc; +} diff --git a/lib/page/roles/create_role_custom.dart b/lib/page/roles/create_role_custom.dart index 90fe008..4768a02 100644 --- a/lib/page/roles/create_role_custom.dart +++ b/lib/page/roles/create_role_custom.dart @@ -22,11 +22,13 @@ class CreateRoleCustom extends State { appBar: globalData.appBarBuilder(i18n.tr('Change Role data')), drawer: globalData.drawerBuilder(context), body: SafeArea( - child: WidgetForm.flexibleGrid(attendedPage!.attendedWidgets(), + child: WidgetForm.flexibleGridAttended( + attendedPage!.attendedWidgets(), screenWidth: attendedPage!.pageStates.screenWidth, padding: padding))); return rc; } + @override void initState() { super.initState(); diff --git a/lib/page/roles/create_role_page.dart b/lib/page/roles/create_role_page.dart index 4ec6c88..2cd8530 100644 --- a/lib/page/roles/create_role_page.dart +++ b/lib/page/roles/create_role_page.dart @@ -20,7 +20,7 @@ class CreateRolePage extends StatefulWidget { } class _CreateRolePageState extends CreateRoleCustom { - _CreateRolePageState(): super(); + _CreateRolePageState() : super(); @override void didChangeDependencies() { final size = MediaQuery.of(context).size; diff --git a/lib/page/roles/edit_role_custom.dart b/lib/page/roles/edit_role_custom.dart index 99b664b..5f2006d 100644 --- a/lib/page/roles/edit_role_custom.dart +++ b/lib/page/roles/edit_role_custom.dart @@ -23,11 +23,13 @@ class EditRoleCustom extends State { appBar: globalData.appBarBuilder(i18n.tr('Change Role data')), drawer: globalData.drawerBuilder(context), body: SafeArea( - child: WidgetForm.flexibleGrid(attendedPage!.attendedWidgets(), + child: WidgetForm.flexibleGridAttended( + attendedPage!.attendedWidgets(), screenWidth: attendedPage!.pageStates.screenWidth, padding: padding))); return rc; } + @override void initState() { super.initState(); diff --git a/lib/page/roles/edit_role_page.dart b/lib/page/roles/edit_role_page.dart index ef15ac4..6c32f01 100644 --- a/lib/page/roles/edit_role_page.dart +++ b/lib/page/roles/edit_role_page.dart @@ -21,7 +21,7 @@ class EditRolePage extends StatefulWidget { } class _EditRolePageState extends EditRoleCustom { - _EditRolePageState(int primaryKey): super(primaryKey); + _EditRolePageState(int primaryKey) : super(primaryKey); @override void didChangeDependencies() { final size = MediaQuery.of(context).size; diff --git a/lib/page/roles/list_role_custom.dart b/lib/page/roles/list_role_custom.dart index d85fc58..d23445c 100644 --- a/lib/page/roles/list_role_custom.dart +++ b/lib/page/roles/list_role_custom.dart @@ -22,11 +22,13 @@ class ListRoleCustom extends State { appBar: globalData.appBarBuilder(i18n.tr('Change Role data')), drawer: globalData.drawerBuilder(context), body: SafeArea( - child: WidgetForm.flexibleGrid(attendedPage!.attendedWidgets(), + child: WidgetForm.flexibleGridAttended( + attendedPage!.attendedWidgets(), screenWidth: attendedPage!.pageStates.screenWidth, padding: padding))); return rc; } + @override void initState() { super.initState(); diff --git a/lib/page/roles/list_role_page.dart b/lib/page/roles/list_role_page.dart index b42f052..0915e10 100644 --- a/lib/page/roles/list_role_page.dart +++ b/lib/page/roles/list_role_page.dart @@ -20,7 +20,7 @@ class ListRolePage extends StatefulWidget { } class _ListRolePageState extends ListRoleCustom { - _ListRolePageState(): super(); + _ListRolePageState() : super(); @override void didChangeDependencies() { final size = MediaQuery.of(context).size; diff --git a/lib/page/start_page.dart b/lib/page/start_page.dart index 71f72c6..d2876bf 100644 --- a/lib/page/start_page.dart +++ b/lib/page/start_page.dart @@ -96,7 +96,7 @@ class StartPageState extends State { RestPersistence.fromConfig(configuration, logger), logger); globalData.initializeAsync().then((value) { - globalData.navigate(context, '/users/edit'); + globalData.navigate(context, '/Users/list'); }); } }); diff --git a/lib/page/structures/create_structure_custom.dart b/lib/page/structures/create_structure_custom.dart new file mode 100644 index 0000000..a0f39d6 --- /dev/null +++ b/lib/page/structures/create_structure_custom.dart @@ -0,0 +1,36 @@ +// This file is created by the meta_tool. But it can be customized. +// It will never overridden by the meta_tool. +import 'package:flutter/material.dart'; + +import '../../base/i18n.dart'; +import '../../setting/global_data.dart'; +import '../../widget/attended_page.dart'; +import '../../widget/widget_form.dart'; +import 'create_structure_page.dart'; + +final i18n = I18N(); + +class CreateStructureCustom extends State { + final globalData = GlobalData(); + AttendedPage? attendedPage; + + CreateStructureCustom(); + @override + Widget build(BuildContext context) { + final padding = 16.0; + final rc = Scaffold( + appBar: globalData.appBarBuilder(i18n.tr('Change Structure data')), + drawer: globalData.drawerBuilder(context), + body: SafeArea( + child: WidgetForm.flexibleGridAttended( + attendedPage!.attendedWidgets(), + screenWidth: attendedPage!.pageStates.screenWidth, + padding: padding))); + return rc; + } + + @override + void initState() { + super.initState(); + } +} diff --git a/lib/page/structures/create_structure_page.dart b/lib/page/structures/create_structure_page.dart new file mode 100644 index 0000000..e27c01e --- /dev/null +++ b/lib/page/structures/create_structure_page.dart @@ -0,0 +1,31 @@ +// DO NOT CHANGE. This file is created by the meta_tool! +import 'package:flutter/material.dart'; + +import '../../meta/structures_meta.dart'; +import '../../setting/global_data.dart'; +import '../../widget/attended_page.dart'; +import 'create_structure_custom.dart'; + +class CreateStructurePage extends StatefulWidget { + final PageStates pageStates = PageStates(); + CreateStructurePage() : super(); + @override + _CreateStructurePageState createState() { + final rc = _CreateStructurePageState(); + rc.attendedPage = AttendedPage( + this, rc, GlobalData(), StructureMeta.instance, pageStates); + pageStates.attendedPage = rc.attendedPage; + return rc; + } +} + +class _CreateStructurePageState extends CreateStructureCustom { + _CreateStructurePageState() : super(); + @override + void didChangeDependencies() { + final size = MediaQuery.of(context).size; + attendedPage!.pageStates.screenWidth = size.width; + attendedPage!.pageStates.screenHeight = size.height; + super.didChangeDependencies(); + } +} diff --git a/lib/page/structures/delete_structure_custom.dart b/lib/page/structures/delete_structure_custom.dart new file mode 100644 index 0000000..c6b9ba0 --- /dev/null +++ b/lib/page/structures/delete_structure_custom.dart @@ -0,0 +1,37 @@ +// This file is created by the meta_tool. But it can be customized. +// It will never overridden by the meta_tool. +import 'package:flutter/material.dart'; + +import '../../base/i18n.dart'; +import '../../setting/global_data.dart'; +import '../../widget/attended_page.dart'; +import '../../widget/widget_form.dart'; +import 'delete_structure_page.dart'; + +final i18n = I18N(); + +class DeleteStructureCustom extends State { + final int primaryKey; + final globalData = GlobalData(); + AttendedPage? attendedPage; + + DeleteStructureCustom(this.primaryKey); + @override + Widget build(BuildContext context) { + final padding = 16.0; + final rc = Scaffold( + appBar: globalData.appBarBuilder(i18n.tr('Change Structure data')), + drawer: globalData.drawerBuilder(context), + body: SafeArea( + child: WidgetForm.flexibleGridAttended( + attendedPage!.attendedWidgets(), + screenWidth: attendedPage!.pageStates.screenWidth, + padding: padding))); + return rc; + } + + @override + void initState() { + super.initState(); + } +} diff --git a/lib/page/structures/delete_structure_page.dart b/lib/page/structures/delete_structure_page.dart new file mode 100644 index 0000000..cc888a8 --- /dev/null +++ b/lib/page/structures/delete_structure_page.dart @@ -0,0 +1,32 @@ +// DO NOT CHANGE. This file is created by the meta_tool! +import 'package:flutter/material.dart'; + +import '../../meta/structures_meta.dart'; +import '../../setting/global_data.dart'; +import '../../widget/attended_page.dart'; +import 'delete_structure_custom.dart'; + +class DeleteStructurePage extends StatefulWidget { + final int primaryKey; + final PageStates pageStates = PageStates(); + DeleteStructurePage(this.primaryKey) : super(); + @override + _DeleteStructurePageState createState() { + final rc = _DeleteStructurePageState(this.primaryKey); + rc.attendedPage = AttendedPage( + this, rc, GlobalData(), StructureMeta.instance, pageStates); + pageStates.attendedPage = rc.attendedPage; + return rc; + } +} + +class _DeleteStructurePageState extends DeleteStructureCustom { + _DeleteStructurePageState(int primaryKey) : super(primaryKey); + @override + void didChangeDependencies() { + final size = MediaQuery.of(context).size; + attendedPage!.pageStates.screenWidth = size.width; + attendedPage!.pageStates.screenHeight = size.height; + super.didChangeDependencies(); + } +} diff --git a/lib/page/structures/edit_structure_custom.dart b/lib/page/structures/edit_structure_custom.dart new file mode 100644 index 0000000..3f2aa2a --- /dev/null +++ b/lib/page/structures/edit_structure_custom.dart @@ -0,0 +1,37 @@ +// This file is created by the meta_tool. But it can be customized. +// It will never overridden by the meta_tool. +import 'package:flutter/material.dart'; + +import '../../base/i18n.dart'; +import '../../setting/global_data.dart'; +import '../../widget/attended_page.dart'; +import '../../widget/widget_form.dart'; +import 'edit_structure_page.dart'; + +final i18n = I18N(); + +class EditStructureCustom extends State { + final int primaryKey; + final globalData = GlobalData(); + AttendedPage? attendedPage; + + EditStructureCustom(this.primaryKey); + @override + Widget build(BuildContext context) { + final padding = 16.0; + final rc = Scaffold( + appBar: globalData.appBarBuilder(i18n.tr('Change Structure data')), + drawer: globalData.drawerBuilder(context), + body: SafeArea( + child: WidgetForm.flexibleGridAttended( + attendedPage!.attendedWidgets(), + screenWidth: attendedPage!.pageStates.screenWidth, + padding: padding))); + return rc; + } + + @override + void initState() { + super.initState(); + } +} diff --git a/lib/page/structures/edit_structure_page.dart b/lib/page/structures/edit_structure_page.dart new file mode 100644 index 0000000..a689d34 --- /dev/null +++ b/lib/page/structures/edit_structure_page.dart @@ -0,0 +1,32 @@ +// DO NOT CHANGE. This file is created by the meta_tool! +import 'package:flutter/material.dart'; + +import '../../meta/structures_meta.dart'; +import '../../setting/global_data.dart'; +import '../../widget/attended_page.dart'; +import 'edit_structure_custom.dart'; + +class EditStructurePage extends StatefulWidget { + final int primaryKey; + final PageStates pageStates = PageStates(); + EditStructurePage(this.primaryKey) : super(); + @override + _EditStructurePageState createState() { + final rc = _EditStructurePageState(this.primaryKey); + rc.attendedPage = AttendedPage( + this, rc, GlobalData(), StructureMeta.instance, pageStates); + pageStates.attendedPage = rc.attendedPage; + return rc; + } +} + +class _EditStructurePageState extends EditStructureCustom { + _EditStructurePageState(int primaryKey) : super(primaryKey); + @override + void didChangeDependencies() { + final size = MediaQuery.of(context).size; + attendedPage!.pageStates.screenWidth = size.width; + attendedPage!.pageStates.screenHeight = size.height; + super.didChangeDependencies(); + } +} diff --git a/lib/page/structures/list_structure_custom.dart b/lib/page/structures/list_structure_custom.dart new file mode 100644 index 0000000..3bf1b62 --- /dev/null +++ b/lib/page/structures/list_structure_custom.dart @@ -0,0 +1,36 @@ +// This file is created by the meta_tool. But it can be customized. +// It will never overridden by the meta_tool. +import 'package:flutter/material.dart'; + +import '../../base/i18n.dart'; +import '../../setting/global_data.dart'; +import '../../widget/attended_page.dart'; +import '../../widget/widget_form.dart'; +import 'list_structure_page.dart'; + +final i18n = I18N(); + +class ListStructureCustom extends State { + final globalData = GlobalData(); + AttendedPage? attendedPage; + + ListStructureCustom(); + @override + Widget build(BuildContext context) { + final padding = 16.0; + final rc = Scaffold( + appBar: globalData.appBarBuilder(i18n.tr('Change Structure data')), + drawer: globalData.drawerBuilder(context), + body: SafeArea( + child: WidgetForm.flexibleGridAttended( + attendedPage!.attendedWidgets(), + screenWidth: attendedPage!.pageStates.screenWidth, + padding: padding))); + return rc; + } + + @override + void initState() { + super.initState(); + } +} diff --git a/lib/page/structures/list_structure_page.dart b/lib/page/structures/list_structure_page.dart new file mode 100644 index 0000000..55c901c --- /dev/null +++ b/lib/page/structures/list_structure_page.dart @@ -0,0 +1,31 @@ +// DO NOT CHANGE. This file is created by the meta_tool! +import 'package:flutter/material.dart'; + +import '../../meta/structures_meta.dart'; +import '../../setting/global_data.dart'; +import '../../widget/attended_page.dart'; +import 'list_structure_custom.dart'; + +class ListStructurePage extends StatefulWidget { + final PageStates pageStates = PageStates(); + ListStructurePage() : super(); + @override + _ListStructurePageState createState() { + final rc = _ListStructurePageState(); + rc.attendedPage = AttendedPage( + this, rc, GlobalData(), StructureMeta.instance, pageStates); + pageStates.attendedPage = rc.attendedPage; + return rc; + } +} + +class _ListStructurePageState extends ListStructureCustom { + _ListStructurePageState() : super(); + @override + void didChangeDependencies() { + final size = MediaQuery.of(context).size; + attendedPage!.pageStates.screenWidth = size.width; + attendedPage!.pageStates.screenHeight = size.height; + super.didChangeDependencies(); + } +} diff --git a/lib/page/users/create_user_custom.dart b/lib/page/users/create_user_custom.dart index f6f70af..48a581e 100644 --- a/lib/page/users/create_user_custom.dart +++ b/lib/page/users/create_user_custom.dart @@ -22,11 +22,13 @@ class CreateUserCustom extends State { appBar: globalData.appBarBuilder(i18n.tr('Change User data')), drawer: globalData.drawerBuilder(context), body: SafeArea( - child: WidgetForm.flexibleGrid(attendedPage!.attendedWidgets(), + child: WidgetForm.flexibleGridAttended( + attendedPage!.attendedWidgets(), screenWidth: attendedPage!.pageStates.screenWidth, padding: padding))); return rc; } + @override void initState() { super.initState(); diff --git a/lib/page/users/create_user_page.dart b/lib/page/users/create_user_page.dart index 14e69e2..71a2c05 100644 --- a/lib/page/users/create_user_page.dart +++ b/lib/page/users/create_user_page.dart @@ -20,7 +20,7 @@ class CreateUserPage extends StatefulWidget { } class _CreateUserPageState extends CreateUserCustom { - _CreateUserPageState(): super(); + _CreateUserPageState() : super(); @override void didChangeDependencies() { final size = MediaQuery.of(context).size; diff --git a/lib/page/users/delete_user_custom.dart b/lib/page/users/delete_user_custom.dart index d36681a..7fa7866 100644 --- a/lib/page/users/delete_user_custom.dart +++ b/lib/page/users/delete_user_custom.dart @@ -23,11 +23,13 @@ class DeleteUserCustom extends State { appBar: globalData.appBarBuilder(i18n.tr('Change User data')), drawer: globalData.drawerBuilder(context), body: SafeArea( - child: WidgetForm.flexibleGrid(attendedPage!.attendedWidgets(), + child: WidgetForm.flexibleGridAttended( + attendedPage!.attendedWidgets(), screenWidth: attendedPage!.pageStates.screenWidth, padding: padding))); return rc; } + @override void initState() { super.initState(); diff --git a/lib/page/users/delete_user_page.dart b/lib/page/users/delete_user_page.dart index 85cbfc9..8bbef4c 100644 --- a/lib/page/users/delete_user_page.dart +++ b/lib/page/users/delete_user_page.dart @@ -21,7 +21,7 @@ class DeleteUserPage extends StatefulWidget { } class _DeleteUserPageState extends DeleteUserCustom { - _DeleteUserPageState(int primaryKey): super(primaryKey); + _DeleteUserPageState(int primaryKey) : super(primaryKey); @override void didChangeDependencies() { final size = MediaQuery.of(context).size; diff --git a/lib/page/users/edit_user_custom.dart b/lib/page/users/edit_user_custom.dart index 3e5f5a3..d4ab150 100644 --- a/lib/page/users/edit_user_custom.dart +++ b/lib/page/users/edit_user_custom.dart @@ -23,11 +23,13 @@ class EditUserCustom extends State { appBar: globalData.appBarBuilder(i18n.tr('Change User data')), drawer: globalData.drawerBuilder(context), body: SafeArea( - child: WidgetForm.flexibleGrid(attendedPage!.attendedWidgets(), + child: WidgetForm.flexibleGridAttended( + attendedPage!.attendedWidgets(), screenWidth: attendedPage!.pageStates.screenWidth, padding: padding))); return rc; } + @override void initState() { super.initState(); diff --git a/lib/page/users/edit_user_page.dart b/lib/page/users/edit_user_page.dart index 1179663..4b2e75f 100644 --- a/lib/page/users/edit_user_page.dart +++ b/lib/page/users/edit_user_page.dart @@ -21,7 +21,7 @@ class EditUserPage extends StatefulWidget { } class _EditUserPageState extends EditUserCustom { - _EditUserPageState(int primaryKey): super(primaryKey); + _EditUserPageState(int primaryKey) : super(primaryKey); @override void didChangeDependencies() { final size = MediaQuery.of(context).size; diff --git a/lib/page/users/list_user_custom.dart b/lib/page/users/list_user_custom.dart index b3e40b8..c90b9ec 100644 --- a/lib/page/users/list_user_custom.dart +++ b/lib/page/users/list_user_custom.dart @@ -12,21 +12,45 @@ final i18n = I18N(); class ListUserCustom extends State { final globalData = GlobalData(); + final fieldData = FieldData(); AttendedPage? attendedPage; - + final textController = TextEditingController(); ListUserCustom(); @override Widget build(BuildContext context) { final padding = 16.0; - final filters = WidgetForm.flexibleGrid(attendedPage!.attendedWidgets(), - screenWidth: attendedPage!.pageStates.screenWidth, padding: padding); + final itemsRole = >[]; + final fieldWidgets = [ + TextFormField( + controller: textController, + decoration: InputDecoration(labelText: i18n.tr('Text')), + onChanged: (value) => fieldData.text = value, + ), + DropdownButtonFormField( + value: fieldData.role, + items: itemsRole, + isExpanded: true, + decoration: InputDecoration(labelText: i18n.tr('Role')), + onChanged: (value) => fieldData.role = value ?? 0, + ), + ]; + final weights = [6, 6]; + final form = WidgetForm.flexibleGrid(fieldWidgets, + weights: weights, + screenWidth: attendedPage!.pageStates.screenWidth, + padding: padding); final rows = attendedPage!.getRows( - 'user_id;user_displayname;user_email;role', - '/users/list', - {}, - () => setState(() => 1), - '/users/edit', - context); + columnList: 'user_id;user_displayname;user_email;role', + what: 'query', + parameters: { + 'module': 'Users', + 'sql': 'list', + 'offset': '0', + 'size': '10' + }, + onDone: () => setState(() => 1), + routeEdit: '/users/edit', + context: context); final table = DataTable( columns: [ DataColumn( @@ -45,13 +69,20 @@ class ListUserCustom extends State { rows: rows, ); final frameWidget = Column(children: [ - filters, + form, SizedBox(height: padding), SizedBox(width: double.infinity, child: table), ]); final rc = Scaffold( appBar: globalData.appBarBuilder(i18n.tr('Change User data')), drawer: globalData.drawerBuilder(context), + floatingActionButton: FloatingActionButton( + onPressed: () { + globalData.navigate(context, '/Users/create'); + }, + child: const Icon(Icons.add), + //backgroundColor: Colors.green, + ), body: SafeArea(child: frameWidget)); return rc; } @@ -61,3 +92,8 @@ class ListUserCustom extends State { super.initState(); } } + +class FieldData { + String text = ''; + int role = 0; +} diff --git a/lib/page/users/list_user_page.dart b/lib/page/users/list_user_page.dart index 1938d3d..2723f48 100644 --- a/lib/page/users/list_user_page.dart +++ b/lib/page/users/list_user_page.dart @@ -20,7 +20,7 @@ class ListUserPage extends StatefulWidget { } class _ListUserPageState extends ListUserCustom { - _ListUserPageState(): super(); + _ListUserPageState() : super(); @override void didChangeDependencies() { final size = MediaQuery.of(context).size; diff --git a/lib/persistence/file_persistence.dart b/lib/persistence/file_persistence.dart index 9637e75..2159ee3 100644 --- a/lib/persistence/file_persistence.dart +++ b/lib/persistence/file_persistence.dart @@ -40,11 +40,11 @@ class FilePersistence extends Persistence { } else { final content = await file.readAsString(); final data = convert.jsonDecode(content); - if (data is Map){ + if (data is Map) { rc = DbData.single(data as JsonMap); - } else if (data is List){ - rc = DbData.list(data as JsonList, -1); - } else if (data is String){ + } else if (data is List) { + rc = DbData.list(data, -1); + } else if (data is String) { rc = DbData.message(data); } else { rc = DbData.message('unknown data type: ${data.runtimeType}'); diff --git a/lib/persistence/persistence.dart b/lib/persistence/persistence.dart index 39949ef..cf7794a 100644 --- a/lib/persistence/persistence.dart +++ b/lib/persistence/persistence.dart @@ -8,10 +8,11 @@ class DbData { final int? count; final String? message; DbData(this.singleRecord, this.recordList, this.count, this.message); - DbData.single(JsonMap record): this(record, null, null, null); - DbData.list(JsonList records, int count): this(null, records, count, null); - DbData.message(String message): this(null, null, null, message); + DbData.single(JsonMap record) : this(record, null, null, null); + DbData.list(JsonList records, int count) : this(null, records, count, null); + DbData.message(String message) : this(null, null, null, message); } + abstract class Persistence { /// Fetches data specified by [what] and some [data]. /// diff --git a/lib/persistence/rest_persistence.dart b/lib/persistence/rest_persistence.dart index a00a6b7..b870ed6 100644 --- a/lib/persistence/rest_persistence.dart +++ b/lib/persistence/rest_persistence.dart @@ -4,6 +4,7 @@ import 'dart:math'; import 'package:dart_bones/dart_bones.dart'; import 'package:http/http.dart' as http; +import '../../base/defines.dart'; import 'data_record.dart'; import 'persistence.dart'; import '../setting/error_handler_exhibition.dart'; @@ -52,7 +53,7 @@ class RestPersistence extends Persistence { {String? body, Map? headers}) async { var rc = ''; final uri = - Uri(scheme: scheme, host: host, port: port, path: '/$what/$version'); + Uri(scheme: scheme, host: host, port: port, path: '$what/$version'); http.Response response; logger.log('request: POST $uri', LEVEL_LOOP); try { @@ -99,24 +100,24 @@ class RestPersistence extends Persistence { DbData rc; final params2 = data == null ? '{}' : convert.jsonEncode(data); final answer = await runRequest(what, body: params2, headers: jsonHeader); - if (answer.isEmpty){ + if (answer.isEmpty) { rc = DbData.message('query(): empty result'); - } else if (answer.startsWith('{')){ + } else if (answer.startsWith('{')) { rc = DbData.single(convert.jsonDecode(answer)); } else if (answer.startsWith('[')) { rc = DbData.list(convert.jsonDecode(answer), -1); } else if (answer.startsWith('#')) { int ix = answer.indexOf('['); - if (ix > 2 && ix < 8){ + if (ix >= 2 && ix < 8) { final count = int.tryParse(answer.substring(1, ix)); - rc = DbData.list(convert.jsonDecode(answer.substring(ix)), count ?? -1); + final data = convert.jsonDecode(answer.substring(ix)); + rc = DbData.list(data as JsonList, count ?? -1); } else { - rc = DbData.message( - 'query(): unexpected answer prefix: ' + rc = DbData.message('query(): unexpected answer prefix: ' '${answer.substring(0, min(answer.length, 8))}'); } } else { - rc = DbData.message(answer.substring(0, max(answer.length, 120))); + rc = DbData.message(answer.substring(0, max(answer.length, 120))); } if (logger.logLevel >= LEVEL_FINE) { logger.log('answer: ${limitString(answer, sqlTraceLimit)}'); diff --git a/lib/setting/drawer_exhibition.dart b/lib/setting/drawer_exhibition.dart index 49822ef..d8a156d 100644 --- a/lib/setting/drawer_exhibition.dart +++ b/lib/setting/drawer_exhibition.dart @@ -1,5 +1,5 @@ import 'package:dart_bones/dart_bones.dart'; -import 'package:exhibition/page/page_collection.dart'; +import 'package:exhibition/page/page_manager.dart'; import 'package:flutter/material.dart'; import 'global_data.dart'; @@ -112,7 +112,7 @@ class MenuItem { static List menuItems(MenuConverter converter) { final globalData = GlobalData(); final logger = globalData.logger; - final collection = PageCollection(); + final collection = PageManager(); return [ MenuItem('Users', () => collection.newPageByRoute('/users/list'), converter.iconByName('people_outlined', logger)), diff --git a/lib/setting/global_data.dart b/lib/setting/global_data.dart index 1d8f79b..e470cb0 100644 --- a/lib/setting/global_data.dart +++ b/lib/setting/global_data.dart @@ -1,9 +1,13 @@ +import 'dart:io'; import 'package:dart_bones/dart_bones.dart'; -import 'package:exhibition/page/page_collection.dart'; +import 'package:exhibition/page/page_manager.dart'; import 'package:exhibition/persistence/rest_persistence.dart'; import 'package:exhibition/widget/attended_page.dart'; import 'package:flutter/material.dart'; +import 'package:path/path.dart' as path; +import 'package:path_provider/path_provider.dart'; +import '../base/application_name.dart'; import '../base/defines.dart'; import '../page/page_controller_exhibition.dart'; @@ -27,19 +31,37 @@ abstract class FooterInterface { Widget widget(PageControllerExhibition controller); } +class HomeDirectories { + Directory configurations = Directory.current; + Directory localData = Directory.current; + HomeDirectories.dummy(); + HomeDirectories(String applicationName) { + if (Platform.isLinux) { + configurations = Directory('/etc/$applicationName'); + localData = Directory(path.join('/home', Platform.environment['LOGNAME'], + '.local/share/$applicationName')); + } else if (Platform.isAndroid) { + getExternalStorageDirectory().then((dir) => localData = dir!); + } else { + throw FormatException('HomeDirectory.getHomePath(): unknown platform'); + } + } +} + /// Storage for global resources. This is a singleton. class GlobalData { static const version = '2021.08.24.00'; static GlobalData? _instance; static String baseDirectory = ''; static var serverEnvironment = ServerEnvironment.productive; - + static final String applicationName = theApplicationName; final BaseLogger logger; final AppBarBuilder appBarBuilder; final DrawerBuilder drawerBuilder; final FooterBuilder footerBuilder; final BaseConfiguration configuration; final RestPersistence? restPersistence; + HomeDirectories homeDirectories = HomeDirectories.dummy(); AttendedPage? currentPage; factory GlobalData() => _instance ?? GlobalData(); @@ -67,7 +89,7 @@ class GlobalData { /// Switches the page given by a [route]. void navigate(BuildContext context, String route) { - final page = PageCollection().existingPageByRoute(route); + final page = PageManager().existingPageByRoute(route); if (page != null) { currentPage = page as AttendedPage; } diff --git a/lib/setting/installation.dart b/lib/setting/installation.dart index 68ae7d4..f4bf2fe 100644 --- a/lib/setting/installation.dart +++ b/lib/setting/installation.dart @@ -2,11 +2,13 @@ import 'dart:async'; import 'dart:convert' as convert; import 'dart:io'; +import 'package:yaml/yaml.dart'; import 'package:dart_bones/dart_bones.dart'; import 'package:path/path.dart' as path; import 'package:permission_handler/permission_handler.dart'; import 'global_data.dart'; +import '../base/application_name.dart'; import '../base/defines.dart'; import '../base/i18n.dart'; @@ -19,6 +21,8 @@ class Installation { static Installation? instance; static ServerEnvironment serverEnvironment = ServerEnvironment.productive; static int retries = 1; + static final homeDirectory = HomeDirectories(theApplicationName); + final neededPermissions = [ Permission.camera, Permission.location, @@ -52,7 +56,8 @@ class Installation { if (GlobalData.baseDirectory.isEmpty) { GlobalData.baseDirectory = home.path; } - configurationFile = File(path.join(home.path, 'exhibition.json')); + configurationFile = File( + path.join(homeDirectory.configurations.path, '$theVariantName.yaml')); if (!(forceInit || !configurationFile.existsSync() || !hasValidConfiguration())) { @@ -130,7 +135,7 @@ class Installation { final fileContent = configurationFile.readAsStringSync(); var rc = true; try { - final map = convert.jsonDecode(fileContent); + final map = loadYaml(fileContent); final config = BaseConfiguration(map, logger); final section = enumToString(serverEnvironment); if (config.asString('host', section: section) == null || @@ -149,9 +154,10 @@ class Installation { return rc; } - /// Prüft, ob eine gültige Datei mit den Bienenstockdaten existiert. - /// Liefert true, wenn ja. - bool hasValidHives() { + /// Checks whether there is a valid structures file. + /// + // / Returns true on success. + bool hasValidStructures() { final fileContent = hivesFile.readAsStringSync(); final filename = hivesFile.path; var rc = false; @@ -159,43 +165,42 @@ class Installation { final list = convert.jsonDecode(fileContent); rc = list is Iterable; if (!rc) { - logger.error('$filename: "hives" ist kein Array'); + logger.error('$filename: "structures" is not an array'); } else { var ix = -1; for (var item in list) { ix++; rc = item is Map; if (!rc) { - logger.error('$filename: hives[$ix] ist keine Map'); + logger.error('$filename: structures[$ix] is not a map'); } else { final map2 = item; - rc = (map2.containsKey('hiveid') && - map2.containsKey('name') && - map2.containsKey('lat') && - map2.containsKey('long')); + rc = (map2.containsKey('structure_scope') && + map2.containsKey('structure_name') && + map2.containsKey('structure_value')); if (!rc) { - logger.error('$filename: Fehler in hives[$ix]'); + logger.error('$filename: Error in structures[$ix]'); break; } } } } } on FormatException catch (exc) { - logger.error('$filename: kein JSon: $exc'); + logger.error('$filename: not a Json file: $exc'); } return rc; } - /// Liefert den Namen des Rechts [permission]. + /// Returns the name of [permission]. static String nameOfPermission(Permission permission) { var rc; if (permission == Permission.storage) { - rc = 'Speicher'; + rc = i18n.tr('Storage'); } else if (permission == Permission.camera) { - rc = 'Kamera'; + rc = i18n.tr('Camera'); } else { if (permission == Permission.location) { - rc = 'Geo-Position'; + rc = i18n.tr('Geo position'); } else { rc = 'Permission-${permission.value}'; } @@ -203,30 +208,30 @@ class Installation { return rc; } - /// Liefert den Namen des [status]. + /// Returns the human readable name of the [status]. static String nameOfStatus(PermissionStatus status) { String rc; switch (status) { case PermissionStatus.granted: - rc = 'erlaubt'; + rc = i18n.tr('granted'); break; case PermissionStatus.denied: - rc = 'verweigert'; + rc = i18n.tr('denied'); break; case PermissionStatus.restricted: - rc = 'eingeschränkt'; + rc = i18n.tr('restricted'); break; case PermissionStatus.limited: - rc = 'begrenzt'; + rc = i18n.tr('limited'); break; case PermissionStatus.permanentlyDenied: - rc = 'gesperrt'; + rc = i18n.tr('locked'); break; } return rc; } - /// Initialisiert die einzige Instanz (Singleton). + /// Initializes the singleton instance. static Future initialize(BaseLogger logger) async { instance = Installation.internal(logger); } diff --git a/lib/setting/structure.dart b/lib/setting/structure.dart new file mode 100644 index 0000000..79f25f2 --- /dev/null +++ b/lib/setting/structure.dart @@ -0,0 +1,90 @@ +import 'dart:convert' as convert; +import 'dart:io'; + +import 'package:exhibition/setting/global_data.dart'; + +/// Defines an ([name], [value]) pair, grouped by a [scope] and ordered inside +/// the scope by a [position]. +/// +/// Example: {'Status', 'Active', 'A', 1} and {'Status', 'Inactive', 'S', 2} +/// represent a set of status information. +class Structure { + final String scope; + final String name; + final String value; + final int position; + Structure( + {required this.scope, + required this.name, + required this.value, + this.position = 0}); +} + +/// Manages a list of [Structure] items. +class Structures { + static Structures? _instance; + GlobalData? globalData; + final structures = []; + final scopes = >{}; + + /// Returns the singleton instance. + factory Structures() => _instance!; + + /// Builds the singleton instance with items from a JSon file. + /// + /// The file named [filename] contains the items in a JSon format with + /// names given by the database table structures. + Structures.fromFile(String filename) { + this.globalData = GlobalData(); + final file = File(filename); + if (!file.existsSync()) { + globalData!.logger.error('cannot read structures from $filename'); + } else { + final content = file.readAsStringSync(); + final data = convert.jsonDecode(content); + for (var record in data) { + structures.add(Structure( + scope: record['structure_scope'], + name: record['structure_name'], + value: record['structure_value'], + position: int.tryParse(record['structure_position']) ?? 0)); + } + initialize(); + } + } + + /// Returns the [Structure] items belonging to a given [scope]. + List? byScope(String scope) { + final rc = scopes[scope]; + return rc; + } + + /// Initializes the internal structures. + /// + /// Should be called after the constructor. + void initialize() { + for (var structure in structures) { + if (scopes.containsKey(structure.scope)) { + scopes[structure.scope]!.add(structure); + scopes[structure.scope]!.sort((a, b) => a.position == b.position + ? a.name.compareTo(b.name) + : a.position - b.position); + } else { + scopes[structure.scope] = [structure]; + } + } + } + + /// Returns the value of a Structure given by [scope] and [name]. + String valueOf(String scope, String name) { + final list = scopes[scope]; + String rc = ''; + if (list != null) { + rc = list + .firstWhere((element) => element.name == name, + orElse: () => list.first) + .value; + } + return rc; + } +} diff --git a/lib/widget/attended_page.dart b/lib/widget/attended_page.dart index de14e2a..fa19c31 100644 --- a/lib/widget/attended_page.dart +++ b/lib/widget/attended_page.dart @@ -1,9 +1,10 @@ -import 'package:exhibition/persistence/persistence.dart'; -import 'package:exhibition/widget/attended_widget.dart'; import 'package:flutter/material.dart'; +import 'package:synchronized/synchronized.dart'; import '../../base/i18n.dart'; +import '../../persistence/persistence.dart'; import '../../setting/global_data.dart'; +import '../../widget/attended_widget.dart'; import '../meta/module_meta_data.dart'; final i18n = I18N(); @@ -51,30 +52,38 @@ class AttendedPage { /// database records in a [DataTable]. /// /// [columnList]: a ';' delimited list of column names, e.g. 'user_id;user_name' - List getRows(String columnList, String what, - Map parameters, Function() onDone, String routeEdit, BuildContext context) { + List getRows( + {required String columnList, + required String what, + required Map parameters, + required Function() onDone, + required String routeEdit, + required BuildContext context}) { List? rc; - if (!pageStates.openQuery) { - pageStates.openQuery = true; - globalData.restPersistence! - .query(what: what, data: parameters) - .then((value) { - pageStates.dbData = value; - pageStates.openQuery = false; - onDone(); - }); - } else if (pageStates.dbData != null) { - if (pageStates.dbData?.recordList != null) { + String name = 'rows'; + if (!pageStates.dbDataState.hasEntry(name)) { + pageStates.dbDataState.addRequest(name).then((_) => globalData + .restPersistence! + .query(what: what, data: parameters) + .then((value) { + pageStates.dbDataState + .storeDbResult(name, value) + .then((_) => onDone()); + })); + } else { + final dbData = pageStates.dbDataState.dataOf(name); + if (dbData != null && dbData.recordList != null) { rc = []; - pageStates.dbData?.recordList!.map((record) { + dbData.recordList!.forEach((record) { final cells = []; - for (var column in columnList.split(';')){ - final primaryKey = moduleMetaData.primaryOf(); + for (var column in columnList.split(';')) { + final primaryKey = moduleMetaData.primaryOf()?.columnName; cells.add(DataCell(Text(record[column].toString()), - onTap: () => globalData.navigate(context, routeEdit + ';${record[primaryKey]}'))); + onTap: () => globalData.navigate( + context, routeEdit + ';${record[primaryKey]}'))); } final rc3 = DataRow(cells: cells); - return rc3; + rc!.add(rc3); }); } } @@ -96,6 +105,58 @@ class AttendedPage { } } +/// Manages multiple asynchronous database request from the backend. +/// +/// This class can handle multiple request: Therefore each request needs a +/// unique name to allow the access. +class DbDataState { + final lock = Lock(); + final dbDataMap = {}; + int expectedCount = 0; + int currentCount = 0; + + /// Adds a database request to the instance. + /// + /// [name] is a (over all requests) unique name to identify the request. + /// It is needed to fetch the db data. + Future addRequest(String name) async { + await lock.synchronized(() async { + dbDataMap[name] = null; + ++expectedCount; + }); + } + + /// Removes all current requests. + void clear() { + dbDataMap.clear(); + expectedCount = currentCount = 0; + } + + /// Returns the DB data given by the [name]. + DbData? dataOf(String name) => dbDataMap[name]; + + /// Tests whether the map contains an entry with [name]. + bool hasEntry(String name) => dbDataMap.containsKey(name); + + /// Tests whether all requests are answered. + /// + /// Return true, if all answers of the requested DB data are stored in + /// [dbDataMap]. + bool isWaiting() => currentCount < expectedCount; + + /// Stores the DB data fetched from the backend. + /// + /// [name] is a (over all requests) unique name to identify the request. + /// + /// [data] is the result of a RestPersistence.query() call. + Future storeDbResult(String name, DbData data) async { + await lock.synchronized(() async { + dbDataMap[name] = data; + ++currentCount; + }); + } +} + enum DeliveryState { undef, waiting, delivered } class PageStates { @@ -103,6 +164,6 @@ class PageStates { double screenWidth = 0; double screenHeight = 0; AttendedPage? attendedPage; - DbData? dbData; - bool openQuery = false; + final dbDataState = DbDataState(); + dynamic customData; } diff --git a/lib/widget/attended_stateful.dart b/lib/widget/attended_stateful.dart new file mode 100644 index 0000000..dbadd10 --- /dev/null +++ b/lib/widget/attended_stateful.dart @@ -0,0 +1,6 @@ +import 'package:flutter/material.dart'; +import 'attended_page.dart'; + +abstract class AttendedStateful extends StatefulWidget { + final PageStates pageStates = PageStates(); +} diff --git a/lib/widget/widget_form.dart b/lib/widget/widget_form.dart index 7b6cdab..b820fae 100644 --- a/lib/widget/widget_form.dart +++ b/lib/widget/widget_form.dart @@ -16,29 +16,28 @@ class WidgetForm { /// [minWidth] is the minimum width of the above defined segment. /// If the [screenwidth] has not place for 6 segments all widgets are placed /// in a single row. - static Widget flexibleGrid(List widgets, - {required double screenWidth, + static Widget flexibleGrid(List widgets, + {required List weights, + required double screenWidth, double minWidth = 800, double padding = 16.0}) { Widget rc; + assert(widgets.length == weights.length); if (minWidth > screenWidth) { - final children = widgets.map((element) => element.widgetOf()).toList(); - rc = Column(children: children); + rc = Column(children: widgets); } else { int position = 0; final List childrenColumn = []; List childrenRow = []; - for (var widget in widgets) { - final flex = widget.propertyMetaData.weight; + for (var ix = 0; ix < widgets.length; ix++) { + final widget = widgets[ix]; + final flex = weights[ix]; if (position + flex <= 12) { var child2 = position == 0 - ? widget.widgetOf() + ? widget : Row( mainAxisAlignment: MainAxisAlignment.end, - children: [ - SizedBox(width: padding), - Expanded(child: widget.widgetOf()) - ], + children: [SizedBox(width: padding), Expanded(child: widget)], ); childrenRow.add(Expanded(flex: flex, child: child2)); position += flex; @@ -51,12 +50,9 @@ class WidgetForm { childrenColumn.add(Row(children: childrenRow)); childrenRow = []; final child2 = position == 0 - ? widget.widgetOf() + ? widget : Row( - children: [ - SizedBox(width: padding), - Expanded(child: widget.widgetOf()) - ], + children: [SizedBox(width: padding), Expanded(child: widget)], ); childrenRow.add(Expanded(flex: flex, child: child2)); position = flex; @@ -71,4 +67,30 @@ class WidgetForm { } return rc; } + + /// Places the attended widgets in a flexible grid. + /// + /// Returns a column of rows. + /// + /// The place of the row is divided in 12 segments with the same width. + /// Each widget has a "weight" which means the count of segments to use. + /// + /// [widgets] is the list of attended widgets to position in the grid. + /// + /// [screenWidth] is the width of the output device in pixel. + /// + /// [minWidth] is the minimum width of the above defined segment. + /// If the [screenwidth] has not place for 6 segments all widgets are placed + /// in a single row. + static Widget flexibleGridAttended(List widgets, + {required double screenWidth, + double minWidth = 800, + double padding = 16.0}) { + final weights = + widgets.map((element) => element.propertyMetaData.weight).toList(); + final widgets2 = widgets.map((element) => element.widgetOf()).toList(); + final rc = + flexibleGrid(widgets2, weights: weights, screenWidth: screenWidth); + return rc; + } } diff --git a/metatool/bin/page_generator.dart b/metatool/bin/page_generator.dart index 34ba5f2..369b373 100644 --- a/metatool/bin/page_generator.dart +++ b/metatool/bin/page_generator.dart @@ -78,18 +78,18 @@ DEF_PRIMARY final globalData = GlobalData(); } } '''; - static final templateCollection = '''// DO NOT CHANGE. This file is created by the meta_tool! + static final templatePageManager = '''// DO NOT CHANGE. This file is created by the meta_tool! import 'package:flutter/material.dart'; -import 'page_collection_custom.dart'; +import 'page_manager_custom.dart'; #IMPORTS# /// Manages all meta data driven pages of the program. -class PageCollection { - static PageCollection? _instance; - PageCollection.internal(); - factory PageCollection() { - _instance ??= PageCollection.internal(); +class PageManager { + static PageManager? _instance; + PageManager.internal(); + factory PageManager() { + _instance ??= PageManager.internal(); return _instance!; } final map = {}; @@ -116,7 +116,7 @@ class PageCollection { } } '''; - static final templateCollectionCustom = '''// This file is created by the meta_tool. But it can be customized. + static final templatePageManagerCustom = '''// This file is created by the meta_tool. But it can be customized. // It will never overridden by the meta_tool. import 'package:flutter/material.dart'; import 'info_page.dart'; @@ -142,7 +142,7 @@ StatefulWidget? customPageByRoute(String route) { return rc; } '''; - static final templateCase = ''' case '/users/edit': + static final templateCase = ''' case '/Users/edit': rc = EditUserPage(ARG1); break; '''; @@ -191,9 +191,9 @@ StatefulWidget? customPageByRoute(String route) { return rc; } - /// Creates the file page_collection.dart and (if it does not exist) the file - /// page_collection_custom.dart. - void updateCollection(){ + /// Creates the file page_manager.dart and (if it does not exist) the file + /// page_manager_custom.dart. + void updatePageManager(){ final modules = moduleNames(); final imports = StringBuffer(); final cases = StringBuffer(); @@ -211,14 +211,14 @@ StatefulWidget? customPageByRoute(String route) { cases.write(case1); } } - final content = templateCollection.replaceFirst('#IMPORTS#', imports.toString()) + final content = templatePageManager.replaceFirst('#IMPORTS#', imports.toString()) .replaceFirst('#CASES#', cases.toString()); - writeFile('../lib/page/page_collection.dart', content); - final full = '../lib/page/page_collection_custom.dart'; + writeFile('../lib/page/page_manager.dart', content); + final full = '../lib/page/page_manager_custom.dart'; if (File(full).existsSync()){ logger.log('$full already exists. We do not override'); } else { - writeFile(full, templateCollectionCustom); + writeFile(full, templatePageManagerCustom); } } /// Generates all modules defined in the meta data. @@ -254,7 +254,7 @@ StatefulWidget? customPageByRoute(String route) { } } } - updateCollection(); + updatePageManager(); } } } diff --git a/pubspec.yaml b/pubspec.yaml index ed0fb86..37e93f6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,12 +30,16 @@ dependencies: flutter: sdk: flutter path: ^1.8.0 + yaml: ^3.1.0 dart_bones: ^1.2.2 url_launcher: ^6.0.9 flutter_markdown: ^0.6.1 flutter_bloc: ^7.1.0 equatable: ^2.0.3 permission_handler: ^8.1.6 + path_provider: ^2.0.1 + synchronized: ^3.0.0 + # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 diff --git a/rest_server/data/sql/roles.sql.yaml b/rest_server/data/sql/roles.sql.yaml index 3f68681..8e5f31c 100644 --- a/rest_server/data/sql/roles.sql.yaml +++ b/rest_server/data/sql/roles.sql.yaml @@ -8,25 +8,25 @@ list: parameters: [] sql: "SELECT t0.* - FROM Roles t0 + FROM roles t0 ;" byId: type: record parameters: [ "id" ] - sql: "SELECT * FROM Roles WHERE role_id=:id;" + sql: "SELECT * FROM roles WHERE role_id=:id;" delete: type: delete parameters: [ "id" ] - sql: "DELETE * FROM Roles WHERE role_id=:id;" + sql: "DELETE * FROM roles WHERE role_id=:id;" update: type: update parameters: [":id",":name",":changedBy"] - sql: "UPDATE Roles SET + sql: "UPDATE roles SET role_id=:id,role_name=:name,role_changedby=:changedBy,role_changed=NOW() WHERE role_id=:id;" insert: type: insert parameters: [":id",":name",":createdBy"] - sql: "INSERT INTO Roles(role_id,role_name,role_createdby,role_created) + sql: "INSERT INTO roles(role_id,role_name,role_createdby,role_created) VALUES(:id,:name,:createdBy,NOW());" diff --git a/rest_server/data/sql/structures.sql.yaml b/rest_server/data/sql/structures.sql.yaml new file mode 100644 index 0000000..f09192a --- /dev/null +++ b/rest_server/data/sql/structures.sql.yaml @@ -0,0 +1,35 @@ +--- +# DO NOT CHANGE. This file is created by the meta_tool +# SQL statements of the module "Structures": + +module: Structures +list: + type: list + parameters: [] + sql: "SELECT + t0.* + FROM structures t0 + + ;" +byId: + type: record + parameters: [ "id" ] + sql: "SELECT * FROM structures WHERE structure_id=:id;" +delete: + type: delete + parameters: [ "id" ] + sql: "DELETE * FROM structures WHERE structure_id=:id;" +update: + type: update + parameters: [":id",":scope",":name",":value",":position",":changedBy"] + sql: "UPDATE structures SET + structure_id=:id,structure_scope=:scope,structure_name=:name, + structure_value=:value,structure_position=:position, + structure_changedby=:changedBy,structure_changed=NOW() + WHERE structure_id=:id;" +insert: + type: insert + parameters: [":id",":scope",":name",":value",":position",":createdBy"] + sql: "INSERT INTO structures(structure_id,structure_scope,structure_name, + structure_value,structure_position,structure_createdby,structure_created) + VALUES(:id,:scope,:name,:value,:position,:createdBy,NOW());" diff --git a/rest_server/data/sql/users.sql.yaml b/rest_server/data/sql/users.sql.yaml index 85a06af..67cddba 100644 --- a/rest_server/data/sql/users.sql.yaml +++ b/rest_server/data/sql/users.sql.yaml @@ -8,27 +8,27 @@ list: parameters: [] sql: "SELECT t0.*,t1.role_name AS role - FROM Users t0 + FROM users t0 JOIN roles t1 ON t1.role_id=t0.user_role ;" byId: type: record parameters: [ "id" ] - sql: "SELECT * FROM Users WHERE user_id=:id;" + sql: "SELECT * FROM users WHERE user_id=:id;" delete: type: delete parameters: [ "id" ] - sql: "DELETE * FROM Users WHERE user_id=:id;" + sql: "DELETE * FROM users WHERE user_id=:id;" update: type: update parameters: [":id",":name",":displayName",":email",":role",":changedBy"] - sql: "UPDATE Users SET + sql: "UPDATE users SET user_id=:id,user_name=:name,user_displayname=:displayName,user_email=:email, user_role=:role,user_changedby=:changedBy,user_changed=NOW() WHERE user_id=:id;" insert: type: insert parameters: [":id",":name",":displayName",":email",":role",":createdBy"] - sql: "INSERT INTO Users(user_id,user_name,user_displayname,user_email, + sql: "INSERT INTO users(user_id,user_name,user_displayname,user_email, user_role,user_createdby,user_created) VALUES(:id,:name,:displayName,:email,:role,:createdBy,NOW());" diff --git a/rest_server/lib/rest_server.dart b/rest_server/lib/rest_server.dart index eb66cb2..7a45e64 100644 --- a/rest_server/lib/rest_server.dart +++ b/rest_server/lib/rest_server.dart @@ -586,16 +586,23 @@ class ServiceWorker { final ix = sql.lastIndexOf(';'); sql2 = sql.substring(0, ix) + ' limit ${parameters["offset"]},${parameters["size"]};'; - final sqlCount = - 'SELECT count(*) ' + sql.substring(sql.indexOf(' FROM')); - final countParams = '?'.allMatches(sqlCount).length; - final positionalParameters2 = countParams == positionalParameters.length - ? positionalParameters - : positionalParameters - .skip(positionalParameters.length - countParams) - .toList(); - prefix = - '#${await db!.readOneInt(sqlCount, params: positionalParameters2)}'; + final ix2 = sql.indexOf(' FROM ${module.toLowerCase()} t0 '); + if (ix2 > 0) + { + final sqlCount = + 'SELECT count(*) ' + sql.substring(ix2); + final countParams = '?' + .allMatches(sqlCount) + .length; + final positionalParameters2 = countParams == + positionalParameters.length + ? positionalParameters + : positionalParameters + .skip(positionalParameters.length - countParams) + .toList(); + prefix = + '#${await db!.readOneInt(sqlCount, params: positionalParameters2)}'; + } } final records = await db!.readAll(sql2, params: positionalParameters); rc = records == null diff --git a/rest_server/lib/sql_storage.dart b/rest_server/lib/sql_storage.dart index 5429928..8a18a64 100644 --- a/rest_server/lib/sql_storage.dart +++ b/rest_server/lib/sql_storage.dart @@ -110,6 +110,7 @@ class SqlStorage { /// Reads multiple module data from a directory. /// Only files ending with '.yaml' and containing '.sql.' are respected. void read(String path) { + logger.log('reading SQL from $path', LEVEL_DETAIL); for (var entry in Directory(path).listSync()) { final full = entry.path; final node = basename(full);