From: Hamatoma Date: Tue, 29 Jun 2021 13:29:17 +0000 (+0200) Subject: initial commit X-Git-Url: https://gitweb.hamatoma.de/?a=commitdiff_plain;h=f053fc347d3d3977a5a10f2692e82732de0cf7b7;p=exhibition.git initial commit --- f053fc347d3d3977a5a10f2692e82732de0cf7b7 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f8ae0fd --- /dev/null +++ b/.gitignore @@ -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 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 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 index 0000000..61b6c4d --- /dev/null +++ b/analysis_options.yaml @@ -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 index 0000000..7939f7e --- /dev/null +++ b/bin/meta_tool.dart @@ -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 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 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 = []; + final files = []; + final fileOfModule = {}; + + 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 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 index 0000000..202509b --- /dev/null +++ b/lib/main.dart @@ -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 createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + 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: [ + 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 index 0000000..712fdb8 --- /dev/null +++ b/lib/meta/module_meta_data.dart @@ -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 list; + final Map 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 index 0000000..e8e8425 --- /dev/null +++ b/lib/meta/modules.dart @@ -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 moduleNames(){ + return [ + 'Users', + ]; +} diff --git a/lib/meta/users_meta.dart b/lib/meta/users_meta.dart new file mode 100644 index 0000000..092f07a --- /dev/null +++ b/lib/meta/users_meta.dart @@ -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 index 0000000..9e8b839 --- /dev/null +++ b/lib/page/info_page.dart @@ -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 { + final GlobalData globalData = GlobalData(); + + final GlobalKey _formKey = GlobalKey(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 index 0000000..3e7d363 --- /dev/null +++ b/lib/page/log_page.dart @@ -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 { + final GlobalData globalData = GlobalData(); + + final GlobalKey _formKey = GlobalKey(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 index 0000000..c7292d1 --- /dev/null +++ b/lib/page/page_controller_exhibition.dart @@ -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 index 0000000..f35600e --- /dev/null +++ b/lib/page/r_data.dart @@ -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 index 0000000..03244c5 --- /dev/null +++ b/lib/page/user/user_data.dart @@ -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 index 0000000..e69de29 diff --git a/lib/setting/app_bar_exhibition.dart b/lib/setting/app_bar_exhibition.dart new file mode 100644 index 0000000..d0ed168 --- /dev/null +++ b/lib/setting/app_bar_exhibition.dart @@ -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 index 0000000..e0a8f44 --- /dev/null +++ b/lib/setting/drawer_exhibition.dart @@ -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 menuItems(MenuConverter converter) { + final globalData = GlobalData(); + final logger = globalData.logger; + return [ + 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 index 0000000..2627bf0 --- /dev/null +++ b/lib/setting/footer_exhibition.dart @@ -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 index 0000000..abe004c --- /dev/null +++ b/lib/setting/global_data.dart @@ -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 index 0000000..078722e --- /dev/null +++ b/pubspec.yaml @@ -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 index 0000000..af01327 --- /dev/null +++ b/test/widget_test.dart @@ -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); + }); +}