]> gitweb.hamatoma.de Git - exhibition.git/commitdiff
Generator routes Structures
authorHamatoma <author.hamatoma.de>
Fri, 8 Oct 2021 06:05:21 +0000 (08:05 +0200)
committerHamatoma <author.hamatoma.de>
Fri, 8 Oct 2021 06:05:21 +0000 (08:05 +0200)
* 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)

51 files changed:
lib/base/application_name.dart [new file with mode: 0644]
lib/base/defines.dart
lib/exhibition_app.dart
lib/meta/module_meta_data.dart
lib/meta/modules.dart
lib/meta/structures_meta.dart [new file with mode: 0644]
lib/meta/users_meta.dart
lib/page/page_collection.dart [deleted file]
lib/page/page_collection_custom.dart [deleted file]
lib/page/page_manager.dart [new file with mode: 0644]
lib/page/page_manager_custom.dart [new file with mode: 0644]
lib/page/roles/create_role_custom.dart
lib/page/roles/create_role_page.dart
lib/page/roles/edit_role_custom.dart
lib/page/roles/edit_role_page.dart
lib/page/roles/list_role_custom.dart
lib/page/roles/list_role_page.dart
lib/page/start_page.dart
lib/page/structures/create_structure_custom.dart [new file with mode: 0644]
lib/page/structures/create_structure_page.dart [new file with mode: 0644]
lib/page/structures/delete_structure_custom.dart [new file with mode: 0644]
lib/page/structures/delete_structure_page.dart [new file with mode: 0644]
lib/page/structures/edit_structure_custom.dart [new file with mode: 0644]
lib/page/structures/edit_structure_page.dart [new file with mode: 0644]
lib/page/structures/list_structure_custom.dart [new file with mode: 0644]
lib/page/structures/list_structure_page.dart [new file with mode: 0644]
lib/page/users/create_user_custom.dart
lib/page/users/create_user_page.dart
lib/page/users/delete_user_custom.dart
lib/page/users/delete_user_page.dart
lib/page/users/edit_user_custom.dart
lib/page/users/edit_user_page.dart
lib/page/users/list_user_custom.dart
lib/page/users/list_user_page.dart
lib/persistence/file_persistence.dart
lib/persistence/persistence.dart
lib/persistence/rest_persistence.dart
lib/setting/drawer_exhibition.dart
lib/setting/global_data.dart
lib/setting/installation.dart
lib/setting/structure.dart [new file with mode: 0644]
lib/widget/attended_page.dart
lib/widget/attended_stateful.dart [new file with mode: 0644]
lib/widget/widget_form.dart
metatool/bin/page_generator.dart
pubspec.yaml
rest_server/data/sql/roles.sql.yaml
rest_server/data/sql/structures.sql.yaml [new file with mode: 0644]
rest_server/data/sql/users.sql.yaml
rest_server/lib/rest_server.dart
rest_server/lib/sql_storage.dart

diff --git a/lib/base/application_name.dart b/lib/base/application_name.dart
new file mode 100644 (file)
index 0000000..897d202
--- /dev/null
@@ -0,0 +1,2 @@
+const theApplicationName = 'exhibition';
+const theVariantName = 'exhibition';
index 1496a8018d6b65e8e897b6e6812f3d50a149c666..69cd576476586950cef02242d9df1db695529cf4 100644 (file)
@@ -1,6 +1,6 @@
 enum ServerEnvironment { productive, development }
 typedef JsonMap = Map<String, dynamic>;
-typedef JsonList = List<JsonMap>;
+typedef JsonList = List<dynamic>;
 
 ///JsonMap or JsonList
 typedef JsonData = Object;
