--- /dev/null
+# 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
--- /dev/null
+#! /bin/bash
+APP=meta_tool
+dart compile exe bin/$APP.dart -o /usr/local/bin/$APP
+ls -ld /usr/local/bin/$APP
--- /dev/null
+# 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.
--- /dev/null
+# 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
--- /dev/null
+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
--- /dev/null
+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.
+ );
+ }
+}
--- /dev/null
+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
--- /dev/null
+// 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',
+ ];
+}
--- /dev/null
+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
--- /dev/null
+//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();
+ }
+ }
+}
--- /dev/null
+//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();
+ }
+ }
+}
--- /dev/null
+import '../setting/global_data.dart';
+
+class PageControllerExhibition {
+ final String pageName;
+ final GlobalData globalData;
+
+ PageControllerExhibition(this.pageName, this.globalData);
+}
--- /dev/null
+// 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});
+}
--- /dev/null
+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});
+}
--- /dev/null
+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);
+}
--- /dev/null
+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)),
+ ];
+ }
+}
--- /dev/null
+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();
+}
--- /dev/null
+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');
+ }
+}
--- /dev/null
+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
--- /dev/null
+// 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);
+ });
+}