Skip to content

Commit

Permalink
Added ability to set default view preset (#180)
Browse files Browse the repository at this point in the history
- Added a star next to each preset to set it as the default
- The default preset will be loaded when the app is opened
- Moved split mode setting to the views sidebar
- Split mode is now preserved as part of a preset
- Fixed timing issues with `flutter_resizable_container`
- Cleaned up some code
- Added option to update a preset with current content
- override `flutter_resizable_container` until andyhorn/flutter_resizable_container#61 is merged

---------

Co-authored-by: Levi Lesches <[email protected]>
  • Loading branch information
Gold872 and Levi-Lesches authored Nov 19, 2024
1 parent 36af765 commit bfa09a7
Show file tree
Hide file tree
Showing 14 changed files with 472 additions and 327 deletions.
15 changes: 11 additions & 4 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ library main;

import "dart:async";
import "dart:io";
import "package:flutter/foundation.dart";
import "package:flutter/material.dart";

import "package:rover_dashboard/app.dart";
Expand All @@ -32,8 +33,11 @@ void logError(Object error, StackTrace? stackTrace) => models.logs.handleLog(
void main() async {
// Logs sync errors to the logs page
FlutterError.onError = (FlutterErrorDetails details) {
logError(details.exception, details.stack);
FlutterError.presentError(details); // do the regular error behavior
if (kDebugMode) {
FlutterError.presentError(details); // do the regular error behavior
} else {
logError(details.exception, details.stack);
}
};
// Logs async errors to the logs page
runZonedGuarded(
Expand All @@ -42,8 +46,11 @@ void main() async {
if (error is SocketException && networkErrors.contains(error.osError!.errorCode)) {
models.home.setMessage(severity: Severity.critical, text: "Network error, restart by clicking the network icon");
} else {
logError(error, stackTrace);
Error.throwWithStackTrace(error, stackTrace); // do the regular error behavior
if (kDebugMode) {
Error.throwWithStackTrace(error, stackTrace); // do the regular error behavior
} else {
logError(error, stackTrace);
}
}
}
);
Expand Down
14 changes: 10 additions & 4 deletions lib/src/data/settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ extension ThemeModeUtils on ThemeMode {
/// Settings related to the dashboard itself, not the rover.
class DashboardSettings {
/// How the Dashboard should split when only two views are present.
final SplitMode splitMode;
SplitMode splitMode;

/// The precision of the GPS grid.
///
Expand Down Expand Up @@ -247,16 +247,20 @@ class DashboardSettings {
/// A list of ViewPresets
final List<ViewPreset> presets;

/// The default preset to load on startup
String? defaultPreset;

/// A const constructor.
const DashboardSettings({
required this.presets,
DashboardSettings({
required this.splitMode,
required this.mapBlockSize,
required this.maxFps,
required this.themeMode,
required this.splitCameras,
required this.preferTankControls,
required this.versionChecking,
required this.presets,
required this.defaultPreset,
});

/// Parses settings from JSON.
Expand All @@ -265,6 +269,7 @@ class DashboardSettings {
for (final presetJson in json?["presets"] ?? [])
ViewPreset.fromJson(presetJson),
],
defaultPreset = json?["defaultPreset"],
splitMode = SplitMode.values[json?["splitMode"] ?? SplitMode.horizontal.index],
mapBlockSize = json?["mapBlockSize"] ?? 1.0,
maxFps = (json?["maxFps"] ?? 60) as int,
Expand All @@ -275,14 +280,15 @@ class DashboardSettings {

/// Serializes these settings to JSON.
Json toJson() => {
"presets" : presets,
"splitMode": splitMode.index,
"mapBlockSize": mapBlockSize,
"maxFps": maxFps,
"theme": themeMode.name,
"splitCameras": splitCameras,
"preferTankControls": preferTankControls,
"versionChecking": versionChecking,
"presets": presets,
"defaultPreset": defaultPreset,
};
}

Expand Down
2 changes: 1 addition & 1 deletion lib/src/data/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ extension MapRecords<K, V> on Map<K, V> {
/// Helpful extensions on [DateTime]s.
extension DateTimeTimestamp on DateTime{
/// Formats this [DateTime] as a simple timestamp.
String get timeStamp => "$year-$month-$day-$hour-$minute";
String get timeStamp => "$year-$month-$day-$hour-$minute";
}

/// A list that can manage its own length.
Expand Down
6 changes: 6 additions & 0 deletions lib/src/data/view_preset.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ class ViewPreset {
/// Preset name.
final String name;

/// The split mode of this preset.
final SplitMode splitMode;

/// List of views that comes with the views name (and if it is a camera view, its camera name).
final List<DashboardView> views;

Expand All @@ -30,6 +33,7 @@ class ViewPreset {
/// A const constructor.
ViewPreset({
required this.name,
required this.splitMode,
required this.views,
required this.horizontal1,
required this.horizontal2,
Expand All @@ -42,6 +46,7 @@ class ViewPreset {
/// Parses a view preset from JSON.
ViewPreset.fromJson(Json? json) :
name = json?["name"] ?? "No Name",
splitMode = SplitMode.values.byName(json?["splitMode"] ?? "vertical"),
views = [
for (final viewJson in json?["views"] ?? [])
DashboardView.fromJson(viewJson) ?? DashboardView.blank,
Expand All @@ -56,6 +61,7 @@ class ViewPreset {
/// Serializes a view preset to JSON.
Json toJson() => {
"name": name,
"splitMode": splitMode.name,
"views" : views,
"horizontal1" : horizontal1,
"horizontal2" : horizontal2,
Expand Down
113 changes: 113 additions & 0 deletions lib/src/models/data/view_presets.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@

import "package:flutter/foundation.dart";
import "package:collection/collection.dart";

import "package:rover_dashboard/data.dart";
import "package:rover_dashboard/models.dart";

/// A mixin to provide preset functionality.
mixin PresetsModel on ChangeNotifier {
/// The user's Dashboard settings.
DashboardSettings get settings => models.settings.dashboard;

/// Swaps two presets in the user's settings.
void swapPresets(int oldIndex, int newIndex) {
final presets = settings.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();
}

/// Sets the default preset to [preset]
Future<void> setDefaultPreset(String? preset) async {
settings.defaultPreset = preset;
await models.settings.update();
}

/// Deletes presets and rewrites Json file
Future<void> deletePreset(ViewPreset preset) async{
if (settings.defaultPreset == preset.name) {
settings.defaultPreset = null;
}
settings.presets.remove(preset);
await models.settings.update();
}

/// Returns a [ViewPreset] to match the current state.
ViewPreset toPreset(String name);

/// Loads the given preset.
void loadPreset(ViewPreset preset);

/// Saves the current state as a preset and updates the user's settings.
Future<void> saveAsPreset(String? name) async {
if (name == null) return;
if (settings.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);
settings.presets.add(preset);
await models.settings.update();
}

/// Retreives and loads the default settings.
Future<void> loadDefaultPreset() async {
final defaultPresetName = settings.defaultPreset;
final defaultPreset = settings.presets
.firstWhereOrNull((preset) => preset.name == defaultPresetName);
if (defaultPreset == null) return;
loadPreset(defaultPreset);
}

/// Sets or clears this preset as the default.
void toggleDefaultPreset(ViewPreset preset) {
if (settings.defaultPreset != preset.name) {
models.views.setDefaultPreset(preset.name);
} else {
models.views.setDefaultPreset(null);
}
}

/// Returns whether this preset is the default.
bool isDefaultPreset(ViewPreset preset) =>
settings.defaultPreset == preset.name;

/// The list of all available presets.
List<ViewPreset> get presets => settings.presets;

/// The current [DashboardSettings.splitMode].
SplitMode get splitMode => settings.splitMode;

/// Updates [DashboardSettings.splitMode].
void updateSplitMode(SplitMode? value) {
if (value == null) return;
settings.splitMode = value;
models.settings.update();
notifyListeners();
}

/// Replaces the given preset with the current state using [toPreset].
Future<void> updatePreset(ViewPreset preset) async {
final index = presets.indexOf(preset);
presets[index] = toPreset(preset.name);
await models.settings.update();
}
}
Loading

0 comments on commit bfa09a7

Please sign in to comment.