]> gitweb.hamatoma.de Git - exhibition.git/commitdiff
Daily work: app runs until first page
authorHamatoma <author.hamatoma.de>
Wed, 25 Aug 2021 21:57:54 +0000 (23:57 +0200)
committerHamatoma <author.hamatoma.de>
Wed, 25 Aug 2021 22:01:23 +0000 (00:01 +0200)
28 files changed:
.gitignore
dart_tools/bin/i18n_text_parser.dart
exhibition.json [new file with mode: 0644]
lib/base/defines.dart
lib/base/i18n.dart
lib/base/i18n_io.dart
lib/exhibition_app.dart [new file with mode: 0644]
lib/main.dart
lib/meta/roles_meta.dart
lib/meta/users_meta.dart
lib/page/start_page.dart [new file with mode: 0644]
lib/page/users/user_list.dart [deleted file]
lib/page/users/users_list_page.dart [new file with mode: 0644]
lib/setting/app_bar_exhibition.dart
lib/setting/drawer_exhibition.dart
lib/setting/footer_exhibition.dart
lib/setting/global_data.dart
lib/setting/installation.dart [new file with mode: 0644]
pubspec.yaml
rest_server/lib/rest_server.dart
rest_server/lib/services.dart
rest_server/pubspec.yaml
rest_server/test/sql_services_test.dart [new file with mode: 0644]
test/i18n_test.dart
test/i18n_text_parser_test.dart
test/widget_test.dart
tools/InitProject
tools/PackRestServer

index d3472d3cc6b04bcf47cd64e0212fdfdb2a38fc92..5bc5626befafa356e5f218f4396e7b8c5a30bfba 100644 (file)
@@ -60,3 +60,4 @@ data/i18n/exhibition.lokalize
 data/i18n/main.lqa
 *~
 data/i18n/lokalize-scripts/
