From 362a2da1e503cea123c8970aeeda3adea723a051 Mon Sep 17 00:00:00 2001 From: Max Mazer <67406618+max-mazer@users.noreply.github.com> Date: Wed, 9 Oct 2024 11:18:25 -0400 Subject: [PATCH] Presets (#173) Introduced the ability to save, load, delete, and organize presets that remember which views were on screen, in what order, and their sizes. ![image](https://github.com/user-attachments/assets/9d262071-11b5-450e-b8c2-06045198ffbd) --------- Co-authored-by: Gold87 <91761103+Gold872@users.noreply.github.com> Co-authored-by: aidan ahram Co-authored-by: Levi Lesches --- assets/Rocks_Minerals_Images/Augite.jpg | Bin 0 -> 88362 bytes lib/data.dart | 1 + lib/models.dart | 2 + lib/src/data/settings.dart | 62 +++++---- lib/src/data/view_preset.dart | 67 +++++++++ lib/src/models/data/settings.dart | 6 +- lib/src/models/data/views.dart | 102 +++++++++++++- .../models/view/builders/preset_builder.dart | 20 +++ .../view/builders/settings_builder.dart | 7 +- lib/src/pages/view.dart | 16 ++- lib/src/services/files.dart | 33 +++-- lib/src/widgets/atomic/editors.dart | 26 ++-- lib/src/widgets/atomic/preset_editors.dart | 35 +++++ lib/src/widgets/navigation/sidebar.dart | 16 +-- lib/src/widgets/navigation/views.dart | 53 +++----- lib/src/widgets/navigation/views_list.dart | 127 ++++++++++++++---- lib/widgets.dart | 1 + pubspec.lock | 4 +- pubspec.yaml | 2 +- 19 files changed, 442 insertions(+), 138 deletions(-) create mode 100644 assets/Rocks_Minerals_Images/Augite.jpg create mode 100644 lib/src/data/view_preset.dart create mode 100644 lib/src/models/view/builders/preset_builder.dart create mode 100644 lib/src/widgets/atomic/preset_editors.dart diff --git a/assets/Rocks_Minerals_Images/Augite.jpg b/assets/Rocks_Minerals_Images/Augite.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bbaf02e306a1841a2174dc2b2015da1cd046b7d4 GIT binary patch literal 88362 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 literal 0 HcmV?d00001 diff --git a/lib/data.dart b/lib/data.dart index 66a39dbdf..90b4a28ec 100644 --- a/lib/data.dart +++ b/lib/data.dart @@ -28,3 +28,4 @@ export "src/data/settings.dart"; export "src/data/socket.dart"; export "src/data/taskbar_message.dart"; export "src/data/utils.dart"; +export "src/data/view_preset.dart"; diff --git a/lib/models.dart b/lib/models.dart index 1fb905c1a..3be7ae8cd 100644 --- a/lib/models.dart +++ b/lib/models.dart @@ -57,6 +57,8 @@ export "src/models/view/builders/settings_builder.dart"; export "src/models/view/builders/throttle.dart"; export "src/models/view/builders/timer_builder.dart"; export "src/models/view/builders/video_builder.dart"; +export "src/models/view/builders/preset_builder.dart"; + /// A wrapper model around all other data models used by the app. /// diff --git a/lib/src/data/settings.dart b/lib/src/data/settings.dart index 50516d153..32877f854 100644 --- a/lib/src/data/settings.dart +++ b/lib/src/data/settings.dart @@ -1,8 +1,7 @@ import "package:flutter/material.dart"; +import "package:rover_dashboard/data.dart"; -import "socket.dart"; - -/// A collection of functions for parsing [Settings]. +/// A collection of functions for parsing [Settings]. extension SettingsParser on Json { /// Parses a [SocketInfo] that may not be present. SocketInfo? getSocket(String key) { @@ -15,7 +14,7 @@ extension SettingsParser on Json { /// Settings relating to science. class ScienceSettings { /// How many frames to render per second. - /// + /// /// This does not affect how many frames are sent by the rover per second. final bool scrollableGraphs; @@ -26,7 +25,7 @@ class ScienceSettings { const ScienceSettings({required this.scrollableGraphs, required this.numSamples}); /// Parses a [ScienceSettings] from JSON. - ScienceSettings.fromJson(Json? json) : + ScienceSettings.fromJson(Json? json) : numSamples = json?["numSamples"] ?? 3, scrollableGraphs = json?["scrollableGraphs"] ?? false; @@ -76,7 +75,7 @@ class ArmSettings { }); /// Parses arm settings from a JSON map. - ArmSettings.fromJson(Json? json) : + ArmSettings.fromJson(Json? json) : shoulder = json?["shoulder"] ?? 0.005, elbow = json?["elbow"] ?? 0.005, swivel = json?["swivel"] ?? 0.2, @@ -102,8 +101,8 @@ class ArmSettings { /// Settings related to network configuration. class NetworkSettings { /// The amount of time, in seconds, the dashboard should wait before determining it's - /// lost connection to the rover. For reference, the rover should be sending messages - /// at least once per second. + /// lost connection to the rover. For reference, the rover should be sending messages + /// at least once per second. final double connectionTimeout; /// The address and port of the subsystems program. @@ -116,7 +115,7 @@ class NetworkSettings { final SocketInfo autonomySocket; /// The address of the tank. The port is ignored. - /// + /// /// The Tank is a model rover that has all the same programs as the rover. This field does not /// include port numbers because ports are specific to the program, and the tank will have many /// programs running. Instead, the IP address of all the other programs should be swapped with @@ -133,7 +132,7 @@ class NetworkSettings { }); /// Parses network settings from a JSON map. - NetworkSettings.fromJson(Json? json) : + NetworkSettings.fromJson(Json? json) : subsystemsSocket = json?.getSocket("subsystemsSocket") ?? SocketInfo.raw("192.168.1.20", 8001), videoSocket = json?.getSocket("videoSocket") ?? SocketInfo.raw("192.168.1.30", 8002), autonomySocket = json?.getSocket("autonomySocket") ?? SocketInfo.raw("192.168.1.30", 8003), @@ -151,7 +150,7 @@ class NetworkSettings { } /// Settings relating to easter eggs. -/// +/// /// Implement these! Ask Levi for details. class EasterEggsSettings { /// Whether to do a SEGA-like intro during boot. @@ -161,7 +160,7 @@ class EasterEggsSettings { /// Whether clippy should appear by log messages. final bool enableClippy; /// Whether to render Bad Apple in the Map page. - final bool badApple; + final bool badApple; /// A const constructor. const EasterEggsSettings({ @@ -172,7 +171,7 @@ class EasterEggsSettings { }); /// Parses easter eggs settings from JSON. - EasterEggsSettings.fromJson(Json? json) : + EasterEggsSettings.fromJson(Json? json) : segaIntro = json?["segaIntro"] ?? true, segaSound = json?["segaSound"] ?? true, enableClippy = json?["enableClippy"] ?? true, @@ -191,9 +190,9 @@ class EasterEggsSettings { enum SplitMode { /// Two views are split horizontally, one atop the other. horizontal("Top and bottom"), - /// Two views are split vertically, side-by-side. + /// Two views are split vertically, side-by-side. vertical("Side by side"); - + /// The name to show in the UI. final String humanName; /// A const constructor. @@ -216,7 +215,7 @@ class DashboardSettings { final SplitMode splitMode; /// The precision of the GPS grid. - /// + /// /// Since GPS coordinates are decimal values, we divide by this value to get the index of the cell /// each coordinate belongs to. Smaller sizes means more blocks, but we should be careful that the /// blocks are big enough to the margin of error of our GPS. This value must be synced with the @@ -224,29 +223,33 @@ class DashboardSettings { final double mapBlockSize; /// How many frames to render per second. - /// + /// /// This does not affect how many frames are sent by the rover per second. final int maxFps; - /// The theme of the Dashboard. + /// The theme of the Dashboard. final ThemeMode themeMode; - /// Whether to split cameras into their own controls. - /// + /// Whether to split cameras into their own controls. + /// /// When this is disabled, some other modes, like arm or drive, may move the cameras. /// When this is enabled, only the dedicated camera control mode can move the cameras. final bool splitCameras; /// Whether to default to tank drive controls. - /// + /// /// Tank controls offer more custom control, but modern drive controls are more intuitive. final bool preferTankControls; /// Whether to have version checking on protobuf messages. final bool versionChecking; + /// A list of ViewPresets + final List presets; + /// A const constructor. const DashboardSettings({ + required this.presets, required this.splitMode, required this.mapBlockSize, required this.maxFps, @@ -256,8 +259,12 @@ class DashboardSettings { required this.versionChecking, }); - /// Parses Dashboard settings from JSON. - DashboardSettings.fromJson(Json? json) : + /// Parses settings from JSON. + DashboardSettings.fromJson(Json? json) : + presets = [ + for (final presetJson in json?["presets"] ?? []) + ViewPreset.fromJson(presetJson), + ], splitMode = SplitMode.values[json?["splitMode"] ?? SplitMode.horizontal.index], mapBlockSize = json?["mapBlockSize"] ?? 1.0, maxFps = (json?["maxFps"] ?? 60) as int, @@ -268,6 +275,7 @@ class DashboardSettings { /// Serializes these settings to JSON. Json toJson() => { + "presets" : presets, "splitMode": splitMode.index, "mapBlockSize": mapBlockSize, "maxFps": maxFps, @@ -278,13 +286,13 @@ class DashboardSettings { }; } -/// Contains the settings for running the dashboard and the rover. +/// Contains the settings for running the dashboard and the rover. class Settings { /// Settings for the network, like IP addresses and ports. final NetworkSettings network; /// Settings for easter eggs. - /// + /// /// Please, please, please -- do not remove these (Levi Lesches, '25). final EasterEggsSettings easterEggs; @@ -307,7 +315,7 @@ class Settings { }); /// Initialize settings from Json. - Settings.fromJson(Json json) : + Settings.fromJson(Json json) : network = NetworkSettings.fromJson(json["network"]), easterEggs = EasterEggsSettings.fromJson(json["easterEggs"]), science = ScienceSettings.fromJson(json["science"]), @@ -315,7 +323,7 @@ class Settings { dashboard = DashboardSettings.fromJson(json["dashboard"]); /// Converts the data from the settings instance to Json. - Json toJson() => { + Json toJson() => { "network": network.toJson(), "easterEggs": easterEggs.toJson(), "science": science.toJson(), diff --git a/lib/src/data/view_preset.dart b/lib/src/data/view_preset.dart new file mode 100644 index 000000000..24b8772c2 --- /dev/null +++ b/lib/src/data/view_preset.dart @@ -0,0 +1,67 @@ +import "package:rover_dashboard/data.dart"; +import "package:rover_dashboard/pages.dart"; + +/// Preset for the dashboard. +class ViewPreset { + /// Preset name. + final String name; + + /// List of views that comes with the views name (and if it is a camera view, its camera name). + final List views; + + /// Ratio of the controller for the resizable row on top. + final List horizontal1; + + /// Ratio of the controller for resizable row on bottom. + final List horizontal2; + + /// Ratio of the controller for screen 2's first row. + final List horizontal3; + + /// Ratio of the controller for screen 2's second row. + final List horizontal4; + + /// Ratio of the controller for the resizable column. + final List vertical1; + + /// The vertical controller for screen 2. + final List vertical2; + + /// A const constructor. + ViewPreset({ + required this.name, + required this.views, + required this.horizontal1, + required this.horizontal2, + required this.vertical1, + required this.vertical2, + required this.horizontal3, + required this.horizontal4, + }); + + /// Parses a view preset from JSON. + ViewPreset.fromJson(Json? json) : + name = json?["name"] ?? "No Name", + views = [ + for (final viewJson in json?["views"] ?? []) + DashboardView.fromJson(viewJson) ?? DashboardView.blank, + ], + horizontal1 = List.from(json?["horizontal1"] ?? []), + horizontal2 = List.from(json?["horizontal2"] ?? []), + horizontal3 = List.from(json?["horizontal3"] ?? []), + horizontal4 = List.from(json?["horizontal4"] ?? []), + vertical1 = List.from(json?["vertical1"] ?? []), + vertical2 = List.from(json?["vertical2"] ?? []); + + /// Serializes a view preset to JSON. + Json toJson() => { + "name": name, + "views" : views, + "horizontal1" : horizontal1, + "horizontal2" : horizontal2, + "horizontal3" : horizontal3, + "horizontal4" : horizontal4, + "vertical1" : vertical1, + "vertical2" : vertical2, + }; +} diff --git a/lib/src/models/data/settings.dart b/lib/src/models/data/settings.dart index e991e382d..94212de96 100644 --- a/lib/src/models/data/settings.dart +++ b/lib/src/models/data/settings.dart @@ -29,10 +29,10 @@ class SettingsModel extends Model { } /// Replaces the current settings with the provided ones. - Future update(Settings value) async { + Future update([Settings? value]) async { try { - await services.files.writeSettings(value); - all = value; + await services.files.writeSettings(value ?? all); + if (value != null) all = value; notifyListeners(); } catch (error) { models.home.setMessage(severity: Severity.critical, text: "Could not save settings: $error"); diff --git a/lib/src/models/data/views.dart b/lib/src/models/data/views.dart index d1e4dd18b..6a8f8d346 100644 --- a/lib/src/models/data/views.dart +++ b/lib/src/models/data/views.dart @@ -1,9 +1,20 @@ +import "package:flutter/material.dart"; import "package:flutter_resizable_container/flutter_resizable_container.dart"; import "package:rover_dashboard/data.dart"; import "package:rover_dashboard/models.dart"; import "package:rover_dashboard/pages.dart"; +extension on ResizableController { + void setRatios(List ratios) => setSizes([ + for (final ratio in ratios) + ResizableSize.ratio(ratio), + ]); +} + +/// A function that builds a view of the given index. +typedef ViewBuilder = Widget Function(BuildContext context, int index); + /// A data model for keeping track of the on-screen views. class ViewsModel extends Model { /// The controller for the resizable row on top. @@ -29,11 +40,67 @@ class ViewsModel extends Model { DashboardView.cameraViews[0], ]; + @override Future init() async { models.settings.addListener(notifyListeners); } + /// Saves the current state as a preset and updates the user's settings. + Future saveAsPreset(String? name) async { + if (name == null) return; + if (models.settings.dashboard.presets.any((otherPreset) => otherPreset.name == name)) { + models.home.setMessage( + severity: Severity.error, + text: "Name is already taken, please rename preset", + ); + return; + } + final preset = toPreset(name); + models.settings.dashboard.presets.add(preset); + await models.settings.update(); + } + + /// Returns a [ViewPreset] to match the current state. + ViewPreset toPreset(String name) => ViewPreset( + name: name, + views: views.toList(), + horizontal1: horizontalController1.ratios, + horizontal2: horizontalController2.ratios, + vertical1: verticalController.ratios, + vertical2: verticalController2.ratios, + horizontal3: horizontalController3.ratios, + horizontal4: horizontalController4.ratios, + ); + + /// Loads preset from Json Row + Future loadPreset(ViewPreset preset) async { + setNumViews(preset.views.length); + resetSizes(); + for(var i = 0; i < preset.views.length; i++){ + replaceView(i, preset.views[i], ignoreErrors: true); + } + // This delay is needed to prevent an error + // + // While [setNumViews] does update the number of views in the view model, + // it does not cause a build to occur. This small delay allows the next frame to be + // built, the UI to update, and *then* updates the ratios. This is necessary because + // the controllers listed below are directly tied to the UI. + await Future.delayed(const Duration(milliseconds: 200)); + if (preset.horizontal1.isNotEmpty) horizontalController1.setRatios(preset.horizontal1); + if (preset.horizontal2.isNotEmpty) horizontalController2.setRatios(preset.horizontal2); + if (preset.horizontal3.isNotEmpty) horizontalController3.setRatios(preset.horizontal3); + if (preset.horizontal4.isNotEmpty) horizontalController4.setRatios(preset.horizontal4); + if (preset.vertical1.isNotEmpty) verticalController.setRatios(preset.vertical1); + if (preset.vertical2.isNotEmpty) verticalController2.setRatios(preset.vertical2); + } + + /// Deletes presets and rewrites Json file + Future deletePreset(ViewPreset preset) async{ + models.settings.dashboard.presets.remove(preset); + await models.settings.update(); + } + @override void dispose() { models.settings.removeListener(notifyListeners); @@ -71,12 +138,14 @@ class ViewsModel extends Model { } /// Replaces the view at the given index with the new view. - void replaceView(int index, DashboardView newView) { + void replaceView(int index, DashboardView newView, {bool ignoreErrors = false}) { if (views.contains(newView) && newView.name != Routes.blank) { - models.home.setMessage( - severity: Severity.error, - text: "That view is already on-screen", - ); + if (!ignoreErrors) { + models.home.setMessage( + severity: Severity.error, + text: "That view is already on-screen", + ); + } return; } views[index] = newView; @@ -94,7 +163,28 @@ class ViewsModel extends Model { views.add(DashboardView.blank); } } - // resetSizes(); notifyListeners(); } + + /// Swaps two presets in the user's settings. + void swapPresets(int oldIndex, int newIndex) { + final presets = models.settings.dashboard.presets; + // ignore: parameter_assignments + if (oldIndex < newIndex) newIndex -= 1; + final element = presets.removeAt(oldIndex); + presets.insert(newIndex, element); + // This notifyListeners call is needed to update the UI smoothly. + // + // A ResizableListView simply *simulates* re-ordering its children. After + // the child is dropped in its new position, it is sent back to its original + // position, and it is the backend's job to actually update the underlying data. + // + // Calling [SettingsModel.update] here does the job, but its [notifyListeners] call + // is (correctly) placed *after* the settings file is updated on disk. This introduces + // a delay in the re-ordering, so items will animate back and forth. + // + // This call will update the UI based on the in-memory list before the disk is updated. + notifyListeners(); + models.settings.update(); + } } diff --git a/lib/src/models/view/builders/preset_builder.dart b/lib/src/models/view/builders/preset_builder.dart new file mode 100644 index 000000000..ba44f2844 --- /dev/null +++ b/lib/src/models/view/builders/preset_builder.dart @@ -0,0 +1,20 @@ +import "package:flutter/material.dart"; +import "package:rover_dashboard/models.dart"; + +/// A builder for presets. +class PresetBuilder extends ValueBuilder { + /// The text controller for the Preset name. + final nameController = TextEditingController(); + + @override + List get otherBuilders => [nameController]; + + @override + bool get isValid => nameController.text.isNotEmpty; + + @override + void get value { /* Use [save] instead */ } + + /// Calls [models.views.saveAsPreset] in views.dart + void save() => models.views.saveAsPreset(nameController.text); +} diff --git a/lib/src/models/view/builders/settings_builder.dart b/lib/src/models/view/builders/settings_builder.dart index 4bb4f53c6..a5f141c69 100644 --- a/lib/src/models/view/builders/settings_builder.dart +++ b/lib/src/models/view/builders/settings_builder.dart @@ -197,6 +197,9 @@ class DashboardSettingsBuilder extends ValueBuilder { /// Whether to use version checking. See [DashboardSettings.versionChecking]. bool versionChecking; + + ///Builder for the presets. + List preset; /// Modifies the given [DashboardSettings]. DashboardSettingsBuilder(DashboardSettings initial) : @@ -206,7 +209,8 @@ class DashboardSettingsBuilder extends ValueBuilder { splitCameras = initial.splitCameras, preferTankControls = initial.preferTankControls, versionChecking = initial.versionChecking, - themeMode = initial.themeMode; + themeMode = initial.themeMode, + preset = initial.presets; @override bool get isValid => fps.isValid && blockSize.isValid; @@ -220,6 +224,7 @@ class DashboardSettingsBuilder extends ValueBuilder { splitCameras: splitCameras, preferTankControls: preferTankControls, versionChecking: versionChecking, + presets: preset, ); /// Updates the [splitMode] when a new one is selected. diff --git a/lib/src/pages/view.dart b/lib/src/pages/view.dart index e83e41e52..df41b66a3 100644 --- a/lib/src/pages/view.dart +++ b/lib/src/pages/view.dart @@ -1,3 +1,4 @@ +import "package:collection/collection.dart"; import "package:flutter/material.dart"; import "package:rover_dashboard/data.dart"; @@ -22,7 +23,7 @@ class DashboardView { Widget Function() iconFunc; /// A unique key to use while selecting this view. - final Object? key; + final CameraName? key; /// A function to build this view. final ViewBuilder builder; @@ -34,6 +35,19 @@ class DashboardView { DashboardView({required this.name, required this.builder, required this.iconFunc, this.key}) : flutterKey = UniqueKey(); + /// Expands or contracts based on number of camera/ui views. + static final List allViews = [...cameraViews, ...uiViews, blank]; + + /// Finds the right view in [allViews] that matches the given JSON. + static DashboardView? fromJson(Json json) => allViews + .firstWhereOrNull((view) => view.name == json["name"] && view.key?.value == json["cameraName"]); + + /// Converts name of uiView/cameraKey into json format + Json toJson() => { + "name": name, + "cameraName": key?.value, + }; + /// A list of views that represent all the camera feeds. static final List cameraViews = [ for (final name in CameraName.values) diff --git a/lib/src/services/files.dart b/lib/src/services/files.dart index 0d4bb7a9e..ba17523c8 100644 --- a/lib/src/services/files.dart +++ b/lib/src/services/files.dart @@ -3,6 +3,7 @@ import "dart:convert"; import "dart:io"; import "dart:async"; +import "package:flutter/foundation.dart"; import "package:path_provider/path_provider.dart"; import "package:rover_dashboard/data.dart"; @@ -18,29 +19,29 @@ extension on Directory { File operator / (String filename) => File("$path/$filename"); } -/// A service to read and write to the file system. -/// +/// A service to read and write to the file system. +/// /// The dashboard reads and writes to files in [outputDir]. class FilesService extends Service { - /// The directory where the dashboard keeps its files. - /// + /// The directory where the dashboard keeps its files. + /// /// This includes settings, data, images, and anything else the user or dashboard - /// may want to keep between sessions. Categories of output, like screenshots, + /// may want to keep between sessions. Categories of output, like screenshots, /// should get their own subdirectory. late final Directory outputDir; /// The directory where all logging data is outputted - /// + /// /// This includes all the different operating modes with specified folders inside late final Directory loggingDir; /// The directory where screenshots are stored. - /// + /// /// These are only screenshots of video feeds, not of the dashboard itself. Directory get screenshotsDir => Directory("${outputDir.path}/screenshots"); - + /// The file containing the user's [Settings], in JSON form. - /// + /// /// This file should contain the result of [Settings.toJson], and loading settings /// from the file should be done with [Settings.fromJson]. File get settingsFile => outputDir / "settings.json"; @@ -49,7 +50,7 @@ class FilesService extends Service { final JsonEncoder jsonEncoder = const JsonEncoder.withIndent(" "); /// Ensure that files and directories that are expected to be present actually - /// exist on the system. If not, create them. + /// exist on the system. If not, create them. @override Future init() async { final appDir = await getApplicationDocumentsDirectory(); @@ -78,6 +79,10 @@ class FilesService extends Service { await writeSettings(settings); // re-save any default values return settings; } catch (error) { + if (kDebugMode) { + print("Here are the contents of the settings file: \n$json"); + rethrow; + } services.error = "Settings were corrupted and reset back to defaults"; await writeSettings(Settings.fromJson({})); // delete corrupt settings if (retry) { @@ -90,17 +95,17 @@ class FilesService extends Service { /// Saves the current frame in the feed to the camera's output directory. Future writeImage(List image, String cameraName) async { - final dir = await Directory("${screenshotsDir.path}/$cameraName").create(recursive: true); + final dir = await Directory("${screenshotsDir.path}/$cameraName").create(recursive: true); final files = dir.listSync(); final number = files.isEmpty ? 1 : (int.parse(files.last.filename) + 1); - await File("${dir.path}/$number.jpg").writeAsBytes(image); + await File("${dir.path}/$number.jpg").writeAsBytes(image); } /// Saves all the data in [batchedLogs] to a file by calling [logAllData]. late final Timer dataLogger; - + /// Holds data to be logged by [logData] when [dataLogger] fires. - /// + /// /// This is used by [logData] instead of writing the data immediately because data can come in at /// an unpredictable and burdensome rate, which would make the dashboard write a lot of data at /// once to the same file(s) and overload the user's device. diff --git a/lib/src/widgets/atomic/editors.dart b/lib/src/widgets/atomic/editors.dart index 0483b60cc..1d6c03639 100644 --- a/lib/src/widgets/atomic/editors.dart +++ b/lib/src/widgets/atomic/editors.dart @@ -16,7 +16,7 @@ class SocketEditor extends ReusableReactiveWidget { /// Creates a widget to edit host and port data for a socket. const SocketEditor({ required this.name, - required SocketBuilder model, + required SocketBuilder model, this.editPort = true, }) : super(model); @@ -62,7 +62,7 @@ class NumberEditor extends ReusableReactiveWidget { /// Creates a widget to modify a number. const NumberEditor({ required NumberBuilder model, - required this.name, + required this.name, this.subtitle, this.titleFlex = 4, this.width, @@ -121,11 +121,11 @@ class DropdownEditor extends StatelessWidget { Widget build(BuildContext context) => Row( children: [ Text(name), - const SizedBox(width: 12), + const SizedBox(width: 12), DropdownButton( focusNode: FocusNode(), value: value, - onChanged: (input) { + onChanged: (input) { if (input == null) return; onChanged(input); }, @@ -155,7 +155,7 @@ class ColorEditor extends ReusableReactiveWidget { final result = await model.setColor(); if (result && context.mounted) Navigator.of(context).pop(); }, - child: const Text("Save"), + child: const Text("Save"), ), ], content: Column( @@ -186,7 +186,7 @@ class ColorEditor extends ReusableReactiveWidget { ), const SizedBox(height: 16), CheckboxListTile( - value: model.blink, + value: model.blink, onChanged: model.updateBlink, title: const Text("Blink"), ), @@ -199,7 +199,7 @@ class ColorEditor extends ReusableReactiveWidget { class TimerEditor extends ReactiveWidget { @override TimerBuilder createModel() => TimerBuilder(); - + @override Widget build(BuildContext context, TimerBuilder model) => AlertDialog( title: const Text("Start a timer"), @@ -207,7 +207,7 @@ class TimerEditor extends ReactiveWidget { mainAxisSize: MainAxisSize.min, children: [ SizedBox( - height: 50, + height: 50, width: double.infinity, child: TextField( controller: model.nameController, @@ -216,7 +216,7 @@ class TimerEditor extends ReactiveWidget { ), ), SizedBox( - height: 50, + height: 50, width: double.infinity, child: TextField( onChanged: model.duration.update, @@ -230,7 +230,7 @@ class TimerEditor extends ReactiveWidget { TextButton(child: const Text("Cancel"), onPressed: () => Navigator.of(context).pop()), ElevatedButton( onPressed: model.isValid ? () { model.start(); Navigator.of(context).pop(); } : null, - child: const Text("Save"), + child: const Text("Save"), ), ], ); @@ -246,7 +246,7 @@ class GpsEditor extends ReusableReactiveWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ DropdownEditor( - name: "Type", + name: "Type", value: model.type, onChanged: model.updateType, items: GpsType.values, @@ -293,7 +293,7 @@ class ThrottleEditor extends ReactiveWidget { actions: [ ElevatedButton( onPressed: !model.isValid || model.isLoading ? null : () async { - await model.save(); + await model.save(); if (!context.mounted) return; Navigator.of(context).pop(); }, @@ -304,5 +304,5 @@ class ThrottleEditor extends ReactiveWidget { child: const Text("Cancel"), ), ], - ); + ); } diff --git a/lib/src/widgets/atomic/preset_editors.dart b/lib/src/widgets/atomic/preset_editors.dart new file mode 100644 index 000000000..401e25470 --- /dev/null +++ b/lib/src/widgets/atomic/preset_editors.dart @@ -0,0 +1,35 @@ + +import "package:flutter/material.dart"; +import "package:rover_dashboard/models.dart"; +import "package:rover_dashboard/widgets.dart"; + +///A widget to save a preset backed by [PresetBuilder]. +class PresetSave extends ReactiveWidget { + @override + PresetBuilder createModel() => PresetBuilder(); + + @override + Widget build(BuildContext context, PresetBuilder model) => AlertDialog( + title: const Text("Save a preset"), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: 50, + width: double.infinity, + child: TextField( + controller: model.nameController, + decoration: const InputDecoration(hintText: "Preset Name"), + ), + ), + ], + ), + actions: [ + TextButton(child: const Text("Cancel"), onPressed: () => Navigator.of(context).pop()), + ElevatedButton( + onPressed: model.isValid ? () {model.save(); Navigator.of(context).pop(); } : null, + child: const Text("Save"), + ), + ], + ); +} diff --git a/lib/src/widgets/navigation/sidebar.dart b/lib/src/widgets/navigation/sidebar.dart index 9156b9d69..43344d9f7 100644 --- a/lib/src/widgets/navigation/sidebar.dart +++ b/lib/src/widgets/navigation/sidebar.dart @@ -65,14 +65,14 @@ class Sidebar extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ ViewsCounter(), - IconButton( + TextButton.icon( icon: const Icon(Icons.aspect_ratio), - tooltip: "Reset View Sizes", + label: const Text("Reset View Sizes"), onPressed: models.views.resetSizes, ), ], ), - Expanded(child: ViewsList()), + const Expanded(child: ViewsList()), ], ), ], @@ -114,11 +114,11 @@ class MetricsList extends ReusableReactiveWidget { extension SeverityUtil on Severity { /// Fetch the color based on the severity Color? get color => switch (this) { - Severity.info => Colors.blueGrey, - Severity.warning => Colors.orange, - Severity.error => Colors.red, - Severity.critical => Colors.red.shade900, - }; + Severity.info => Colors.blueGrey, + Severity.warning => Colors.orange, + Severity.error => Colors.red, + Severity.critical => Colors.red.shade900, + }; } /// Displays controls for the given [Controller]. diff --git a/lib/src/widgets/navigation/views.dart b/lib/src/widgets/navigation/views.dart index 8548d851b..1da8c7984 100644 --- a/lib/src/widgets/navigation/views.dart +++ b/lib/src/widgets/navigation/views.dart @@ -13,11 +13,10 @@ class ViewsWidget extends ReusableReactiveWidget { @override Widget build(BuildContext context, ViewsModel model) { - final childData = List.generate( + final childData = List.generate( (model.views.length > 1) ? model.views.length : 0, - (index) => ResizableChildData( + (index) => ResizableChild( minSize: 100, - startingRatio: 0.5, child: _ViewArea(model: model, index: index), ), ); @@ -33,42 +32,36 @@ class ViewsWidget extends ReusableReactiveWidget { SplitMode.horizontal => Axis.vertical, SplitMode.vertical => Axis.horizontal, }, - dividerWidth: 8, + divider: const ResizableDivider(thickness: 8, color: Colors.black), controller: switch (models.settings.dashboard.splitMode) { SplitMode.horizontal => model.verticalController, SplitMode.vertical => model.horizontalController1, }, - dividerColor: Colors.black, children: childData.sublist(0, 2), ), 3 || 4 => ResizableContainer( controller: model.verticalController, direction: Axis.vertical, - dividerWidth: 8, - dividerColor: Colors.black, + divider: const ResizableDivider(thickness: 8, color: Colors.black), children: [ - ResizableChildData( + ResizableChild( minSize: 100, - startingRatio: 0.5, child: ResizableContainer( controller: model.horizontalController1, + divider: const ResizableDivider(thickness: 8, color: Colors.black), direction: Axis.horizontal, - dividerWidth: 8, - dividerColor: Colors.black, children: childData.sublist(0, 2), ), ), if (model.views.length == 3) childData[2] else - ResizableChildData( + ResizableChild( minSize: 100, - startingRatio: 0.5, child: ResizableContainer( controller: model.horizontalController2, direction: Axis.horizontal, - dividerWidth: 8, - dividerColor: Colors.black, + divider: const ResizableDivider(thickness: 8, color: Colors.black), children: childData.sublist(2, 4), ), ), @@ -80,28 +73,23 @@ class ViewsWidget extends ReusableReactiveWidget { child: ResizableContainer( controller: model.verticalController, direction: Axis.vertical, - dividerWidth: 8, - dividerColor: Colors.black, + divider: const ResizableDivider(thickness: 8, color: Colors.black), children: [ - ResizableChildData( + ResizableChild( minSize: 100, - startingRatio: 0.5, child: ResizableContainer( controller: model.horizontalController1, direction: Axis.horizontal, - dividerWidth: 8, - dividerColor: Colors.black, + divider: const ResizableDivider(thickness: 8, color: Colors.black), children: childData.sublist(0, 2), ), ), - ResizableChildData( + ResizableChild( minSize: 100, - startingRatio: 0.5, child: ResizableContainer( controller: model.horizontalController2, direction: Axis.horizontal, - dividerWidth: 8, - dividerColor: Colors.black, + divider: const ResizableDivider(thickness: 8, color: Colors.black), children: childData.sublist(2, 4), ), ), @@ -117,28 +105,23 @@ class ViewsWidget extends ReusableReactiveWidget { child: ResizableContainer( controller: model.verticalController2, direction: Axis.vertical, - dividerWidth: 8, - dividerColor: Colors.black, + divider: const ResizableDivider(thickness: 8, color: Colors.black), children: [ - ResizableChildData( + ResizableChild( minSize: 100, - startingRatio: 0.5, child: ResizableContainer( controller: model.horizontalController3, direction: Axis.horizontal, - dividerWidth: 8, - dividerColor: Colors.black, + divider: const ResizableDivider(thickness: 8, color: Colors.black), children: childData.sublist(4, 6), ), ), - ResizableChildData( + ResizableChild( minSize: 100, - startingRatio: 0.5, child: ResizableContainer( controller: model.horizontalController4, direction: Axis.horizontal, - dividerWidth: 8, - dividerColor: Colors.black, + divider: const ResizableDivider(thickness: 8, color: Colors.black), children: childData.sublist(6, 8), ), ), diff --git a/lib/src/widgets/navigation/views_list.dart b/lib/src/widgets/navigation/views_list.dart index bd8b005de..cda704bcd 100644 --- a/lib/src/widgets/navigation/views_list.dart +++ b/lib/src/widgets/navigation/views_list.dart @@ -1,16 +1,38 @@ import "package:flutter/material.dart"; +import "package:rover_dashboard/data.dart"; import "package:rover_dashboard/models.dart"; import "package:rover_dashboard/pages.dart"; import "package:rover_dashboard/widgets.dart"; +/// A simple model that just listens for changes in the network and settings. +class ViewsSidebarModel with ChangeNotifier { + /// Listens for changes in the network and settings. + ViewsSidebarModel() { + models.sockets.addListener(notifyListeners); + models.views.addListener(notifyListeners); + models.settings.addListener(notifyListeners); + } + + @override + void dispose() { + models.sockets.removeListener(notifyListeners); + models.views.removeListener(notifyListeners); + models.settings.removeListener(notifyListeners); + super.dispose(); + } +} + /// A list of views for the user to drag into their desired view area -class ViewsList extends ReusableReactiveWidget { +class ViewsList extends ReactiveWidget { /// The size of the icon to appear under the mouse pointer when dragging static const double draggingIconSize = 100; + @override + ViewsSidebarModel createModel() => ViewsSidebarModel(); + /// A const constructor - ViewsList() : super(models.sockets); + const ViewsList(); Widget _buildDraggable(DashboardView view, {Widget? dragIcon}) => Draggable( data: view, @@ -33,8 +55,36 @@ class ViewsList extends ReusableReactiveWidget { ); @override - Widget build(BuildContext context, Sockets model) => ListView( + Widget build(BuildContext context, ViewsSidebarModel model) => ListView( children: [ + ExpansionTile( + title: const Text("Presets"), + children: [ + ReorderableListView( + shrinkWrap: true, + onReorder: models.views.swapPresets, + children: [ + for (final preset in models.settings.dashboard.presets) ListTile( + key: ValueKey(preset.name), + title: Text(preset.name), + onTap: () => models.views.loadPreset(preset), + leading: IconButton( + onPressed: () => _deletePreset(context, preset), + icon: const Icon(Icons.remove_circle), + splashColor: Colors.blueGrey, + color: Colors.red, + ), + ), + ], + ), + + ListTile( + title: const Text("Save current layout"), + onTap: () => _savePreset(context), + trailing: const Icon(Icons.save), + ), + ], + ), ExpansionTile( title: const Text("Cameras"), children: [ @@ -45,13 +95,38 @@ class ViewsList extends ReusableReactiveWidget { ExpansionTile( title: const Text("Controls"), children: [ - for (final view in DashboardView.uiViews) - _buildDraggable(view), + for (final view in DashboardView.uiViews) _buildDraggable(view), ], ), _buildDraggable(DashboardView.blank), ], ); + + void _deletePreset(BuildContext context, ViewPreset preset) => showDialog( + context: context, + builder: (_) => AlertDialog( + title: const Text("Delete a preset"), + content: Text("Are you sure you want to delete the preset ${preset.name}?"), + actions: [ + TextButton( + child: const Text("Cancel"), + onPressed: () => Navigator.of(context).pop(), + ), + ElevatedButton( + onPressed: () { + models.views.deletePreset(preset); + Navigator.of(context).pop(); + }, + child: const Text("Delete"), + ), + ], + ), + ); + + void _savePreset(BuildContext context) => showDialog( + context: context, + builder: (_) => PresetSave(), + ); } /// A button for the user to select a new view. @@ -64,28 +139,26 @@ class ViewsSelector extends StatelessWidget { @override Widget build(BuildContext context) => PopupMenuButton( - tooltip: "Select a feed", - icon: const Icon(Icons.expand_more), - onSelected: (view) => models.views.replaceView(index, view), - itemBuilder: (_) => [ - const PopupMenuItem(enabled: false, child: Text("Cameras")), - for (final view in DashboardView.cameraViews) - _buildItem(view), - const PopupMenuDivider(), - const PopupMenuItem(enabled: false, child: Text("Controls")), - for (final view in DashboardView.uiViews) - _buildItem(view), - ], - ); + tooltip: "Select a feed", + icon: const Icon(Icons.expand_more), + onSelected: (view) => models.views.replaceView(index, view), + itemBuilder: (_) => [ + const PopupMenuItem(enabled: false, child: Text("Cameras")), + for (final view in DashboardView.cameraViews) _buildItem(view), + const PopupMenuDivider(), + const PopupMenuItem(enabled: false, child: Text("Controls")), + for (final view in DashboardView.uiViews) _buildItem(view), + ], + ); PopupMenuItem _buildItem(DashboardView view) => PopupMenuItem( - value: view, - child: Row( - children: [ - view.icon, - const SizedBox(width: 8), - Text(view.name), - ], - ), - ); + value: view, + child: Row( + children: [ + view.icon, + const SizedBox(width: 8), + Text(view.name), + ], + ), + ); } diff --git a/lib/widgets.dart b/lib/widgets.dart index 79902a3d5..3fda1a61c 100644 --- a/lib/widgets.dart +++ b/lib/widgets.dart @@ -20,6 +20,7 @@ import "package:flutter/material.dart"; export "src/widgets/atomic/autonomy_command.dart"; export "src/widgets/atomic/camera_editor.dart"; export "src/widgets/atomic/editors.dart"; +export "src/widgets/atomic/preset_editors.dart"; export "src/widgets/atomic/science_command.dart"; export "src/widgets/atomic/video_feed.dart"; diff --git a/pubspec.lock b/pubspec.lock index b977052d0..7672c09d2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -203,10 +203,10 @@ packages: dependency: "direct main" description: name: flutter_resizable_container - sha256: fb0a8790a1dfd0a7ffb189a84d3376a43bdb6d94749d07ba3336acfa1851aba9 + sha256: "5b15c79c6cc338ed79640c706bb5176baa3333d92fd3627ad279aa3e25d2f0e7" url: "https://pub.dev" source: hosted - version: "0.4.2" + version: "3.0.0" flutter_test: dependency: "direct dev" description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 0662f8c52..4af8f55ab 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ dependencies: fl_chart: ^0.68.0 flutter_libserialport: ^0.4.0 package_info_plus: ^8.0.0 - flutter_resizable_container: ^0.4.1 + flutter_resizable_container: ^3.0.0 path_provider: ^2.0.14 protobuf: ^3.0.0 url_launcher: ^6.1.10