From 6f1390c6a128810226072d3968b8debf626e302c Mon Sep 17 00:00:00 2001 From: Hamatoma Date: Sat, 10 Oct 2020 12:58:53 +0200 Subject: [PATCH] listForm() and createForm() * listForm() works in in role_list_page * createForm() works in role_create_page * changeForm() works in role_change_page --- .gitignore | 2 + lib/app.dart | 40 ++-- lib/flutter_bones.dart | 3 +- lib/src/helper/filters.dart | 103 ----------- lib/src/model/button_model.dart | 18 +- lib/src/model/column_model.dart | 6 + lib/src/model/field_model.dart | 6 + lib/src/model/model_types.dart | 16 +- lib/src/model/module_model.dart | 7 + lib/src/model/page_model.dart | 1 + lib/src/model/section_model.dart | 5 +- lib/src/model/standard/role_model.dart | 9 +- lib/src/model/text_field_model.dart | 4 +- lib/src/page/role/role_change_page.dart | 43 +++++ lib/src/page/role/role_controller.dart | 12 ++ lib/src/page/role/role_create_page.dart | 43 +++++ lib/src/page/{ => role}/role_list_page.dart | 37 ++-- lib/src/page/role_create_page.dart | 67 ------- lib/src/widget/edit_form.dart | 57 ++++++ lib/src/widget/filters.dart | 172 +++++++++++++++++ lib/src/widget/list_form.dart | 68 ++++--- lib/src/widget/module_controller.dart | 144 +++++++++++++++ lib/src/widget/raised_button_bone.dart | 53 ++++-- lib/src/widget/simple_form.dart | 35 ++-- lib/src/widget/text_form_field_bone.dart | 129 +++++++++++++ lib/src/widget/view.dart | 1 + lib/src/widget/widget_helper.dart | 16 ++ lib/src/widget/widget_list.dart | 48 +++++ test/widget/widget_test.dart | 195 ++++++++++++++++++++ test/widget_test.dart | 42 ++--- 30 files changed, 1092 insertions(+), 290 deletions(-) delete mode 100644 lib/src/helper/filters.dart create mode 100644 lib/src/page/role/role_change_page.dart create mode 100644 lib/src/page/role/role_controller.dart create mode 100644 lib/src/page/role/role_create_page.dart rename lib/src/page/{ => role}/role_list_page.dart (76%) delete mode 100644 lib/src/page/role_create_page.dart create mode 100644 lib/src/widget/edit_form.dart create mode 100644 lib/src/widget/filters.dart create mode 100644 lib/src/widget/module_controller.dart create mode 100644 lib/src/widget/text_form_field_bone.dart create mode 100644 lib/src/widget/widget_helper.dart create mode 100644 lib/src/widget/widget_list.dart create mode 100644 test/widget/widget_test.dart diff --git a/.gitignore b/.gitignore index 77fcaac..49bef3d 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ pubspec.lock android/ ios/ web/ +linux/ # IntelliJ related *.iml @@ -46,3 +47,4 @@ app.*.symbols # Obfuscation related app.*.map.json +/linux/ diff --git a/lib/app.dart b/lib/app.dart index f345958..3f40034 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,8 +1,12 @@ import 'package:dart_bones/dart_bones.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bones/flutter_bones.dart'; -import 'package:flutter_bones/src/page/role_list_page.dart'; -import 'package:flutter_bones/src/private/bsettings.dart'; + +import 'src/helper/settings.dart'; +import 'src/page/login_page.dart'; +import 'src/page/role/role_change_page.dart'; +import 'src/page/role/role_create_page.dart'; +import 'src/page/role/role_list_page.dart'; +import 'src/private/bsettings.dart'; class BoneApp extends StatefulWidget { @override @@ -20,7 +24,6 @@ class BoneApp extends StatefulWidget { } class BoneAppState extends State { - @override Widget build(BuildContext context) { return MaterialApp( @@ -29,26 +32,33 @@ class BoneAppState extends State { primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), - initialRoute: '/role/list', + initialRoute: '/role/change', onGenerateRoute: _getRoute, ); } - } Route _getRoute(RouteSettings settings) { MaterialPageRoute route; - if (settings.name == '/role/list') { - route = MaterialPageRoute( - settings: settings, - builder: (BuildContext context) => - RoleListPage(BSettings.instance.pageData), - fullscreenDialog: false, - ); - } else { + StatefulWidget page; + switch (settings.name) { + case '/role/list': + page = RoleListPage(BSettings.instance.pageData); + break; + case '/role/change': + page = RoleChangePage(BSettings.instance.pageData); + break; + case '/role/create': + page = RoleCreatePage(BSettings.instance.pageData); + break; + default: + page = LoginPage(BSettings.instance.pageData); + break; + } + if (page != null) { route = MaterialPageRoute( settings: settings, - builder: (BuildContext context) => LoginPage(BSettings.instance.pageData), + builder: (BuildContext context) => page, fullscreenDialog: false, ); } diff --git a/lib/flutter_bones.dart b/lib/flutter_bones.dart index cf34b7d..0d8fa51 100644 --- a/lib/flutter_bones.dart +++ b/lib/flutter_bones.dart @@ -22,8 +22,9 @@ export 'src/model/text_model.dart'; export 'src/model/widget_model.dart'; export 'src/page/login_page.dart'; export 'src/page/page_data.dart'; -export 'src/page/role_create_page.dart'; +export 'src/page/role/role_create_page.dart'; export 'src/page/user_page.dart'; export 'src/widget/raised_button_bone.dart'; export 'src/widget/simple_form.dart'; +export 'src/widget/text_form_field_bone.dart'; export 'src/widget/view.dart'; diff --git a/lib/src/helper/filters.dart b/lib/src/helper/filters.dart deleted file mode 100644 index c31d21a..0000000 --- a/lib/src/helper/filters.dart +++ /dev/null @@ -1,103 +0,0 @@ -import 'package:dart_bones/dart_bones.dart'; -import 'package:flutter/material.dart'; - -typedef FilterPredicate = bool Function(Map row); - -class FilterItem { - final String name; - final String label; - final FilterType filterType; - final String toolTip; - final FilterPredicate filterPredicate; - var value; - - FilterItem( - {this.label, - this.filterType, - this.toolTip, - this.name, - this.filterPredicate}); - - bool isValid(Map row) { - bool rc = true; - if (filterPredicate != null) { - rc = filterPredicate(row); - } else { - final current = row[name].toString(); - final value2 = value ?? ''; - switch (filterType) { - case FilterType.pattern: - rc = value2 == '' - ? true - : (value2.startsWidth('*') - ? current.contains(value2.substring(1)) - : current.startsWith(value2)); - break; - case FilterType.dateFrom: - // TODO: Handle this case. - break; - case FilterType.dateTil: - // TODO: Handle this case. - break; - case FilterType.dateTimeFrom: - // TODO: Handle this case. - break; - case FilterType.dateTimeTil: - // TODO: Handle this case. - break; - default: - rc = true; - break; - } - } - return rc; - } -} - -class Filters { - var filters = []; - final BaseLogger logger; - - Filters(this.logger); - - Filters.ready(this.filters, this.logger); - - void add(FilterItem item) => filters.add(item); - - FilterItem byName(String name) => - filters.firstWhere((element) => element.name == name); - - /// Returns a list of widgets for the filter fields. - List getWidgets() { - final rc = filters.map((filter) { - Widget rc = TextFormField( - decoration: InputDecoration(labelText: filter.label), - ); - if (filter.toolTip != null) { - rc = Tooltip(message: filter.toolTip, child: rc); - } - return rc; - }).toList(); - return rc; - } - - /// Tests whether the [row] belongs to the result. - bool isValid(Map row) { - var rc = true; - for (var filter in filters) { - if (!filter.isValid(row)) { - rc = false; - break; - } - } - return rc; - } -} - -enum FilterType { - pattern, - dateFrom, - dateTil, - dateTimeFrom, - dateTimeTil, -} diff --git a/lib/src/model/button_model.dart b/lib/src/model/button_model.dart index 3fba73b..eb5721c 100644 --- a/lib/src/model/button_model.dart +++ b/lib/src/model/button_model.dart @@ -5,7 +5,7 @@ import 'package:flutter_bones/flutter_bones.dart'; typedef ButtonOnPressed = void Function(String name); /// Describes a button widget. -class ButtonModel extends WidgetModel { +class ButtonModel extends WidgetModel implements ButtonCallbackController { static final regExprOptions = RegExp(r'^(undef)$'); String text; String name; @@ -48,6 +48,22 @@ class ButtonModel extends WidgetModel { @override String widgetName() => name; + + @override + getOnHighlightChanged( + String customString, ButtonCallbackController controller) { + return null; + } + + @override + getOnLongPressed(String customString, ButtonCallbackController controller) { + return null; + } + + @override + getOnPressed(String customString, ButtonCallbackController controller) { + return onPressed; + } } enum ButtonModelType { diff --git a/lib/src/model/column_model.dart b/lib/src/model/column_model.dart index 92f521f..f9a3467 100644 --- a/lib/src/model/column_model.dart +++ b/lib/src/model/column_model.dart @@ -34,6 +34,12 @@ class ColumnModel extends WidgetModel { @override String fullName() => '${table.name}.$name'; + /// Tests whether a given [option] is part of [options]. + bool hasOption(String option) { + bool rc = options.contains(option); + return rc; + } + /// Parses the map and stores the data in the instance. void parse() { name = parseString('name', map, required: true); diff --git a/lib/src/model/field_model.dart b/lib/src/model/field_model.dart index 1d205f5..4ddc9e0 100644 --- a/lib/src/model/field_model.dart +++ b/lib/src/model/field_model.dart @@ -41,6 +41,12 @@ abstract class FieldModel extends WidgetModel { @override String fullName() => '${page.name}.$name'; + /// Tests whether a given [option] is part of [options]. + bool hasOption(String option) { + bool rc = options.contains(option); + return rc; + } + /// Parses the map and stores the data in the instance. void parse() { name = parseString('name', map, required: true); diff --git a/lib/src/model/model_types.dart b/lib/src/model/model_types.dart index 16667a9..292a9ec 100644 --- a/lib/src/model/model_types.dart +++ b/lib/src/model/model_types.dart @@ -1,4 +1,18 @@ // on change: adapt StringHelper.fromString() enum DataType { - bool, currency, date, dateTime, float, int, reference, string, + bool, + currency, + date, + dateTime, + float, + int, + reference, + string, +} +enum FilterType { + dateFrom, + dateTil, + dateTimeFrom, + dateTimeTil, + pattern, } diff --git a/lib/src/model/module_model.dart b/lib/src/model/module_model.dart index d5fbd23..7763206 100644 --- a/lib/src/model/module_model.dart +++ b/lib/src/model/module_model.dart @@ -128,6 +128,13 @@ class ModuleModel extends ModelBase { return rc; } + /// Returns the main table of the module. + /// This is the first defined table. + TableModel mainTable() { + TableModel rc = tables.length == 0 ? null : tables[0]; + return rc; + } + /// Returns a child page given by [name], null otherwise. PageModel pageByName(String name) { final found = pages.firstWhere((element) => element.name == name); diff --git a/lib/src/model/page_model.dart b/lib/src/model/page_model.dart index 3a8d658..01d5f6e 100644 --- a/lib/src/model/page_model.dart +++ b/lib/src/model/page_model.dart @@ -152,5 +152,6 @@ enum PageModelType { change, create, delete, + list, overview, } diff --git a/lib/src/model/section_model.dart b/lib/src/model/section_model.dart index b35f1c5..cce657e 100644 --- a/lib/src/model/section_model.dart +++ b/lib/src/model/section_model.dart @@ -53,6 +53,7 @@ class SectionModel extends WidgetModel { checkSuperfluousAttributes( map, 'children fields name options sectionType widgetType'.split(' ')); options = parseOptions('options', map); + checkOptionsByRegExpr(options, regExprOptions); if (!map.containsKey('children')) { logger.error('missing children in ${fullName()}'); @@ -162,6 +163,8 @@ class SectionModel extends WidgetModel { } enum SectionModelType { + changeForm, + createForm, simpleForm, - query, + filterPanel, } diff --git a/lib/src/model/standard/role_model.dart b/lib/src/model/standard/role_model.dart index a1ded34..bc6fec5 100644 --- a/lib/src/model/standard/role_model.dart +++ b/lib/src/model/standard/role_model.dart @@ -2,7 +2,7 @@ import 'package:dart_bones/dart_bones.dart'; import 'package:flutter_bones/flutter_bones.dart'; class RoleModel extends ModuleModel { - static final model = { + static final yamlMap = { "module": "role", "tables": [ { @@ -78,8 +78,9 @@ class RoleModel extends ModuleModel { "sectionType": "filterPanel", "children": [ { - "widgetType": "textfield", - "searchMode": "pattern", + "widgetType": "textField", + "filterType": "pattern", + "name": "role_name", } ] } @@ -88,7 +89,7 @@ class RoleModel extends ModuleModel { ], }; - RoleModel(BaseLogger logger) : super(model, logger); + RoleModel(BaseLogger logger) : super(yamlMap, logger); /// Returns the name including the names of the parent @override diff --git a/lib/src/model/text_field_model.dart b/lib/src/model/text_field_model.dart index 0cae1dd..2360c79 100644 --- a/lib/src/model/text_field_model.dart +++ b/lib/src/model/text_field_model.dart @@ -20,6 +20,7 @@ class TextFieldModel extends FieldModel { FormFieldSetter onSaved; final Map map; + FilterType filterType; TextFieldModel( SectionModel section, PageModel page, this.map, BaseLogger logger) @@ -37,10 +38,11 @@ class TextFieldModel extends FieldModel { super.parse(); checkSuperfluousAttributes( map, - 'dataType label maxSize name options rows toolTip value widgetType' + 'dataType filterType label maxSize name options rows toolTip value widgetType' .split(' ')); maxSize = parseInt('maxSize', map, required: false); rows = parseInt('rows', map, required: false); + filterType = parseEnum('filterType', map, FilterType.values); switch (dataType) { case DataType.int: case DataType.reference: diff --git a/lib/src/page/role/role_change_page.dart b/lib/src/page/role/role_change_page.dart new file mode 100644 index 0000000..4aa6b23 --- /dev/null +++ b/lib/src/page/role/role_change_page.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bones/flutter_bones.dart'; + +import '../../widget/edit_form.dart'; +import 'role_controller.dart'; + +class RoleChangePage extends StatefulWidget { + final PageData pageData; + final logger = Settings().logger; + RoleChangePageState lastState; + + RoleChangePage(this.pageData, {Key key}) : super(key: key); + + @override + RoleChangePageState createState() { + final rc = lastState = RoleChangePageState(pageData); + return rc; + } +} + +class RoleChangePageState extends State { + final PageData pageData; + + final GlobalKey _formKey = GlobalKey(); + + RoleController controller; + + RoleChangePageState(this.pageData); + + @override + Widget build(BuildContext context) { + controller = controller ?? RoleController(_formKey, this); + return Scaffold( + appBar: pageData.appBarBuilder('Rolle ändern'), + drawer: pageData.drawerBuilder(context), + body: EditForm.editForm( + key: _formKey, + isCreateForm: false, + moduleController: controller, + configuration: pageData.configuration, + )); + } +} diff --git a/lib/src/page/role/role_controller.dart b/lib/src/page/role/role_controller.dart new file mode 100644 index 0000000..76b27c3 --- /dev/null +++ b/lib/src/page/role/role_controller.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bones/flutter_bones.dart'; + +import '../../model/standard/role_model.dart'; +import '../../widget/module_controller.dart'; + +class RoleController extends ModuleController { + RoleController(GlobalKey formKey, State parent) + : super(formKey, parent, RoleModel(Settings().logger)) { + moduleModel.parse(); + } +} diff --git a/lib/src/page/role/role_create_page.dart b/lib/src/page/role/role_create_page.dart new file mode 100644 index 0000000..85902e8 --- /dev/null +++ b/lib/src/page/role/role_create_page.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bones/flutter_bones.dart'; + +import '../../widget/edit_form.dart'; +import 'role_controller.dart'; + +class RoleCreatePage extends StatefulWidget { + final PageData pageData; + final logger = Settings().logger; + RoleCreatePageState lastState; + + RoleCreatePage(this.pageData, {Key key}) : super(key: key); + + @override + RoleCreatePageState createState() { + final rc = lastState = RoleCreatePageState(pageData); + return rc; + } +} + +class RoleCreatePageState extends State { + final PageData pageData; + + final GlobalKey _formKey = GlobalKey(); + + RoleController controller; + + RoleCreatePageState(this.pageData); + + @override + Widget build(BuildContext context) { + controller = controller ?? RoleController(_formKey, this); + return Scaffold( + appBar: pageData.appBarBuilder('Neue Rolle'), + drawer: pageData.drawerBuilder(context), + body: EditForm.editForm( + key: _formKey, + isCreateForm: true, + moduleController: controller, + configuration: pageData.configuration, + )); + } +} diff --git a/lib/src/page/role_list_page.dart b/lib/src/page/role/role_list_page.dart similarity index 76% rename from lib/src/page/role_list_page.dart rename to lib/src/page/role/role_list_page.dart index bbbb26d..f8aa522 100644 --- a/lib/src/page/role_list_page.dart +++ b/lib/src/page/role/role_list_page.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; -import '../helper/filters.dart'; -import '../helper/settings.dart'; -import '../page/page_data.dart'; -import '../widget/list_form.dart'; +import '../../helper/settings.dart'; +import '../../model/model_types.dart'; +import '../../widget/filters.dart'; +import '../../widget/list_form.dart'; +import '../../widget/raised_button_bone.dart'; +import '../page_data.dart'; class RoleListPage extends StatefulWidget { final PageData pageData; @@ -54,12 +56,23 @@ class RoleListPageState extends State { return rc; } + Filters filters; + @override Widget build(BuildContext context) { final logger = Settings().logger; - final filters = Filters.ready([ - FilterItem(filterType: FilterType.pattern, label: 'Name'), - ], logger); + if (filters == null) { + filters = Filters.ready( + _formKey, + this, + [ + FilterItem( + name: 'role_name', + filterType: FilterType.pattern, + label: 'Name'), + ], + logger); + } return Scaffold( appBar: pageData.appBarBuilder('Rollen'), drawer: pageData.drawerBuilder(context), @@ -71,8 +84,8 @@ class RoleListPageState extends State { rows: getRows(filters), showEditIcon: true, buttons: [ - RaisedButton( - onPressed: () => search(context), + RaisedButtonBone( + 'search', filters, child: Text('Suchen'), ), ], @@ -80,12 +93,6 @@ class RoleListPageState extends State { ), ); } - - void search(context) async { - if (_formKey.currentState.validate()) { - _formKey.currentState.save(); - } - } } class Role { diff --git a/lib/src/page/role_create_page.dart b/lib/src/page/role_create_page.dart deleted file mode 100644 index cde36c7..0000000 --- a/lib/src/page/role_create_page.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bones/flutter_bones.dart'; - -class RoleCreatePage extends StatefulWidget { - final PageData pageData; - - RoleCreatePage(this.pageData, {Key key}) : super(key: key); - - @override - RoleCreatePageState createState() { - // RoleCreatePageState.setPageData(pageData); - final rc = RoleCreatePageState(pageData); - - return rc; - } -} - -class RoleCreatePageState extends State { - RoleCreatePageState(this.pageData); - - final PageData pageData; - - final GlobalKey _formKey = GlobalKey(); - static Role currentRole = Role(); - - @override - Widget build(BuildContext context) { - final role = Role(); - return Scaffold( - appBar: pageData.appBarBuilder('Rollen'), - drawer: pageData.drawerBuilder(context), - body: SimpleForm.simpleForm( - key: _formKey, - configuration: pageData.configuration, - fields: [ - TextFormField( - validator: checkNotEmpty, - decoration: InputDecoration(labelText: 'Name'), - onSaved: (input) => role.name = input, - ), - TextFormField( - validator: checkNat, - decoration: InputDecoration(labelText: 'Priorität'), - onSaved: (input) => role.priority = input, - ), - ], - buttons: [ - RaisedButton( - onPressed: () => login(context), - child: Text('Anmelden'), - ), - ], - )); - } - - void login(context) async { - if (_formKey.currentState.validate()) { - _formKey.currentState.save(); - //@ToDo: store in database - } - } -} - -class Role { - String priority; - String name; -} \ No newline at end of file diff --git a/lib/src/widget/edit_form.dart b/lib/src/widget/edit_form.dart new file mode 100644 index 0000000..d33ddbd --- /dev/null +++ b/lib/src/widget/edit_form.dart @@ -0,0 +1,57 @@ +import 'package:dart_bones/dart_bones.dart'; +import 'package:flutter/material.dart'; + +import 'module_controller.dart'; +import 'raised_button_bone.dart'; + +typedef Function OnEditTap(Map row, int index); + +/// Contains helper functions for creating/changing data of a model based module. +class EditForm { + /// Returns a widget with a form containing at least some input fields + /// and a save/cancel button. + /// [titles] is used for the table header. + /// [columnNames] are the keys to display: @precondition: titles.length == columnNames.length + /// [rows] is a list of rows normally delivered from a database query: + /// each row is a map with (key, value) pairs. + /// If [showEditItems] is true the edit icon is shown in the first column. + static Form editForm({ + @required Key key, + @required bool isCreateForm, + @required ModuleController moduleController, + @required BaseConfiguration configuration, + }) { + final padding = + configuration.asFloat('form.card.padding', defaultValue: 16.0); + return Form( + key: key, + child: Card( + margin: EdgeInsets.symmetric(vertical: padding, horizontal: padding), + child: Padding( + padding: EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0), + child: ListView( + children: [ + ...moduleController.getWidgets(isCreateForm), + SizedBox( + height: configuration.asFloat( + 'form.gap.field_button.height', + defaultValue: 16.0)), + ButtonBar( + children: [ + FlatButton( + child: Text('Abbruch'), + onPressed: moduleController.getOnPressed( + 'cancel', moduleController), + ), + RaisedButtonBone( + 'store', + moduleController, + child: Text('Speichern'), + ), + ], + ), + ], + ))), + ); + } +} diff --git a/lib/src/widget/filters.dart b/lib/src/widget/filters.dart new file mode 100644 index 0000000..fbdb7dd --- /dev/null +++ b/lib/src/widget/filters.dart @@ -0,0 +1,172 @@ +import 'package:dart_bones/dart_bones.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bones/flutter_bones.dart'; +import 'package:flutter_bones/src/widget/list_form.dart'; + +import 'text_form_field_bone.dart'; + +typedef FilterPredicate = bool Function(Map row); + +class FilterItem { + final String name; + final String label; + final FilterType filterType; + final String toolTip; + final FilterPredicate filterPredicate; + var value; + + FilterItem({ + this.label, + this.filterType, + this.toolTip, + this.name, + this.filterPredicate, + }); + + bool isValid(Map row) { + bool rc = true; + if (filterPredicate != null) { + rc = filterPredicate(row); + } else { + final current = row[name].toString(); + final value2 = value ?? ''; + switch (filterType) { + case FilterType.pattern: + rc = value2 == '' + ? true + : (value2.startsWith('*') + ? current.contains(value2.replaceAll('*', '')) + : current.startsWith(value2)); + break; + case FilterType.dateFrom: + // TODO: Handle this case. + break; + case FilterType.dateTil: + // TODO: Handle this case. + break; + case FilterType.dateTimeFrom: + // TODO: Handle this case. + break; + case FilterType.dateTimeTil: + // TODO: Handle this case. + break; + default: + rc = true; + break; + } + } + return rc; + } +} + +class Filters + implements + TextCallbackController, + ButtonCallbackController, + TableCallbackController { + var filters = []; + final BaseLogger logger; + final GlobalKey globalKey; + + State parent; + + Filters(this.globalKey, this.parent, this.logger); + + Filters.ready(this.globalKey, this.parent, this.filters, this.logger); + + void add(FilterItem item) => filters.add(item); + + FilterItem byName(String name) => + filters.firstWhere((element) => element.name == name); + + @override + getOnChanged(String customString, TextCallbackController controller) { + return null; + } + + @override + getOnEditingComplete(String customString, TextCallbackController controller) { + return null; + } + + @override + getOnEditTap(String customString, TableCallbackController controller, + Map row) { + return null; + } + + @override + getOnFieldSubmitted(String customString, TextCallbackController controller) { + return null; + } + + @override + getOnHighlightChanged(String customString, + ButtonCallbackController controller) { + return null; + } + + @override + getOnLongPressed(String customString, ButtonCallbackController controller) { + return null; + } + + @override + getOnPressed(String customString, ButtonCallbackController controller) { + var rc; + if (customString == 'search') { + rc = () { + if (globalKey.currentState.validate()) { + globalKey.currentState.save(); + parent.setState(() => null); + } + }; + } + return rc; + } + + @override + getOnSaved(String customString, TextCallbackController controller) { + return (input) { + byName(customString).value = input; + }; + } + + @override + getOnTap(String customString, TextCallbackController controller) { + return null; + } + + @override + getValidator(String customString, TextCallbackController controller) { + return null; + } + + /// Returns a list of widgets for the filter fields. + List getWidgets() { + final rc = filters.map((filter) { + Widget rc = TextFormFieldBone( + filter.name, + this, + decoration: InputDecoration(labelText: filter.label), + ); + if (filter.toolTip != null) { + rc = Tooltip(message: filter.toolTip, child: rc); + } + return rc; + }).toList(); + return rc; + } + + /// Tests whether the [row] belongs to the result. + bool isValid(Map row) { + var rc = true; + for (var filter in filters) { + if (!filter.isValid(row)) { + rc = false; + break; + } + } + return rc; + } +} diff --git a/lib/src/widget/list_form.dart b/lib/src/widget/list_form.dart index 424868f..d3db052 100644 --- a/lib/src/widget/list_form.dart +++ b/lib/src/widget/list_form.dart @@ -1,10 +1,15 @@ import 'package:dart_bones/dart_bones.dart'; import 'package:flutter/material.dart'; -import '../helper/filters.dart'; +import 'filters.dart'; typedef Function OnEditTap(Map row, int index); +abstract class TableCallbackController { + OnEditTap getOnEditTap(String customString, + TableCallbackController controller, Map row); +} + class ListForm { /// Converts a string with [titles] into a list of widgets of type Text(). /// Format of [titles]: first character is the delimiter, e.g. ";Id;Name;" @@ -25,30 +30,41 @@ class ListForm { /// [columnNames] are the keys to display: @precondition: titles.length == columnNames.length /// [rows] is a list of rows normally delivered from a database query: /// each row is a map with (key, value) pairs. - /// If [showEditItems] is true the edit icon is shown in the first column. + /// If [showEditItems] is true the edit icon is shown in the additional first column. + /// If [sortIndex] is not null the rows will be sorted by this index. static Widget table({ @required List titles, @required List columnNames, @required List> rows, bool showEditIcon = false, - OnEditTap onEditTap, + int sortIndex, + TableCallbackController tableCallbackController, + String customString, }) { + assert(titles.length == columnNames.length); + final titles2 = titles.map((item) => DataColumn(label: item)).toList(); + if (showEditIcon) { + titles2.insert(0, DataColumn(label: SizedBox(width: 1))); + sortIndex = sortIndex == null ? null : sortIndex + 1; + } Widget rc = Container( margin: EdgeInsets.all(2), child: DataTable( showCheckboxColumn: true, showBottomBorder: true, - sortColumnIndex: 1, - columns: titles.map((item) => DataColumn(label: item)).toList(), + sortColumnIndex: sortIndex, + columns: titles2, rows: rows.map((row) { final cells = []; - int ix = -1; + if (showEditIcon) { + cells.add(DataCell(SizedBox(width: 1), showEditIcon: true)); + } for (var key in columnNames) { - ix++; cells.add(DataCell( Text(row[key]), - showEditIcon: showEditIcon && ix == 0, - onTap: () => onEditTap == null ? null : onEditTap(row, ix), + onTap: () => + tableCallbackController.getOnEditTap( + customString, tableCallbackController, row), )); } return DataRow(cells: cells); @@ -62,18 +78,18 @@ class ListForm { /// [rows] is a list of rows normally delivered from a database query: /// each row is a map with (key, value) pairs. /// If [showEditItems] is true the edit icon is shown in the first column. - static Form listForm( - {@required Key key, - @required Filters filters, - @required List buttons, - @required List titles, - @required List columnNames, - @required List> rows, - bool showEditIcon = false, - OnEditTap onEditTap, - @required BaseConfiguration configuration}) { + static Form listForm({@required Key key, + @required Filters filters, + @required List buttons, + @required List titles, + @required List columnNames, + @required List> rows, + bool showEditIcon = false, + TableCallbackController tableCallbackController, + @required BaseConfiguration configuration, + String customString}) { final padding = - configuration.asFloat('form.card.padding', defaultValue: 16.0); + configuration.asFloat('form.card.padding', defaultValue: 16.0); return Form( key: key, child: Card( @@ -88,11 +104,13 @@ class ListForm { defaultValue: 16.0)), ...buttons, table( - titles: titles, - columnNames: columnNames, - rows: rows, - showEditIcon: showEditIcon, - onEditTap: onEditTap), + titles: titles, + columnNames: columnNames, + rows: rows, + showEditIcon: showEditIcon, + tableCallbackController: tableCallbackController, + customString: customString, + ) ])), )); } diff --git a/lib/src/widget/module_controller.dart b/lib/src/widget/module_controller.dart new file mode 100644 index 0000000..49c10c8 --- /dev/null +++ b/lib/src/widget/module_controller.dart @@ -0,0 +1,144 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bones/flutter_bones.dart'; +import 'package:flutter_bones/src/widget/widget_list.dart'; + +import '../model/model_types.dart'; +import '../model/module_model.dart'; +import '../model/table_model.dart'; +import 'raised_button_bone.dart'; +import 'text_form_field_bone.dart'; +import 'widget_helper.dart'; + +// This interface allows the generic handling of the edit form by a model driven module. +class ModuleController + implements TextCallbackController, ButtonCallbackController { + final ModuleModel moduleModel; + String primaryColumn; + WidgetList createWidgets; + WidgetList changeWidgets; + final modelsMap = {}; + final dataTypes = {}; + final Map values = {}; + final GlobalKey globalKey; + + State parent; + + ModuleController(this.globalKey, this.parent, this.moduleModel) { + createWidgets = WidgetList( + '${moduleModel.fullName()}.createWidgets', moduleModel.logger); + changeWidgets = WidgetList( + '${moduleModel.fullName()}.changeWidgets', moduleModel.logger); + } + + ModuleModel getModuleModel() => moduleModel; + + @override + getOnChanged(String customString, TextCallbackController controller) { + return null; + } + + @override + getOnEditingComplete(String customString, TextCallbackController controller) { + return null; + } + + @override + getOnFieldSubmitted(String customString, TextCallbackController controller) { + return null; + } + + @override + getOnHighlightChanged( + String customString, ButtonCallbackController controller) { + return null; + } + + @override + getOnLongPressed(String customString, ButtonCallbackController controller) { + return null; + } + + @override + getOnPressed(String customString, ButtonCallbackController controller) { + var rc; + if (customString == 'store') { + rc = () { + if (globalKey.currentState.validate()) { + globalKey.currentState.save(); + moduleModel.logger.log('missing storage in onPressed'); + } + }; + } + return rc; + } + + @override + getOnSaved(String customString, TextCallbackController controller) { + final rc = (input) { + values[customString] = + StringHelper.fromString(input, dataTypes[customString]); + }; + return rc; + } + + @override + getOnTap(String customString, TextCallbackController controller) { + return null; + } + + @override + getValidator(String customString, TextCallbackController controller) { + return null; + } + + /// Returns the widgets with at least the input fields of the form + /// + List getWidgets(bool isCreateForm) { + List rc; + if (isCreateForm) { + if (createWidgets == null || createWidgets.widgets.length == 0) { + modelToWidgets(moduleModel.mainTable(), this, createWidgets); + } + rc = createWidgets.widgets; + } else { + if (changeWidgets.widgets.length == 0) { + modelToWidgets(moduleModel.mainTable(), this, changeWidgets); + } + rc = changeWidgets.widgets; + } + return rc; + } + + /// Reads the [tableModel] and creates the [widgetList] with all relevant + /// input fields. + void modelToWidgets(TableModel tableModel, ModuleController controller, + WidgetList widgetList) { + for (var column in tableModel.columns) { + if (!column.hasOption('hidden')) { + if (column.hasOption('primary')) { + primaryColumn = column.name; + } + Widget widget; + modelsMap[column.name] = column; + dataTypes[column.name] = column.dataType; + switch (column.dataType) { + case DataType.bool: + widget = null; + break; + default: + widget = WidgetHelper.toolTip( + TextFormFieldBone( + column.name, + controller, + decoration: InputDecoration(labelText: column.label), + ), + column.toolTip); + break; + } + if (widget != null) { + widgetList.addWidget(column.name, widget); + } + } + } + } +} diff --git a/lib/src/widget/raised_button_bone.dart b/lib/src/widget/raised_button_bone.dart index 1506fb4..402c611 100644 --- a/lib/src/widget/raised_button_bone.dart +++ b/lib/src/widget/raised_button_bone.dart @@ -1,12 +1,26 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bones/flutter_bones.dart'; +/// Interface for a button specific callback controller. +abstract class ButtonCallbackController { + ValueChanged getOnHighlightChanged( + String customString, ButtonCallbackController controller); + + VoidCallback getOnLongPressed( + String customString, ButtonCallbackController controller); + + VoidCallback getOnPressed( + String customString, ButtonCallbackController controller); +} + +/// Implements a raised button with two additional properties: +/// [customString] a string often used for a name needed in a callback method +/// [customObject] an object known by the controller, often used in callback methods like onPressed class RaisedButtonBone extends RaisedButton { - final ButtonModel model; + final String customString; + final ButtonCallbackController callbackController; - RaisedButtonBone(this.model, - {ValueChanged onHighlightChanged, - ButtonTextTheme textTheme, + RaisedButtonBone(this.customString, this.callbackController, + {ButtonTextTheme textTheme, Color textColor, Color disabledTextColor, Color color, @@ -31,19 +45,22 @@ class RaisedButtonBone extends RaisedButton { Duration animationDuration, Widget child}) : super( - onPressed: model.onPressed, - onLongPress: model.onLongPressed, - onHighlightChanged: model.onHighlightChanged, - textTheme: textTheme, - textColor: textColor, - disabledTextColor: disabledTextColor, - color: color, - disabledColor: disabledColor, - focusColor: focusColor, - hoverColor: hoverColor, - highlightColor: highlightColor, - splashColor: splashColor, - colorBrightness: colorBrightness, + onPressed: callbackController.getOnPressed( + customString, callbackController), + onLongPress: callbackController.getOnLongPressed( + customString, callbackController), + onHighlightChanged: callbackController.getOnHighlightChanged( + customString, callbackController), + textTheme: textTheme, + textColor: textColor, + disabledTextColor: disabledTextColor, + color: color, + disabledColor: disabledColor, + focusColor: focusColor, + hoverColor: hoverColor, + highlightColor: highlightColor, + splashColor: splashColor, + colorBrightness: colorBrightness, elevation: elevation, focusElevation: focusElevation, hoverElevation: hoverElevation, diff --git a/lib/src/widget/simple_form.dart b/lib/src/widget/simple_form.dart index 0b6a712..e2d6252 100644 --- a/lib/src/widget/simple_form.dart +++ b/lib/src/widget/simple_form.dart @@ -2,22 +2,27 @@ import 'package:dart_bones/dart_bones.dart'; import 'package:flutter/material.dart'; class SimpleForm { - static Form simpleForm({@required Key key, @required List fields, - @required List buttons, @required BaseConfiguration configuration} ) { - final padding = configuration.asFloat('form.card.padding', defaultValue: 16.0); + static Form simpleForm( + {@required Key key, + @required List fields, + @required List buttons, + @required BaseConfiguration configuration}) { + final padding = + configuration.asFloat('form.card.padding', defaultValue: 16.0); return Form( key: key, child: Card( - margin: EdgeInsets.symmetric(vertical: padding, horizontal: padding), - child: Padding( - padding: - EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0), - child: ListView( - children: - [...fields, - SizedBox(height: configuration.asFloat('form.gap.field_button.height', defaultValue: 16.0)), - ...buttons] - )), - )); + margin: EdgeInsets.symmetric(vertical: padding, horizontal: padding), + child: Padding( + padding: EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0), + child: ListView(children: [ + ...fields, + SizedBox( + height: configuration.asFloat( + 'form.gap.field_button.height', + defaultValue: 16.0)), + ...buttons + ])), + )); } -} \ No newline at end of file +} diff --git a/lib/src/widget/text_form_field_bone.dart b/lib/src/widget/text_form_field_bone.dart new file mode 100644 index 0000000..e8c01de --- /dev/null +++ b/lib/src/widget/text_form_field_bone.dart @@ -0,0 +1,129 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +/// Interface for a [TextFormField] specific callback controller. +abstract class TextCallbackController { + ValueChanged getOnChanged( + String customString, TextCallbackController controller); + + VoidCallback getOnEditingComplete( + String customString, TextCallbackController controller); + + ValueChanged getOnFieldSubmitted( + String customString, TextCallbackController controller); + + FormFieldSetter getOnSaved( + String customString, TextCallbackController controller); + + GestureTapCallback getOnTap( + String customString, TextCallbackController controller); + + FormFieldValidator getValidator( + String customString, TextCallbackController controller); +} + +/// Implements a [TextFormField] with "outsourced" callbacks: +/// [customString] a string mostly used for a name needed in the [customController] +/// [callbackController] handles the callback methods. +class TextFormFieldBone extends TextFormField { + final String customString; + final TextCallbackController callbackController; + + TextFormFieldBone( + this.customString, + this.callbackController, { + Key key, + TextEditingController controller, + String initialValue, + FocusNode focusNode, + InputDecoration decoration = const InputDecoration(), + TextInputType keyboardType, + TextCapitalization textCapitalization = TextCapitalization.none, + TextInputAction textInputAction, + TextStyle style, + StrutStyle strutStyle, + TextDirection textDirection, + TextAlign textAlign = TextAlign.start, + TextAlignVertical textAlignVertical, + bool autofocus = false, + bool readOnly = false, + ToolbarOptions toolbarOptions, + bool showCursor, + String obscuringCharacter = '•', + bool obscureText = false, + bool autocorrect = true, + SmartDashesType smartDashesType, + SmartQuotesType smartQuotesType, + bool enableSuggestions = true, + bool maxLengthEnforced = true, + int maxLines = 1, + int minLines, + bool expands = false, + int maxLength, + List inputFormatters, + bool enabled, + double cursorWidth = 2.0, + double cursorHeight, + Radius cursorRadius, + Color cursorColor, + Brightness keyboardAppearance, + EdgeInsets scrollPadding = const EdgeInsets.all(20.0), + bool enableInteractiveSelection = true, + InputCounterWidgetBuilder buildCounter, + ScrollPhysics scrollPhysics, + Iterable autofillHints, + AutovalidateMode autovalidateMode, + }) : super( + controller: controller, + initialValue: initialValue, + focusNode: focusNode, + decoration: decoration, + keyboardType: keyboardType, + textCapitalization: textCapitalization, + textInputAction: textInputAction, + style: style, + strutStyle: strutStyle, + textDirection: textDirection, + textAlign: textAlign, + textAlignVertical: textAlignVertical, + autofocus: autofocus, + readOnly: readOnly, + toolbarOptions: toolbarOptions, + showCursor: showCursor, + obscuringCharacter: obscuringCharacter, + obscureText: obscureText, + autocorrect: autocorrect, + smartDashesType: smartDashesType, + smartQuotesType: smartQuotesType, + enableSuggestions: enableSuggestions, + maxLengthEnforced: maxLengthEnforced, + maxLines: maxLines, + minLines: minLines, + expands: expands, + maxLength: maxLength, + onChanged: + callbackController.getOnChanged(customString, callbackController), + onTap: callbackController.getOnTap(customString, callbackController), + onEditingComplete: callbackController.getOnEditingComplete( + customString, callbackController), + onFieldSubmitted: callbackController.getOnFieldSubmitted( + customString, callbackController), + onSaved: + callbackController.getOnSaved(customString, callbackController), + validator: + callbackController.getValidator(customString, callbackController), + inputFormatters: inputFormatters, + enabled: enabled, + cursorWidth: cursorWidth, + cursorHeight: cursorHeight, + cursorRadius: cursorRadius, + cursorColor: cursorColor, + keyboardAppearance: keyboardAppearance, + scrollPadding: scrollPadding, + enableInteractiveSelection: enableInteractiveSelection, + buildCounter: buildCounter, + scrollPhysics: scrollPhysics, + autofillHints: autofillHints, + autovalidateMode: autovalidateMode, + ); +} diff --git a/lib/src/widget/view.dart b/lib/src/widget/view.dart index b57b2a9..d820d8f 100644 --- a/lib/src/widget/view.dart +++ b/lib/src/widget/view.dart @@ -32,6 +32,7 @@ class View { /// Creates a button from the [model]. Widget button(ButtonModel model) { final rc = RaisedButtonBone( + model.widgetName(), model, child: Text(model.text), ); diff --git a/lib/src/widget/widget_helper.dart b/lib/src/widget/widget_helper.dart new file mode 100644 index 0000000..64e1aac --- /dev/null +++ b/lib/src/widget/widget_helper.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + +/// Some little static helper methods round about [Widgets]. +class WidgetHelper { + /// If a [toolTip] is not empty the [widget] is completed with that. + /// Otherwise [widget] is returned. + static Widget toolTip(Widget widget, String toolTip) { + Widget rc; + if (toolTip == null || toolTip.isEmpty) { + rc = widget; + } else { + rc = Tooltip(message: toolTip, child: widget); + } + return rc; + } +} diff --git a/lib/src/widget/widget_list.dart b/lib/src/widget/widget_list.dart new file mode 100644 index 0000000..9ba2514 --- /dev/null +++ b/lib/src/widget/widget_list.dart @@ -0,0 +1,48 @@ +import 'package:dart_bones/dart_bones.dart'; +import 'package:flutter/material.dart'; + +/// Manages a list of named widgets. +class WidgetList { + final String name; + final BaseLogger logger; + final widgetMap = {}; + final widgets = []; + + WidgetList(this.name, this.logger); + + /// Adds a [widget] to the [widgets] behind the position of [predecessor]. + /// If [predecessor] is null the widget will be the first in [widgets] + void addSuccessorOf(String predecessor, String name, Widget widget) { + if (predecessor == null) { + widgets.insert(0, widget); + } else { + if (!widgetMap.containsKey(predecessor)) { + logger + .error('$name.addSuccessorOf(): missing predecessor $predecessor'); + } else { + final ix = widgets.indexOf(widgetMap[predecessor]); + widgets.insert(ix, widget); + } + } + addWidget(name, widget); + } + + /// Adds a widget to the widget list. + void addWidget(String name, Widget widget) { + if (widgetMap.containsKey(name)) { + logger.error('$name.addWidget(): widget $name already defined'); + } else { + widgetMap[name] = widget; + widgets.add(widget); + } + } + + /// Returns the widget given by [name] or null if not found. + Widget byName(String name) { + final rc = widgetMap.containsKey(name) ? widgetMap[name] : null; + if (rc == null) { + logger.error('$name.byName(): missing widget $name'); + } + return rc; + } +} diff --git a/test/widget/widget_test.dart b/test/widget/widget_test.dart new file mode 100644 index 0000000..c662812 --- /dev/null +++ b/test/widget/widget_test.dart @@ -0,0 +1,195 @@ +// 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:dart_bones/dart_bones.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/src/foundation/diagnostics.dart'; +import 'package:flutter/src/widgets/framework.dart' as x; +import 'package:flutter_bones/flutter_bones.dart'; +import 'package:flutter_bones/src/widget/widget_helper.dart'; +import 'package:test/test.dart'; + +void main() { + final logger = MemoryLogger(); + final widgetConfiguration = BaseConfiguration({}, logger); + Settings(logger: logger, widgetConfiguration: widgetConfiguration); + + group('WidgetHelper', () { + test('toolTip', () { + final widget = Text('Hi'); + expect(WidgetHelper.toolTip(widget, null), equals(widget)); + expect(WidgetHelper.toolTip(widget, ''), equals(widget)); + Tooltip toolTip = WidgetHelper.toolTip(widget, 'Hi'); + expect(toolTip.child, equals(widget)); + }); + }); + group('ModuleController', () { + test('basic', () { + PageData pageData = + PageData(BaseConfiguration({}, logger), (title) {}, (context) {}); + final role = RoleCreatePage(pageData); + if (role.lastState == null) { + role.createState(); + } + expect(role, isNotNull); + BuildContext context = MyContext(); + role.lastState.build(context); + final widgets = role.lastState.controller.getWidgets(true); + expect(widgets.length, equals(3)); + }); + }); +} + +class MyContext extends BuildContext { + @override + InheritedElement ancestorInheritedElementForWidgetOfExactType( + Type targetType) { + // TODO: implement ancestorInheritedElementForWidgetOfExactType + throw UnimplementedError(); + } + + @override + RenderObject ancestorRenderObjectOfType(x.TypeMatcher matcher) { + // TODO: implement ancestorRenderObjectOfType + throw UnimplementedError(); + } + + @override + State ancestorStateOfType(x.TypeMatcher matcher) { + // TODO: implement ancestorStateOfType + throw UnimplementedError(); + } + + @override + Widget ancestorWidgetOfExactType(Type targetType) { + // TODO: implement ancestorWidgetOfExactType + throw UnimplementedError(); + } + + @override + // TODO: implement debugDoingBuild + bool get debugDoingBuild => throw UnimplementedError(); + + @override + InheritedWidget dependOnInheritedElement(InheritedElement ancestor, + {Object aspect}) { + // TODO: implement dependOnInheritedElement + throw UnimplementedError(); + } + + @override + T dependOnInheritedWidgetOfExactType( + {Object aspect}) { + // TODO: implement dependOnInheritedWidgetOfExactType + throw UnimplementedError(); + } + + @override + DiagnosticsNode describeElement(String name, + {DiagnosticsTreeStyle style = DiagnosticsTreeStyle.errorProperty}) { + // TODO: implement describeElement + throw UnimplementedError(); + } + + @override + List describeMissingAncestor({Type expectedAncestorType}) { + // TODO: implement describeMissingAncestor + throw UnimplementedError(); + } + + @override + DiagnosticsNode describeOwnershipChain(String name) { + // TODO: implement describeOwnershipChain + throw UnimplementedError(); + } + + @override + DiagnosticsNode describeWidget(String name, + {DiagnosticsTreeStyle style = DiagnosticsTreeStyle.errorProperty}) { + // TODO: implement describeWidget + throw UnimplementedError(); + } + + @override + T findAncestorRenderObjectOfType() { + // TODO: implement findAncestorRenderObjectOfType + throw UnimplementedError(); + } + + @override + T findAncestorStateOfType>() { + // TODO: implement findAncestorStateOfType + throw UnimplementedError(); + } + + @override + T findAncestorWidgetOfExactType() { + // TODO: implement findAncestorWidgetOfExactType + throw UnimplementedError(); + } + + @override + RenderObject findRenderObject() { + // TODO: implement findRenderObject + throw UnimplementedError(); + } + + @override + T findRootAncestorStateOfType>() { + // TODO: implement findRootAncestorStateOfType + throw UnimplementedError(); + } + + @override + InheritedElement + getElementForInheritedWidgetOfExactType() { + // TODO: implement getElementForInheritedWidgetOfExactType + throw UnimplementedError(); + } + + @override + InheritedWidget inheritFromElement(InheritedElement ancestor, + {Object aspect}) { + // TODO: implement inheritFromElement + throw UnimplementedError(); + } + + @override + InheritedWidget inheritFromWidgetOfExactType(Type targetType, + {Object aspect}) { + // TODO: implement inheritFromWidgetOfExactType + throw UnimplementedError(); + } + + @override + // TODO: implement owner + BuildOwner get owner => throw UnimplementedError(); + + @override + State rootAncestorStateOfType(x.TypeMatcher matcher) { + // TODO: implement rootAncestorStateOfType + throw UnimplementedError(); + } + + @override + // TODO: implement size + Size get size => throw UnimplementedError(); + + @override + void visitAncestorElements(bool Function(Element element) visitor) { + // TODO: implement visitAncestorElements + } + + @override + void visitChildElements(visitor) { + // TODO: implement visitChildElements + } + + @override + // TODO: implement widget + Widget get widget => throw UnimplementedError(); +} diff --git a/test/widget_test.dart b/test/widget_test.dart index 49ab1a7..57ec2b7 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -5,29 +5,25 @@ // 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:flutter/material.dart'; +//import 'package:flutter_bones/main.dart'; +//import 'package:flutter_test/flutter_test.dart'; -import 'package:flutter_bones/flutter_bones.dart'; -*/ void main() { - /* - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(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); - }); - */ + // testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // // Build our app and trigger a frame. + // await tester.pumpWidget(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); + // }); } -- 2.39.5