+rest_server/tools/rest_server
index a816ed2c59b4670a47d9013647acd2a8f71712c7..48928cad923a6c995934328ac9df18828a1a3a78 100644 (file)
@@ -106,10 +106,10 @@ class I18nTextParser {
   RegExp? regExpExcluded;
   // ...........................1.............1.........2
   final regExpModule =
-      RegExp(r'''(\w+) = (I18N\(\)|i18N)\.module\(["'](.*?)['"]\);''');
+      RegExp(r'''(\w+) = (I18N\(\)|i18n)\.module\(["'](.*?)['"]\);''');
   final regExpDelimiter = RegExp(r'''["']''');
   // ..........1.............1..2..............................2
-  final regExpText = RegExp(r'(i18N|I18N\(\))\.(tr|ntr|trMulti|trArgs)\(');
+  final regExpText = RegExp(r'(i18n|I18N\(\))\.(tr|ntr|trMulti|trArgs)\(');
   final regExpStringConstant = RegExp(r'''^\s*(r?)(["'])(.*?)\2''');
   final regExpVariable = RegExp(r'^\w+');
   final regExpEmptyString = RegExp(r'^\s*$');
diff --git a/exhibition.json b/exhibition.json
new file mode 100644 (file)
index 0000000..3048aaf
--- /dev/null
@@ -0,0 +1,20 @@
+{
+  "form.card.padding": "16.0",
+  "form.gap.field_button.height": "16.0",
+  "productive": {
+    "version": "1.0",
+    "host": "localhost",
+    "port": 6000,
+    "scheme": "http",
+    "timeout": 10
+  },
+  "development": {
+    "version": "1.0",
+    "host": "pollsam.hamatoma.de",
+    "port": 443,
+    "scheme": "https",
+    "timeout": 10
+  },
+  "configDirectory": ".",
+  "dataDirectory": "."
+}
index 0b8ecc606be11173381b3f5650e43639bfc64f77..37a569a689b3aa8953a6f13869e47b9804197c7d 100644 (file)
@@ -1,3 +1,4 @@
+enum ServerEnvironment { productive, development }
 typedef YamlMap = Map<String, dynamic>;
 typedef YamlList = List<YamlMap>;
 
index ebb313d9a0e4c1b948d07f1e96e228fdf2563d57..018810618a8b39310ad42d907efd538707b66199 100644 (file)
@@ -1,8 +1,8 @@
 import 'package:dart_bones/dart_bones.dart';
 typedef MapModule = Map<String, String>;
 typedef MapPlural = Map<String, PluralInfo>;
-class I18n {
-  static I18n? instance;
+class I18N {
+  static I18N? instance;
   final BaseLogger logger;
   final globalModule = '!global';
   Map<String, MapModule> mapModules = {};
@@ -10,11 +10,11 @@ class I18n {
   String locale = 'de_DE';
   String loadedLocale = '';
   final regExpTag = RegExp(r'<#\d+>$');
-  factory I18n() {
+  factory I18N() {
     return instance!;
   }
 
-  I18n.internal(this.logger) {
+  I18N.internal(this.logger) {
     instance = this;
   }
 
index 0f39dd2f9cab00c49e4eb44d5b132f6d568ddb9b..e2633a2f4eb28306e958f343d8faa4c6a210734d 100644 (file)
@@ -5,7 +5,7 @@ import 'package:path/path.dart';
 
 import 'i18n.dart';
 
-class I18nIo extends I18n {
+class I18nIo extends I18N {
   String dataDirectory;
   final regExpId = RegExp(r'^msgid "(.+?)"$');
   final regExpTranslation = RegExp(r'^msgstr "(.+?)"$');
diff --git a/lib/exhibition_app.dart b/lib/exhibition_app.dart
new file mode 100644 (file)
index 0000000..d3957cc
--- /dev/null
@@ -0,0 +1,60 @@
+import 'package:dart_bones/dart_bones.dart';
+import 'package:exhibition/page/start_page.dart';
+import 'package:flutter/material.dart';
+import 'page/users/users_list_page.dart';
+import 'page/info_page.dart';
+import 'page/log_page.dart';
+
+import 'setting/global_data.dart';
+
+class ExhibitionApp extends StatefulWidget {
+  static BaseLogger logger = globalLogger;
+
+  @override
+  ExhibitionAppState createState() {
+    return ExhibitionAppState();
+  }
+}
+
+class ExhibitionAppState extends State<ExhibitionApp> {
+  @override
+  Widget build(BuildContext context) {
+    return MaterialApp(
+      debugShowCheckedModeBanner: true,
+      title: 'Exhibition',
+      theme: ThemeData(
+        primarySwatch: Colors.blue,
+        visualDensity: VisualDensity.adaptivePlatformDensity,
+      ),
+      initialRoute: '/start',
+      onGenerateRoute: _getRoute,
+    );
+  }
+}
+
+Route<dynamic>? _getRoute(RouteSettings settings) {
+  MaterialPageRoute? route;
+  StatefulWidget? page;
+  switch (settings.name) {
+    case '/users/list':
+      page = UsersListPage(GlobalData());
+      break;
+    case '/start':
+      page = StartPage(globalLogger);
+      break;
+    case '/info':
+      page = InfoPage(GlobalData());
+      break;
+    case '/log':
+      page = LogPage(GlobalData());
+      break;
+  }
+  if (page != null) {
+    route = MaterialPageRoute<void>(
+      settings: settings,
+      builder: (BuildContext context) => page!,
+      fullscreenDialog: false,
+    );
+  }
+  return route;
+}
index 202509be15d8afab8cc0096ea1b9610bd3832dae..5ba9748fb8b573b37e9bcb6231af5e450fa56707 100644 (file)
+import 'package:dart_bones/dart_bones.dart';
+import 'package:exhibition/base/i18n.dart';
+import 'package:exhibition/setting/global_data.dart';
 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.
-    );
-  }
+import 'exhibition_app.dart';
+import 'persistence/rest_persistence.dart';
+
+void main() async {
+  final logger = MemoryLogger(LEVEL_FINE);
+  ExhibitionApp.logger = logger;
+  I18N.internal(logger);
+  final configuration = BaseConfiguration({}, logger);
+  // We create a simplified instance. The real instance will be created in StartPage
+  GlobalData.internal(
+      configuration,
+      (p0) => null,
+      (p0) => null,
+      () => null,
+      RestPersistence(
+          version: '0.0.1',
+          host: '',
+          port: 0,
+          sessionTimeout: 30,
+          logger: logger),
+      logger);
+  runApp(ExhibitionApp());
 }
index 8023c454045f6cfa5d95fe65afba248d69275eb4..209a139889457cf78d046dfdb61211fd4a413329 100644 (file)
@@ -2,8 +2,8 @@ import '../base/defines.dart';
 import 'module_meta_data.dart';
 import '../base/i18n.dart';
 
-final i18N = I18n();
-final M = i18N.module("Roles");
+final i18n = I18N();
+final M = i18n.module("Roles");
 
 class RoleMeta extends ModuleMetaData {
   static RoleMeta instance = RoleMeta.internal();
@@ -12,19 +12,19 @@ class RoleMeta extends ModuleMetaData {
           'Roles',
           [
             PropertyMetaData('id', DataType.reference, ':primary:', 'combo',
-                i18N.tr('Id')),
+                i18n.tr('Id')),
             PropertyMetaData(
-                'name', DataType.string, ':notnull:', '', i18N.tr('Name'),
+                'name', DataType.string, ':notnull:', '', i18n.tr('Name'),
                 size: 64),
             PropertyMetaData('created', DataType.datetime, ':hidden:', '',
-                i18N.tr('Created')),
+                i18n.tr('Created')),
             PropertyMetaData('createdBy', DataType.string, ':hidden:', '',
-                i18N.tr('Created by'),
+                i18n.tr('Created by'),
                 size: 32),
             PropertyMetaData('changed', DataType.datetime, ':hidden:', '',
-                i18N.tr('Changed')),
+                i18n.tr('Changed')),
             PropertyMetaData('changedBy', DataType.string, ':hidden:', '',
-                i18N.tr('Changed by'),
+                i18n.tr('Changed by'),
                 size: 32),
           ],
         );
index daec6321594024616a50c374abcc5e29d7096ac6..095fc44a56d77d7bd02da554ac198e763a425b96 100644 (file)
@@ -1,8 +1,8 @@
 import '../base/defines.dart';
 import 'module_meta_data.dart';
 import '../base/i18n.dart';
-final i18N = I18n();
-final M = i18N.module("Users");
+final i18n = I18N();
+final M = i18n.module("Users");
 
 class UserMeta extends ModuleMetaData {
   static UserMeta instance = UserMeta.internal();
@@ -11,27 +11,27 @@ class UserMeta extends ModuleMetaData {
           'Users',
           [
             PropertyMetaData('id', DataType.reference, ':primary:', 'combo',
-                i18N.tr('Id')),
+                i18n.tr('Id')),
             PropertyMetaData('name', DataType.string, ':notnull:', '',
-                i18N.tr('Name'),
+                i18n.tr('Name'),
                 size: 64),
             PropertyMetaData('displayName', DataType.string, ':unique:notnull:',
-                '', i18N.tr('Display name', M),
+                '', i18n.tr('Display name', M),
                 size: 32),
             PropertyMetaData('email', DataType.string, ':unique:notnull:', '',
-                i18N.tr('EMail', M),
+                i18n.tr('EMail', M),
                 size: 255),
             PropertyMetaData('role', DataType.reference, ':notnull:', '',
-                i18N.tr('Role'), reference: 'roles.role_id'),
+                i18n.tr('Role'), reference: 'roles.role_id'),
             PropertyMetaData('created', DataType.datetime, ':hidden:', '',
-                i18N.tr('Created')),
+                i18n.tr('Created')),
             PropertyMetaData('createdBy', DataType.string, ':hidden:', '',
-                i18N.tr('Created by'),
+                i18n.tr('Created by'),
                 size: 32),
             PropertyMetaData('changed', DataType.datetime, ':hidden:', '',
-                i18N.tr('Changed')),
+                i18n.tr('Changed')),
             PropertyMetaData('changedBy', DataType.string, ':hidden:', '',
-                i18N.tr('Changed by'),
+                i18n.tr('Changed by'),
                 size: 32),
           ],
         );
diff --git a/lib/page/start_page.dart b/lib/page/start_page.dart
new file mode 100644 (file)
index 0000000..fbec21d
--- /dev/null
@@ -0,0 +1,114 @@
+import 'dart:convert' as convert;
+
+import 'package:dart_bones/dart_bones.dart';
+import 'package:exhibition/persistence/rest_persistence.dart';
+import 'package:exhibition/setting/app_bar_exhibition.dart';
+import 'package:exhibition/setting/drawer_exhibition.dart';
+import 'package:exhibition/setting/footer_exhibition.dart';
+import 'package:flutter/material.dart';
+
+//import '../helper/exhibition_defines.dart';
+import '../setting/installation.dart';
+import '../setting/global_data.dart';
+
+class StartPage extends StatefulWidget {
+  final BaseLogger logger;
+
+  StartPage(this.logger, {Key? key}) : super(key: key);
+
+  @override
+  StartPageState createState() {
+    // UserPageState.setPageData(pageData);
+    final rc = StartPageState(logger);
+    logger.log('Start');
+    return rc;
+  }
+}
+
+class StartPageState extends State<StartPage> {
+  final BaseLogger logger;
+  final GlobalKey<FormState> _formKey =
+      GlobalKey<FormState>(debugLabel: 'Start');
+
+  String logName = '';
+
+  StartPageState(this.logger);
+
+  @override
+  Widget build(BuildContext context) {
+    final padding = 16.0;
+    final listItems = <Widget>[
+      Text(
+        'Herzlich Willkommen',
+        style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
+      ),
+      SizedBox(height: padding),
+      ...(logger as MemoryLogger)
+          .messages
+          .map((line) => Text(
+                line,
+                style: TextStyle(
+                    color: line.startsWith('+++') ? Colors.red : Colors.black),
+              ))
+          .toList(),
+      SizedBox(height: padding),
+      ElevatedButton(
+          onPressed: () => setState(() => 1), child: Text('Aktualisieren')),
+    ];
+    return Scaffold(
+        //appBar: applicationData.appBarBuilder('Start'),
+        //drawer: applicationData.drawerBuilder(context),
+        body: Form(
+      key: _formKey,
+      child: Card(
+          margin: EdgeInsets.symmetric(vertical: padding, horizontal: padding),
+          child: Padding(
+              padding:
+                  EdgeInsets.symmetric(vertical: padding, horizontal: padding),
+              child: ListView(
+                children: listItems,
+              ))),
+    ));
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    Installation.initialize(logger);
+    // Note: Installation.kind must be set before.
+    final installation = Installation();
+    installation.checkPermissions().then((success) {
+      setState(() {
+        if (success) {
+          setState(() {
+            installation.checkConfiguration().then((success) {
+              if (success) {
+                logger.log(i18n.tr('Starting application'));
+                final content = FileSync()
+                    .fileAsString(installation.configurationFile.path);
+                final map = convert.jsonDecode(content);
+                final configuration = BaseConfiguration(map, logger);
+                final globalData = GlobalData.internal(
+                    configuration,
+                    AppBarExhibition.builder(),
+                    DrawerExhibition.builder(),
+                    FooterExhibition.builder(),
+                    RestPersistence.fromConfig(configuration, logger),
+                    logger);
+                globalData.initializeAsync().then((value) {
+                  Navigator.pushNamed(context, '/users/list');
+                });
+              }
+            });
+          });
+        };
+      });
+    });
+  }
+
+  void store(context) async {
+    if (_formKey.currentState!.validate()) {
+      _formKey.currentState!.save();
+    }
+  }
+}
diff --git a/lib/page/users/user_list.dart b/lib/page/users/user_list.dart
deleted file mode 100644 (file)
index 1675056..0000000
+++ /dev/null
@@ -1,150 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-import '../../base/defines.dart';
-import 'user_state.dart';
-import 'package:equatable/equatable.dart';
-
-class User extends StatefulWidget {
-  @override
-  _UserState createState() => _UserState();
-}
-
-class _UserState extends State<User> {
-  double? width;
-
-  @override
-  void initState() {
-    //context.read<UserBloc>().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<UserBloc, UserState>(
-      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,
-    );
-     */
-    return SizedBox(width: 10);
-  }
-
-  numberPressed(int number) {
-    //context.read<UserBloc>().add(NumberPressed(number: number));
-  }
-
-  operatorPressed(String operator) {
-    //context.read<UserBloc>().add(OperatorPressed(operator: operator));
-  }
-
-  calculateResult() {
-    //context.read<UserBloc>().add(CalculateResult());
-  }
-
-  clear() {
-    //context.read<UserBloc>().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/users_list_page.dart b/lib/page/users/users_list_page.dart
new file mode 100644 (file)
index 0000000..505c897
--- /dev/null
@@ -0,0 +1,54 @@
+import 'package:exhibition/setting/global_data.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import '../../base/defines.dart';
+import 'user_state.dart';
+import 'package:equatable/equatable.dart';
+import '../../base/i18n.dart';
+final i18n = I18N();
+class UsersListPage extends StatefulWidget {
+  final GlobalData globalData;
+  UsersListPage(this.globalData): super();
+  @override
+  _UsersListPageState createState() => _UsersListPageState();
+}
+
+class _UsersListPageState extends State<UsersListPage> {
+  double? width;
+
+  @override
+  void initState() {
+    //context.read<UserBloc>().add(UserEvent(EventSource.undef, EventCardinality.undef));
+    super.initState();
+  }
+
+  @override
+  void didChangeDependencies() {
+    width = MediaQuery.of(context).size.width;
+    super.didChangeDependencies();
+  }
+  final nameController = TextEditingController();
+  @override
+  Widget build(BuildContext context) {
+    final padding = 16.0;
+    return BlocBuilder<UserBloc, UserState>(
+      builder: (context, UserState state) {
+        return Column(
+          children: [
+            TextFormField(
+              controller: nameController,
+              readOnly: true,
+              decoration: InputDecoration(labelText: i18n.tr('Name')),
+            ),
+            SizedBox(height: padding),
+            Text('Liste'),
+            Spacer(),
+            //UserHistoryContainer(
+            //    calculations: state.history.reversed.toList())
+          ],
+        );
+      },
+    );
+  }
+
+}
index d0ed168941f4e4962555220b0b5774078c91fa83..1ec7c6aa1e0614e44bb350cf4a731b20dc881a33 100644 (file)
@@ -1,9 +1,10 @@
+import 'package:exhibition/setting/global_data.dart';
 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);
+  static AppBarBuilder builder() =>
+    (String title) => AppBarExhibition(title: title);
 }
