802 lines
22 KiB
Dart
802 lines
22 KiB
Dart
|
|
// Singleton
|
|
import 'dart:async';
|
|
import 'dart:convert';
|
|
|
|
import 'package:appwrite/appwrite.dart' as appwrite;
|
|
import 'package:appwrite/models.dart' as models;
|
|
import 'package:bus_infotainment/audio_cache.dart';
|
|
import 'package:bus_infotainment/auth/api_constants.dart';
|
|
import 'package:bus_infotainment/auth/auth_api.dart';
|
|
import 'package:bus_infotainment/backend/modules/announcement.dart';
|
|
import 'package:bus_infotainment/backend/modules/commands.dart';
|
|
import 'package:bus_infotainment/backend/modules/directconnection.dart';
|
|
import 'package:bus_infotainment/backend/modules/networking.dart';
|
|
import 'package:bus_infotainment/backend/modules/synced_time.dart';
|
|
import 'package:bus_infotainment/backend/modules/tracker.dart';
|
|
import 'package:bus_infotainment/backend/modules/tube_info.dart';
|
|
import 'package:bus_infotainment/tfl_datasets.dart';
|
|
import 'package:bus_infotainment/utils/audio%20wrapper.dart';
|
|
import 'package:bus_infotainment/utils/delegates.dart';
|
|
import 'package:bus_infotainment/workaround/keepalive_realtime.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:ntp/ntp.dart';
|
|
import 'package:permission_handler/permission_handler.dart';
|
|
|
|
enum RoomConnectionMethod {
|
|
Cloud,
|
|
Local,
|
|
P2P,
|
|
None
|
|
}
|
|
|
|
|
|
|
|
class LiveInformation {
|
|
|
|
static final LiveInformation _singleton = LiveInformation._internal();
|
|
|
|
factory LiveInformation() {
|
|
return _singleton;
|
|
}
|
|
|
|
LiveInformation._internal();
|
|
|
|
Future<void> initialize() async {
|
|
|
|
{
|
|
// By default, load the bus sequences from the assets
|
|
print("Loading bus sequences from assets");
|
|
|
|
String destinations = await rootBundle.loadString("assets/datasets/destinations.json");
|
|
String routes = await rootBundle.loadString("assets/datasets/bus-sequences.csv");
|
|
|
|
// Try to grab the routes from TfL
|
|
try {
|
|
|
|
http.Response response = await http.get(Uri.parse('https://tfl.gov.uk/bus-sequences.csv'));
|
|
|
|
routes = response.body;
|
|
|
|
print("Loaded bus sequences from TFL");
|
|
|
|
} catch (e) {
|
|
print("Failed to load bus sequences from TFL. Using local copy.");
|
|
}
|
|
|
|
// Try to grab the destinations from github
|
|
try {
|
|
|
|
http.Response response = await http.get(Uri.parse('https://raw.githubusercontent.com/RailboundStudios/LondonBusDatasets/main/destinations.json'));
|
|
|
|
destinations = response.body;
|
|
|
|
print("Loaded destinations from Github");
|
|
|
|
} catch (e) {
|
|
print("Failed to load destinations from Github. Using local copy.");
|
|
}
|
|
|
|
busSequences = BusSequences.fromCSV(
|
|
destinations,
|
|
routes
|
|
);
|
|
|
|
print("Loaded all datasets");
|
|
|
|
try {
|
|
|
|
http.Response response = await http.get(Uri.parse('https://tfl.gov.uk/bus-sequences.csv'));
|
|
|
|
busSequences = BusSequences.fromCSV(
|
|
await rootBundle.loadString("assets/datasets/destinations.json"),
|
|
response.body
|
|
);
|
|
|
|
print("Loaded bus sequences from TFL");
|
|
|
|
} catch (e) {
|
|
print("Failed to load bus sequences from TFL. Using local copy.");
|
|
}
|
|
|
|
// Load tube stations
|
|
print("Loading tube stations from assets");
|
|
tubeStations = TubeStations.fromJson(json.decode(await rootBundle.loadString("assets/datasets/tube_stations.json")));
|
|
print("Loaded tube stations from assets");
|
|
}
|
|
|
|
// Initialise modules
|
|
syncedTimeModule = SyncedTimeModule();
|
|
announcementModule = AnnouncementModule();
|
|
|
|
initTrackerModule();
|
|
|
|
print("Initialised LiveInformation");
|
|
if (!auth.isAuthenticated()) {
|
|
auth.loadAnonymousUser();
|
|
}
|
|
|
|
networkingModule = NetworkingModule();
|
|
|
|
if (defaultTargetPlatform == TargetPlatform.android){
|
|
p2pModule = NearbyServiceWrapper();
|
|
}
|
|
|
|
}
|
|
|
|
Future<void> initTrackerModule() async {
|
|
if (await Permission.location.isGranted) {
|
|
trackerModule = TrackerModule();
|
|
}
|
|
}
|
|
|
|
// Multi-device stuff
|
|
RoomConnectionMethod connectionMethod = RoomConnectionMethod.None;
|
|
|
|
// Auth
|
|
AuthAPI auth = AuthAPI(
|
|
autoLoad: false,
|
|
);
|
|
String? roomCode;
|
|
String? roomDocumentID;
|
|
bool isHost = false;
|
|
bool inRoom = false;
|
|
appwrite.RealtimeSubscription? _subscription;
|
|
RealtimeKeepAliveConnection? _keepAliveConnection; // This is a workaround for a bug in the appwrite SDK
|
|
|
|
// Local room stuff
|
|
ListenerReceipt<String>? _listenerReciept;
|
|
|
|
// Modules
|
|
// late CommandModule commandModule; This needs to be deprecated
|
|
late BusSequences busSequences;
|
|
late AnnouncementModule announcementModule;
|
|
late SyncedTimeModule syncedTimeModule;
|
|
late TrackerModule trackerModule;
|
|
late TubeStations tubeStations;
|
|
late NetworkingModule networkingModule;
|
|
late NearbyServiceWrapper p2pModule;
|
|
|
|
// Important variables
|
|
BusRouteVariant? _currentRouteVariant;
|
|
|
|
// Events
|
|
EventDelegate<BusRouteVariant?> routeVariantDelegate = EventDelegate();
|
|
|
|
// Internal methods
|
|
|
|
|
|
|
|
|
|
|
|
Future<void> setRouteVariant(BusRouteVariant? routeVariant, {bool sendToServer = false}) async {
|
|
|
|
if (routeVariant == null) {
|
|
_currentRouteVariant = null;
|
|
|
|
await announcementModule.queueAnnounceByAudioName(
|
|
displayText: "*** NO MESSAGE ***",
|
|
);
|
|
|
|
routeVariantDelegate.trigger(null);
|
|
return;
|
|
}
|
|
|
|
if (roomCode != null) {
|
|
try {
|
|
final client = auth.client;
|
|
final databases = appwrite.Databases(client);
|
|
|
|
final response = await databases.listDocuments(
|
|
databaseId: "6633e85400036415ab0f",
|
|
collectionId: "6633e85d0020f52f3771",
|
|
queries: [
|
|
appwrite.Query.search("SessionID", roomCode!)
|
|
]
|
|
);
|
|
|
|
final document = response.documents.first;
|
|
|
|
// Check if the route is not the same
|
|
if (document.data["CurrentRoute"] != routeVariant.busRoute.routeNumber || document.data["CurrentRouteVariant"] != routeVariant.busRoute.routeVariants.values.toList().indexOf(routeVariant)) {
|
|
final updatedDocument = await databases.updateDocument(
|
|
databaseId: "6633e85400036415ab0f",
|
|
collectionId: "6633e85d0020f52f3771",
|
|
documentId: document.$id,
|
|
data: {
|
|
"CurrentRoute": routeVariant.busRoute.routeNumber,
|
|
"CurrentRouteVariant": routeVariant.busRoute.routeVariants.values.toList().indexOf(routeVariant),
|
|
"LastUpdater": auth.userID,
|
|
}
|
|
);
|
|
print("Updated route on server");
|
|
}
|
|
} catch (e) {
|
|
print("Failed to update route on server");
|
|
}
|
|
|
|
}
|
|
if (inRoom && sendToServer) {
|
|
|
|
SendCommand("setroute ${routeVariant.busRoute.routeNumber} ${routeVariant.busRoute.routeVariants.values.toList().indexOf(routeVariant)}");
|
|
|
|
}
|
|
Continuation:
|
|
|
|
// Set the current route variant
|
|
_currentRouteVariant = routeVariant;
|
|
|
|
// Let everyone know that the route variant has been set/changed
|
|
routeVariantDelegate.trigger(routeVariant);
|
|
|
|
// Get all of the files that need to be cached
|
|
List<String> audioFiles = [];
|
|
|
|
for (BusRouteStop stop in routeVariant.busStops) {
|
|
audioFiles.add(stop.getAudioFileName());
|
|
}
|
|
|
|
// Cache/Load the audio files
|
|
await announcementModule
|
|
.announcementCache
|
|
.loadAnnouncementsFromBytes(await LiveInformation().announcementModule.getBundleBytes(), [
|
|
...audioFiles,
|
|
if (!routeVariant.busRoute.routeNumber.toLowerCase().startsWith("ul"))
|
|
"R_${routeVariant.busRoute.routeNumber}_001.mp3"
|
|
else
|
|
"R_RAIL_REPLACEMENT_SERVICE_001.mp3",
|
|
|
|
]
|
|
);
|
|
|
|
// Display the route variant
|
|
announcementModule.queueAnnouncementByRouteVariant(routeVariant: routeVariant);
|
|
|
|
}
|
|
|
|
// Public methods
|
|
|
|
BusRouteVariant? getRouteVariant() {
|
|
return _currentRouteVariant;
|
|
}
|
|
|
|
Future<void> setRouteVariantQuery(String routeNumber, int routeVariantIndex, {bool sendToServer = false}) async {
|
|
BusRoute route = busSequences.routes[routeNumber]!;
|
|
BusRouteVariant routeVariant = route.routeVariants.values.toList()[routeVariantIndex];
|
|
|
|
await setRouteVariant(
|
|
routeVariant,
|
|
sendToServer: sendToServer
|
|
);
|
|
}
|
|
|
|
|
|
// Multi device support
|
|
|
|
Future<void> createRoom(String roomCode) async {
|
|
|
|
{
|
|
// Local Room
|
|
await networkingModule.startWebSocketServer();
|
|
inRoom = true;
|
|
_listenerReciept = networkingModule.onMessageReceived?.addListener(
|
|
(p0) {
|
|
print("Received local command: $p0");
|
|
executeCommand(p0);
|
|
}
|
|
);
|
|
}
|
|
|
|
{
|
|
// Cloud Room
|
|
print("Creating room with code $roomCode");
|
|
|
|
// Update the room code
|
|
this.roomCode = roomCode;
|
|
|
|
// Enable host mode
|
|
isHost = true;
|
|
inRoom = true;
|
|
// Access the database
|
|
final client = auth.client;
|
|
final databases = appwrite.Databases(client);
|
|
|
|
// Remove any existing documents
|
|
final existingDocuments = await databases.listDocuments(
|
|
databaseId: "6633e85400036415ab0f",
|
|
collectionId: "6633e85d0020f52f3771",
|
|
queries: [
|
|
appwrite.Query.search("SessionID", roomCode)
|
|
]
|
|
);
|
|
for (var document in existingDocuments.documents) {
|
|
await databases.deleteDocument(
|
|
databaseId: "6633e85400036415ab0f",
|
|
collectionId: "6633e85d0020f52f3771",
|
|
documentId: document.$id
|
|
);
|
|
}
|
|
|
|
// Create the document
|
|
final document = await databases.createDocument(
|
|
databaseId: "6633e85400036415ab0f",
|
|
collectionId: "6633e85d0020f52f3771",
|
|
documentId: appwrite.ID.unique(),
|
|
data: {
|
|
"SessionID": roomCode,
|
|
"LastUpdater": auth.userID,
|
|
}
|
|
);
|
|
|
|
// Listen for changes
|
|
if (_keepAliveConnection != null) {
|
|
try {
|
|
_keepAliveConnection!.close();
|
|
} catch (e) {
|
|
print("Failed to close connection... oh well");
|
|
}
|
|
}
|
|
|
|
String APPWRITE_ENDPOINT_URL = "https://cloud.appwrite.io/v1";
|
|
String domain = APPWRITE_ENDPOINT_URL.replaceAll("https://", "").trim();
|
|
_keepAliveConnection = RealtimeKeepAliveConnection(
|
|
channels: ['databases.6633e85400036415ab0f.collections.6633e85d0020f52f3771.documents.${document.$id}'],
|
|
onData: ServerListener,
|
|
domain: domain,
|
|
client: auth.client,
|
|
onError: (e) {
|
|
print("Workarround Error: $e");
|
|
},
|
|
);
|
|
_keepAliveConnection!.initialize();
|
|
|
|
|
|
// Update the room document ID
|
|
roomDocumentID = document.$id;
|
|
|
|
print("Created room with code $roomCode");
|
|
}
|
|
|
|
}
|
|
|
|
Future<bool> joinRoom(String info) async {
|
|
|
|
String infoJson = utf8.decode(base64.decode(info));
|
|
|
|
try {
|
|
{
|
|
// sync
|
|
String routeNumber = jsonDecode(infoJson)["sync"]["route"];
|
|
int routeVariantIndex = jsonDecode(infoJson)["sync"]["routeVariant"];
|
|
|
|
setRouteVariantQuery(routeNumber, routeVariantIndex);
|
|
|
|
LiveInformation().announcementModule.queueAnnouncementByRouteVariant(routeVariant: _currentRouteVariant!, sendToServer: false);
|
|
}
|
|
} catch (e) {
|
|
print("Failed to sync route");
|
|
}
|
|
|
|
{
|
|
// Local Room
|
|
|
|
String host = jsonDecode(infoJson)["local"]["host"];
|
|
|
|
if (await networkingModule.connectToWebSocketServer(host)){
|
|
print("Connected to local room at $host");
|
|
|
|
_listenerReciept = networkingModule.onMessageReceived?.addListener(
|
|
(p0) {
|
|
print("Received local command: $p0");
|
|
executeCommand(p0);
|
|
}
|
|
);
|
|
inRoom = true;
|
|
connectionMethod = RoomConnectionMethod.Local;
|
|
return true; // We dont need to connect to the cloud room if we are connected to the local room.
|
|
} else {
|
|
print("Failed to connect to local room at $host");
|
|
print("Falling back to cloud room");
|
|
}
|
|
}
|
|
|
|
{
|
|
// Cloud Room
|
|
|
|
String roomCode = jsonDecode(infoJson)["cloud"]["roomCode"];
|
|
|
|
print("Joining cloud room with code $roomCode");
|
|
|
|
// Disable host mode
|
|
isHost = false;
|
|
|
|
// Update the room code
|
|
this.roomCode = roomCode;
|
|
|
|
// Access the database
|
|
final client = auth.client;
|
|
final databases = appwrite.Databases(client);
|
|
|
|
// Get the document
|
|
final response = await databases.listDocuments(
|
|
databaseId: "6633e85400036415ab0f",
|
|
collectionId: "6633e85d0020f52f3771",
|
|
queries: [
|
|
appwrite.Query.search("SessionID", roomCode)
|
|
]
|
|
);
|
|
|
|
if (response.documents.isEmpty) {
|
|
return false;
|
|
}
|
|
|
|
final document = response.documents.first;
|
|
|
|
// Listen for changes
|
|
if (_keepAliveConnection != null) {
|
|
try {
|
|
_keepAliveConnection!.close();
|
|
} catch (e) {
|
|
print("Failed to close connection... oh well");
|
|
}
|
|
}
|
|
|
|
String APPWRITE_ENDPOINT_URL = "https://cloud.appwrite.io/v1";
|
|
String domain = APPWRITE_ENDPOINT_URL.replaceAll("https://", "").trim();
|
|
_keepAliveConnection = RealtimeKeepAliveConnection(
|
|
channels: ['databases.6633e85400036415ab0f.collections.6633e85d0020f52f3771.documents.${document.$id}'],
|
|
onData: ServerListener,
|
|
domain: domain,
|
|
client: auth.client,
|
|
onError: (e) {
|
|
print("Workarround Error: $e");
|
|
},
|
|
);
|
|
_keepAliveConnection!.initialize();
|
|
|
|
// Update the room document ID
|
|
roomDocumentID = document.$id;
|
|
|
|
// Get the current route
|
|
try {
|
|
String routeNumber = document.data["CurrentRoute"];
|
|
int routeVariantIndex = document.data["CurrentRouteVariant"];
|
|
|
|
await setRouteVariantQuery(routeNumber, routeVariantIndex);
|
|
print("Set route to $routeNumber $routeVariantIndex");
|
|
} catch (e) {
|
|
print("Failed to set route");
|
|
}
|
|
inRoom = true;
|
|
connectionMethod = RoomConnectionMethod.Cloud;
|
|
print("Joined cloud room with code $roomCode");
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|
|
Future<void> leaveRoom() async {
|
|
|
|
if (!inRoom) {
|
|
return;
|
|
}
|
|
|
|
{
|
|
// Local Room
|
|
networkingModule.stopWebSocketServer();
|
|
inRoom = false;
|
|
networkingModule.onMessageReceived?.removeListener(_listenerReciept!);
|
|
}
|
|
|
|
{
|
|
// Cloud Room
|
|
if (_keepAliveConnection != null) {
|
|
_keepAliveConnection!.close();
|
|
_keepAliveConnection = null;
|
|
}
|
|
}
|
|
|
|
inRoom = false;
|
|
|
|
if (isHost) {
|
|
// Access the database
|
|
final client = auth.client;
|
|
final databases = appwrite.Databases(client);
|
|
|
|
// Remove any existing documents
|
|
final existingDocuments = await databases.listDocuments(
|
|
databaseId: "6633e85400036415ab0f",
|
|
collectionId: "6633e85d0020f52f3771",
|
|
queries: [
|
|
appwrite.Query.search("SessionID", roomCode!)
|
|
]
|
|
);
|
|
for (var document in existingDocuments.documents) {
|
|
await databases.deleteDocument(
|
|
databaseId: "6633e85400036415ab0f",
|
|
collectionId: "6633e85d0020f52f3771",
|
|
documentId: document.$id
|
|
);
|
|
}
|
|
}
|
|
|
|
roomCode = null;
|
|
roomDocumentID = null;
|
|
isHost = false;
|
|
inRoom = false;
|
|
_keepAliveConnection?.close();
|
|
_keepAliveConnection = null;
|
|
|
|
// Reset stuff
|
|
setRouteVariant(null);
|
|
}
|
|
|
|
String generateRoomInfo() {
|
|
|
|
// Room Info Example
|
|
/*
|
|
{
|
|
"cloud": {
|
|
"roomCode": "6633e85d0020f52f3771"
|
|
},
|
|
"local":
|
|
{
|
|
"host": "ws://192.168.0.123:8080"
|
|
},
|
|
"sync":
|
|
{
|
|
"route": "W11",
|
|
"routeVariant": 1
|
|
}
|
|
}
|
|
*/
|
|
|
|
String json = jsonEncode({
|
|
"cloud": {
|
|
"roomCode": roomCode,
|
|
},
|
|
"local": {
|
|
"host": "ws://${networkingModule.localIP}:8080"
|
|
},
|
|
if (_currentRouteVariant != null)
|
|
"sync": {
|
|
"route": _currentRouteVariant!.busRoute.routeNumber,
|
|
"routeVariant": _currentRouteVariant!.busRoute.routeVariants.values.toList().indexOf(_currentRouteVariant!),
|
|
}
|
|
});
|
|
|
|
// Encode in base64
|
|
|
|
return base64Encode(utf8.encode(json));
|
|
|
|
}
|
|
|
|
String? lastCommand;
|
|
Future<void> ServerListener(appwrite.RealtimeMessage response) async {
|
|
print("Session update");
|
|
|
|
// Only do something if the document was created or updated
|
|
if (!(response.events.first.contains("create") || response.events.first.contains("update"))) {
|
|
return;
|
|
}
|
|
|
|
// Get the user that caused the event
|
|
|
|
String senderID = response.payload["LastUpdater"];
|
|
// If the sender is the same as the client, then ignore the event
|
|
if (senderID == auth.userID) {
|
|
print("Ignoring event");
|
|
return;
|
|
}
|
|
|
|
// Check to see if the commands are updated
|
|
|
|
try {
|
|
// Get the new route
|
|
String routeNumber = response.payload["CurrentRoute"];
|
|
int routeVariantIndex = response.payload["CurrentRouteVariant"];
|
|
|
|
// If the route arent the same, then update the route
|
|
if (routeNumber != _currentRouteVariant!.busRoute.routeNumber || routeVariantIndex != _currentRouteVariant!.busRoute.routeVariants.values.toList().indexOf(_currentRouteVariant!)) {
|
|
// Set the route
|
|
await setRouteVariantQuery(routeNumber, routeVariantIndex,
|
|
sendToServer: false
|
|
);
|
|
|
|
// announce the route
|
|
// announcementModule.queueAnnouncementByRouteVariant(routeVariant: _currentRouteVariant!);
|
|
}
|
|
} catch (e) {
|
|
print("Failed to set route");
|
|
}
|
|
|
|
// Execute the command
|
|
List<String> commands = response.payload["Commands"].cast<String>();
|
|
|
|
executeCommand(commands.last);
|
|
|
|
}
|
|
|
|
void executeCommand(String command) {
|
|
if (command == lastCommand) {
|
|
return;
|
|
}
|
|
|
|
print("Executing command: $command");
|
|
|
|
lastCommand = command;
|
|
|
|
List<String> commandParts = _splitCommand(command);
|
|
String commandName = commandParts[0];
|
|
|
|
if (commandName == "announce") {
|
|
print("Announce command received");
|
|
String mode = commandParts[1];
|
|
|
|
print ("Command: $command");
|
|
|
|
if (mode == "info") {
|
|
print("Announce info command received");
|
|
announcementModule.queueAnnouncementByInfoIndex(
|
|
sendToServer: false,
|
|
infoIndex: int.parse(commandParts[2]),
|
|
scheduledTime: DateTime.fromMillisecondsSinceEpoch(int.parse(commandParts[3])),
|
|
);
|
|
} else if (mode == "dest") {
|
|
print("Announce dest command received");
|
|
|
|
String routeNumber = commandParts[2];
|
|
int routeVariantIndex = int.parse(commandParts[3]);
|
|
|
|
announcementModule.queueAnnouncementByRouteVariant(
|
|
sendToServer: false,
|
|
routeVariant: busSequences.routes[routeNumber]!.routeVariants.values.toList()[routeVariantIndex],
|
|
scheduledTime: DateTime.fromMillisecondsSinceEpoch(int.parse(commandParts[4])),
|
|
);
|
|
|
|
} else if (mode == "manual") {
|
|
print("Announce manual command received");
|
|
|
|
final displayText = commandParts[2];
|
|
|
|
List<String> audioFileNames = commandParts.sublist(3);
|
|
try {
|
|
if (int.parse(audioFileNames.last) != null) {
|
|
audioFileNames.removeLast();
|
|
}
|
|
} catch (e) {}
|
|
|
|
DateTime scheduledTime = LiveInformation().syncedTimeModule.Now().add(Duration(seconds: 1));
|
|
try {
|
|
if (int.parse(commandParts.last) != null) {
|
|
scheduledTime = DateTime.fromMillisecondsSinceEpoch(int.parse(commandParts.last));
|
|
}
|
|
} catch (e) {}
|
|
|
|
announcementModule.queueAnnounceByAudioName(
|
|
displayText: displayText,
|
|
audioNames: audioFileNames,
|
|
scheduledTime: scheduledTime,
|
|
sendToServer: false
|
|
);
|
|
|
|
}
|
|
} else if (commandName == "setroute") {
|
|
print("Set route command received");
|
|
String routeNumber = commandParts[1];
|
|
int routeVariantIndex = int.parse(commandParts[2]);
|
|
|
|
setRouteVariantQuery(routeNumber, routeVariantIndex, sendToServer: false);
|
|
}
|
|
}
|
|
|
|
String _extractId(String input) {
|
|
RegExp regExp = RegExp(r'\("user:(.*)"\)');
|
|
Match match = regExp.firstMatch(input)!;
|
|
return match.group(1)!;
|
|
}
|
|
|
|
Future<void> SendCommand(String command) async {
|
|
|
|
{
|
|
// Wfi Direct Commands
|
|
|
|
p2pModule.sendMessage(command);
|
|
}
|
|
|
|
{
|
|
// Local Commands
|
|
|
|
try {
|
|
networkingModule.sendMessage(command);
|
|
} catch (e) {
|
|
print("Failed to send local command: $e");
|
|
}
|
|
}
|
|
|
|
|
|
{
|
|
// Cloud Commands
|
|
|
|
final client = auth.client;
|
|
final databases = appwrite.Databases(client);
|
|
|
|
final response = await databases.listDocuments(
|
|
databaseId: "6633e85400036415ab0f",
|
|
collectionId: "6633e85d0020f52f3771",
|
|
queries: [
|
|
appwrite.Query.search("SessionID", roomCode!)
|
|
]
|
|
);
|
|
|
|
List<String> pastCommands = [];
|
|
|
|
response.documents.first.data["Commands"].forEach((element) {
|
|
pastCommands.add(element);
|
|
});
|
|
|
|
pastCommands.add(command);
|
|
|
|
final document = await databases.updateDocument(
|
|
databaseId: "6633e85400036415ab0f",
|
|
collectionId: "6633e85d0020f52f3771",
|
|
documentId: roomDocumentID!,
|
|
data: {
|
|
"Commands": pastCommands,
|
|
"LastUpdater": auth.userID,
|
|
}
|
|
);
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
List<String> _splitCommand(String command) {
|
|
var regex = RegExp(r'([^\s"]+)|"([^"]*)"');
|
|
var matches = regex.allMatches(command);
|
|
return matches.map((match) => match.group(0)!.replaceAll('"', '')).toList();
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
Everything under this will be considered legacy code
|
|
*/
|
|
|
|
|
|
|
|
}
|
|
|
|
class AnnouncementQueueEntry {
|
|
final String displayText;
|
|
final List<AudioWrapperSource> audioSources;
|
|
bool sendToServer = true;
|
|
DateTime? scheduledTime;
|
|
DateTime? timestamp;
|
|
|
|
AnnouncementQueueEntry({required this.displayText, required this.audioSources, this.sendToServer = true, this.scheduledTime, this.timestamp});
|
|
}
|
|
|
|
class NamedAnnouncementQueueEntry extends AnnouncementQueueEntry {
|
|
final String shortName;
|
|
|
|
NamedAnnouncementQueueEntry({
|
|
required this.shortName,
|
|
required String displayText,
|
|
required List<AudioWrapperSource> audioSources,
|
|
DateTime? scheduledTime,
|
|
DateTime? timestamp,
|
|
bool sendToServer = true,
|
|
}) : super(
|
|
displayText: displayText,
|
|
audioSources: audioSources,
|
|
sendToServer: sendToServer,
|
|
scheduledTime: scheduledTime,
|
|
timestamp: timestamp,
|
|
);
|
|
}
|
|
|
|
var abs = (int value) => value < 0 ? -value : value; |