diff --git a/lib/pages/dashboard_page.dart b/lib/pages/dashboard_page.dart index 5da4e2b1..12400dc8 100644 --- a/lib/pages/dashboard_page.dart +++ b/lib/pages/dashboard_page.dart @@ -24,6 +24,7 @@ import 'package:elastic_dashboard/services/robot_notifications_listener.dart'; import 'package:elastic_dashboard/services/settings.dart'; import 'package:elastic_dashboard/services/shuffleboard_nt_listener.dart'; import 'package:elastic_dashboard/services/update_checker.dart'; +import 'package:elastic_dashboard/util/tab_data.dart'; import 'package:elastic_dashboard/widgets/custom_appbar.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_toggle_switch.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/layout_drag_tile.dart'; @@ -58,8 +59,6 @@ class _DashboardPageState extends State with WindowListener { late final UpdateChecker _updateChecker; late final RobotNotificationsListener _robotNotificationListener; - final List _grids = []; - final List _tabData = []; final Function _mapEquals = const DeepCollectionEquality().equals; @@ -114,7 +113,7 @@ class _DashboardPageState extends State with WindowListener { widget.ntConnection.addConnectedListener(() { setState(() { - for (TabGrid grid in _grids) { + for (TabGridModel grid in _tabData.map((e) => e.tabGrid)) { grid.onNTConnect(); } }); @@ -122,7 +121,7 @@ class _DashboardPageState extends State with WindowListener { widget.ntConnection.addDisconnectedListener(() { setState(() { - for (TabGrid grid in _grids) { + for (TabGridModel grid in _tabData.map((e) => e.tabGrid)) { grid.onNTDisconnect(); } }); @@ -167,15 +166,14 @@ class _DashboardPageState extends State with WindowListener { return; } - _tabData.add(TabData(name: tab)); - _grids.add( - TabGrid( - key: GlobalKey(), + _tabData.add(TabData( + name: tab, + tabGrid: TabGridModel( ntConnection: widget.ntConnection, preferences: widget.preferences, onAddWidgetPressed: _displayAddWidgetDialog, ), - ); + )); }, onWidgetAdded: (widgetData) { if (preferences.getBool(PrefKeys.layoutLocked) ?? @@ -193,13 +191,16 @@ class _DashboardPageState extends State with WindowListener { String tabName = widgetDataCopy['tab']; if (!tabNamesList.contains(tabName)) { - _tabData.add(TabData(name: tabName)); - _grids.add(TabGrid( - key: GlobalKey(), - ntConnection: widget.ntConnection, - preferences: widget.preferences, - onAddWidgetPressed: _displayAddWidgetDialog, - )); + _tabData.add( + TabData( + name: tabName, + tabGrid: TabGridModel( + ntConnection: widget.ntConnection, + preferences: widget.preferences, + onAddWidgetPressed: _displayAddWidgetDialog, + ), + ), + ); tabNamesList.add(tabName); } @@ -210,7 +211,7 @@ class _DashboardPageState extends State with WindowListener { return; } - _grids[tabIndex].addWidgetFromTabJson(widgetDataCopy); + _tabData[tabIndex].tabGrid.addWidgetFromTabJson(widgetDataCopy); setState(() {}); }, @@ -288,11 +289,10 @@ class _DashboardPageState extends State with WindowListener { for (int i = 0; i < _tabData.length; i++) { TabData data = _tabData[i]; - TabGrid grid = _grids[i]; gridData.add({ 'name': data.name, - 'grid_layout': grid.toJson(), + 'grid_layout': data.tabGrid.toJson(), }); } @@ -584,7 +584,6 @@ class _DashboardPageState extends State with WindowListener { } _tabData.clear(); - _grids.clear(); for (Map data in jsonData['tabs']) { if (tryCast(data['name']) == null) { @@ -598,48 +597,47 @@ class _DashboardPageState extends State with WindowListener { continue; } - _tabData.add(TabData(name: data['name'])); - - _grids.add( - TabGrid.fromJson( - key: GlobalKey(), - ntConnection: widget.ntConnection, - preferences: widget.preferences, - jsonData: data['grid_layout'], - onAddWidgetPressed: _displayAddWidgetDialog, - onJsonLoadingWarning: _showJsonLoadingWarning, + _tabData.add( + TabData( + name: data['name'], + tabGrid: TabGridModel.fromJson( + ntConnection: widget.ntConnection, + preferences: widget.preferences, + jsonData: data['grid_layout'], + onAddWidgetPressed: _displayAddWidgetDialog, + onJsonLoadingWarning: _showJsonLoadingWarning, + ), ), ); } _createDefaultTabs(); - if (_currentTabIndex >= _grids.length) { - _currentTabIndex = _grids.length - 1; + if (_currentTabIndex >= _tabData.length) { + _currentTabIndex = _tabData.length - 1; } } void _createDefaultTabs() { - if (_tabData.isEmpty || _grids.isEmpty) { + if (_tabData.isEmpty) { logger.info('Creating default Teleoperated and Autonomous tabs'); setState(() { _tabData.addAll([ - TabData(name: 'Teleoperated'), - TabData(name: 'Autonomous'), - ]); - - _grids.addAll([ - TabGrid( - key: GlobalKey(), - ntConnection: widget.ntConnection, - preferences: widget.preferences, - onAddWidgetPressed: _displayAddWidgetDialog, + TabData( + name: 'Teleoperated', + tabGrid: TabGridModel( + ntConnection: widget.ntConnection, + preferences: widget.preferences, + onAddWidgetPressed: _displayAddWidgetDialog, + ), ), - TabGrid( - key: GlobalKey(), - ntConnection: widget.ntConnection, - preferences: widget.preferences, - onAddWidgetPressed: _displayAddWidgetDialog, + TabData( + name: 'Autonomous', + tabGrid: TabGridModel( + ntConnection: widget.ntConnection, + preferences: widget.preferences, + onAddWidgetPressed: _displayAddWidgetDialog, + ), ), ]); }); @@ -783,13 +781,14 @@ class _DashboardPageState extends State with WindowListener { String newTabName = 'Tab ${_tabData.length + 1}'; int newTabIndex = _tabData.length; - _tabData.add(TabData(name: newTabName)); - _grids.add( - TabGrid( - key: GlobalKey(), - ntConnection: widget.ntConnection, - preferences: widget.preferences, - onAddWidgetPressed: _displayAddWidgetDialog, + _tabData.add( + TabData( + name: newTabName, + tabGrid: TabGridModel( + ntConnection: widget.ntConnection, + preferences: widget.preferences, + onAddWidgetPressed: _displayAddWidgetDialog, + ), ), ); @@ -820,11 +819,10 @@ class _DashboardPageState extends State with WindowListener { _currentTabIndex--; } - _grids[oldTabIndex].onDestroy(); + _tabData[oldTabIndex].tabGrid.onDestroy(); setState(() { _tabData.removeAt(oldTabIndex); - _grids.removeAt(oldTabIndex); }); }); }, @@ -832,14 +830,14 @@ class _DashboardPageState extends State with WindowListener { } void _lockLayout() async { - for (TabGrid grid in _grids) { + for (TabGridModel grid in _tabData.map((e) => e.tabGrid)) { grid.lockLayout(); } await preferences.setBool(PrefKeys.layoutLocked, true); } void _unlockLayout() async { - for (TabGrid grid in _grids) { + for (TabGridModel grid in _tabData.map((e) => e.tabGrid)) { grid.unlockLayout(); } await preferences.setBool(PrefKeys.layoutLocked, false); @@ -1051,7 +1049,7 @@ class _DashboardPageState extends State with WindowListener { await preferences.setInt(PrefKeys.gridSize, newGridSize); - for (TabGrid grid in _grids) { + for (TabGridModel grid in _tabData.map((e) => e.tabGrid)) { grid.resizeGrid(_gridSize, _gridSize); } }, @@ -1070,7 +1068,7 @@ class _DashboardPageState extends State with WindowListener { await preferences.setDouble(PrefKeys.cornerRadius, newRadius); setState(() { - for (TabGrid grid in _grids) { + for (TabGridModel grid in _tabData.map((e) => e.tabGrid)) { grid.refreshAllContainers(); } }); @@ -1254,16 +1252,11 @@ class _DashboardPageState extends State with WindowListener { logger.info('Moving current tab at index $_currentTabIndex to the left'); setState(() { - // Swap the tab data + // Swap the tabs TabData tempData = _tabData[_currentTabIndex - 1]; _tabData[_currentTabIndex - 1] = _tabData[_currentTabIndex]; _tabData[_currentTabIndex] = tempData; - // Swap the tab grids - TabGrid tempGrid = _grids[_currentTabIndex - 1]; - _grids[_currentTabIndex - 1] = _grids[_currentTabIndex]; - _grids[_currentTabIndex] = tempGrid; - _currentTabIndex -= 1; }); } @@ -1281,16 +1274,11 @@ class _DashboardPageState extends State with WindowListener { logger.info('Moving current tab at index $_currentTabIndex to the right'); setState(() { - // Swap the tab data + // Swap the tabs TabData tempData = _tabData[_currentTabIndex + 1]; _tabData[_currentTabIndex + 1] = _tabData[_currentTabIndex]; _tabData[_currentTabIndex] = tempData; - // Swap the tab grids - TabGrid tempGrid = _grids[_currentTabIndex + 1]; - _grids[_currentTabIndex + 1] = _grids[_currentTabIndex]; - _grids[_currentTabIndex] = tempGrid; - _currentTabIndex += 1; }); } @@ -1379,7 +1367,9 @@ class _DashboardPageState extends State with WindowListener { Defaults.layoutLocked) ? () { setState(() { - _grids[_currentTabIndex].clearWidgets(context); + _tabData[_currentTabIndex] + .tabGrid + .clearWidgets(context); }); } : null, @@ -1514,15 +1504,18 @@ class _DashboardPageState extends State with WindowListener { _tabData[index] = newData; }); }, - onTabCreate: (tab) { + onTabCreate: () { + String tabName = 'Tab ${_tabData.length + 1}'; setState(() { - _tabData.add(tab); - _grids.add(TabGrid( - key: GlobalKey(), - ntConnection: widget.ntConnection, - preferences: widget.preferences, - onAddWidgetPressed: _displayAddWidgetDialog, - )); + _tabData.add( + TabData( + name: tabName, + tabGrid: TabGridModel( + ntConnection: widget.ntConnection, + preferences: widget.preferences, + onAddWidgetPressed: _displayAddWidgetDialog, + )), + ); }); }, onTabDestroy: (index) { @@ -1537,11 +1530,10 @@ class _DashboardPageState extends State with WindowListener { _currentTabIndex--; } - _grids[index].onDestroy(); + _tabData[index].tabGrid.onDestroy(); setState(() { _tabData.removeAt(index); - _grids.removeAt(index); }); }); }, @@ -1549,26 +1541,31 @@ class _DashboardPageState extends State with WindowListener { setState(() => _currentTabIndex = index); }, tabData: _tabData, - tabViews: _grids, ), _AddWidgetDialog( ntConnection: widget.ntConnection, preferences: widget.preferences, - grid: () => _grids[_currentTabIndex], + grid: () => _tabData[_currentTabIndex].tabGrid, visible: _addWidgetDialogVisible, onNTDragUpdate: (globalPosition, widget) { - _grids[_currentTabIndex] + _tabData[_currentTabIndex] + .tabGrid .addDragInWidget(widget, globalPosition); }, onNTDragEnd: (widget) { - _grids[_currentTabIndex].placeDragInWidget(widget); + _tabData[_currentTabIndex] + .tabGrid + .placeDragInWidget(widget); }, onLayoutDragUpdate: (globalPosition, widget) { - _grids[_currentTabIndex] + _tabData[_currentTabIndex] + .tabGrid .addDragInWidget(widget, globalPosition); }, onLayoutDragEnd: (widget) { - _grids[_currentTabIndex].placeDragInWidget(widget); + _tabData[_currentTabIndex] + .tabGrid + .placeDragInWidget(widget); }, onClose: () { setState(() => _addWidgetDialogVisible = false); @@ -1636,7 +1633,7 @@ class _DashboardPageState extends State with WindowListener { class _AddWidgetDialog extends StatefulWidget { final NTConnection ntConnection; final SharedPreferences preferences; - final TabGrid Function() _grid; + final TabGridModel Function() _grid; final bool _visible; final Function(Offset globalPosition, WidgetContainerModel widget) @@ -1652,7 +1649,7 @@ class _AddWidgetDialog extends StatefulWidget { const _AddWidgetDialog({ required this.ntConnection, required this.preferences, - required TabGrid Function() grid, + required TabGridModel Function() grid, required bool visible, required dynamic Function(Offset, WidgetContainerModel) onNTDragUpdate, required dynamic Function(WidgetContainerModel) onNTDragEnd, diff --git a/lib/util/tab_data.dart b/lib/util/tab_data.dart new file mode 100644 index 00000000..78dc7cfd --- /dev/null +++ b/lib/util/tab_data.dart @@ -0,0 +1,11 @@ +import 'package:elastic_dashboard/widgets/tab_grid.dart'; + +class TabData { + String name; + TabGridModel tabGrid; + + TabData({ + required this.name, + required this.tabGrid, + }); +} diff --git a/lib/widgets/draggable_containers/draggable_layout_container.dart b/lib/widgets/draggable_containers/draggable_layout_container.dart index 0bfa1064..13e13008 100644 --- a/lib/widgets/draggable_containers/draggable_layout_container.dart +++ b/lib/widgets/draggable_containers/draggable_layout_container.dart @@ -3,12 +3,6 @@ import 'package:elastic_dashboard/widgets/draggable_containers/draggable_widget_ abstract class DraggableLayoutContainer extends DraggableWidgetContainer { const DraggableLayoutContainer({ super.key, - required super.tabGrid, - super.onUpdate, - super.onDragBegin, - super.onDragEnd, - super.onDragCancel, - super.onResizeBegin, - super.onResizeEnd, + super.updateFunctions, }) : super(); } diff --git a/lib/widgets/draggable_containers/draggable_list_layout.dart b/lib/widgets/draggable_containers/draggable_list_layout.dart index 41e6db90..a1725666 100644 --- a/lib/widgets/draggable_containers/draggable_list_layout.dart +++ b/lib/widgets/draggable_containers/draggable_list_layout.dart @@ -8,13 +8,7 @@ import 'models/list_layout_model.dart'; class DraggableListLayout extends DraggableLayoutContainer { const DraggableListLayout({ super.key, - required super.tabGrid, - super.onUpdate, - super.onDragBegin, - super.onDragEnd, - super.onDragCancel, - super.onResizeBegin, - super.onResizeEnd, + super.updateFunctions, }) : super(); @override diff --git a/lib/widgets/draggable_containers/draggable_nt_widget_container.dart b/lib/widgets/draggable_containers/draggable_nt_widget_container.dart index ec850baa..00b6c910 100644 --- a/lib/widgets/draggable_containers/draggable_nt_widget_container.dart +++ b/lib/widgets/draggable_containers/draggable_nt_widget_container.dart @@ -8,13 +8,7 @@ import 'models/nt_widget_container_model.dart'; class DraggableNTWidgetContainer extends DraggableWidgetContainer { const DraggableNTWidgetContainer({ super.key, - required super.tabGrid, - super.onUpdate, - super.onDragBegin, - super.onDragEnd, - super.onDragCancel, - super.onResizeBegin, - super.onResizeEnd, + super.updateFunctions, }) : super(); @override diff --git a/lib/widgets/draggable_containers/draggable_widget_container.dart b/lib/widgets/draggable_containers/draggable_widget_container.dart index df4016a4..cbfb5446 100644 --- a/lib/widgets/draggable_containers/draggable_widget_container.dart +++ b/lib/widgets/draggable_containers/draggable_widget_container.dart @@ -4,31 +4,26 @@ import 'package:flutter_box_transform/flutter_box_transform.dart'; import 'package:provider/provider.dart'; import 'package:elastic_dashboard/services/settings.dart'; -import 'package:elastic_dashboard/widgets/tab_grid.dart'; import 'models/widget_container_model.dart'; +typedef DraggableContainerUpdateFunctions = ({ + Function(WidgetContainerModel widget, Rect newRect, + TransformResult result) onUpdate, + Function(WidgetContainerModel widget) onDragBegin, + Function(WidgetContainerModel widget, Rect releaseRect, + {Offset? globalPosition}) onDragEnd, + Function(WidgetContainerModel widget) onDragCancel, + Function(WidgetContainerModel widget) onResizeBegin, + Function(WidgetContainerModel widget, Rect releaseRect) onResizeEnd, + bool Function(WidgetContainerModel widget, Rect location) isValidMoveLocation, +}); + class DraggableWidgetContainer extends StatelessWidget { - final TabGrid tabGrid; - - final Function( - WidgetContainerModel widget, Rect newRect, TransformResult result)? - onUpdate; - final Function(WidgetContainerModel widget)? onDragBegin; - final Function(WidgetContainerModel widget, Rect releaseRect, - {Offset? globalPosition})? onDragEnd; - final Function(WidgetContainerModel widget)? onDragCancel; - final Function(WidgetContainerModel widget)? onResizeBegin; - final Function(WidgetContainerModel widget, Rect releaseRect)? onResizeEnd; + final DraggableContainerUpdateFunctions? updateFunctions; const DraggableWidgetContainer({ super.key, - required this.tabGrid, - this.onUpdate, - this.onDragBegin, - this.onDragEnd, - this.onDragCancel, - this.onResizeBegin, - this.onResizeEnd, + this.updateFunctions, }); static double snapToGrid(double value, [double? gridSize]) { @@ -65,9 +60,10 @@ class DraggableWidgetContainer extends StatelessWidget { model.setDragStartLocation(model.displayRect); model.setPreviewRect(model.dragStartLocation); model.setValidLocation( - tabGrid.isValidMoveLocation(model, model.previewRect)); + updateFunctions?.isValidMoveLocation(model, model.previewRect) ?? + true); - onDragBegin?.call(model); + updateFunctions?.onDragBegin(model); controller?.setRect(model.draggingRect); }, @@ -79,21 +75,22 @@ class DraggableWidgetContainer extends StatelessWidget { model.setDragStartLocation(model.displayRect); model.setPreviewRect(model.dragStartLocation); model.setValidLocation( - tabGrid.isValidMoveLocation(model, model.previewRect)); + updateFunctions?.isValidMoveLocation(model, model.previewRect) ?? + true); - onResizeBegin?.call(model); + updateFunctions?.onResizeBegin.call(model); controller?.setRect(model.draggingRect); }, onChanged: (result, event) { if (!model.dragging && !model.resizing) { - onDragCancel?.call(model); + updateFunctions?.onDragCancel(model); return; } model.setCursorGlobalLocation(event.globalPosition); - onUpdate?.call(model, result.rect, result); + updateFunctions?.onUpdate(model, result.rect, result); controller?.setRect(model.draggingRect); }, @@ -103,7 +100,7 @@ class DraggableWidgetContainer extends StatelessWidget { } model.setDragging(false); - onDragEnd?.call(model, model.draggingRect, + updateFunctions?.onDragEnd(model, model.draggingRect, globalPosition: model.cursorGlobalLocation); controller?.setRect(model.draggingRect); @@ -113,7 +110,7 @@ class DraggableWidgetContainer extends StatelessWidget { model.setDragging(false); }); - onDragCancel?.call(model); + updateFunctions?.onDragCancel(model); controller?.setRect(model.draggingRect); }, @@ -124,7 +121,7 @@ class DraggableWidgetContainer extends StatelessWidget { model.setDragging(false); model.setResizing(false); - onResizeEnd?.call(model, model.draggingRect); + updateFunctions?.onResizeEnd(model, model.draggingRect); controller?.setRect(model.draggingRect); }, @@ -132,7 +129,7 @@ class DraggableWidgetContainer extends StatelessWidget { model.setDragging(false); model.setResizing(false); - onDragCancel?.call(model); + updateFunctions?.onDragCancel(model); controller?.setRect(model.draggingRect); }, diff --git a/lib/widgets/draggable_containers/models/list_layout_model.dart b/lib/widgets/draggable_containers/models/list_layout_model.dart index cb1eefe4..9ee32ae6 100644 --- a/lib/widgets/draggable_containers/models/list_layout_model.dart +++ b/lib/widgets/draggable_containers/models/list_layout_model.dart @@ -6,8 +6,8 @@ import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; import 'package:dot_cast/dot_cast.dart'; import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; -import 'package:elastic_dashboard/services/nt_connection.dart'; import 'package:elastic_dashboard/services/settings.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_dropdown_chooser.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_text_input.dart'; @@ -26,8 +26,14 @@ class ListLayoutModel extends LayoutContainerModel { String labelPosition = 'TOP'; - final NTConnection ntConnection; - final TabGrid tabGrid; + final TabGridModel tabGrid; + final NTWidgetContainerModel? Function( + SharedPreferences preferences, + Map jsonData, + bool enabled, { + Function(String errorMessage)? onJsonLoadingWarning, + })? ntWidgetBuilder; + final Function(WidgetContainerModel model)? onDragCancel; static List labelPositions = const [ @@ -42,9 +48,9 @@ class ListLayoutModel extends LayoutContainerModel { required super.preferences, required super.initialPosition, required super.title, - required this.ntConnection, required this.tabGrid, required this.onDragCancel, + this.ntWidgetBuilder, List? children, super.minWidth, super.minHeight, @@ -58,7 +64,7 @@ class ListLayoutModel extends LayoutContainerModel { ListLayoutModel.fromJson({ required super.jsonData, required super.preferences, - required this.ntConnection, + required this.ntWidgetBuilder, required this.tabGrid, required this.onDragCancel, super.enabled, @@ -125,15 +131,13 @@ class ListLayoutModel extends LayoutContainerModel { } for (Map childData in jsonData['children']) { - children.add( - NTWidgetContainerModel.fromJson( - ntConnection: ntConnection, - preferences: preferences, - jsonData: childData, - enabled: enabled, - onJsonLoadingWarning: onJsonLoadingWarning, - ), - ); + NTWidgetContainerModel? widgetModel = ntWidgetBuilder!( + preferences, childData, enabled, + onJsonLoadingWarning: onJsonLoadingWarning); + + if (widgetModel != null) { + children.add(widgetModel); + } } } diff --git a/lib/widgets/editable_tab_bar.dart b/lib/widgets/editable_tab_bar.dart index 83d2f648..5a736b53 100644 --- a/lib/widgets/editable_tab_bar.dart +++ b/lib/widgets/editable_tab_bar.dart @@ -7,22 +7,16 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:transitioned_indexed_stack/transitioned_indexed_stack.dart'; import 'package:elastic_dashboard/services/settings.dart'; +import 'package:elastic_dashboard/util/tab_data.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_text_input.dart'; import 'package:elastic_dashboard/widgets/tab_grid.dart'; -class TabData { - String name; - - TabData({required this.name}); -} - class EditableTabBar extends StatelessWidget { final SharedPreferences preferences; - final List tabViews; final List tabData; - final Function(TabData tab) onTabCreate; + final Function() onTabCreate; final Function(int index) onTabDestroy; final Function() onTabMoveLeft; final Function() onTabMoveRight; @@ -36,7 +30,6 @@ class EditableTabBar extends StatelessWidget { required this.preferences, required this.currentIndex, required this.tabData, - required this.tabViews, required this.onTabCreate, required this.onTabDestroy, required this.onTabMoveLeft, @@ -79,10 +72,7 @@ class EditableTabBar extends StatelessWidget { } void createTab() { - String tabName = 'Tab ${tabData.length + 1}'; - TabData data = TabData(name: tabName); - - onTabCreate.call(data); + onTabCreate(); } void closeTab(int index) { @@ -291,10 +281,10 @@ class EditableTabBar extends StatelessWidget { curve: Curves.decelerate, index: currentIndex, children: [ - for (TabGrid grid in tabViews) - ChangeNotifierProvider( - create: (context) => TabGridModel(), - child: grid, + for (TabGridModel grid in tabData.map((e) => e.tabGrid)) + ChangeNotifierProvider.value( + value: grid, + child: const TabGrid(), ), ], ), diff --git a/lib/widgets/tab_grid.dart b/lib/widgets/tab_grid.dart index 61404920..5d5472e2 100644 --- a/lib/widgets/tab_grid.dart +++ b/lib/widgets/tab_grid.dart @@ -22,30 +22,21 @@ import 'draggable_containers/models/widget_container_model.dart'; // Used to refresh the tab grid when a widget is added or removed // This doesn't use a stateful widget since everything has to be rendered at program startup or data will be lost class TabGridModel extends ChangeNotifier { - void notify() { - notifyListeners(); - } -} - -class TabGrid extends StatelessWidget { final NTConnection ntConnection; final SharedPreferences preferences; final List _widgetModels = []; MapEntry? _containerDraggingIn; + BuildContext? tabGridContext; final VoidCallback onAddWidgetPressed; - TabGridModel? model; - - TabGrid( - {super.key, - required this.ntConnection, + TabGridModel( + {required this.ntConnection, required this.preferences, required this.onAddWidgetPressed}); - TabGrid.fromJson({ - super.key, + TabGridModel.fromJson({ required this.ntConnection, required this.preferences, required Map jsonData, @@ -91,9 +82,16 @@ class TabGrid extends StatelessWidget { switch (layoutData['type']) { case 'List Layout': widget = ListLayoutModel.fromJson( - ntConnection: ntConnection, preferences: preferences, jsonData: layoutData, + ntWidgetBuilder: (preferences, jsonData, enabled, + {onJsonLoadingWarning}) => + NTWidgetContainerModel.fromJson( + ntConnection: ntConnection, + jsonData: jsonData, + preferences: preferences, + onJsonLoadingWarning: onJsonLoadingWarning, + ), enabled: ntConnection.isNT4Connected, tabGrid: this, onDragCancel: _layoutContainerOnDragCancel, @@ -127,13 +125,12 @@ class TabGrid extends StatelessWidget { } Offset getLocalPosition(Offset globalPosition) { - BuildContext? context = (key as GlobalKey).currentContext; - - if (context == null) { + if (tabGridContext == null) { return Offset.zero; } - RenderBox? ancestor = context.findAncestorRenderObjectOfType(); + RenderBox? ancestor = + tabGridContext!.findAncestorRenderObjectOfType(); Offset localPosition = ancestor!.globalToLocal(globalPosition); @@ -156,10 +153,9 @@ class TabGrid extends StatelessWidget { /// /// This only applies to widgets that already have a place on the grid bool isValidMoveLocation(WidgetContainerModel widget, Rect location) { - BuildContext? context = (key as GlobalKey).currentContext; Size? gridSize; - if (context != null) { - gridSize = MediaQuery.of(context).size; + if (tabGridContext != null) { + gridSize = MediaQuery.of(tabGridContext!).size; } for (WidgetContainerModel container in _widgetModels) { @@ -536,7 +532,6 @@ class TabGrid extends StatelessWidget { ListLayoutModel createListLayout( {String title = 'List Layout', List? children}) { return ListLayoutModel( - ntConnection: ntConnection, preferences: preferences, title: title, initialPosition: Rect.fromLTWH( @@ -604,9 +599,16 @@ class TabGrid extends StatelessWidget { case 'List Layout': _widgetModels.add( ListLayoutModel.fromJson( - ntConnection: ntConnection, preferences: preferences, jsonData: widgetData, + ntWidgetBuilder: (preferences, jsonData, enabled, + {onJsonLoadingWarning}) => + NTWidgetContainerModel.fromJson( + ntConnection: ntConnection, + jsonData: jsonData, + preferences: preferences, + onJsonLoadingWarning: onJsonLoadingWarning, + ), enabled: ntConnection.isNT4Connected, tabGrid: this, onDragCancel: _layoutContainerOnDragCancel, @@ -692,12 +694,6 @@ class TabGrid extends StatelessWidget { _widgetModels.clear(); } - void refresh() { - Future(() async { - model?.notify(); - }); - } - void resizeGrid(int oldSize, int newSize) { for (WidgetContainerModel widget in _widgetModels) { widget.updateGridSize(oldSize, newSize); @@ -713,37 +709,51 @@ class TabGrid extends StatelessWidget { }); } + void refresh() { + notifyListeners(); + } +} + +class TabGrid extends StatelessWidget { + const TabGrid({super.key}); + @override Widget build(BuildContext context) { - model = context.watch(); + TabGridModel model = context.watch(); - Widget getWidgetFromModel(WidgetContainerModel model) { - if (model is NTWidgetContainerModel) { + model.tabGridContext = context; + + Widget getWidgetFromModel(WidgetContainerModel widgetModel) { + if (widgetModel is NTWidgetContainerModel) { return ChangeNotifierProvider.value( - value: model, + value: widgetModel, child: DraggableNTWidgetContainer( - key: model.key, - tabGrid: this, - onUpdate: _ntContainerOnUpdate, - onDragBegin: _ntContainerOnDragBegin, - onDragEnd: _ntContainerOnDragEnd, - onDragCancel: _ntContainerOnDragCancel, - onResizeBegin: _ntContainerOnResizeBegin, - onResizeEnd: _ntContainerOnResizeEnd, + key: widgetModel.key, + updateFunctions: ( + onUpdate: model._ntContainerOnUpdate, + onDragBegin: model._ntContainerOnDragBegin, + onDragEnd: model._ntContainerOnDragEnd, + onDragCancel: model._ntContainerOnDragCancel, + onResizeBegin: model._ntContainerOnResizeBegin, + onResizeEnd: model._ntContainerOnResizeEnd, + isValidMoveLocation: model.isValidMoveLocation, + ), ), ); - } else if (model is ListLayoutModel) { + } else if (widgetModel is ListLayoutModel) { return ChangeNotifierProvider.value( - value: model, + value: widgetModel, child: DraggableListLayout( - key: model.key, - tabGrid: this, - onUpdate: _layoutContainerOnUpdate, - onDragBegin: _layoutContainerOnDragBegin, - onDragEnd: _layoutContainerOnDragEnd, - onDragCancel: _layoutContainerOnDragCancel, - onResizeBegin: _layoutContainerOnResizeBegin, - onResizeEnd: _layoutContainerOnResizeEnd, + key: widgetModel.key, + updateFunctions: ( + onUpdate: model._layoutContainerOnUpdate, + onDragBegin: model._layoutContainerOnDragBegin, + onDragEnd: model._layoutContainerOnDragEnd, + onDragCancel: model._layoutContainerOnDragCancel, + onResizeBegin: model._layoutContainerOnResizeBegin, + onResizeEnd: model._layoutContainerOnResizeEnd, + isValidMoveLocation: model.isValidMoveLocation, + ), ), ); } @@ -755,7 +765,7 @@ class TabGrid extends StatelessWidget { List draggingInWidgets = []; List previewOutlines = []; - for (WidgetContainerModel container in _widgetModels) { + for (WidgetContainerModel container in model._widgetModels) { if (container.dragging) { draggingWidgets.add( Positioned( @@ -773,7 +783,7 @@ class TabGrid extends StatelessWidget { ); } else { LayoutContainerModel? layoutContainer = - getLayoutAtLocation(container.cursorGlobalLocation); + model.getLayoutAtLocation(container.cursorGlobalLocation); if (layoutContainer == null) { previewOutlines.add( @@ -792,7 +802,7 @@ class TabGrid extends StatelessWidget { decoration: BoxDecoration( color: Colors.white.withOpacity(0.25), borderRadius: BorderRadius.circular( - preferences.getDouble(PrefKeys.cornerRadius) ?? + model.preferences.getDouble(PrefKeys.cornerRadius) ?? Defaults.cornerRadius), border: Border.all(color: Colors.yellow, width: 5.0), ), @@ -808,7 +818,7 @@ class TabGrid extends StatelessWidget { GestureDetector( onTap: () {}, onSecondaryTapUp: (details) { - if (preferences.getBool(PrefKeys.layoutLocked) ?? + if (model.preferences.getBool(PrefKeys.layoutLocked) ?? Defaults.layoutLocked) { return; } @@ -830,7 +840,7 @@ class TabGrid extends StatelessWidget { label: 'Remove', icon: Icons.delete_outlined, onSelected: () { - removeWidget(container); + model.removeWidget(container); }), ]; @@ -861,8 +871,8 @@ class TabGrid extends StatelessWidget { } // Also render any containers that are being dragged into the grid - if (_containerDraggingIn != null) { - WidgetContainerModel container = _containerDraggingIn!.key; + if (model._containerDraggingIn != null) { + WidgetContainerModel container = model._containerDraggingIn!.key; draggingWidgets.add( Positioned( @@ -882,15 +892,16 @@ class TabGrid extends StatelessWidget { Rect previewLocation = Rect.fromLTWH(previewX, previewY, container.displayRect.width, container.displayRect.height); - bool validLocation = isValidMoveLocation(container, previewLocation) || - isValidLayoutLocation(container.cursorGlobalLocation); + bool validLocation = + model.isValidMoveLocation(container, previewLocation) || + model.isValidLayoutLocation(container.cursorGlobalLocation); Color borderColor = (validLocation) ? Colors.lightGreenAccent.shade400 : Colors.red; - if (isValidLayoutLocation(container.cursorGlobalLocation)) { + if (model.isValidLayoutLocation(container.cursorGlobalLocation)) { LayoutContainerModel layoutContainer = - getLayoutAtLocation(container.cursorGlobalLocation)!; + model.getLayoutAtLocation(container.cursorGlobalLocation)!; previewLocation = layoutContainer.displayRect; @@ -909,7 +920,7 @@ class TabGrid extends StatelessWidget { ? Colors.white.withOpacity(0.25) : Colors.black.withOpacity(0.1), borderRadius: BorderRadius.circular( - preferences.getDouble(PrefKeys.cornerRadius) ?? + model.preferences.getDouble(PrefKeys.cornerRadius) ?? Defaults.cornerRadius), border: Border.all(color: borderColor, width: 5.0), ), @@ -922,7 +933,7 @@ class TabGrid extends StatelessWidget { behavior: HitTestBehavior.translucent, onTap: () {}, onSecondaryTapUp: (details) { - if (preferences.getBool(PrefKeys.layoutLocked) ?? + if (model.preferences.getBool(PrefKeys.layoutLocked) ?? Defaults.layoutLocked) { return; } @@ -934,12 +945,12 @@ class TabGrid extends StatelessWidget { MenuItem( label: 'Add Widget', icon: Icons.add, - onSelected: () => onAddWidgetPressed.call(), + onSelected: () => model.onAddWidgetPressed.call(), ), MenuItem( label: 'Clear Layout', icon: Icons.clear, - onSelected: () => clearWidgets(context), + onSelected: () => model.clearWidgets(context), ), ], ); diff --git a/test/widgets/editable_tab_bar_test.dart b/test/widgets/editable_tab_bar_test.dart index f60879aa..19eb6735 100644 --- a/test/widgets/editable_tab_bar_test.dart +++ b/test/widgets/editable_tab_bar_test.dart @@ -6,6 +6,7 @@ import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:elastic_dashboard/util/tab_data.dart'; import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_text_input.dart'; import 'package:elastic_dashboard/widgets/editable_tab_bar.dart'; import 'package:elastic_dashboard/widgets/tab_grid.dart'; @@ -50,22 +51,24 @@ void main() { preferences: preferences, currentIndex: 0, tabData: [ - TabData(name: 'Teleoperated'), - TabData(name: 'Autonomous'), - ], - tabViews: [ - TabGrid( - ntConnection: mockNTConnection, - preferences: preferences, - onAddWidgetPressed: () {}, + TabData( + name: 'Teleoperated', + tabGrid: TabGridModel( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ), - TabGrid( - ntConnection: mockNTConnection, - preferences: preferences, - onAddWidgetPressed: () {}, + TabData( + name: 'Autonomous', + tabGrid: TabGridModel( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ), ], - onTabCreate: (tab) {}, + onTabCreate: () {}, onTabDestroy: (index) {}, onTabMoveLeft: () {}, onTabMoveRight: () {}, @@ -99,22 +102,24 @@ void main() { preferences: preferences, currentIndex: 0, tabData: [ - TabData(name: 'Teleoperated'), - TabData(name: 'Autonomous'), - ], - tabViews: [ - TabGrid( - ntConnection: mockNTConnection, - preferences: preferences, - onAddWidgetPressed: () {}, + TabData( + name: 'Teleoperated', + tabGrid: TabGridModel( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ), - TabGrid( - ntConnection: mockNTConnection, - preferences: preferences, - onAddWidgetPressed: () {}, + TabData( + name: 'Autonomous', + tabGrid: TabGridModel( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ), ], - onTabCreate: (tab) { + onTabCreate: () { tabBarFunctions.onTabCreate(); }, onTabDestroy: (index) { @@ -159,22 +164,24 @@ void main() { preferences: preferences, currentIndex: 0, tabData: [ - TabData(name: 'Teleoperated'), - TabData(name: 'Autonomous'), - ], - tabViews: [ - TabGrid( - ntConnection: mockNTConnection, - preferences: preferences, - onAddWidgetPressed: () {}, + TabData( + name: 'Teleoperated', + tabGrid: TabGridModel( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ), - TabGrid( - ntConnection: mockNTConnection, - preferences: preferences, - onAddWidgetPressed: () {}, + TabData( + name: 'Autonomous', + tabGrid: TabGridModel( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ), ], - onTabCreate: (tab) { + onTabCreate: () { tabBarFunctions.onTabCreate(); }, onTabDestroy: (index) { @@ -219,22 +226,24 @@ void main() { preferences: preferences, currentIndex: 0, tabData: [ - TabData(name: 'Teleoperated'), - TabData(name: 'Autonomous'), - ], - tabViews: [ - TabGrid( - ntConnection: mockNTConnection, - preferences: preferences, - onAddWidgetPressed: () {}, + TabData( + name: 'Teleoperated', + tabGrid: TabGridModel( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ), - TabGrid( - ntConnection: mockNTConnection, - preferences: preferences, - onAddWidgetPressed: () {}, + TabData( + name: 'Autonomous', + tabGrid: TabGridModel( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ), ], - onTabCreate: (tab) { + onTabCreate: () { tabBarFunctions.onTabCreate(); }, onTabDestroy: (index) { @@ -292,22 +301,24 @@ void main() { preferences: preferences, currentIndex: 0, tabData: [ - TabData(name: 'Teleoperated'), - TabData(name: 'Autonomous'), - ], - tabViews: [ - TabGrid( - ntConnection: mockNTConnection, - preferences: preferences, - onAddWidgetPressed: () {}, + TabData( + name: 'Teleoperated', + tabGrid: TabGridModel( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ), - TabGrid( - ntConnection: mockNTConnection, - preferences: preferences, - onAddWidgetPressed: () {}, + TabData( + name: 'Autonomous', + tabGrid: TabGridModel( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ), ], - onTabCreate: (tab) { + onTabCreate: () { tabBarFunctions.onTabCreate(); }, onTabDestroy: (index) { @@ -376,22 +387,24 @@ void main() { preferences: preferences, currentIndex: 0, tabData: [ - TabData(name: 'Teleoperated'), - TabData(name: 'Autonomous'), - ], - tabViews: [ - TabGrid( - ntConnection: mockNTConnection, - preferences: preferences, - onAddWidgetPressed: () {}, + TabData( + name: 'Teleoperated', + tabGrid: TabGridModel( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ), - TabGrid( - ntConnection: mockNTConnection, - preferences: preferences, - onAddWidgetPressed: () {}, + TabData( + name: 'Autonomous', + tabGrid: TabGridModel( + ntConnection: mockNTConnection, + preferences: preferences, + onAddWidgetPressed: () {}, + ), ), ], - onTabCreate: (tab) { + onTabCreate: () { tabBarFunctions.onTabCreate(); }, onTabDestroy: (index) { diff --git a/test/widgets/tab_grid_test.dart b/test/widgets/tab_grid_test.dart index b25c7b8b..cfc7fe6d 100644 --- a/test/widgets/tab_grid_test.dart +++ b/test/widgets/tab_grid_test.dart @@ -77,14 +77,14 @@ void main() async { await widgetTester.pumpWidget( MaterialApp( home: Scaffold( - body: ChangeNotifierProvider( - create: (context) => TabGridModel(), - child: TabGrid.fromJson( + body: ChangeNotifierProvider.value( + value: TabGridModel.fromJson( ntConnection: createMockOfflineNT4(), preferences: preferences, jsonData: jsonData['tabs'][0]['grid_layout'], onAddWidgetPressed: () {}, ), + child: const TabGrid(), ), ), ), @@ -126,14 +126,14 @@ void main() async { await widgetTester.pumpWidget( MaterialApp( home: Scaffold( - body: ChangeNotifierProvider( - create: (context) => TabGridModel(), - child: TabGrid.fromJson( + body: ChangeNotifierProvider.value( + value: TabGridModel.fromJson( ntConnection: createMockOfflineNT4(), preferences: preferences, jsonData: jsonData['tabs'][1]['grid_layout'], onAddWidgetPressed: () {}, ), + child: const TabGrid(), ), ), ), @@ -168,15 +168,14 @@ void main() async { await widgetTester.pumpWidget( MaterialApp( home: Scaffold( - body: ChangeNotifierProvider( - create: (context) => TabGridModel(), - child: TabGrid.fromJson( - key: GlobalKey(), + body: ChangeNotifierProvider.value( + value: TabGridModel.fromJson( ntConnection: createMockOfflineNT4(), preferences: preferences, jsonData: jsonData['tabs'][0]['grid_layout'], onAddWidgetPressed: () {}, ), + child: const TabGrid(), ), ), ), @@ -246,15 +245,14 @@ void main() async { await widgetTester.pumpWidget( MaterialApp( home: Scaffold( - body: ChangeNotifierProvider( - create: (context) => TabGridModel(), - child: TabGrid.fromJson( - key: GlobalKey(), + body: ChangeNotifierProvider.value( + value: TabGridModel.fromJson( ntConnection: createMockOfflineNT4(), preferences: preferences, jsonData: jsonData['tabs'][0]['grid_layout'], onAddWidgetPressed: () {}, ), + child: const TabGrid(), ), ), ),