index e0a8f447c9fb4fca11ac442f0c5371775f6a76b3..a8f7d1bb253c16712ad91404314a03183019fe71 100644 (file)
@@ -10,7 +10,7 @@ class DrawerExhibition extends Drawer {
       : super(child: buildGrid(context), key: key);
 
   /// Returns a method creating a drawer.
-  static DrawerExhibition builder(dynamic context) => DrawerExhibition(context);
+  static DrawerBuilder builder() => (context) => DrawerExhibition(context);
 
   static Widget buildGrid(context) {
     final converter = MenuConverter();
index 2627bf0c5708cb498d9074bbaeee7b83291fe4eb..5488cce53850370a2c02b5523cd344109967451d 100644 (file)
@@ -34,5 +34,5 @@ class FooterExhibition implements FooterInterface {
   }
 
   /// Returns a method creating a footer.
-  static FooterExhibition builder() => FooterExhibition();
+  static FooterBuilder builder() => () => FooterExhibition();
 }
index 8ec169443f6f1388c73e1a04e542390f676b505b..de67da352d64db25367d2035c8c75acf2ee828d5 100644 (file)
@@ -2,6 +2,7 @@ import 'package:dart_bones/dart_bones.dart';
 import 'package:exhibition/persistence/rest_persistence.dart';
 import 'package:flutter/material.dart';
 
+import '../base/defines.dart';
 import '../page/page_controller_exhibition.dart';
 
 typedef AppBarBuilder = Function(String);
@@ -24,10 +25,13 @@ abstract class FooterInterface {
   Widget widget(PageControllerExhibition controller);
 }
 
-/// Storage for  globale resources. This is a singleton.
+/// Storage for  global resources. This is a singleton.
 class GlobalData {
-  static const version = '2021.06.027.00';
+  static const version = '2021.08.24.00';
   static GlobalData? _instance;
+  static String baseDirectory = '';
+  static var serverEnvironment = ServerEnvironment.productive;
+
   final BaseLogger logger;
   final AppBarBuilder appBarBuilder;
   final DrawerBuilder drawerBuilder;
@@ -47,5 +51,10 @@ class GlobalData {
       this.drawerBuilder, this.footerBuilder,
       this.restPersistence, this.logger) {
     logger.log('Start');
+    _instance = this;
+  }
+  Future initializeAsync() async{
+    // Do customize!
+    return;
   }
 }
diff --git a/lib/setting/installation.dart b/lib/setting/installation.dart
new file mode 100644 (file)
index 0000000..68ae7d4
--- /dev/null
@@ -0,0 +1,233 @@
+import 'dart:async';
+import 'dart:convert' as convert;
+import 'dart:io';
+
+import 'package:dart_bones/dart_bones.dart';
+import 'package:path/path.dart' as path;
+import 'package:permission_handler/permission_handler.dart';
+
+import 'global_data.dart';
+import '../base/defines.dart';
+import '../base/i18n.dart';
+
+final i18n = I18N();
+final M = i18n.module('Example');
+
+/// Initializes the program if needed.
+class Installation {
+  static String osType = Platform.operatingSystem;
+  static Installation? instance;
+  static ServerEnvironment serverEnvironment = ServerEnvironment.productive;
+  static int retries = 1;
+  final neededPermissions = <Permission>[
+    Permission.camera,
+    Permission.location,
+    Permission.storage,
+    Permission.manageExternalStorage,
+  ];
+  bool _success = true;
+  Directory home = Directory('.');
+  Directory dataDirectory = Directory('.');
+  File configurationFile = File('');
+  File hivesFile = File('');
+  File dataFile = File('');
+  String user = '?';
+  final BaseLogger logger;
+
+  factory Installation() => instance ?? Installation.internal(globalLogger);
+
+  /// The private constructor.
+  /// [logger] does the logging.
+  Installation.internal(this.logger) {
+    logger.log('Version: ${GlobalData.version}');
+  }
+
+  bool get success => _success;
+
+  /// Checks whether there is a valid configuration file.
+  /// If not we try to create one.
+  /// Returns true: a valid configuration exists.
+  Future<bool> checkConfiguration() async {
+    bool forceInit = false;
+    if (GlobalData.baseDirectory.isEmpty) {
+      GlobalData.baseDirectory = home.path;
+    }
+    configurationFile = File(path.join(home.path, 'exhibition.json'));
+    if (!(forceInit ||
+        !configurationFile.existsSync() ||
+        !hasValidConfiguration())) {
+      logger.log(i18n.tr('Configuration OK'), LEVEL_DETAIL);
+    } else {
+      final content = '''{
+  "form.card.padding": "16.0",
+  "form.gap.field_button.height": "16.0",
+  "productive": {
+    "version": "1.0",
+    "host": "localhost",
+    "port": 6000,
+    "scheme": "http",
+    "timeout": 10
+  },
+  "development": {
+    "version": "1.0",
+    "host": "pollsam.hamatoma.de",
+    "port": 443,
+    "scheme": "https",
+    "timeout": 10
+  },
+  "configDirectory": "${home.path}",
+  "dataDirectory": "${dataDirectory.path}"
+}
+''';
+      logger.log(
+          i18n.tr('creating configuration:') + ' ${configurationFile.path}');
+      configurationFile.writeAsStringSync(content, mode: FileMode.writeOnly);
+    }
+    var rc = hasValidConfiguration();
+    if (rc) {
+      // do needed things!
+    }
+    if (rc) {
+      logger.log(i18n.tr('Configuration is OK'), LEVEL_DETAIL);
+    }
+    return rc;
+  }
+
+  /// Checks the needed access rights. If missing we request that.
+  /// Returns true: all rights are available
+  Future<bool> checkPermissions() async {
+    var rc = true;
+    //logger.log(i18n.tr('We do not check access rights'));
+    if (Platform.isAndroid) {
+      for (var permission in neededPermissions) {
+        logger.log(nameOfPermission(permission), LEVEL_FINE);
+        var status = await permission.status;
+        if (status == PermissionStatus.granted) {
+          logger.log('${nameOfPermission(permission)}: ${nameOfStatus(status)}',
+              LEVEL_DETAIL);
+        } else {
+          logger.log(i18n.tr('right') +
+              ' ${nameOfPermission(permission)}: ${nameOfStatus(status)}');
+          await permission.request();
+          status = await permission.status;
+          if (status != PermissionStatus.granted) {
+            logger.error(i18n.trArgs('missing right %1: %2', M,
+                [nameOfPermission(permission), nameOfStatus(status)]));
+            rc = _success = false;
+          }
+        }
+      }
+    }
+    if (rc) {
+      logger.log(i18n.tr('access rights OK'), LEVEL_DETAIL);
+    }
+    return rc;
+  }
+
+  /// Checks whether a valid configuration exists.
+  /// Returns true: a valid configuration exists.
+  bool hasValidConfiguration() {
+    final fileContent = configurationFile.readAsStringSync();
+    var rc = true;
+    try {
+      final map = convert.jsonDecode(fileContent);
+      final config = BaseConfiguration(map, logger);
+      final section = enumToString(serverEnvironment);
+      if (config.asString('host', section: section) == null ||
+          config.asInt('port', section: section) == null) {
+        rc = logger
+            .error('${configurationFile.path}: ' + i18n.tr('wrong: host/port'));
+      } else if (config.asString('configDirectory') == null ||
+          config.asString('dataDirectory') == null) {
+        rc = logger.error('${configurationFile.path}: ' +
+            i18n.tr('wrong: configDirectory/dataDirectory'));
+      }
+    } on FormatException catch (exc) {
+      rc = logger
+          .error('${configurationFile.path}: ' + i18n.tr('failure:' + ' $exc'));
+    }
+    return rc;
+  }
+
+  /// Prüft, ob eine gültige Datei mit den Bienenstockdaten existiert.
+  /// Liefert true, wenn ja.
+  bool hasValidHives() {
+    final fileContent = hivesFile.readAsStringSync();
+    final filename = hivesFile.path;
+    var rc = false;
+    try {
+      final list = convert.jsonDecode(fileContent);
+      rc = list is Iterable;
+      if (!rc) {
+        logger.error('$filename: "hives" ist kein Array');
+      } else {
+        var ix = -1;
+        for (var item in list) {
+          ix++;
+          rc = item is Map;
+          if (!rc) {
+            logger.error('$filename: hives[$ix] ist keine Map');
+          } else {
+            final map2 = item;
+            rc = (map2.containsKey('hiveid') &&
+                map2.containsKey('name') &&
+                map2.containsKey('lat') &&
+                map2.containsKey('long'));
+            if (!rc) {
+              logger.error('$filename: Fehler in hives[$ix]');
+              break;
+            }
+          }
+        }
+      }
+    } on FormatException catch (exc) {
+      logger.error('$filename: kein JSon: $exc');
+    }
+    return rc;
+  }
+
+  /// Liefert den Namen des Rechts [permission].
+  static String nameOfPermission(Permission permission) {
+    var rc;
+    if (permission == Permission.storage) {
+      rc = 'Speicher';
+    } else if (permission == Permission.camera) {
+      rc = 'Kamera';
+    } else {
+      if (permission == Permission.location) {
+        rc = 'Geo-Position';
+      } else {
+        rc = 'Permission-${permission.value}';
+      }
+    }
+    return rc;
+  }
+
+  /// Liefert den Namen des [status].
+  static String nameOfStatus(PermissionStatus status) {
+    String rc;
+    switch (status) {
+      case PermissionStatus.granted:
+        rc = 'erlaubt';
+        break;
+      case PermissionStatus.denied:
+        rc = 'verweigert';
+        break;
+      case PermissionStatus.restricted:
+        rc = 'eingeschränkt';
+        break;
+      case PermissionStatus.limited:
+        rc = 'begrenzt';
+        break;
+      case PermissionStatus.permanentlyDenied:
+        rc = 'gesperrt';
+        break;
+    }
+    return rc;
+  }
+
+  /// Initialisiert die einzige Instanz (Singleton).
+  static Future initialize(BaseLogger logger) async {
+    instance = Installation.internal(logger);
+  }
+}
index a4d9bf19f8309c02c61b8de091dc3aa62d4a1ee3..1de4d8344bfdbb4f923ff6e1cec44f5a60ef4e37 100644 (file)
@@ -35,6 +35,7 @@ dependencies:
   flutter_markdown: ^0.6.1
   flutter_bloc: ^7.1.0
   equatable: ^2.0.3
+  permission_handler: ^7.0.0
   # The following adds the Cupertino Icons font to your application.
   # Use with the CupertinoIcons class for iOS style icons.
   cupertino_icons: ^1.0.2
index cacd2278c48dd99ceb02dab5ee6d708a26ac5346..1db96728ea7db40337303f01faf417c9f16d95c6 100644 (file)
@@ -8,9 +8,9 @@ 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 'sql_storage.dart';
 import 'package:mysql1/mysql1.dart';
 
+import 'sql_storage.dart';
 const forbidden = 'forbidden';
 const wrongData = 'wrong data';
 const wrongParameters = 'wrong parameters';
@@ -46,7 +46,7 @@ void runIsolate(SendPort isolateToMainStream) {
 /// That is a HTTP server serving only the method POST (for queries and storages).
 /// The server is multi threaded by isolates.
 class RestServer {
-  static const _version = 'V2021.06.29.00';
+  static const _version = 'V2021.08.19.00';
   static BaseLogger? unittestLogger;
   BaseLogger logger = MemoryLogger(LEVEL_FINE);
   BaseConfiguration configuration = BaseConfiguration({}, globalLogger);
@@ -177,7 +177,7 @@ service:
   address: 0.0.0.0
   port: 58021
   dataDirectory: /var/cache/exhibition/data
-  sqlDirectory: /usr/share/exhibition/rest_server/data/sql
+  sqlDirectory: /usr/share/exhibition/data/sql
   threads: 2
   watchDogPause: 60
   # logFile: /var/log/local/exhibition.log
@@ -185,7 +185,7 @@ trace:
   answerLength: 200
 db:
   db: appexhibition
-  user: jonny
+  user: exhibition
   code: "Top Secret"
   host: localhost
   port: 3306
@@ -240,7 +240,28 @@ clientSessionTimeout: 900
     service.uninstallService(appName, user: appName, group: appName);
   }
 
+  /// Returns the program version.
   static String version() => _version;
+  static Future<String> sql(List<String> args, ArgResults results) async {
+    final logger = MemoryLogger(LEVEL_FINE);
+    final rc = StringBuffer();
+    var filename = results['configuration'];
+    if (!File(filename).existsSync()) {
+      print('+++ missing $filename');
+      unittestLogger?.error('missing configuration: $filename');
+    } else {
+      final configuration = Configuration.fromFile(filename, logger);
+      MySqlDb db = MySqlDb.fromConfiguration(configuration, logger);
+      await db.connect();
+      if (db.hasConnection){
+        for (var filename in args){
+          logger.log('=== script $filename:', LEVEL_DETAIL);
+          rc.writeln(await db.executeScriptFile(filename));
+        }
+      }
+    }
+    return rc.toString();
+  }
 }
 
 /// Implements an isolate instance.
@@ -282,7 +303,7 @@ class ServiceWorker {
     buffer.write('[');
     String? separator;
     for (var item in list) {
-      if (separator == null){
+      if (separator == null) {
         separator = ',';
       } else {
         buffer.write(separator);
@@ -292,7 +313,7 @@ class ServiceWorker {
       } else if (item is Map) {
         rowToJson(item, buffer);
       } else if (item is Iterable) {
-          arrayToJson(item, buffer);
+        arrayToJson(item, buffer);
       }
     }
     buffer.write(']');
@@ -681,8 +702,7 @@ class ServiceWorker {
           }
           break;
         case SqlStatementType.update:
-          final count =
-              await db!.updateRaw(sql, params: positionalParameters);
+          final count = await db!.updateRaw(sql, params: positionalParameters);
           rc = 'rows:$count';
           break;
         default:
index 88dff1b1fff9117074d8559c17f3b5b7bdd6702d..0052deba5ecc65314b3d0f424df5a5d60cbf6e61 100644 (file)
@@ -19,7 +19,7 @@ Future<int> run(List<String> args) async {
     if (results['help']) {
       try {
         try {
-          final serviceName = 'exhibition-sv';
+          final serviceName = 'exhibition.sv';
           print('''Usage $serviceName [mode] [options]
   Starts a REST server answering the client database requests.
 <mode>:
@@ -31,6 +31,8 @@ Future<int> run(List<String> args) async {
     Uninstalls the program.
   version
     Displays the version.
+  sql <script1> [<script2> ...]
+    Executes a SQL script
 <option>:
 ${parser.usage}
 Examples:
@@ -38,9 +40,10 @@ $serviceName install ./exhibition userrest
 $serviceName --help
 $serviceName --example
 $serviceName
-$serviceName --configuration=/tmp/exhibition.yaml
+$serviceName daemon --configuration=/tmp/exhibition.yaml
 $serviceName -c /tmp/exhibition.yaml
 $serviceName version
+$serviceName sql roles.sql users.sql
 ''');
         } catch (e, s) {
           print(s);
@@ -67,6 +70,10 @@ $serviceName version
         case 'version':
           print(RestServer.version());
           break;
+        case 'sql':
+          final data = await RestServer.sql(pureArguments, results);
+          print(data);
+          break;
         default:
           print('Unknown <mode>: $command. Try "--help"');
           break;
index fccd1f914f2fb9cb14627441b401c8c8b3caff73..1f915698b78cbb5e6e34cb5aa732c187c40d8b15 100644 (file)
@@ -11,7 +11,7 @@ dependencies:
   args: ^2.1.0
   path: ^1.8.0
   yaml: ^3.1.0
-  dart_bones: ^1.2.1
+  dart_bones: ^1.2.2
 
 dev_dependencies:
   lints: ^1.0.0
diff --git a/rest_server/test/sql_services_test.dart b/rest_server/test/sql_services_test.dart
new file mode 100644 (file)
index 0000000..3f49d1f
--- /dev/null
@@ -0,0 +1,160 @@
+import 'dart:io';
+import 'package:dart_bones/dart_bones.dart';
+import 'package:rest_server/services.dart';
+import 'package:test/test.dart';
+import 'package:path/path.dart' as path;
+
+void main() async {
+  final logger = MemoryLogger(LEVEL_DETAIL);
+  FileSync.initialize(logger);
+  final fileSync = FileSync();
+  final configFile = init(logger);
+  final baseDir = fileSync.tempDirectory('services', subDirs: 'unittest');
+  final configuration = Configuration.fromFile(configFile, logger);
+  //final fullTest = 'Never'.contains('x');
+  await TestDbInitializer.initTest(configuration, logger);
+  group('services', () {
+    test('version', () async {
+      await run(['version']);
+      expect(logger.errors.isEmpty, isTrue);
+    });
+    test('sql', () async {
+      final script = path.join(baseDir, 'script.sql');
+      fileSync.toFile(script, '''-- ! say === persons:
+select * from persons limit 1;
+''');
+      await run(['--configuration=$configFile', 'sql', script]);
+      expect(logger.errors.isEmpty, isTrue);
+    });
+  });
+}
+String init(BaseLogger logger) {
+  final fileSync = FileSync();
+  var rc = path.join(fileSync.tempDirectory('unittest'), 'config.yaml');
+  final sqlFile = '/tmp/unittest/sql/persons.sql.yaml';
+  fileSync.toFile(rc, '''---
+# Example configuration file for the rest server. Created by rest_server.
+service:
+  address: 0.0.0.0
+  port: 58031
+  dataDirectory: /tmp/unittest/data
+  sqlDirectory: ${path.dirname(sqlFile)}
+  threads: 1
+  watchDogPause: 60
+  # logFile: /var/log/local/exhibition.log
+trace:
+  answerLength: 200
+db:
+  db: dbtest
+  user: dbtest
+  code: "TopSecret"
+  host: localhost
+  port: 3306
+  primaryTable: persons
+  timeout: 30
+  traceDataLength: 200
+clientSessionTimeout: 900
+''');
+  fileSync.ensureDirectory('/tmp/unittest/sql');
+  fileSync.ensureDirectory('/tmp/unittest/data');
+  final file = File(sqlFile);
+  file.writeAsStringSync('''---
+# SQL statements of the module "Persons":
+module: Persons
+list:
+  type: list
+  parameters: []
+  sql: select * from persons;
+byId:
+  type: record
+  parameters: [ ":id" ]
+  sql: "select * from persons where person_id=:id;"
+update:
+  type: update
+  parameters: [":id", ":name", ":email", ":changedby"]
+  sql: "UPDATE persons SET
+    person_name=:name, person_email=:email, person_changed=NOW(), person_changedby=:changedby
+    WHERE person_id=:id;"
+insert:
+  type: insert
+  parameters: [":name", ":email", ":createdby"]
+  sql: "INSERT INTO persons(person_name, person_email, person_created, person_createdby)
+    VALUES(:name, :email, NOW(), :createdby);"
+delete:
+  type: delete
+  parameters: [':id']
+  sql: "DELETE FROM persons WHERE person_id=:id;"
+''');
+  return rc;
+}
+
+
+class TestDbInitializer {
+  MySqlDb? db;
+  final BaseConfiguration configuration;
+  final BaseLogger logger;
+  List<dynamic>? tables;
+  TestDbInitializer(this.configuration, this.logger) {
+    db = MySqlDb.fromConfiguration(configuration, logger);
+    db!.timeout = 30;
+  }
+
+  Future init() async {
+    if (!(db?.hasConnection ?? false)) {
+      await db!.connect();
+    }
+    if (!(db?.hasConnection ?? false)) {
+      logger.error('cannot connect to database');
+    } else {
+      tables = await db!.readAllAsLists('show tables;');
+    }
+  }
+
+  Future initPersons(MySqlDb db, BaseLogger logger) async {
+    String sql;
+    if (!await db.hasTable('persons')) {
+      sql = '''CREATE TABLE persons(
+  person_id int(10) unsigned NOT NULL AUTO_INCREMENT,
+  person_name varchar(200) NOT NULL,
+  person_email varchar(200) NOT NULL,
+  person_created timestamp NULL,
+  person_createdby varchar(16),
+  person_changed timestamp NULL,
+  person_changedby varchar(16),
+  PRIMARY KEY (person_id)
+);
+''';
+      if (await db.execute(sql) < 0) {
+        logger.error('cannot create persons');
+      }
+    }
+    final count = await db.readOneInt('select count(*) from persons;');
+    if (count == null || count < 3) {
+      sql = '''insert into persons 
+(person_id, person_name, person_email, person_created, person_createdby) values
+(11, 'Jones', 'jones@hamatoma.de', NOW(), 'daemon'),
+(12, 'Miller', 'miller@hamatoma.de', NOW(), 'daemon'),
+(13, 'Smith', 'smith@hamatoma.de', NOW(), 'daemon');
+''';
+      if (await db.execute(sql) < 0) {
+        logger.error('cannot create persons records');
+      }
+    }
+  }
+
+  Future initDbData() async {
+    try {
+      await init();
+      await initPersons(db!, logger);
+    } finally {
+      db?.close();
+    }
+  }
+
+  static Future initTest(
+      BaseConfiguration configuration, BaseLogger logger) async {
+    final initializer = TestDbInitializer(configuration, logger);
+    await initializer.initDbData();
+    initializer.db?.close();
+  }
+}
index 4f1cac77be207e690c9515bdba444115e642278b..7a92bf53c53fecf9e2851176ff0960ed4f9a70f5 100644 (file)
@@ -10,6 +10,7 @@ void main() {
   final fileSync = FileSync();
   final baseDir = init(logger);
   final targetDir = join(baseDir, nodeTarget);
+  fileSync.ensureDirectory(targetDir);
   test('parse', () {
     final parser = I18nTextParser(logger);
     parser.scanDirectory(baseDir, 0);
@@ -79,26 +80,26 @@ String init(MemoryLogger logger) {
   final baseDir = fileSync.tempDirectory('i18n', subDirs: 'unittest');
   final subDir = fileSync.tempDirectory('base', subDirs: 'unittest/i18n/lib');
   fileSync.toFile(join(subDir, 'simple.dart'), r'''import 'dart:io';
-final i18N = I18N();
-final M = i18N.module('Example');
+final i18n = I18N();
+final M = i18n.module('Example');
 String header(){
-  return i18N.tr('introduction', M) + " " + i18N.tr(
+  return i18n.tr('introduction', M) + " " + i18n.tr(
     "description",
     M);
 }
-String count() => i18N.ntr('one piece', '%d pieces', "Statistic", 3);
+String count() => i18n.ntr('one piece', '%d pieces', "Statistic", 3);
 String footer(){
   return I18N().tr("Status line");
 }
 ''');
   fileSync.toFile(join(subDir, 'args.dart'), r'''import 'dart:io';
-final i18N = I18N();
-final M = i18N.module("Sample");
-String count() => i18N.ntr(
+final i18n = I18N();
+final M = i18n.module("Sample");
+String count() => i18n.ntr(
   'one piece',
   '%d pieces', M, countItems('any'));
 String header(String user, String role){
-  return i18N.trArgs('Name: {0} Role: {1}', M, [user, role]) + " " + i18N.tr(
+  return i18n.trArgs('Name: {0} Role: {1}', M, [user, role]) + " " + i18n.tr(
     r'info', M);
 }
 String footer(){
index 7f2de1304d2557df50b01b062d090e8cab42aff3..435a75a9c17f877587e5d5db02d2d7b9b5840516 100644 (file)
@@ -140,26 +140,26 @@ String init(MemoryLogger logger) {
   final subDir =
       fileSync.tempDirectory('base', subDirs: 'unittest/i18n_parser/lib');
   fileSync.toFile(join(subDir, 'simple.dart'), r'''import 'dart:io';
-final i18N = I18N();
-final M = i18N.module('Example');
+final i18n = I18N();
+final M = i18n.module('Example');
 String header(){
-  return i18N.tr('introduction', M) + " " + i18N.tr(
+  return i18n.tr('introduction', M) + " " + i18n.tr(
     "description",
     M);
 }
-String count() => i18N.ntr('one piece', '%d pieces', "Statistic", 3);
+String count() => i18n.ntr('one piece', '%d pieces', "Statistic", 3);
 String footer(){
   return I18N().tr("Status line");
 }
 ''');
   fileSync.toFile(join(subDir, 'args.dart'), r'''import 'dart:io';
-final i18N = I18N();
-final M = i18N.module("Sample");
-String count() => i18N.ntr(
+final i18n = I18N();
+final M = i18n.module("Sample");
+String count() => i18n.ntr(
   'one piece',
   '%d pieces', M, countItems('any'));
 String header(String user, String role){
-  return i18N.trArgs('Name: {0} Role: {1}', M, [user, role]) + " " + i18N.tr(
+  return i18n.trArgs('Name: {0} Role: {1}', M, [user, role]) + " " + i18n.tr(
     r'info', M);
 }
 String footer(){
index af013271ceb77cfd234fb567f08e272e1bbe9328..b40338f04f67e54102928690a1a35e7ca3372faf 100644 (file)
@@ -5,6 +5,7 @@
 // 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:exhibition/exhibition_app.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_test/flutter_test.dart';
 
@@ -13,7 +14,7 @@ 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());
+    await tester.pumpWidget(ExhibitionApp());
 
     // Verify that our counter starts at 0.
     expect(find.text('0'), findsOneWidget);
index 10bd3e6efda4955b3952ae9b0256a03eb43dadd5..0786b560352c77818a7fece8a62c857a95d4d36b 100755 (executable)
@@ -1,8 +1,9 @@
 #! /bin/bash
 APP=$1
 APP2=$(echo ${APP:0:1} | tr a-z A-Z)${APP:1}
+DO_UPDATE=$2
 function Usage(){
-  echo "Usage: InitProject PROJECT"
+  echo "Usage: InitProject PROJECT [--update]"
   echo "  Prepares the project PROJECT for using the framework exhibition"
   echo "PROJECT: the name of the already existing project (created with 'flutter create')"
   echo "Example:"
@@ -25,46 +26,74 @@ function CopyFiles(){
     local projDir=$(pwd)
     cd ../exhibition
     local srcDir=$(pwd)    
-    for file in lib/base/*.dart lib/meta/module_meta_data.dart lib/meta/modules.dart \
-    rest_server/pubspec.yaml \
-    tools/CompileMerge tools/PackRestServer \
-    ; do
+    for file in lib/base/*.dart \
+        lib/meta/module_meta_data.dart \
+        lib/meta/modules.dart \
+        rest_server/pubspec.yaml \
+        tools/PackRestServer \
+      ; do
       cd $projDir
-      local dir=$(dirname $file)
-      EnsureDir $dir
-      echo "creating $file"
-      cp -a ../exhibition/$file $file
+      if [ -z "$DO_UPDATE" -o ! -e $file ]; then
+        local dir=$(dirname $file)
+        EnsureDir $dir
+        echo "creating $file"
+        cp -a ../exhibition/$file $file
+      fi
       cd $srcDir
     done
     cd $projDir
 }
+function CopyFile(){
+  local projDir=$1
+  local file=$2
+  local srcDir=$3
+  cd $projDir
+  local dir=$(dirname $file)
+  EnsureDir $dir
+  local file2=${file/exhibition/$APP}
+  echo "copy and replace $file -> $(basename $file2)"
+  sed <../exhibition/$file >$file2 -e "s/exhibition/$APP/g" -e "s/Exhibition/$APP2/g"
+  chmod --reference=../exhibition/$file $file2
+  cd $srcDir
+}
 function CopyAndReplace(){
     local projDir=$(pwd)
     cd ../exhibition
     local srcDir=$(pwd)
-    for file in lib/setting/*.dart \
-      pubspec.yaml \
+    local filesNewOnly=$(echo pubspec.yaml \
       lib/persistence/*.dart \
       lib/page/*.dart \
-      rest_server/lib/*.dart rest_server/bin/*.dart rest_server/tools/project.inc \
-      rest_server/CR \
       lib/main.dart \
       lib/meta/*_meta.dart \
       bin/*.dart \
-    ; do
-        cd $projDir
-        local dir=$(dirname $file)
-        EnsureDir $dir
-        local file2=${file/exhibition/$APP}
-        echo "copy and replace $file -> $(basename $file2)"
-        sed <../exhibition/$file >$file2 -e "s/exhibition/$APP/g" -e "s/Exhibition/$APP2/g"
-        chmod --reference=../exhibition/$file $file2
-        cd $srcDir
-    done
+    )
+    local filesAlways=$(echo rest_server/lib/*.dart \
+      rest_server/bin/*.dart \
+      rest_server/tools/project.inc \
+      rest_server/CR
+    )
+
+    if [ -z "$DO_UPDATE" ]; then
+      for file in lib/setting/*.dart \
+        pubspec.yaml \
+        lib/persistence/*.dart \
+        lib/page/*.dart \
+        rest_server/lib/*.dart rest_server/bin/*.dart rest_server/tools/project.inc \
+        rest_server/CR \
+        lib/main.dart \
+        lib/meta/*_meta.dart \
+        bin/*.dart \
+      ; do
+        CopyFile $projDir $file $srcDir
+      done
+    else
+    fi
     cd $projDir
 }
 function SymbolicLinks(){
-  for links in ReCreateMetaTool:. Meta:. ; do
+  for links in ReCreateMetaTool:. Meta:. dart_tools:. \
+      ; do
     local src=../exhibition/${links%:*}
     local trg=${links#*:}
     local trg2=$trg
@@ -75,18 +104,59 @@ function SymbolicLinks(){
       ln -s $src $trg2
     fi
   done
+  for links in dart_tools/tools/i18n_text_parser:tools \
+      dart_tools/tools/yaml_merger:tools \
+      ; do
+    local src=../../exhibition/${links%:*}
+    local trg=${links#*:}
+    local trg2=$trg
+    test $trg = . && trg2=$(basename $src)
+    echo "trg: $trg2"
+    if [ ! -L $trg2 ]; then
+      echo "linking $src -> $trg2"
+      ln -s $src $trg2
+    fi
+  done
 }
 function PrepareTools(){
-  test -e tools/yaml_merger || tools/CompileMerger
+  local tool
+  local curDir=$(pwd)
+  echo "PrepareTools: pwd=$curDir"
+  for tool in i18n_text_parser:CompileI18n \
+      yaml_merger:CompileMerge; do
+    local exe=${tool%:*}
+    local script=${tool#*:}
+    if [ ! -e ../exhibition/dart_tools/tools/$exe ]; then
+      cd ../exhibition/dart_tools
+      echo "executing $script"
+      tools/$script
+    fi
+    cd $curDir
+  done
+}
+function Flutter(){
+  local app=$1
+  flutter create $app
+  cd $app
+  flutter config linux
+}
+function Finish(){
+  pub upgrade
 }
 if [ -z "$APP" ]; then
   Usage "missing PROJECT"
-elif [ ! -d ../$APP ]; then
+elif [ -n $DO_UPDATE -a $DO_UPDATE != '--update' ]; then
+  Usage "Unknown option: $DO_UPDATE"
+elif [ -d $APP ]; then
+  Usage "PROJECT already exists."
+elif [ ! -d exhibition/dart_tools ]; then
   Usage "wrong current directory: Please go into the base folder of the project."
 else
+  test -n "$DO_UPDATE" && Flutter $APP
   PrepareTools
   MkDirs
   CopyFiles
   CopyAndReplace
   SymbolicLinks
+  Finish
 fi
index 4fbc0775ccc5091df3510ca50f2e0075ac838782..52766811650231a235c7d444fe4edd179c374378 100755 (executable)
@@ -4,17 +4,17 @@ SCRIPT_INSTALL=/tmp/InstallRestServer
 TAR_NODE=rest_server.tgz
 TAR=/tmp/$TAR_NODE
 source rest_server/tools/project.inc
+EXE=$PROJECT.sv
 
 function DoIt(){
   test -d $BASE_DIR && rm -Rf $BASE_DIR
-  mkdir -p $BASE_DIR/data/sql
+  mkdir -p $BASE_DIR/data/yaml_merger
   cd rest_server
   ./CR
   test -d data/sql/precedence || mkdir -p data/sql/precedence
   cd ..
-  cp -av rest_server/tools/rest_server $BASE_DIR
-  tools/CompileMerge
-  tools/yaml_merge data/sql data/sql/precedence $BASE_DIR/data/sql
+  cp -av rest_server/tools/rest_server $BASE_DIR/$EXE
+  tools/yaml_merger rest_server/data/sql rest_server/data/sql/precedence $BASE_DIR/data/sql
   cat <<EOS >$BASE_DIR/INSTALL.TXT
 # ------------
 # Installation:
@@ -23,8 +23,8 @@ function DoIt(){
 DIR=\$(pwd)
 mkdir -p /usr/share/$PROJECT && cd /usr/share/$PROJECT
 tar xzf \DIR/$TAR_NODE
-# ./rest_server install <executable> <service-name>
-./rest_server install /usr/share/$PROJECT/rest_server $PROJECT
+# ./$EXE install <executable> <service-name>
+./$EXE install /usr/share/$PROJECT/$EXE $PROJECT
 # see /usr/share/$PROJECT and /etc/$PROJECT
 EOS
   tar -C $BASE_DIR -czf $TAR .
@@ -33,7 +33,11 @@ EOS
 DIR=\$(pwd)
 mkdir -p /usr/share/$PROJECT && cd /usr/share/$PROJECT
 tar xzf \$DIR/rest_server.tgz
-./rest_server install /usr/share/$PROJECT/rest_server $PROJECT
+./$EXE install /usr/share/$PROJECT/$EXE $PROJECT
+sed -i -e "s/WorkingDirectory=.*/WorkingDirectory=\/usr\/share\/$PROJECT/" \
+  /etc/systemd/system/$PROJECT.service
+cd /etc/$PROJECT
+test -e $PROJECT.yaml && ln -s $PROJECT.yaml rest_server.yaml
 EOS
   chmod uog+x $SCRIPT_INSTALL
   echo "================"