From c67cf56e7afd1cdbf30fd05d89b2f0f7cf0fa2c2 Mon Sep 17 00:00:00 2001 From: Hamatoma Date: Mon, 2 Aug 2021 10:38:33 +0200 Subject: [PATCH] daily work * adaptions to fulfill the wiki descriptions --- ReCreateMetaTool | 3 + analysis_options.yaml | 2 +- bin/meta_tool.dart | 3 +- lib/base/defines.dart | 19 ++++ lib/base/helper.dart | 28 +++++ lib/meta/module_meta_data.dart | 84 +++++++++----- lib/meta/users_meta.dart | 42 ++++--- lib/page/user/user_data.dart | 21 ---- lib/page/user/user_list.dart | 0 lib/page/users/user_data.dart | 62 +++++++++++ lib/page/users/user_list.dart | 147 +++++++++++++++++++++++++ lib/page/users/user_state.dart | 55 +++++++++ lib/persistence/rest_persistence.dart | 2 +- lib/setting/global_data.dart | 7 +- pubspec.yaml | 4 +- rest_client/test/rest_client_test.dart | 1 - rest_server/lib/rest_server.dart | 2 +- 17 files changed, 408 insertions(+), 74 deletions(-) create mode 100755 ReCreateMetaTool create mode 100644 lib/base/defines.dart create mode 100644 lib/base/helper.dart delete mode 100644 lib/page/user/user_data.dart delete mode 100644 lib/page/user/user_list.dart create mode 100644 lib/page/users/user_data.dart create mode 100644 lib/page/users/user_list.dart create mode 100644 lib/page/users/user_state.dart diff --git a/ReCreateMetaTool b/ReCreateMetaTool new file mode 100755 index 0000000..bbfbb6b --- /dev/null +++ b/ReCreateMetaTool @@ -0,0 +1,3 @@ +#! /bin/bash +APP=meta_tool +dart compile exe bin/$APP.dart -o tools/$APP diff --git a/analysis_options.yaml b/analysis_options.yaml index 61b6c4d..1383f10 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -7,7 +7,7 @@ # 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 +#include: package:flutter_lints/flutter.yaml linter: # The lint rules applied to this project can be customized in the diff --git a/bin/meta_tool.dart b/bin/meta_tool.dart index 7939f7e..5f0f97c 100644 --- a/bin/meta_tool.dart +++ b/bin/meta_tool.dart @@ -156,6 +156,7 @@ void updateModules(){ for (var name in modules){ ModuleMetaData module = moduleByName(name); String filename = classToFilename(module.moduleNameSingular) + '_data.dart'; - writeFile('lib/page/$filename', module.createModuleData()); + final directory = name.toLowerCase(); + writeFile('lib/page/$directory/$filename', module.createModuleData()); } } \ No newline at end of file diff --git a/lib/base/defines.dart b/lib/base/defines.dart new file mode 100644 index 0000000..0b8ecc6 --- /dev/null +++ b/lib/base/defines.dart @@ -0,0 +1,19 @@ +typedef YamlMap = Map; +typedef YamlList = List; + +///JsonMap or JsonList +typedef YamlData = Object; + +enum EventSource { undef, localData, remoteData } +enum EventCardinality { undef, record, list } +enum DataType { + bool, + currency, + date, + datetime, + float, + int, + nat, + reference, + string +} diff --git a/lib/base/helper.dart b/lib/base/helper.dart new file mode 100644 index 0000000..1ee83b3 --- /dev/null +++ b/lib/base/helper.dart @@ -0,0 +1,28 @@ +import 'defines.dart'; + +/// Returns the [dataType] specific value of [data]. +dynamic valueOf(DataType dataType, String data) { + dynamic rc; + switch (dataType) { + case DataType.bool: + rc = data == 'true'; + break; + case DataType.currency: + case DataType.float: + rc = double.tryParse(data); + break; + case DataType.date: + case DataType.datetime: + rc = DateTime.parse(data); + break; + case DataType.int: + case DataType.nat: + case DataType.reference: + rc = int.tryParse(data); + break; + case DataType.string: + rc = data; + break; + } + return rc; +} diff --git a/lib/meta/module_meta_data.dart b/lib/meta/module_meta_data.dart index 712fdb8..46b9f63 100644 --- a/lib/meta/module_meta_data.dart +++ b/lib/meta/module_meta_data.dart @@ -1,18 +1,8 @@ import 'package:path/path.dart'; -enum DataType { - bool, - currency, - date, - datetime, - float, - int, - nat, - reference, - string, -} +import '../base/defines.dart'; + +class MetaException extends FormatException {} -class MetaException extends FormatException{ -} /// Stores the meta data of a module. class ModuleMetaData { /// The module name, e.g. users @@ -23,26 +13,36 @@ class ModuleMetaData { /// The related database table. String tableName; + final bool shortModifiedLabel; List list; final Map properties = {}; String columnPrefix; ModuleMetaData(this.moduleName, this.list, {this.tableName = '', this.moduleNameSingular = '', - this.columnPrefix = ''}) { + this.columnPrefix = '', + this.shortModifiedLabel = false}) { 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(); + for (var item in list) { + item.module = this; + properties[item.name] = item; + if (item.columnName.isEmpty) { + final prefix = shortModifiedLabel && + ['created', 'createdBy', 'changed', 'changedBy'] + .contains(item.name) + ? '' + : columnPrefix + '_'; + item.columnName = prefix + item.name.toLowerCase(); + } + } } /// Returns a DDL statement creating the database table. @@ -68,17 +68,43 @@ class ModuleMetaData { String createModuleData() { final buffer = StringBuffer(); buffer.write('// DO NOT CHANGE. This file is created by the meta_tool\n'); - buffer.write('class $moduleNameSingular{\n'); + buffer.write("import '../../base/defines.dart';\n"); + buffer.write("import '../../base/helper.dart';\n"); + buffer.write('class ${moduleNameSingular}Data{\n'); for (var item in list) { buffer.write(' ' + dartType(item.dataType) + '? ${item.name};\n'); } - buffer.write(' $moduleNameSingular({'); + buffer.write(' ${moduleNameSingular}Data({'); String separator = ''; for (var item in list) { buffer.write('$separator this.${item.name}'); separator = ','; } - buffer.write('});\n}\n'); + buffer.write('});\n'); + buffer.write(' ${moduleNameSingular}Data.fromYaml(YamlMap data) {\n'); + for (var item in list) { + final name = item.columnName; + final type = item.dataType.toString(); + //id = data.containsKey('id') ? int.tryParse(data['id']) : null; + buffer.write( + " ${item.name} = data.containsKey('$name') ? valueOf($type, data['$name']) : null;\n"); + } + buffer.write(' }\n'); + buffer.write(' static DataType? dataTypeOf(String name) {\n'); + buffer.write(' DataType? rc;\n'); + buffer.write(' switch(name){\n'); + for (var item in list) { + buffer.write(" case '${item.name}':\n"); + buffer.write(' rc = ${item.dataType};\n'); + buffer.write(' break;\n'); + } + buffer.write(''' default: + break; + } + return rc; + } +'''); + buffer.write('}\n'); return buffer.toString(); } @@ -180,6 +206,7 @@ class ModuleMetaData { class PropertyMetaData { /// Name of the property (Dart name). final String name; + ModuleMetaData module = ModuleMetaData('', []); /// A colon delimited list of options, e.g. ':notnull:unique:'. final String options; @@ -190,15 +217,15 @@ class PropertyMetaData { /// The size if dataType is DataType.string. final int size; - String? columnName; + 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}); + {this.columnName = '', this.size = 0, this.reference}); } -String filenameToClass(String filename){ +String filenameToClass(String filename) { final parts = basenameWithoutExtension(filename).split('_'); String rc = ''; for (var part in parts) { @@ -208,14 +235,15 @@ String filenameToClass(String filename){ } return rc; } -String classToFilename(String className){ + +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){ + 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/users_meta.dart b/lib/meta/users_meta.dart index 092f07a..8a6ec10 100644 --- a/lib/meta/users_meta.dart +++ b/lib/meta/users_meta.dart @@ -1,22 +1,30 @@ +import '../base/defines.dart'; import 'module_meta_data.dart'; -class UserMeta extends ModuleMetaData { +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(){ + 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', + shortModifiedLabel: true, + ); + factory UserMeta() { return instance; } -} \ No newline at end of file +} diff --git a/lib/page/user/user_data.dart b/lib/page/user/user_data.dart deleted file mode 100644 index 03244c5..0000000 --- a/lib/page/user/user_data.dart +++ /dev/null @@ -1,21 +0,0 @@ -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 deleted file mode 100644 index e69de29..0000000 diff --git a/lib/page/users/user_data.dart b/lib/page/users/user_data.dart new file mode 100644 index 0000000..8ff59ad --- /dev/null +++ b/lib/page/users/user_data.dart @@ -0,0 +1,62 @@ +// DO NOT CHANGE. This file is created by the meta_tool +import '../../base/defines.dart'; +import '../../base/helper.dart'; +class UserData{ + int? id; + String? name; + String? displayName; + String? email; + int? role; + DateTime? created; + String? createdBy; + DateTime? changed; + String? changedBy; + UserData({ this.id, this.name, this.displayName, this.email, this.role, this.created, this.createdBy, this.changed, this.changedBy}); + UserData.fromYaml(YamlMap data) { + id = data.containsKey('user_id') ? valueOf(DataType.reference, data['user_id']) : null; + name = data.containsKey('user_name') ? valueOf(DataType.string, data['user_name']) : null; + displayName = data.containsKey('user_displayname') ? valueOf(DataType.string, data['user_displayname']) : null; + email = data.containsKey('user_email') ? valueOf(DataType.string, data['user_email']) : null; + role = data.containsKey('user_role') ? valueOf(DataType.reference, data['user_role']) : null; + created = data.containsKey('created') ? valueOf(DataType.datetime, data['created']) : null; + createdBy = data.containsKey('createdby') ? valueOf(DataType.string, data['createdby']) : null; + changed = data.containsKey('changed') ? valueOf(DataType.datetime, data['changed']) : null; + changedBy = data.containsKey('changedby') ? valueOf(DataType.string, data['changedby']) : null; + } + static DataType? dataTypeOf(String name) { + DataType? rc; + switch(name){ + case 'id': + rc = DataType.reference; + break; + case 'name': + rc = DataType.string; + break; + case 'displayName': + rc = DataType.string; + break; + case 'email': + rc = DataType.string; + break; + case 'role': + rc = DataType.reference; + break; + case 'created': + rc = DataType.datetime; + break; + case 'createdBy': + rc = DataType.string; + break; + case 'changed': + rc = DataType.datetime; + break; + case 'changedBy': + rc = DataType.string; + break; + default: + break; + } + return rc; + } +} + diff --git a/lib/page/users/user_list.dart b/lib/page/users/user_list.dart new file mode 100644 index 0000000..316e529 --- /dev/null +++ b/lib/page/users/user_list.dart @@ -0,0 +1,147 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../base/defines.dart'; +import 'user_state.dart'; +import 'user_data.dart'; +import 'package:equatable/equatable.dart'; + +class User extends StatefulWidget { + @override + _UserState createState() => _UserState(); +} + +class _UserState extends State { + double? width; + + @override + void initState() { + context.read().add(UserEvent(EventSource.undef, EventCardinality.undef)); + super.initState(); + } + + @override + void didChangeDependencies() { + width = MediaQuery.of(context).size.width; + super.didChangeDependencies(); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, UserState state) { + return Column( + children: [ + ResultDisplay( + text: _getDisplayText(state.calculationModel), + ), + Row( + children: [ + _getButton(text: '7', onTap: () => numberPressed(7)), + _getButton(text: '8', onTap: () => numberPressed(8)), + _getButton(text: '9', onTap: () => numberPressed(9)), + _getButton( + text: 'x', + onTap: () => operatorPressed('*'), + backgroundColor: Color.fromRGBO(220, 220, 220, 1)), + ], + ), + Row( + children: [ + _getButton(text: '4', onTap: () => numberPressed(4)), + _getButton(text: '5', onTap: () => numberPressed(5)), + _getButton(text: '6', onTap: () => numberPressed(6)), + _getButton( + text: '/', + onTap: () => operatorPressed('/'), + backgroundColor: Color.fromRGBO(220, 220, 220, 1)), + ], + ), + Row( + children: [ + _getButton(text: '1', onTap: () => numberPressed(1)), + _getButton(text: '2', onTap: () => numberPressed(2)), + _getButton(text: '3', onTap: () => numberPressed(3)), + _getButton( + text: '+', + onTap: () => operatorPressed('+'), + backgroundColor: Color.fromRGBO(220, 220, 220, 1)) + ], + ), + Row( + children: [ + _getButton( + text: '=', + onTap: calculateResult, + backgroundColor: Colors.orange, + textColor: Colors.white), + _getButton(text: '0', onTap: () => numberPressed(0)), + _getButton( + text: 'C', + onTap: clear, + backgroundColor: Color.fromRGBO(220, 220, 220, 1)), + _getButton( + text: '-', + onTap: () => operatorPressed('-'), + backgroundColor: Color.fromRGBO(220, 220, 220, 1)), + ], + ), + Spacer(), + UserHistoryContainer( + calculations: state.history.reversed.toList()) + ], + ); + }, + ); + } + + Widget _getButton( + {required String text, + required void Function() onTap, + Color backgroundColor = Colors.white, + Color textColor = Colors.black}) { + return CalculatorButton( + label: text, + onTap: onTap, + // @ToDo: width always != null? + size: width! / 4 - 12, + backgroundColor: backgroundColor, + labelColor: textColor, + ); + } + + numberPressed(int number) { + context.read().add(NumberPressed(number: number)); + } + + operatorPressed(String operator) { + context.read().add(OperatorPressed(operator: operator)); + } + + calculateResult() { + context.read().add(CalculateResult()); + } + + clear() { + context.read().add(ClearUser()); + } + + String _getDisplayText(UserModel model) { + if (model.result != null) { + return '${model.result}'; + } + + if (model.secondOperand != null) { + return '${model.firstOperand}${model.operator}${model.secondOperand}'; + } + + if (model.operator != null) { + return '${model.firstOperand}${model.operator}'; + } + + if (model.firstOperand != null) { + return '${model.firstOperand}'; + } + + return "${model.result ?? 0}"; + } +} diff --git a/lib/page/users/user_state.dart b/lib/page/users/user_state.dart new file mode 100644 index 0000000..e23b4a5 --- /dev/null +++ b/lib/page/users/user_state.dart @@ -0,0 +1,55 @@ +import 'package:equatable/equatable.dart'; +import 'package:exhibition/setting/global_data.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../base/defines.dart'; +import '../../persistence/rest_persistence.dart'; +import 'user_data.dart'; + +class RemoteListUserEvent extends UserEvent { + final Map filters; + RemoteListUserEvent(this.filters) + : super(EventSource.remoteData, EventCardinality.list); + @override + List get props => [filters]; +} + +class UserBloc extends Bloc { + final YamlList? recordList; + + UserBloc({this.recordList}) : super(UserStateInitial()); + + @override + Stream mapEventToState( + UserEvent event, + ) async* { + if (event is RemoteListUserEvent) { + final list = await GlobalData() + .restPersistence + ?.query(what: 'query', data: event.filters); + yield UserListRemote(list ?? []); + } else { + yield UserStateInitial(); + } + } +} + +abstract class UserEvent extends Equatable { + final EventSource eventSource; + final EventCardinality eventCardinality; + UserEvent(this.eventSource, this.eventCardinality); +} + +class UserListRemote extends UserState { + final YamlList records; + UserListRemote(this.records); + @override + List get props => [records]; +} + +abstract class UserState extends Equatable {} + +class UserStateInitial extends UserState { + @override + List get props => []; +} diff --git a/lib/persistence/rest_persistence.dart b/lib/persistence/rest_persistence.dart index d94597c..54206b7 100644 --- a/lib/persistence/rest_persistence.dart +++ b/lib/persistence/rest_persistence.dart @@ -89,7 +89,7 @@ class RestPersistence extends Persistence { @override Future query( {required String what, Map? data}) async { - var rc; + String rc; final params2 = data == null ? '{}' : convert.jsonEncode(data); final answer = await runRequest(what, body: params2, headers: jsonHeader); if (answer.isNotEmpty && (answer.startsWith('{') || answer.startsWith('['))) { diff --git a/lib/setting/global_data.dart b/lib/setting/global_data.dart index abe004c..8ec1694 100644 --- a/lib/setting/global_data.dart +++ b/lib/setting/global_data.dart @@ -1,4 +1,5 @@ import 'package:dart_bones/dart_bones.dart'; +import 'package:exhibition/persistence/rest_persistence.dart'; import 'package:flutter/material.dart'; import '../page/page_controller_exhibition.dart'; @@ -32,17 +33,19 @@ class GlobalData { final DrawerBuilder drawerBuilder; final FooterBuilder footerBuilder; final BaseConfiguration configuration; + final RestPersistence? restPersistence; factory GlobalData() => _instance ?? GlobalData(); GlobalData.dummy() : this.internal(BaseConfiguration({}, globalLogger), (input) => '', - (input) => '', DummyFooter.builder, globalLogger); + (input) => '', DummyFooter.builder, null, 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) { + this.drawerBuilder, this.footerBuilder, + this.restPersistence, this.logger) { logger.log('Start'); } } diff --git a/pubspec.yaml b/pubspec.yaml index 078722e..a4d9bf1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,7 +18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=2.14.0-321.0.dev <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 @@ -33,6 +33,8 @@ dependencies: dart_bones: "^1.1.1" url_launcher: ^6.0.3 flutter_markdown: ^0.6.1 + flutter_bloc: ^7.1.0 + equatable: ^2.0.3 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 diff --git a/rest_client/test/rest_client_test.dart b/rest_client/test/rest_client_test.dart index 6442bbe..f50395f 100644 --- a/rest_client/test/rest_client_test.dart +++ b/rest_client/test/rest_client_test.dart @@ -24,7 +24,6 @@ void main() async { final configuration = Configuration.fromFile(config, logger); final client = RestPersistence.fromConfig(configuration, logger); group('query', () { - Map result; Map data; test('list', () async { data = {'module': 'Persons', 'sql': 'list'}; diff --git a/rest_server/lib/rest_server.dart b/rest_server/lib/rest_server.dart index f59e323..44b95ad 100644 --- a/rest_server/lib/rest_server.dart +++ b/rest_server/lib/rest_server.dart @@ -8,7 +8,7 @@ import 'package:crypto/crypto.dart'; import 'package:dart_bones/dart_bones.dart'; import 'package:http/http.dart' as http; import 'package:path/path.dart' as path; -import 'package:rest_server/sql_storage.dart'; +import 'sql_storage.dart'; import 'package:mysql1/mysql1.dart'; const forbidden = 'forbidden'; -- 2.39.5