Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added ability to set default view preset #180

Merged
merged 9 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading