]> gitweb.hamatoma.de Git - exhibition.git/commitdiff
initial commit
authorHamatoma <author.hamatoma.de>
Tue, 29 Jun 2021 13:29:17 +0000 (15:29 +0200)
committerHamatoma <author.hamatoma.de>
Tue, 29 Jun 2021 13:29:17 +0000 (15:29 +0200)
21 files changed:
.gitignore [new file with mode: 0644]
C [new file with mode: 0755]
README.md [new file with mode: 0644]
analysis_options.yaml [new file with mode: 0644]
bin/meta_tool.dart [new file with mode: 0644]
lib/main.dart [new file with mode: 0644]
lib/meta/module_meta_data.dart [new file with mode: 0644]
lib/meta/modules.dart [new file with mode: 0644]
lib/meta/users_meta.dart [new file with mode: 0644]
lib/page/info_page.dart [new file with mode: 0644]
lib/page/log_page.dart [new file with mode: 0644]
lib/page/page_controller_exhibition.dart [new file with mode: 0644]
lib/page/r_data.dart [new file with mode: 0644]
lib/page/user/user_data.dart [new file with mode: 0644]
lib/page/user/user_list.dart [new file with mode: 0644]
lib/setting/app_bar_exhibition.dart [new file with mode: 0644]
lib/setting/drawer_exhibition.dart [new file with mode: 0644]
lib/setting/footer_exhibition.dart [new file with mode: 0644]
lib/setting/global_data.dart [new file with mode: 0644]
pubspec.yaml [new file with mode: 0644]
test/widget_test.dart [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..f8ae0fd
--- /dev/null
@@ -0,0 +1,52 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+/build/
+
+# Web related
+lib/generated_plugin_registrant.dart
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
+.metadata
+android/
+ios/
+web/
+linux/
+pubspec.lock
diff --git a/C b/C
new file mode 100755 (executable)
index 0000000..29d809c
--- /dev/null
+++ b/C
@@ -0,0 +1,4 @@
+#! /bin/bash
+APP=meta_tool
+dart compile exe bin/$APP.dart -o /usr/local/bin/$APP
+ls -ld /usr/local/bin/$APP
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..f5673f9
--- /dev/null
+++ b/README.md
@@ -0,0 +1,16 @@
+# exhibition
+
+Trying new concepts
+
+## Getting Started
+
+This project is a starting point for a Flutter application.
+
+A few resources to get you started if this is your first Flutter project:
+
+- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
+- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
+
+For help getting started with Flutter, view our
+[online documentation](https://flutter.dev/docs), which offers tutorials,
+samples, guidance on mobile development, and a full API reference.
diff --git a/analysis_options.yaml b/analysis_options.yaml
new file mode 100644 (file)
index 0000000..61b6c4d
--- /dev/null
@@ -0,0 +1,29 @@
+# This file configures the analyzer, which statically analyzes Dart code to
+# check for errors, warnings, and lints.
+#
+# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
+# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
+# invoked from the command line by running `flutter analyze`.
+
+# The following line activates a set of recommended lints for Flutter apps,
+# packages, and plugins designed to encourage good coding practices.
+include: package:flutter_lints/flutter.yaml
+
+linter:
+  # The lint rules applied to this project can be customized in the
+  # section below to disable rules from the `package:flutter_lints/flutter.yaml`
+  # included above or to enable additional rules. A list of all available lints
+  # and their documentation is published at
+  # https://dart-lang.github.io/linter/lints/index.html.
+  #
+  # Instead of disabling a lint rule for the entire project in the
+  # section below, it can also be suppressed for a single line of code
+  # or a specific dart file by using the `// ignore: name_of_lint` and
+  # `// ignore_for_file: name_of_lint` syntax on the line or in the file
+  # producing the lint.
+  rules:
+    # avoid_print: false  # Uncomment to disable the `avoid_print` rule
+    # prefer_single_quotes: true  # Uncomment to enable the `prefer_single_quotes` rule
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/bin/meta_tool.dart b/bin/meta_tool.dart
new file mode 100644 (file)
index 0000000..7939f7e
--- /dev/null
@@ -0,0 +1,161 @@
+import 'dart:io';
+
+import 'package:exhibition/meta/module_meta_data.dart';
+import 'package:exhibition/meta/modules.dart';
+import 'package:path/path.dart';
+
+void usage(){
+  out('''Usage: meta_tool MODE MODULE
+  Generate code specified by MODE for the module MODULE.
+MODE:
+  all-modules
+    Prints the module names.
+  print-modules
+    Prints the file lib/meta/modules.dart.
+  print-table MODULE
+    Prints the SQL table definition of MODULE.
+  print-data MODULE
+    Creates the data storage class of MODULE
+  update-modules
+    Generates all module files depending on the meta data.
+MODULE: 
+  The name of the module, e.g. "Users"
+Example:
+meta_tool print-table Users
+''');
+}
+void main(List<String> args) {
+  if (args.isEmpty) {
+    usage();
+    out('+++ missing arguments.');
+  } else {
+    ModuleMetaData? metaData;
+    switch (args[0]) {
+      case 'all-modules':
+        out(moduleNames().join('\n'));
+        break;
+      case 'print-modules':
+        out(createModules());
+        break;
+      case 'print-table':
+        if ((metaData = checkModule(args, 1)) != null) {
+          out(metaData!.createDbTable());
+        }
+        break;
+      case 'print-data':
+        if ((metaData = checkModule(args, 1)) != null) {
+          out(metaData!.createModuleData());
+        }
+        break;
+      case 'update-modules':
+        updateModules();
+        break;
+      default:
+        usage();
+        out('+++ unknown MODE: ${args[0]}.');
+        break;
+    }
+  }
+}
+
+ModuleMetaData? checkModule(List<String> args, int index) {
+  ModuleMetaData? rc;
+  if (index >= args.length) {
+    out('+++ too few arguments. Missing MODULE.');
+  } else {
+    rc = moduleByName(args[index]);
+  }
+  return rc;
+}
+
+String createModules() {
+  final buffer = StringBuffer();
+  buffer.write('// DO NOT CHANGE. This file is created by the meta_tool\n');
+  buffer.write("import 'dart:io';\n");
+  buffer.write("import 'module_meta_data.dart';\n");
+  final modules = <String>[];
+  final files = <String>[];
+  final fileOfModule = <String, String>{};
+
+  for (var item in Directory('lib/meta').listSync()) {
+    final name = basename(item.path);
+    if (name.endsWith('_meta.dart')) {
+      final moduleName = filenameToClass(name.replaceFirst('_meta.dart', ''));
+      fileOfModule[moduleName] = name;
+      modules.add(moduleName);
+      files.add(name);
+    }
+  }
+  modules.sort();
+  files.sort();
+  for (var file in files) {
+    buffer.write("import '$file';\n");
+  }
+  buffer.write('''
+/// Returns the meta data of the module given by [name].
+ModuleMetaData moduleByName(String name) {
+  ModuleMetaData rc;
+  switch (name) {
+''');
+  String? firstClass;
+  for (var module in modules) {
+    buffer.write("    case 'Users':\n");
+    final className = findClass(fileOfModule[module]!);
+    firstClass ??= className;
+    buffer.write("      rc = $className();\n");
+    buffer.write("      break;\n");
+  }
+  firstClass ??= 'MissingFirstClass';
+  buffer.write('''    default:
+      stdout.write('+++ unknown module: \$name. Using "$firstClass".');
+      rc = $firstClass();
+      break;
+  }
+  return rc;
+}
+/// Returns the module names as string list.
+List<String> moduleNames(){
+  return [
+''');
+  for (var module in modules) {
+    buffer.write("    '$module',\n");
+  }
+  buffer.write('''  ];
+}
+''');
+  return buffer.toString();
+}
+
+/// Finds the class name of a module meta class given by the file [node].
+String findClass(String node) {
+  final contents = File('lib/meta/$node').readAsStringSync();
+  RegExpMatch? match;
+  String rc;
+  if ((match = RegExp(r'class\s+(\w+)\s').firstMatch(contents)) != null) {
+    rc = match!.group(1)!;
+  } else {
+    out('+++ missing class in $node');
+    rc = filenameToClass(node.replaceFirst('.dart', ''));
+  }
+  return rc;
+}
+
+/// Replaces print().
+void out(String line) {
+  stdout.write(line + '\n');
+}
+
+void writeFile(String filename, String contents){
+  out('creating $filename ...');
+  final file = File(filename);
+  file.writeAsStringSync(contents);
+}
+void updateModules(){
+  writeFile('lib/meta/modules.dart', createModules());
+  final modules = moduleNames();
+  for (var name in modules){
+    ModuleMetaData module = moduleByName(name);
+    String filename = classToFilename(module.moduleNameSingular) + '_data.dart';
+    writeFile('lib/page/$filename', module.createModuleData());
+  }
+}
\ No newline at end of file
diff --git a/lib/main.dart b/lib/main.dart
new file mode 100644 (file)
index 0000000..202509b
--- /dev/null
@@ -0,0 +1,115 @@
+import 'package:flutter/material.dart';
+
+void main() {
+  runApp(const MyApp());
+}
+
+class MyApp extends StatelessWidget {
+  const MyApp({Key? key}) : super(key: key);
+
+  // This widget is the root of your application.
+  @override
+  Widget build(BuildContext context) {
+    return MaterialApp(
+      title: 'Flutter Demo',
+      theme: ThemeData(
+        // This is the theme of your application.
+        //
+        // Try running your application with "flutter run". You'll see the
+        // application has a blue toolbar. Then, without quitting the app, try
+        // changing the primarySwatch below to Colors.green and then invoke
+        // "hot reload" (press "r" in the console where you ran "flutter run",
+        // or simply save your changes to "hot reload" in a Flutter IDE).
+        // Notice that the counter didn't reset back to zero; the application
+        // is not restarted.
+        primarySwatch: Colors.blue,
+      ),
+      home: const MyHomePage(title: 'Flutter Demo Home Page'),
+    );
+  }
+}
+
+class MyHomePage extends StatefulWidget {
+  const MyHomePage({Key? key, required this.title}) : super(key: key);
+
+  // This widget is the home page of your application. It is stateful, meaning
+  // that it has a State object (defined below) that contains fields that affect
+  // how it looks.
+
+  // This class is the configuration for the state. It holds the values (in this
+  // case the title) provided by the parent (in this case the App widget) and
+  // used by the build method of the State. Fields in a Widget subclass are
+  // always marked "final".
+
+  final String title;
+
+  @override
+  State<MyHomePage> createState() => _MyHomePageState();
+}
+
+class _MyHomePageState extends State<MyHomePage> {
+  int _counter = 0;
+
+  void _incrementCounter() {
+    setState(() {
+      // This call to setState tells the Flutter framework that something has
+      // changed in this State, which causes it to rerun the build method below
+      // so that the display can reflect the updated values. If we changed
+      // _counter without calling setState(), then the build method would not be
+      // called again, and so nothing would appear to happen.
+      _counter++;
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    // This method is rerun every time setState is called, for instance as done
+    // by the _incrementCounter method above.
+    //
+    // The Flutter framework has been optimized to make rerunning build methods
+    // fast, so that you can just rebuild anything that needs updating rather
+    // than having to individually change instances of widgets.
+    return Scaffold(
+      appBar: AppBar(
+        // Here we take the value from the MyHomePage object that was created by
+        // the App.build method, and use it to set our appbar title.
+        title: Text(widget.title),
+      ),
+      body: Center(
+        // Center is a layout widget. It takes a single child and positions it
+        // in the middle of the parent.
+        child: Column(
+          // Column is also a layout widget. It takes a list of children and
+          // arranges them vertically. By default, it sizes itself to fit its
+          // children horizontally, and tries to be as tall as its parent.
+          //
+          // Invoke "debug painting" (press "p" in the console, choose the
+          // "Toggle Debug Paint" action from the Flutter Inspector in Android
+          // Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
+          // to see the wireframe for each widget.
+          //
+          // Column has various properties to control how it sizes itself and
+          // how it positions its children. Here we use mainAxisAlignment to
+          // center the children vertically; the main axis here is the vertical
+          // axis because Columns are vertical (the cross axis would be
+          // horizontal).
+          mainAxisAlignment: MainAxisAlignment.center,
+          children: <Widget>[
+            const Text(
+              'You have pushed the button this many times:',
+            ),
+            Text(
+              '$_counter',
+              style: Theme.of(context).textTheme.headline4,
+            ),
+          ],
+        ),
+      ),
+      floatingActionButton: FloatingActionButton(
+        onPressed: _incrementCounter,
+        tooltip: 'Increment',
+        child: const Icon(Icons.add),
+      ), // This trailing comma makes auto-formatting nicer for build methods.
+    );
+  }
+}
diff --git a/lib/meta/module_meta_data.dart b/lib/meta/module_meta_data.dart
new file mode 100644 (file)
index 0000000..712fdb8
--- /dev/null
@@ -0,0 +1,221 @@
+import 'package:path/path.dart';
+enum DataType {
+  bool,
+  currency,
+  date,
+  datetime,
+  float,
+  int,
+  nat,
+  reference,
+  string,
+}
+
+class MetaException extends FormatException{
+}
+/// Stores the meta data of a module.
+class ModuleMetaData {
+  /// The module name, e.g. users
+  final String moduleName;
+
+  /// The singular version of the module name, e.g. 'user'
+  String moduleNameSingular;
+
+  /// The related database table.
+  String tableName;
+  List<PropertyMetaData> list;
+  final Map<String, PropertyMetaData> properties = {};
+  String columnPrefix;
+  ModuleMetaData(this.moduleName, this.list,
+      {this.tableName = '',
+      this.moduleNameSingular = '',
+      this.columnPrefix = ''}) {
+    tableName = tableName.isEmpty ? moduleName : tableName;
+    moduleNameSingular = moduleNameSingular.isEmpty
+        ? (moduleName.endsWith('s')
+            ? moduleName.substring(0, moduleName.length - 1)
+            : moduleName)
+        : moduleNameSingular;
+    for (var item in list) {
+      properties[item.name] = item;
+      item.columnName ??= columnPrefix + '_' + item.name.toLowerCase();
+    }
+    columnPrefix = columnPrefix.isNotEmpty
+        ? columnPrefix
+        : moduleNameSingular.toLowerCase();
+  }
+
+  /// Returns a DDL statement creating the database table.
+  String createDbTable() {
+    final buffer = StringBuffer();
+    buffer.write('-- DO NOT CHANGE. This file is created by the meta_tool\n');
+    buffer.write('CREATE TABLE $tableName (\n');
+    String? primary;
+    for (var item in list) {
+      if (item.options.contains('primary')) {
+        primary = item.columnName;
+      }
+      buffer.write(' ${item.columnName}');
+      buffer.write(' ' + mySqlType(item.dataType, item.size));
+      buffer.write(dbOptions(item) + ',\n');
+    }
+    buffer.write('  PRIMARY KEY ($primary)\n');
+    buffer.write(');\n');
+    return buffer.toString();
+  }
+
+  /// Returns a Dart class definition of the module.
+  String createModuleData() {
+    final buffer = StringBuffer();
+    buffer.write('// DO NOT CHANGE. This file is created by the meta_tool\n');
+    buffer.write('class $moduleNameSingular{\n');
+    for (var item in list) {
+      buffer.write('  ' + dartType(item.dataType) + '? ${item.name};\n');
+    }
+    buffer.write('  $moduleNameSingular({');
+    String separator = '';
+    for (var item in list) {
+      buffer.write('$separator this.${item.name}');
+      separator = ',';
+    }
+    buffer.write('});\n}\n');
+
+    return buffer.toString();
+  }
+
+  String dartType(DataType dataType) {
+    String rc;
+    switch (dataType) {
+      case DataType.bool:
+        rc = 'bool';
+        break;
+      case DataType.currency:
+        rc = 'int';
+        break;
+      case DataType.date:
+      case DataType.datetime:
+        rc = 'DateTime';
+        break;
+      case DataType.float:
+        rc = 'double';
+        break;
+      case DataType.int:
+      case DataType.nat:
+      case DataType.reference:
+        rc = 'int';
+        break;
+      case DataType.string:
+        rc = 'String';
+        break;
+    }
+    return rc;
+  }
+
+  /// Calculates the column option of a column.
+  /// [property] specifies the meta data of the column.
+  String dbOptions(PropertyMetaData property) {
+    String rc = '';
+    String options = property.options;
+    if (options.contains(':notnull:') || options.contains('primary')) {
+      rc += ' NOT NULL';
+    }
+    if (options.contains('unique')) {
+      rc += ' UNIQUE';
+    }
+    if (property.dataType == DataType.date ||
+        property.dataType == DataType.datetime) {
+      rc += ' NULL';
+    }
+    if (options.contains('primary')) {
+      rc += ' AUTO_INCREMENT';
+    }
+    return rc.isEmpty ? '' : ' $rc';
+  }
+
+  /// Calculates the MySQL data type.
+  /// [dataType] specifies the data type.
+  /// [size] specifies the maximal size for a string type. Only relevant if
+  /// [dataType] == DataType.string.
+  String mySqlType(DataType dataType, int size) {
+    String rc;
+    switch (dataType) {
+      case DataType.bool:
+        rc = 'CHAR(1)';
+        break;
+      case DataType.date:
+        rc = 'DATE';
+        break;
+      case DataType.datetime:
+        rc = 'TIMESTAMP';
+        break;
+      case DataType.float:
+        rc = 'FLOAT';
+        break;
+      case DataType.currency:
+        rc = 'DECIMAL(12,2)';
+        break;
+      case DataType.int:
+        rc = 'INT(10)';
+        break;
+      case DataType.nat:
+      case DataType.reference:
+        rc = 'INT(10) UNSIGNED';
+        break;
+      case DataType.string:
+        size = size == 0 ? 255 : size;
+        if (size <= 255) {
+          rc = 'VARCHAR($size)';
+        } else if (size < 65535) {
+          rc = 'TEXT';
+        } else {
+          rc = 'LARGE TEXT';
+        }
+        break;
+    }
+    return rc;
+  }
+}
+
+/// Stores the meta data of a module property.
+class PropertyMetaData {
+  /// Name of the property (Dart name).
+  final String name;
+
+  /// A colon delimited list of options, e.g. ':notnull:unique:'.
+  final String options;
+
+  /// Empty or 'combobox' or 'checkbox'.
+  final String widgetType;
+  final DataType dataType;
+
+  /// The size if dataType is DataType.string.
+  final int size;
+  String? columnName;
+
+  /// The foreign key if dataType is DataType.reference, e.g. 'users.user_id'
+  String? reference;
+  PropertyMetaData(this.name, this.dataType, this.options, this.widgetType,
+      {this.columnName, this.size = 0, this.reference});
+}
+
+String filenameToClass(String filename){
+  final parts = basenameWithoutExtension(filename).split('_');
+  String rc = '';
+  for (var part in parts) {
+    if (part.isNotEmpty) {
+      rc += part[0].toUpperCase() + part.substring(1);
+    }
+  }
+  return rc;
+}
+String classToFilename(String className){
+  String upperCase = className.toUpperCase();
+  String rc = '';
+  for (var ix = 0; ix < className.length; ix++){
+    if (className[ix] == upperCase[ix] && ix > 0){
+      rc += '_';
+    }
+    rc = className[ix];
+  }
+  return rc.toLowerCase();
+}
\ No newline at end of file
diff --git a/lib/meta/modules.dart b/lib/meta/modules.dart
new file mode 100644 (file)
index 0000000..e8e8425
--- /dev/null
@@ -0,0 +1,24 @@
+// DO NOT CHANGE. This file is created by the meta_tool
+import 'dart:io';
+import 'module_meta_data.dart';
+import 'users_meta.dart';
+/// Returns the meta data of the module given by [name].
+ModuleMetaData moduleByName(String name) {
+  ModuleMetaData rc;
+  switch (name) {
+    case 'Users':
+      rc = UserMeta();
+      break;
+    default:
+      stdout.write('+++ unknown module: $name. Using "UserMeta".');
+      rc = UserMeta();
+      break;
+  }
+  return rc;
+}
+/// Returns the module names as string list.
+List<String> moduleNames(){
+  return [
+    'Users',
+  ];
+}
diff --git a/lib/meta/users_meta.dart b/lib/meta/users_meta.dart
new file mode 100644 (file)
index 0000000..092f07a
--- /dev/null
@@ -0,0 +1,22 @@
+import 'module_meta_data.dart';
+
+class UserMeta extends ModuleMetaData  {
+  static UserMeta instance = UserMeta.internal();
+  UserMeta.internal() : super('Users',
+    [
+      PropertyMetaData('id', DataType.reference, ':primary:', 'combo'),
+      PropertyMetaData('name', DataType.string, ':notnull:', '', size: 64),
+      PropertyMetaData('displayName', DataType.string, ':unique:notnull:', '', size: 32),
+      PropertyMetaData('email', DataType.string, ':unique:notnull:', '', size: 255),
+      PropertyMetaData('role', DataType.reference, ':notnull:', ''),
+      PropertyMetaData('created', DataType.datetime, '', ''),
+      PropertyMetaData('createdBy', DataType.string, '', '', size: 32),
+      PropertyMetaData('changed', DataType.datetime, '', ''),
+      PropertyMetaData('changedBy', DataType.string, '', '', size: 32),
+    ],
+    tableName: 'loginusers',
+  );
+  factory UserMeta(){
+    return instance;
+  }
+}
\ No newline at end of file
diff --git a/lib/page/info_page.dart b/lib/page/info_page.dart
new file mode 100644 (file)
index 0000000..9e8b839
--- /dev/null
@@ -0,0 +1,87 @@
+//import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_markdown/flutter_markdown.dart';
+import 'package:url_launcher/url_launcher.dart';
+
+import '../setting/global_data.dart';
+
+class InfoPage extends StatefulWidget {
+  final GlobalData globalData;
+
+  const InfoPage(this.globalData, {Key? key}) : super(key: key);
+
+  @override
+  InfoPageState createState() {
+    return InfoPageState();
+  }
+}
+
+class InfoPageState extends State<InfoPage> {
+  final GlobalData globalData = GlobalData();
+
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>(debugLabel: 'Log');
+
+  final markdownData = '''# Pollector
+
+**Version:** VERSION
+
+Diese App wurde von [**Hamatoma**](https://github.com/hamatoma) 
+![Logo](resource:assets/logo-68x68.png)
+entwickelt.
+
+![ ](resource:assets/white-16x16.png)
+
+Die App dient ...
+
+![ ](resource:assets/white-16x16.png)
+
+------
+
+![ ](resource:assets/white-16x16.png)
+
+Zur Entwicklung wurden einige **Opensource-Pakete** verwendet:
+
+* **Entwicklungsumgebungen:**
+  * **Flutter** https://flutter.dev
+  * **Dart** https://dart.dev
+* **Dart/Flutter-Pakete:**
+  * **dart_bones** https://github.com/hamatoma/dart_bones
+''';
+  int clickCounter = 0;
+
+  InfoPageState();
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+        appBar: globalData.appBarBuilder('Info'),
+        drawer: globalData.drawerBuilder(context),
+        body: SafeArea(
+            child: Markdown(
+          onTapLink: linkOnTapHandler,
+          data: markdownData.replaceFirst('VERSION', GlobalData.version),
+        )));
+  }
+
+  void linkOnTapHandler(
+    String? text,
+    String? href,
+    String? title,
+  ) async {
+    if (href!.startsWith('file:')) {
+      if (++clickCounter > 3) {
+        Navigator.pushNamed(context, '/configuration');
+      }
+    } else {
+      await canLaunch(href)
+          ? await launch(href)
+          : globalData.logger.error('Could not launch $href');
+    }
+  }
+
+  void store(context) async {
+    if (_formKey.currentState!.validate()) {
+      _formKey.currentState!.save();
+    }
+  }
+}
diff --git a/lib/page/log_page.dart b/lib/page/log_page.dart
new file mode 100644 (file)
index 0000000..3e7d363
--- /dev/null
@@ -0,0 +1,58 @@
+//import 'package:dart_bones/dart_bones.dart';
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter/material.dart';
+
+import '../setting/global_data.dart';
+
+class LogPage extends StatefulWidget {
+  final GlobalData globalData;
+
+  const LogPage(this.globalData, {Key? key}) : super(key: key);
+
+  @override
+  LogPageState createState() {
+    return LogPageState();
+  }
+}
+
+class LogPageState extends State<LogPage> {
+  final GlobalData globalData = GlobalData();
+
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>(debugLabel: 'Log');
+
+  String logName = '';
+
+  LogPageState();
+
+  @override
+  Widget build(BuildContext context) {
+    const padding = 16.0;
+    final listItems = (globalData.logger as MemoryLogger)
+        .messages
+        .map((line) => Text(line,
+            style: TextStyle(
+                color: line.startsWith('+++') ? Colors.red : Colors.black)))
+        .toList();
+    return Scaffold(
+        appBar: globalData.appBarBuilder('Protokoll'),
+        drawer: globalData.drawerBuilder(context),
+        body: Form(
+          key: _formKey,
+          child: Card(
+              margin: const EdgeInsets.symmetric(
+                  vertical: padding, horizontal: padding),
+              child: Padding(
+                  padding: const EdgeInsets.symmetric(
+                      vertical: padding, horizontal: padding),
+                  child: ListView(
+                    children: listItems,
+                  ))),
+        ));
+  }
+
+  void store(context) async {
+    if (_formKey.currentState!.validate()) {
+      _formKey.currentState!.save();
+    }
+  }
+}
diff --git a/lib/page/page_controller_exhibition.dart b/lib/page/page_controller_exhibition.dart
new file mode 100644 (file)
index 0000000..c7292d1
--- /dev/null
@@ -0,0 +1,8 @@
+import '../setting/global_data.dart';
+
+class PageControllerExhibition {
+  final String pageName;
+  final GlobalData globalData;
+
+  PageControllerExhibition(this.pageName, this.globalData);
+}
diff --git a/lib/page/r_data.dart b/lib/page/r_data.dart
new file mode 100644 (file)
index 0000000..f35600e
--- /dev/null
@@ -0,0 +1,13 @@
+// DO NOT CHANGE. This file is created by the meta_tool
+class User{
+  int? id;
+  String? name;
+  String? displayName;
+  String? email;
+  int? role;
+  DateTime? created;
+  String? createdBy;
+  DateTime? changed;
+  String? changedBy;
+  User({ this.id, this.name, this.displayName, this.email, this.role, this.created, this.createdBy, this.changed, this.changedBy});
+}
diff --git a/lib/page/user/user_data.dart b/lib/page/user/user_data.dart
new file mode 100644 (file)
index 0000000..03244c5
--- /dev/null
@@ -0,0 +1,21 @@
+class User {
+  int? id;
+  String? name;
+  String? displayName;
+  String? email;
+  int? role;
+  DateTime? created;
+  String? createdBy;
+  DateTime? changed;
+  String? changedBy;
+  User(
+      {this.id,
+      this.name,
+      this.displayName,
+      this.email,
+      this.role,
+      this.created,
+      this.createdBy,
+      this.changed,
+      this.changedBy});
+}
diff --git a/lib/page/user/user_list.dart b/lib/page/user/user_list.dart
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/lib/setting/app_bar_exhibition.dart b/lib/setting/app_bar_exhibition.dart
new file mode 100644 (file)
index 0000000..d0ed168
--- /dev/null
@@ -0,0 +1,9 @@
+import 'package:flutter/material.dart';
+
+class AppBarExhibition extends AppBar {
+  AppBarExhibition({required String title, Key? key})
+      : super(title: Text(title), key: key);
+
+  static AppBarExhibition builder(String title) =>
+      AppBarExhibition(title: title);
+}
diff --git a/lib/setting/drawer_exhibition.dart b/lib/setting/drawer_exhibition.dart
new file mode 100644 (file)
index 0000000..e0a8f44
--- /dev/null
@@ -0,0 +1,143 @@
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter/material.dart';
+
+import '../page/info_page.dart';
+import '../page/log_page.dart';
+import 'global_data.dart';
+
+class DrawerExhibition extends Drawer {
+  DrawerExhibition(context, {Key? key})
+      : super(child: buildGrid(context), key: key);
+
+  /// Returns a method creating a drawer.
+  static DrawerExhibition builder(dynamic context) => DrawerExhibition(context);
+
+  static Widget buildGrid(context) {
+    final converter = MenuConverter();
+    final list = MenuItem.menuItems(converter);
+    final rc = Card(
+        child: GridView.count(
+      crossAxisCount: 2,
+      crossAxisSpacing: 16.0,
+      children: list
+          .map((item) => GridTile(
+                child: InkResponse(
+                  enableFeedback: true,
+                  child: Card(
+                    child: Container(
+                      alignment: Alignment.center,
+                      child: Column(
+                        children: [
+                          const SizedBox(width: 10.0, height: 40.0),
+                          Icon(item.icon),
+                          Text(item.title)
+                        ],
+                      ),
+                    ),
+                  ),
+                  onTap: () {
+                    //ExhibitionSettings().pageData.pushCaller(null);
+                    Navigator.push(context,
+                        MaterialPageRoute(builder: (context) => item.page()));
+                  },
+                ),
+              ))
+          .toList(),
+    ));
+    return rc;
+  }
+
+  static Widget buildListView(context) {
+    final converter = MenuConverter();
+    final list = MenuItem.menuItems(converter);
+    final rc = Card(
+        child: ListView(
+            shrinkWrap: true,
+            physics: const ClampingScrollPhysics(),
+            children: list
+                .map((item) => ListTile(
+                      title: Text(item.title),
+                      onTap: () {
+                        // What happens after you tap the navigation item
+                        Navigator.push(
+                            context,
+                            MaterialPageRoute(
+                                builder: (context) => item.page()));
+                      },
+                    ))
+                .toList()));
+    return rc;
+  }
+}
+
+class MenuConverter {
+  /// Returns the icon given by [name].
+  IconData iconByName(String name, BaseLogger logger) {
+    IconData? rc;
+    switch (name) {
+      case 'addchart_outlined':
+        rc = Icons.addchart_outlined;
+        break;
+      case 'build_circle_outlined':
+        rc = Icons.build_circle_outlined;
+        break;
+      case 'app_registration':
+        rc = Icons.app_registration;
+        break;
+      case 'line_weight_outlined':
+        rc = Icons.line_weight_outlined;
+        break;
+      case 'info_outline':
+        rc = Icons.info_outline;
+        break;
+      case 'settings_applications_outlined':
+        rc = Icons.settings_applications_outlined;
+        break;
+      default:
+        logger.error('MenuConverter.iconByName(): unknown icon $name');
+        break;
+    }
+    return rc ?? Icons.access_alarm;
+  }
+
+  /// Returns the page given by [name].
+  StatefulWidget? pageByName(String name, GlobalData globalData) {
+    StatefulWidget? rc;
+    switch (name) {
+      case 'log':
+        rc = LogPage(globalData);
+        break;
+      case 'info':
+        rc = InfoPage(globalData);
+        break;
+      default:
+        globalData.logger
+            .error('MenuConverter.pageByName(): unknown page $name');
+        break;
+    }
+    return rc;
+  }
+}
+
+class MenuItem {
+  final String title;
+  final dynamic page;
+  final IconData icon;
+
+  MenuItem(this.title, this.page, this.icon);
+
+  static List<MenuItem> menuItems(MenuConverter converter) {
+    final globalData = GlobalData();
+    final logger = globalData.logger;
+    return <MenuItem>[
+      MenuItem('Protokoll', () => converter.pageByName('log', globalData),
+          converter.iconByName('line_weight_outlined', logger)),
+      MenuItem('Information', () => converter.pageByName('info', globalData),
+          converter.iconByName('info_outline', logger)),
+      MenuItem(
+          'Konfiguration',
+          () => converter.pageByName('configuration', globalData),
+          converter.iconByName('settings_applications_outlined', logger)),
+    ];
+  }
+}
diff --git a/lib/setting/footer_exhibition.dart b/lib/setting/footer_exhibition.dart
new file mode 100644 (file)
index 0000000..2627bf0
--- /dev/null
@@ -0,0 +1,38 @@
+import 'package:flutter/material.dart';
+import 'global_data.dart';
+import '../page/page_controller_exhibition.dart';
+import 'package:url_launcher/url_launcher.dart';
+
+class FooterExhibition implements FooterInterface {
+  @override
+  Widget widget(PageControllerExhibition controller) {
+    final rc = ButtonBar(alignment: MainAxisAlignment.spaceBetween, children: [
+      InkWell(
+        child: const Text('Impressum'),
+        onTap: () => launch('https://public.hamatoma.de'),
+      ),
+      // SizedBox(
+      //   width: 100,
+      // ),
+      InkWell(
+        child: const Text('Datenschutz'),
+        onTap: () => launch('https://public.hamatoma.de'),
+      ),
+      // SizedBox(
+      //   width: 150,
+      // ),
+      const Text('Version ${GlobalData.version}'),
+    ]);
+    // final rc2 = [
+    //   GridView.count(crossAxisCount: 3, children: [
+    //     Text('a'),
+    //     Text('b'),
+    //     Text('c'),
+    //   ])
+    // ];
+    return rc;
+  }
+
+  /// Returns a method creating a footer.
+  static FooterExhibition builder() => FooterExhibition();
+}
diff --git a/lib/setting/global_data.dart b/lib/setting/global_data.dart
new file mode 100644 (file)
index 0000000..abe004c
--- /dev/null
@@ -0,0 +1,48 @@
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter/material.dart';
+
+import '../page/page_controller_exhibition.dart';
+
+typedef AppBarBuilder = Function(String);
+
+typedef DrawerBuilder = Function(dynamic);
+
+typedef FooterBuilder = FooterInterface? Function();
+
+class DummyFooter implements FooterInterface {
+  @override
+  Widget widget(PageControllerExhibition controller) {
+    return const Text('');
+  }
+
+  /// Returns a method creating a footer.
+  static DummyFooter? builder() => DummyFooter();
+}
+
+abstract class FooterInterface {
+  Widget widget(PageControllerExhibition controller);
+}
+
+/// Storage for  globale resources. This is a singleton.
+class GlobalData {
+  static const version = '2021.06.027.00';
+  static GlobalData? _instance;
+  final BaseLogger logger;
+  final AppBarBuilder appBarBuilder;
+  final DrawerBuilder drawerBuilder;
+  final FooterBuilder footerBuilder;
+  final BaseConfiguration configuration;
+
+  factory GlobalData() => _instance ?? GlobalData();
+  GlobalData.dummy()
+      : this.internal(BaseConfiguration({}, globalLogger), (input) => '',
+            (input) => '', DummyFooter.builder, globalLogger);
+
+  /// [configuration]: general settings.
+  /// [appBarBuilder]: a factory to create the Hamburger menu.
+  /// [footerBuilder]: a factory to create a footer area.
+  GlobalData.internal(this.configuration, this.appBarBuilder,
+      this.drawerBuilder, this.footerBuilder, this.logger) {
+    logger.log('Start');
+  }
+}
diff --git a/pubspec.yaml b/pubspec.yaml
new file mode 100644 (file)
index 0000000..078722e
--- /dev/null
@@ -0,0 +1,91 @@
+name: exhibition
+description: Trying new concepts
+
+# The following line prevents the package from being accidentally published to
+# pub.dev using `flutter pub publish`. This is preferred for private packages.
+publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+
+# The following defines the version and build number for your application.
+# A version number is three numbers separated by dots, like 1.2.43
+# followed by an optional build number separated by a +.
+# Both the version and the builder number may be overridden in flutter
+# build by specifying --build-name and --build-number, respectively.
+# In Android, build-name is used as versionName while build-number used as versionCode.
+# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
+# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
+# Read more about iOS versioning at
+# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
+version: 1.0.0+1
+
+environment:
+  sdk: ">=2.12.0 <3.0.0"
+
+# Dependencies specify other packages that your package needs in order to work.
+# To automatically upgrade your package dependencies to the latest versions
+# consider running `flutter pub upgrade --major-versions`. Alternatively,
+# dependencies can be manually updated by changing the version numbers below to
+# the latest version available on pub.dev. To see which dependencies have newer
+# versions available, run `flutter pub outdated`.
+dependencies:
+  flutter:
+    sdk: flutter
+  path: ^1.8.0
+  dart_bones: "^1.1.1"
+  url_launcher: ^6.0.3
+  flutter_markdown: ^0.6.1
+  # The following adds the Cupertino Icons font to your application.
+  # Use with the CupertinoIcons class for iOS style icons.
+  cupertino_icons: ^1.0.2
+
+dev_dependencies:
+  flutter_test:
+    sdk: flutter
+
+  # The "flutter_lints" package below contains a set of recommended lints to
+  # encourage good coding practices. The lint set provided by the package is
+  # activated in the `analysis_options.yaml` file located at the root of your
+  # package. See that file for information about deactivating specific lint
+  # rules and activating additional ones.
+  flutter_lints: ^1.0.0
+
+# For information on the generic Dart part of this file, see the
+# following page: https://dart.dev/tools/pub/pubspec
+
+# The following section is specific to Flutter.
+flutter:
+
+  # The following line ensures that the Material Icons font is
+  # included with your application, so that you can use the icons in
+  # the material Icons class.
+  uses-material-design: true
+
+  # To add assets to your application, add an assets section, like this:
+  # assets:
+  #   - images/a_dot_burr.jpeg
+  #   - images/a_dot_ham.jpeg
+
+  # An image asset can refer to one or more resolution-specific "variants", see
+  # https://flutter.dev/assets-and-images/#resolution-aware.
+
+  # For details regarding adding assets from package dependencies, see
+  # https://flutter.dev/assets-and-images/#from-packages
+
+  # To add custom fonts to your application, add a fonts section here,
+  # in this "flutter" section. Each entry in this list should have a
+  # "family" key with the font family name, and a "fonts" key with a
+  # list giving the asset and other descriptors for the font. For
+  # example:
+  # fonts:
+  #   - family: Schyler
+  #     fonts:
+  #       - asset: fonts/Schyler-Regular.ttf
+  #       - asset: fonts/Schyler-Italic.ttf
+  #         style: italic
+  #   - family: Trajan Pro
+  #     fonts:
+  #       - asset: fonts/TrajanPro.ttf
+  #       - asset: fonts/TrajanPro_Bold.ttf
+  #         weight: 700
+  #
+  # For details regarding fonts from package dependencies,
+  # see https://flutter.dev/custom-fonts/#from-packages
diff --git a/test/widget_test.dart b/test/widget_test.dart
new file mode 100644 (file)
index 0000000..af01327
--- /dev/null
@@ -0,0 +1,30 @@
+// This is a basic Flutter widget test.
+//
+// To perform an interaction with a widget in your test, use the WidgetTester
+// utility that Flutter provides. For example, you can send tap and scroll
+// gestures. You can also use WidgetTester to find child widgets in the widget
+// tree, read text, and verify that the values of widget properties are correct.
+
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import 'package:exhibition/main.dart';
+
+void main() {
+  testWidgets('Counter increments smoke test', (WidgetTester tester) async {
+    // Build our app and trigger a frame.
+    await tester.pumpWidget(const MyApp());
+
+    // Verify that our counter starts at 0.
+    expect(find.text('0'), findsOneWidget);
+    expect(find.text('1'), findsNothing);
+
+    // Tap the '+' icon and trigger a frame.
+    await tester.tap(find.byIcon(Icons.add));
+    await tester.pump();
+
+    // Verify that our counter has incremented.
+    expect(find.text('0'), findsNothing);
+    expect(find.text('1'), findsOneWidget);
+  });
+}