index 148a4f24203e8c608490fe5f8db3211d96efa7fa..bb0cca474f11faf9cdaf128ac3a3e46b84d549b8 100644 (file)
@@ -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<dynamic>? _getRoute(RouteSettings settings) {
       page = LogPage();
       break;
     default:
-      page = PageCollection().newPageByRoute(settings.name ?? '');
+      page = PageManager().newPageByRoute(settings.name ?? '');
       break;
   }
   if (page != null) {
index b7c507c489dd79ba1af3ed1019d02c9b16d9d2d8..b7a105d8271f97d5492a82ffca0f697f87f8177c 100644 (file)
@@ -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')) {
index 57e2fe3cf5b07ab4a0c6658590fd21400ecd6642..2ff718cbe1e7dd5b623c7dfe4e2c30058e8dda99 100644 (file)
@@ -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<String> moduleNames(){
+List<String> moduleNames() {
   return [
     'Roles',
+    'Structures',
     'Users',
   ];
 }
diff --git a/lib/meta/structures_meta.dart b/lib/meta/structures_meta.dart
new file mode 100644 (file)
index 0000000..8f0f5e7
--- /dev/null
@@ -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),
+        ]);
+}
index 53b9a2bbe620f1d74107cbe3be1515eef6611b0e..0eba0ff48468d4191ccf6f3e83c238f8be9b1532 100644 (file)
@@ -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 (file)
index eca445f..0000000
+++ /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 = <String, StatefulWidget>{};
-
-  /// 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 (file)
index d9c8dc9..0000000
+++ /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 (file)
index 0000000..77d4168
--- /dev/null
@@ -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 = <String, StatefulWidget>{};
+
+  /// 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 (file)
index 0000000..d9c8dc9
--- /dev/null
@@ -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;
+}
index 90fe008bf9600f84f4fb9766de5d0edb429877c5..4768a0254e4e4f458499bc86475b012f4e01a559 100644 (file)
@@ -22,11 +22,13 @@ class CreateRoleCustom extends State<CreateRolePage> {
         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();
index 4ec6c88d247f4f8f2e59f42a4159ecb4d1dbb6e0..2cd8530ff6ee1cb07d75cb0e9c8dbfc5e5abf649 100644 (file)
@@ -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;
index 99b664ba752efbbb26df1db22a20d6231382ea71..5f2006d418e91d6dce51417971b0d42076f2d6b9 100644 (file)
@@ -23,11 +23,13 @@ class EditRoleCustom extends State<EditRolePage> {
         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();
index ef15ac45d8661adb74e1558911f0d05741b1b6b9..6c32f014345b856ae772492995f4f13872267312 100644 (file)
@@ -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;
index d85fc58eabdc7384cd7935cf0822c747c42ab244..d23445c1411236aa26d20b543025e7222f3bda5a 100644 (file)
@@ -22,11 +22,13 @@ class ListRoleCustom extends State<ListRolePage> {
         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();
index b42f052403af7c5e5350e744c837b61cbd7126b8..0915e10602594259aefa4ae513f0e1c5d86433d2 100644 (file)
@@ -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;
index 71f72c62714841a91542ae415dae0b1242c69e27..d2876bf8a933fc39433973dd264a6ab846d45f43 100644 (file)
@@ -96,7 +96,7 @@ class StartPageState extends State<StartPage> {
                     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 (file)
index 0000000..a0f39d6
--- /dev/null
@@ -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<CreateStructurePage> {
+  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 (file)
index 0000000..e27c01e
--- /dev/null
@@ -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 (file)
index 0000000..c6b9ba0
--- /dev/null
@@ -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<DeleteStructurePage> {
+  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 (file)
index 0000000..cc888a8
--- /dev/null
@@ -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 (file)
index 0000000..3f2aa2a
--- /dev/null
@@ -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<EditStructurePage> {
+  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 (file)
index 0000000..a689d34
--- /dev/null
@@ -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 (file)
index 0000000..3bf1b62
--- /dev/null
@@ -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<ListStructurePage> {
+  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 (file)
index 0000000..55c901c
--- /dev/null
@@ -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();
+  }
+}
index f6f70afcb8b5686d3b785f474323f08d70a0f37a..48a581e5b02307a8f0907c2efc46b873d5e03698 100644 (file)
@@ -22,11 +22,13 @@ class CreateUserCustom extends State<CreateUserPage> {
         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();
index 14e69e258185a24630f5a76f39e934f03133ccc6..71a2c05f8e2aab205a665c480213d491711ed3e2 100644 (file)
@@ -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;
index d36681a1a0ce16bae78175d45033cbbb75917c29..7fa7866b7a06d688c764106605661ae275fda60b 100644 (file)
@@ -23,11 +23,13 @@ class DeleteUserCustom extends State<DeleteUserPage> {
         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();
index 85cbfc907ceebe2c637ac1bc32c7ee0433342d34..8bbef4c01f9cafe001d2b38d6e01af670ac93a85 100644 (file)
@@ -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;
index 3e5f5a386055b0a462b2c6a0baf26df4ce6421c0..d4ab150414d5ffdde9d7462df75b9671e6b45053 100644 (file)
@@ -23,11 +23,13 @@ class EditUserCustom extends State<EditUserPage> {
         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();
index 11796632306f7b6a0d8a5c1b26bd2b9bd80e6d06..4b2e75f96c40371887a1ff257b12738ea63d3dad 100644 (file)
@@ -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;
index b3e40b860ffcb298203433bf71dce44d86a5a3a1..c90b9ec5c2d0484b8617e2a79255ebe10135ad92 100644 (file)
@@ -12,21 +12,45 @@ final i18n = I18N();
 
 class ListUserCustom extends State<ListUserPage> {
   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 = <DropdownMenuItem<int>>[];
+    final fieldWidgets = <Widget>[
+      TextFormField(
+        controller: textController,
+        decoration: InputDecoration(labelText: i18n.tr('Text')),
+        onChanged: (value) => fieldData.text = value,
+      ),
+      DropdownButtonFormField<int>(
+        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>[
         DataColumn(
@@ -45,13 +69,20 @@ class ListUserCustom extends State<ListUserPage> {
       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<ListUserPage> {
     super.initState();
   }
 }
+
+class FieldData {
+  String text = '';
+  int role = 0;
+}
index 1938d3d9a841fa556eb29744f91b85a73d9fde16..2723f48184f044e8e5216c00a0c1bfb8e4cbba5f 100644 (file)
@@ -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;
index 9637e75b89c022f183f4e5ca64d2c2f38a80d6f1..2159ee3811ebab3178931a2f781468192b57b5a8 100644 (file)
@@ -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}');
index 39949efc7a955b376e1fd7f7c83652437f96a2b5..cf7794a4bcce8f453cc7ec64b804926aab595c57 100644 (file)
@@ -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].
   ///
index a00a6b7b70fc0a76e47caf0ddeb2d1ac4f59e446..b870ed6142a369159273c61380b500e152fe5887 100644 (file)
@@ -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<String, String>? 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<int>(answer.length, 8))}');
       }
     } else {
-        rc = DbData.message(answer.substring(0, max<int>(answer.length, 120)));
+      rc = DbData.message(answer.substring(0, max<int>(answer.length, 120)));
     }
     if (logger.logLevel >= LEVEL_FINE) {
       logger.log('answer: ${limitString(answer, sqlTraceLimit)}');
index 49822ef0f279abbb82511ec9e989af2eaa784286..d8a156d6d2ff51eafb5af324f04238670af6ca0a 100644 (file)
@@ -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<MenuItem> menuItems(MenuConverter converter) {
     final globalData = GlobalData();
     final logger = globalData.logger;
-    final collection = PageCollection();
+    final collection = PageManager();
     return <MenuItem>[
       MenuItem('Users', () => collection.newPageByRoute('/users/list'),
           converter.iconByName('people_outlined', logger)),
index 1d8f79bac2811f56b0394120b65d56fc16c79fa8..e470cb0a4d33e7c0f67ea8c9462a3ed33b3b2935 100644 (file)
@@ -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;
     }
index 68ae7d4998e31d03947605d81072763f1b996f21..f4bf2fe39bd7d84946ff0d8a6bbd6e4c700c331f 100644 (file)
@@ -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>[
     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 (file)
index 0000000..79f25f2
--- /dev/null
@@ -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 = <Structure>[];
+  final scopes = <String, List<Structure>>{};
+
+  /// 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<Structure>? 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;
+  }
+}
index de14e2a01530a95e60eb1461cdf7bd44a7f645f2..fa19c319b7694704d07eafe8a9858de62b618428 100644 (file)
@@ -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<DataRow> getRows(String columnList, String what,
-      Map<String, dynamic> parameters, Function() onDone, String routeEdit, BuildContext context) {
+  List<DataRow> getRows(
+      {required String columnList,
+      required String what,
+      required Map<String, dynamic> parameters,
+      required Function() onDone,
+      required String routeEdit,
+      required BuildContext context}) {
     List<DataRow>? 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 = <DataRow>[];
-        pageStates.dbData?.recordList!.map((record) {
+        dbData.recordList!.forEach((record) {
           final cells = <DataCell>[];
-          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 = <String, DbData?>{};
+  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 (file)
index 0000000..dbadd10
--- /dev/null
@@ -0,0 +1,6 @@
+import 'package:flutter/material.dart';
+import 'attended_page.dart';
+
+abstract class AttendedStateful extends StatefulWidget {
+  final PageStates pageStates = PageStates();
+}
index 7b6cdabae42652c0a49e6c3b6e0df0443c6a4b16..b820fae4b3c9035f4dc88e6c58301cc5f8976cbc 100644 (file)
@@ -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<AttendedWidget> widgets,
-      {required double screenWidth,
+  static Widget flexibleGrid(List<Widget> widgets,
+      {required List<int> 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<Widget> childrenColumn = [];
       List<Widget> 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<AttendedWidget> 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;
+  }
 }
index 34ba5f225fbd9354ca6021551790c9de3f03a2ce..369b373d82c48137d136f1946dbc8de8bc8c9f8a 100644 (file)
@@ -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 = <String, StatefulWidget>{};
@@ -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();
     }
   }
 }
index ed0fb863c975e8585a3de96270fcfa5af510cb31..37e93f631d925319d787d80ef3450e8c0a8e8f34 100644 (file)
@@ -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
index 3f6868127e520ad5976db58b36028bd044c833f4..8e5f31c225cabb60b5b41a896b279ea3d28d654d 100644 (file)
@@ -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 (file)
index 0000000..f09192a
--- /dev/null
@@ -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());"
index 85a06af64cfd33a734b9d9aff49bab43d2f1abd6..67cddbacd2e0c0c5d2a5a98f33834882eb5bcf2c 100644 (file)
@@ -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());"
index eb66cb2ba05468b3ad92b88e09dda5c80c6271c6..7a45e64ff0d6717d8167c1dab3435cb1972227d2 100644 (file)
@@ -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
index 542992872d131b237b9660c440699f5e467d3cf2..8a18a642ca788fb3782b39a33b1588be7b00e53e 100644 (file)
@@ -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);