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

Allow robot created notifications #48

Merged
merged 14 commits into from
Jun 25, 2024
70 changes: 70 additions & 0 deletions elasticlib/Elastic.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package frc.robot;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.networktables.PubSubOption;
import edu.wpi.first.networktables.StringPublisher;
import edu.wpi.first.networktables.StringTopic;

public final class Elastic {
private static final StringTopic topic = NetworkTableInstance.getDefault()
.getStringTopic("elastic/robotnotifications");
private static final StringPublisher publisher = topic.publish(PubSubOption.sendAll(true));
private static final ObjectMapper objectMapper = new ObjectMapper();

public static void sendAlert(ElasticNotification alert) {
try {
publisher.set(objectMapper.writeValueAsString(alert));
EmeraldWither marked this conversation as resolved.
Show resolved Hide resolved
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}

public static class ElasticNotification {
@JsonProperty("level")
private NotificationLevel level;
@JsonProperty("title")
private String title;
@JsonProperty("description")
private String description;

public ElasticNotification(NotificationLevel level, String title, String description) {
this.level = level;
this.title = title;
this.description = description;
}

public void setLevel(NotificationLevel level) {
this.level = level;
}

public NotificationLevel getLevel() {
return level;
}

public void setTitle(String title) {
this.title = title;
}

public String getTitle() {
return title;
}

public void setDescription(String description) {
this.description = description;
}

public String getDescription() {
return description;
}

public enum NotificationLevel {
INFO,
WARNING,
ERROR
}
}
}
73 changes: 48 additions & 25 deletions lib/pages/dashboard_page.dart
Original file line number Diff line number Diff line change
@@ -1,24 +1,13 @@
import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';

import 'package:collection/collection.dart';
import 'package:dot_cast/dot_cast.dart';
import 'package:elegant_notification/elegant_notification.dart';
import 'package:file_selector/file_selector.dart';
import 'package:popover/popover.dart';
import 'package:screen_retriever/screen_retriever.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:window_manager/window_manager.dart';

import 'package:elastic_dashboard/services/hotkey_manager.dart';
import 'package:elastic_dashboard/services/ip_address_util.dart';
import 'package:elastic_dashboard/services/log.dart';
import 'package:elastic_dashboard/services/nt_connection.dart';
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';
Expand All @@ -31,6 +20,18 @@ import 'package:elastic_dashboard/widgets/editable_tab_bar.dart';
import 'package:elastic_dashboard/widgets/network_tree/networktables_tree.dart';
import 'package:elastic_dashboard/widgets/settings_dialog.dart';
import 'package:elastic_dashboard/widgets/tab_grid.dart';
import 'package:elegant_notification/elegant_notification.dart';
import 'package:elegant_notification/resources/stacked_options.dart';
import 'package:file_selector/file_selector.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:popover/popover.dart';
import 'package:screen_retriever/screen_retriever.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:window_manager/window_manager.dart';

import '../widgets/draggable_containers/models/layout_container_model.dart';

class DashboardPage extends StatefulWidget {
Expand All @@ -52,6 +53,7 @@ class DashboardPage extends StatefulWidget {
class _DashboardPageState extends State<DashboardPage> with WindowListener {
late final SharedPreferences _preferences;
late final UpdateChecker _updateChecker;
late final RobotNotificationsListener _robotNotificationListener;

final List<TabGrid> _grids = [];

Expand Down Expand Up @@ -206,6 +208,37 @@ class _DashboardPageState extends State<DashboardPage> with WindowListener {
});

Future(() => _checkForUpdates(notifyIfLatest: false, notifyIfError: false));

_robotNotificationListener = RobotNotificationsListener(
connection: ntConnection,
onNotification: (title, description, icon) {
setState(() {
ColorScheme colorScheme = Theme.of(context).colorScheme;
TextTheme textTheme = Theme.of(context).textTheme;
var widget = ElegantNotification(
autoDismiss: true,
showProgressIndicator: true,
background: colorScheme.surface,
width: 350,
position: Alignment.bottomRight,
title: Text(
title,
style: textTheme.bodyMedium!.copyWith(
fontWeight: FontWeight.bold,
),
),
icon: icon,
description: Text(description),
stackedOptions: StackedOptions(
key: 'robotnotification',
type: StackedType.above,
itemOffset: const Offset(0, 5),
),
);
if (mounted) widget.show(context);
});
});
_robotNotificationListener.listen();
}

@override
Expand Down Expand Up @@ -271,7 +304,6 @@ class _DashboardPageState extends State<DashboardPage> with WindowListener {
background: colorScheme.surface,
progressIndicatorBackground: colorScheme.surface,
progressIndicatorColor: const Color(0xff01CB67),
enableShadow: false,
width: 150,
position: Alignment.bottomRight,
toastDuration: const Duration(seconds: 3, milliseconds: 500),
Expand All @@ -291,7 +323,6 @@ class _DashboardPageState extends State<DashboardPage> with WindowListener {
background: colorScheme.surface,
progressIndicatorBackground: colorScheme.surface,
progressIndicatorColor: const Color(0xffFE355C),
enableShadow: false,
width: 150,
position: Alignment.bottomRight,
toastDuration: const Duration(seconds: 3, milliseconds: 500),
Expand Down Expand Up @@ -337,9 +368,7 @@ class _DashboardPageState extends State<DashboardPage> with WindowListener {
background: colorScheme.surface,
progressIndicatorBackground: colorScheme.surface,
progressIndicatorColor: const Color(0xffFE355C),
enableShadow: false,
width: 350,
height: 100,
position: Alignment.bottomRight,
toastDuration: const Duration(seconds: 3, milliseconds: 500),
icon: const Icon(Icons.error, color: Color(0xffFE355C)),
Expand All @@ -365,9 +394,7 @@ class _DashboardPageState extends State<DashboardPage> with WindowListener {
autoDismiss: false,
showProgressIndicator: false,
background: colorScheme.surface,
enableShadow: false,
width: 150,
height: 100,
width: 350,
position: Alignment.bottomRight,
title: Text(
'Version ${updateResponse.latestVersion!} Available',
Expand All @@ -384,7 +411,7 @@ class _DashboardPageState extends State<DashboardPage> with WindowListener {
fontWeight: FontWeight.bold,
),
),
onActionPressed: () async {
onNotificationPressed: () async {
Uri url = Uri.parse(Settings.releasesLink);

if (await canLaunchUrl(url)) {
Expand All @@ -401,9 +428,7 @@ class _DashboardPageState extends State<DashboardPage> with WindowListener {
background: colorScheme.surface,
progressIndicatorBackground: colorScheme.surface,
progressIndicatorColor: const Color(0xff01CB67),
enableShadow: false,
width: 150,
height: 100,
width: 350,
position: Alignment.bottomRight,
toastDuration: const Duration(seconds: 3, milliseconds: 500),
icon: const Icon(Icons.check_circle, color: Color(0xff01CB67)),
Expand Down Expand Up @@ -613,7 +638,6 @@ class _DashboardPageState extends State<DashboardPage> with WindowListener {
background: colorScheme.surface,
progressIndicatorBackground: colorScheme.surface,
progressIndicatorColor: const Color(0xffFE355C),
enableShadow: false,
width: 350,
height: 100 + (lines - 1) * 10,
position: Alignment.bottomRight,
Expand All @@ -640,7 +664,6 @@ class _DashboardPageState extends State<DashboardPage> with WindowListener {
background: colorScheme.surface,
progressIndicatorBackground: colorScheme.surface,
progressIndicatorColor: Colors.yellow,
enableShadow: false,
width: 350,
height: 100 + (lines - 1) * 10,
position: Alignment.bottomRight,
Expand Down
15 changes: 9 additions & 6 deletions lib/services/nt4_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,14 @@ import 'dart:async';
import 'dart:convert';
import 'dart:math';

import 'package:flutter/foundation.dart';

import 'package:collection/collection.dart';
import 'package:dot_cast/dot_cast.dart';
import 'package:elastic_dashboard/services/log.dart';
import 'package:flutter/foundation.dart';
import 'package:messagepack/messagepack.dart';
import 'package:msgpack_dart/msgpack_dart.dart';
import 'package:web_socket_channel/web_socket_channel.dart';

import 'package:elastic_dashboard/services/log.dart';

class NT4TypeStr {
static final Map<String, int> typeMap = {
'boolean': 0,
Expand Down Expand Up @@ -322,11 +320,12 @@ class NT4Client {
_topicAnnounceListeners.remove(onAnnounce);
}

NT4Subscription subscribe(String topic, [double period = 0.1]) {
NT4Subscription subscribe(String topic,
[double period = 0.1, bool all = false]) {
NT4Subscription newSub = NT4Subscription(
topic: topic,
uid: getNewSubUID(),
options: NT4SubscriptionOptions(periodicRateSeconds: period),
options: NT4SubscriptionOptions(periodicRateSeconds: period, all: all),
);

if (_subscribedTopics.contains(newSub)) {
Expand All @@ -351,6 +350,10 @@ class NT4Client {
return newSub;
}

NT4Subscription subscribeAll(String topic, [double period = 0.1]) {
return subscribe(topic, period, true);
}

NT4Subscription subscribeAllSamples(String topic, [double period = 0.1]) {
NT4Subscription newSub = NT4Subscription(
topic: topic,
Expand Down
7 changes: 5 additions & 2 deletions lib/services/nt_connection.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import 'package:flutter/foundation.dart';

import 'package:elastic_dashboard/services/ds_interop.dart';
import 'package:elastic_dashboard/services/nt4_client.dart';
import 'package:flutter/foundation.dart';

NTConnection get ntConnection => NTConnection.instance;

Expand Down Expand Up @@ -132,6 +131,10 @@ class NTConnection {
return _ntClient.subscribe(topic, period);
}

NT4Subscription subscribeAll(String topic, [double period = 0.1]) {
return _ntClient.subscribeAll(topic, period);
}

void unSubscribe(NT4Subscription subscription) {
_ntClient.unSubscribe(subscription);
}
Expand Down
57 changes: 57 additions & 0 deletions lib/services/robot_notifications_listener.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import 'dart:convert';

import 'package:dot_cast/dot_cast.dart';
import 'package:elastic_dashboard/services/nt_connection.dart';
import 'package:flutter/material.dart';

class RobotNotificationsListener {
bool _alertFirstRun = true;
final NTConnection connection;
final Function(String title, String description, Icon icon) onNotification;

RobotNotificationsListener(
{required this.connection, required this.onNotification});

void listen() {
var notifications =
ntConnection.subscribeAll("elastic/robotnotifications", 0.2);
notifications.listen((alertData, alertTimestamp) {
_onAlert(alertData!, alertTimestamp);
});
}

void _onAlert(Object alertData, int timestamp) {
//prevent showing a notification when we connect to NT
if (_alertFirstRun) {
_alertFirstRun = false;
return;
}

Map<String, dynamic> data = jsonDecode(alertData.toString());
Icon icon;

if (data["level"] == "INFO") {
icon = const Icon(Icons.info);
} else if (data["level"] == "WARNING") {
icon = const Icon(
Icons.warning_amber,
color: Colors.orange,
);
} else if (data["level"] == "ERROR") {
icon = const Icon(
Icons.error,
color: Colors.red,
);
} else {
icon = const Icon(Icons.question_mark);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love this

}
String? title = tryCast(data['title']);
String? description = tryCast(data['description']);

if (title == null || description == null) {
return;
}

onNotification(title, description, icon);
}
}
4 changes: 2 additions & 2 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -253,10 +253,10 @@ packages:
dependency: "direct main"
description:
name: elegant_notification
sha256: "94cf7377a939b101183a3093fea409c26c332511ba8d2da445ef8b251d460dc9"
sha256: f6ad40163a06ac5c41fe698d39c4e45b5d6c1617d6cd17062e11e1ecd39b3a97
url: "https://pub.dev"
source: hosted
version: "1.14.0"
version: "2.2.0"
equatable:
dependency: transitive
description:
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ dependencies:
decimal: ^2.3.3
dot_cast: ^1.2.0
dropdown_button2: ^2.3.9
elegant_notification: ^1.11.0
elegant_notification: ^2.2.0
file_selector: ^1.0.1
flex_seed_scheme: ^2.0.0
flutter:
Expand Down
Loading
Loading