paradigm shift
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
|
||||
class ApiConstants {
|
||||
|
||||
static const String APPWRITE_ENDPOINT = "https://cloud.imbenji.net/v1";
|
||||
static const String APPWRITE_PROJECT_ID = "65de530c1c0a7ffc0c3f";
|
||||
static const String APPWRITE_ENDPOINT = "https://cloud.appwrite.io/v1";
|
||||
static const String APPWRITE_PROJECT_ID = "6633d0e00023502890ed";
|
||||
|
||||
static const String INFO_Q_DATABASE_ID = "65de5cab16717444527b";
|
||||
static const String MANUAL_Q_COLLECTION_ID = "65de9f2f925562a2eda8";
|
||||
|
||||
@@ -21,6 +21,7 @@ class AuthAPI extends ChangeNotifier {
|
||||
late final appwrite.Account account;
|
||||
|
||||
late models.User _currentUser;
|
||||
late models.Session _currentSession;
|
||||
|
||||
AuthStatus _status = AuthStatus.UNINITIALIZED;
|
||||
|
||||
@@ -32,11 +33,16 @@ class AuthAPI extends ChangeNotifier {
|
||||
AuthStatus get status => _status;
|
||||
String? get username => _currentUser.name;
|
||||
String? get email => _currentUser.email;
|
||||
String? get userID => _currentUser.$id;
|
||||
String? get userID {
|
||||
try {
|
||||
return _currentUser.$id;
|
||||
} catch (e) {
|
||||
return _currentSession.$id;
|
||||
}
|
||||
}
|
||||
|
||||
AuthAPI() {
|
||||
AuthAPI({bool autoLoad = true}) {
|
||||
init();
|
||||
loadUser();
|
||||
}
|
||||
|
||||
init() {
|
||||
@@ -45,6 +51,29 @@ class AuthAPI extends ChangeNotifier {
|
||||
.setProject(ApiConstants.APPWRITE_PROJECT_ID)
|
||||
.setSelfSigned();
|
||||
account = appwrite.Account(client);
|
||||
try {
|
||||
account.get().then((value) {
|
||||
_currentUser = value;
|
||||
_status = AuthStatus.AUTHENTICATED;
|
||||
print("Auto loaded user: ${_currentUser.name}");
|
||||
print("Auth status: $_status");
|
||||
});
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
loadAnonymousUser() async {
|
||||
try {
|
||||
final user = await account.createAnonymousSession();
|
||||
_currentSession = user;
|
||||
_status = AuthStatus.AUTHENTICATED;
|
||||
|
||||
} catch (e) {
|
||||
_status = AuthStatus.UNAUTHENTICATED;
|
||||
} finally {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
loadUser() async {
|
||||
|
||||
@@ -16,6 +16,7 @@ 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;
|
||||
@@ -62,12 +63,6 @@ class LiveInformation {
|
||||
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");
|
||||
|
||||
|
||||
String sessionID = "test";
|
||||
|
||||
commandModule = CommandModule(sessionID);
|
||||
|
||||
}
|
||||
|
||||
// Initialise modules
|
||||
@@ -77,6 +72,9 @@ class LiveInformation {
|
||||
initTrackerModule();
|
||||
|
||||
print("Initialised LiveInformation");
|
||||
if (!auth.isAuthenticated()) {
|
||||
auth.loadAnonymousUser();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> initTrackerModule() async {
|
||||
@@ -86,10 +84,18 @@ class LiveInformation {
|
||||
}
|
||||
|
||||
// Auth
|
||||
AuthAPI auth = AuthAPI();
|
||||
AuthAPI auth = AuthAPI(
|
||||
autoLoad: false,
|
||||
);
|
||||
String? roomCode;
|
||||
String? roomDocumentID;
|
||||
bool isHost = false;
|
||||
appwrite.RealtimeSubscription? _subscription;
|
||||
RealtimeKeepAliveConnection? _keepAliveConnection; // This is a workaround for a bug in the appwrite SDK
|
||||
|
||||
|
||||
// Modules
|
||||
late CommandModule commandModule;
|
||||
// late CommandModule commandModule; This needs to be deprecated
|
||||
late BusSequences busSequences;
|
||||
late AnnouncementModule announcementModule;
|
||||
late SyncedTimeModule syncedTimeModule;
|
||||
@@ -100,7 +106,7 @@ class LiveInformation {
|
||||
BusRouteVariant? _currentRouteVariant;
|
||||
|
||||
// Events
|
||||
EventDelegate<BusRouteVariant> routeVariantDelegate = EventDelegate();
|
||||
EventDelegate<BusRouteVariant?> routeVariantDelegate = EventDelegate();
|
||||
|
||||
// Internal methods
|
||||
|
||||
@@ -108,7 +114,54 @@ class LiveInformation {
|
||||
|
||||
|
||||
|
||||
Future<void> setRouteVariant_Internal(BusRouteVariant routeVariant) async {
|
||||
Future<void> setRouteVariant(BusRouteVariant? routeVariant) 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");
|
||||
}
|
||||
|
||||
}
|
||||
Continuation:
|
||||
|
||||
// Set the current route variant
|
||||
_currentRouteVariant = routeVariant;
|
||||
@@ -135,6 +188,10 @@ class LiveInformation {
|
||||
|
||||
]
|
||||
);
|
||||
|
||||
// Display the route variant
|
||||
announcementModule.queueAnnouncementByRouteVariant(routeVariant: routeVariant);
|
||||
|
||||
}
|
||||
|
||||
// Public methods
|
||||
@@ -143,16 +200,329 @@ class LiveInformation {
|
||||
return _currentRouteVariant;
|
||||
}
|
||||
|
||||
Future<void> setRouteVariant(BusRouteVariant routeVariant) async {
|
||||
await commandModule.executeCommand(
|
||||
"setroute ${routeVariant.busRoute.routeNumber} ${routeVariant.busRoute.routeVariants.values.toList().indexOf(routeVariant)}"
|
||||
Future<void> setRouteVariantQuery(String routeNumber, int routeVariantIndex) async {
|
||||
BusRoute route = busSequences.routes[routeNumber]!;
|
||||
BusRouteVariant routeVariant = route.routeVariants.values.toList()[routeVariantIndex];
|
||||
|
||||
await setRouteVariant(
|
||||
routeVariant
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Multi device support
|
||||
|
||||
Future<void> createRoom(String roomCode) async {
|
||||
print("Creating room with code $roomCode");
|
||||
|
||||
// Update the room code
|
||||
this.roomCode = roomCode;
|
||||
|
||||
// Enable host mode
|
||||
isHost = 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
|
||||
// { Breaks due to bug in appwrite
|
||||
// final realtime = appwrite.Realtime(client);
|
||||
//
|
||||
// if (_subscription != null) {
|
||||
// _subscription!.close();
|
||||
// }
|
||||
//
|
||||
// _subscription = realtime.subscribe(
|
||||
// ['databases.6633e85400036415ab0f.collections.6633e85d0020f52f3771.documents.${document.$id}']
|
||||
// );
|
||||
// _subscription!.stream.listen(ServerListener);
|
||||
// }
|
||||
// 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<void> JoinRoom(String roomCode) async {
|
||||
print("Joining 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) {
|
||||
throw Exception("Room not found");
|
||||
}
|
||||
|
||||
final document = response.documents.first;
|
||||
|
||||
// Listen for changes
|
||||
// {
|
||||
// final realtime = appwrite.Realtime(client);
|
||||
//
|
||||
// if (_subscription != null) {
|
||||
// _subscription!.close();
|
||||
// }
|
||||
//
|
||||
// _subscription = realtime.subscribe([
|
||||
// 'databases.6633e85400036415ab0f.collections.6633e85d0020f52f3771.documents.${document.$id}'
|
||||
// ]);
|
||||
//
|
||||
// _subscription!.stream.listen(ServerListener);
|
||||
// }
|
||||
// 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");
|
||||
}
|
||||
|
||||
print("Joined room with code $roomCode");
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// 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>();
|
||||
|
||||
String? command = commands.last;
|
||||
|
||||
if (command == lastCommand) {
|
||||
return;
|
||||
}
|
||||
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.queueAnnounementByInfoIndex(
|
||||
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
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String _extractId(String input) {
|
||||
RegExp regExp = RegExp(r'\("user:(.*)"\)');
|
||||
Match match = regExp.firstMatch(input)!;
|
||||
return match.group(1)!;
|
||||
}
|
||||
|
||||
Future<void> SendCommand(String command) async {
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -162,8 +532,6 @@ class LiveInformation {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
class AnnouncementQueueEntry {
|
||||
|
||||
@@ -74,7 +74,7 @@ class AnnouncementModule extends InfoModule {
|
||||
final EventDelegate<AnnouncementQueueEntry> onAnnouncement = EventDelegate();
|
||||
|
||||
// Timer
|
||||
Timer refreshTimer() => Timer.periodic(const Duration(milliseconds: 200), (timer) async {
|
||||
Timer refreshTimer() => Timer.periodic(const Duration(milliseconds: 10), (timer) async {
|
||||
|
||||
if (!isPlaying) {
|
||||
|
||||
@@ -84,7 +84,7 @@ class AnnouncementModule extends InfoModule {
|
||||
|
||||
bool proceeding = await _internalAccountForInconsistentTime(
|
||||
announcement: nextAnnouncement,
|
||||
timerInterval: const Duration(milliseconds: 200),
|
||||
timerInterval: const Duration(milliseconds: 10),
|
||||
callback: () {
|
||||
queue.removeAt(0);
|
||||
print("Announcement proceeding");
|
||||
@@ -105,35 +105,21 @@ class AnnouncementModule extends InfoModule {
|
||||
|
||||
if (currentAnnouncement!.audioSources.isNotEmpty) {
|
||||
|
||||
// audioPlayer.loadSource(AudioWrapperAssetSource("assets/audio/5-seconds-of-silence.mp3"));
|
||||
// audioPlayer.play();
|
||||
// await Future.delayed(const Duration(milliseconds: 300));
|
||||
// audioPlayer.stop();
|
||||
// Prime all of the audio sources to be ready to play
|
||||
for (AudioWrapperSource source in currentAnnouncement!.audioSources) {
|
||||
try {
|
||||
await audioPlayer.loadSource(source);
|
||||
await Future.delayed((await audioPlayer.play())!);
|
||||
audioPlayer.stop();
|
||||
|
||||
// try {
|
||||
for (AudioWrapperSource source in currentAnnouncement!.audioSources) {
|
||||
try {
|
||||
await audioPlayer.loadSource(source);
|
||||
|
||||
Duration? duration = await audioPlayer.play();
|
||||
await Future.delayed(duration!);
|
||||
audioPlayer.stop();
|
||||
// await Future.delayed(const Duration(milliseconds: 100));
|
||||
if (currentAnnouncement?.audioSources.last != source) {
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
}
|
||||
} catch (e) {
|
||||
// Do nothing
|
||||
// print("Error playing announcement: $e on ${currentAnnouncement?.displayText}");
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
if (currentAnnouncement?.audioSources.last != source) {
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
}
|
||||
} catch (e) {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
}
|
||||
// audioPlayer.stop();
|
||||
}
|
||||
|
||||
// } catch (e) {
|
||||
// // Do nothing
|
||||
// print("Error playing announcement: $e on ${currentAnnouncement?.displayTex}");
|
||||
// }
|
||||
} else {
|
||||
if (queue.isNotEmpty) {
|
||||
await Future.delayed(const Duration(seconds: 5));
|
||||
@@ -179,7 +165,7 @@ class AnnouncementModule extends InfoModule {
|
||||
}
|
||||
|
||||
// Configuration
|
||||
int get defaultAnnouncementDelay => liveInformation.auth.isAuthenticated() ? 2 : 0;
|
||||
int get defaultAnnouncementDelay => liveInformation.auth.isAuthenticated() ? 1 : 0;
|
||||
|
||||
// Methods
|
||||
Future<void> queueAnnounceByAudioName({
|
||||
@@ -199,8 +185,12 @@ class AnnouncementModule extends InfoModule {
|
||||
audioNamesString += "\"$audioName\" ";
|
||||
}
|
||||
|
||||
liveInformation.commandModule.executeCommand(
|
||||
"announce manual \"$displayText\" ${audioNamesString} ${scheduledTime?.millisecondsSinceEpoch ?? ""}"
|
||||
liveInformation.SendCommand("announce manual \"$displayText\" $audioNamesString ${scheduledTime.millisecondsSinceEpoch}");
|
||||
queueAnnounceByAudioName(
|
||||
displayText: displayText,
|
||||
audioNames: audioNames,
|
||||
scheduledTime: scheduledTime,
|
||||
sendToServer: false
|
||||
);
|
||||
|
||||
return;
|
||||
@@ -244,9 +234,13 @@ class AnnouncementModule extends InfoModule {
|
||||
|
||||
scheduledTime ??= liveInformation.syncedTimeModule.Now().add(Duration(seconds: defaultAnnouncementDelay));
|
||||
|
||||
liveInformation.commandModule.executeCommand(
|
||||
"announce info $infoIndex ${scheduledTime?.millisecondsSinceEpoch ?? ""}"
|
||||
liveInformation.SendCommand("announce info $infoIndex ${scheduledTime.millisecondsSinceEpoch}");
|
||||
queueAnnounementByInfoIndex(
|
||||
infoIndex: infoIndex,
|
||||
scheduledTime: scheduledTime,
|
||||
sendToServer: false
|
||||
);
|
||||
print("Announcement sent to server");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -270,9 +264,16 @@ class AnnouncementModule extends InfoModule {
|
||||
|
||||
scheduledTime ??= liveInformation.syncedTimeModule.Now().add(Duration(seconds: defaultAnnouncementDelay));
|
||||
|
||||
liveInformation.commandModule.executeCommand(
|
||||
"announce dest \"${routeVariant.busRoute.routeNumber}\" ${routeVariant.busRoute.routeVariants.values.toList().indexOf(routeVariant)} ${scheduledTime?.millisecondsSinceEpoch ?? ""}"
|
||||
String routeNumber = routeVariant.busRoute.routeNumber;
|
||||
int routeVariantIndex = routeVariant.busRoute.routeVariants.values.toList().indexOf(routeVariant);
|
||||
|
||||
liveInformation.SendCommand("announce dest ${routeNumber} ${routeVariantIndex} ${scheduledTime.millisecondsSinceEpoch}");
|
||||
queueAnnouncementByRouteVariant(
|
||||
routeVariant: routeVariant,
|
||||
scheduledTime: scheduledTime,
|
||||
sendToServer: false
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
print("Checkpoint 4");
|
||||
|
||||
@@ -47,17 +47,37 @@ class CommandModule extends InfoModule {
|
||||
|
||||
final databases = appwrite.Databases(client);
|
||||
|
||||
if (liveInformation.auth.status == AuthStatus.AUTHENTICATED) {
|
||||
final document = await databases.createDocument(
|
||||
databaseId: ApiConstants.INFO_Q_DATABASE_ID,
|
||||
collectionId: ApiConstants.COMMANDS_COLLECTION_ID,
|
||||
documentId: appwrite.ID.unique(),
|
||||
if (true) {
|
||||
try {
|
||||
final response = await databases.listDocuments(
|
||||
databaseId: "6633e85400036415ab0f",
|
||||
collectionId: "6633e85d0020f52f3771",
|
||||
queries: [
|
||||
appwrite.Query.search("SessionID", liveInformation.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: liveInformation.roomDocumentID!,
|
||||
data: {
|
||||
"session_id": sessionID,
|
||||
"command": command,
|
||||
"client_id": clientID,
|
||||
"Commands": pastCommands,
|
||||
"LastUpdater": clientID,
|
||||
}
|
||||
);
|
||||
);
|
||||
} catch (e) {
|
||||
print("Failed to send command");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
_onCommandReceived(CommandInfo(command, clientID));
|
||||
@@ -78,6 +98,10 @@ class CommandModule extends InfoModule {
|
||||
|
||||
if (command == "Response:") {
|
||||
|
||||
}
|
||||
else if (command == "initroom") {
|
||||
// initroom <roomCode>
|
||||
|
||||
}
|
||||
else if (command == "announce") {
|
||||
|
||||
@@ -166,10 +190,41 @@ class CommandModule extends InfoModule {
|
||||
BusRoute route = liveInformation.busSequences.routes[routeNumber]!;
|
||||
BusRouteVariant routeVariant = route.routeVariants.values.toList()[routeVariantIndex];
|
||||
|
||||
liveInformation.setRouteVariant_Internal(
|
||||
liveInformation.setRouteVariant(
|
||||
routeVariant
|
||||
);
|
||||
executeCommand("Response: v \"Client $clientID set its route to ($routeNumber to ${routeVariant.busStops.last.formattedStopName})\"");
|
||||
|
||||
|
||||
// Update the server
|
||||
if (liveInformation.isHost) {
|
||||
print("Updating server");
|
||||
final client = liveInformation.auth.client;
|
||||
final databases = appwrite.Databases(client);
|
||||
|
||||
final response = await databases.listDocuments(
|
||||
databaseId: "6633e85400036415ab0f",
|
||||
collectionId: "6633e85d0020f52f3771",
|
||||
queries: [
|
||||
appwrite.Query.search("SessionID", liveInformation.roomCode!)
|
||||
]
|
||||
);
|
||||
|
||||
final document = await databases.updateDocument(
|
||||
databaseId: "6633e85400036415ab0f",
|
||||
collectionId: "6633e85d0020f52f3771",
|
||||
documentId: response.documents.first.$id,
|
||||
data: {
|
||||
"CurrentRoute": routeNumber,
|
||||
"CurrentRouteVariant": routeVariantIndex,
|
||||
}
|
||||
);
|
||||
try {
|
||||
|
||||
print("Updated server");
|
||||
} catch (e) {
|
||||
print("Failed to update server");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -181,26 +236,26 @@ class CommandModule extends InfoModule {
|
||||
return;
|
||||
}
|
||||
|
||||
final realtime = appwrite.Realtime(LiveInformation().auth.client);
|
||||
|
||||
_subscription = realtime.subscribe(
|
||||
['databases.${ApiConstants.INFO_Q_DATABASE_ID}.collections.${ApiConstants.COMMANDS_COLLECTION_ID}.documents']
|
||||
);
|
||||
_subscription!.stream.listen((event) {
|
||||
print(jsonEncode(event.payload));
|
||||
|
||||
// Only do something if the document was created or updated
|
||||
if (!(event.events.first.contains("create") || event.events.first.contains("update"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
final commandInfo = CommandInfo(event.payload['command'], event.payload['client_id']);
|
||||
|
||||
if (commandInfo.clientID != clientID) {
|
||||
_onCommandReceived(commandInfo);
|
||||
}
|
||||
|
||||
});
|
||||
// final realtime = appwrite.Realtime(LiveInformation().auth.client);
|
||||
//
|
||||
// _subscription = realtime.subscribe(
|
||||
// ['databases.${ApiConstants.INFO_Q_DATABASE_ID}.collections.${ApiConstants.COMMANDS_COLLECTION_ID}.documents']
|
||||
// );
|
||||
// _subscription!.stream.listen((event) {
|
||||
// print(jsonEncode(event.payload));
|
||||
//
|
||||
// // Only do something if the document was created or updated
|
||||
// if (!(event.events.first.contains("create") || event.events.first.contains("update"))) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// final commandInfo = CommandInfo(event.payload['command'], event.payload['client_id']);
|
||||
//
|
||||
// if (commandInfo.clientID != clientID) {
|
||||
// _onCommandReceived(commandInfo);
|
||||
// }
|
||||
//
|
||||
// });
|
||||
|
||||
print("Listening for commands");
|
||||
|
||||
|
||||
@@ -183,7 +183,6 @@ class TrackerModule extends InfoModule {
|
||||
print("Closest stop: ${closestStop.formattedStopName} in ${closestDistance.round()} meters");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
double _calculateRelativeDistance(BusRouteStop stop, double latitude, double longitude) {
|
||||
|
||||
@@ -68,7 +68,7 @@ class TubeStations {
|
||||
double distance = Vector2(stop.easting.toDouble(), stop.northing.toDouble()).distanceTo(OSGrid.toNorthingEasting(station.latitude, station.longitude));
|
||||
|
||||
// if the distance is less than 100m, then we can assume that the bus stop is near the tube station
|
||||
if (distance < 200) {
|
||||
if (distance < 400) {
|
||||
for (TubeLine line in station.lines) {
|
||||
lineMatches[line] = lineMatches[line]! + 1;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:bus_infotainment/utils/delegates.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
|
||||
class pages_Home extends StatelessWidget {
|
||||
const pages_Home({super.key});
|
||||
@@ -60,7 +61,7 @@ class pages_Home extends StatelessWidget {
|
||||
outlineColor: Colors.white70,
|
||||
announcements: [
|
||||
for (NamedAnnouncementQueueEntry announcement in LiveInformation().announcementModule.manualAnnouncements)
|
||||
_AnnouncementEntry(
|
||||
AnnouncementEntry(
|
||||
label: announcement.shortName,
|
||||
index: LiveInformation().announcementModule.manualAnnouncements.indexOf(announcement),
|
||||
outlineColor: Colors.white70,
|
||||
@@ -92,12 +93,12 @@ class pages_Home extends StatelessWidget {
|
||||
color: Colors.grey.shade900,
|
||||
),
|
||||
|
||||
child: DelegateBuilder<BusRouteVariant>(
|
||||
child: DelegateBuilder<BusRouteVariant?>(
|
||||
delegate: LiveInformation().routeVariantDelegate,
|
||||
builder: (context, routeVariant) {
|
||||
print("rebuilt stop announcement picker");
|
||||
return StopAnnouncementPicker(
|
||||
routeVariant: routeVariant,
|
||||
routeVariant: routeVariant!,
|
||||
backgroundColor: Colors.grey.shade900,
|
||||
outlineColor: Colors.white70,
|
||||
);
|
||||
@@ -133,11 +134,6 @@ class pages_Home extends StatelessWidget {
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
LiveInformation liveInformation = LiveInformation();
|
||||
final commandModule = liveInformation.commandModule;
|
||||
|
||||
// commandModule.executeCommand(
|
||||
// "announce dest"
|
||||
// );
|
||||
|
||||
liveInformation.announcementModule.queueAnnouncementByRouteVariant(
|
||||
routeVariant: liveInformation.getRouteVariant()!
|
||||
@@ -147,75 +143,7 @@ class pages_Home extends StatelessWidget {
|
||||
child: Text("Announce current destination"),
|
||||
),
|
||||
|
||||
|
||||
// Container(
|
||||
//
|
||||
// margin: EdgeInsets.all(20),
|
||||
//
|
||||
// height: 300-45,
|
||||
//
|
||||
// child: ListView(
|
||||
//
|
||||
// scrollDirection: Axis.vertical,
|
||||
//
|
||||
// children: [
|
||||
//
|
||||
// ElevatedButton(
|
||||
// onPressed: () async {
|
||||
// LiveInformation liveInformation = LiveInformation();
|
||||
// liveInformation.queueAnnouncement(await liveInformation.getDestinationAnnouncement(liveInformation.getRouteVariant()!, sendToServer: false));
|
||||
// },
|
||||
// child: Text("Test announcement"),
|
||||
// ),
|
||||
//
|
||||
// ElevatedButton(
|
||||
// onPressed: () {
|
||||
// LiveInformation liveInformation = LiveInformation();
|
||||
// liveInformation.updateServer();
|
||||
// },
|
||||
// child: Text("Update server"),
|
||||
// ),
|
||||
//
|
||||
// SizedBox(
|
||||
//
|
||||
// width: 100,
|
||||
//
|
||||
// child: TextField(
|
||||
// onChanged: (String value) {
|
||||
// LiveInformation liveInformation = LiveInformation();
|
||||
// // liveInformation.documentID = value;
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
//
|
||||
// SizedBox(
|
||||
//
|
||||
// width: 200,
|
||||
//
|
||||
// child: TextField(
|
||||
// onSubmitted: (String value) {
|
||||
// LiveInformation liveInformation = LiveInformation();
|
||||
// liveInformation.queueAnnouncement(AnnouncementQueueEntry(
|
||||
// displayText: value,
|
||||
// audioSources: []
|
||||
// ));
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
//
|
||||
// ElevatedButton(
|
||||
// onPressed: () {
|
||||
// LiveInformation liveInformation = LiveInformation();
|
||||
// liveInformation.pullServer();
|
||||
// },
|
||||
// child: Text("Pull server"),
|
||||
// ),
|
||||
//
|
||||
// ],
|
||||
//
|
||||
// ),
|
||||
//
|
||||
// ),
|
||||
|
||||
|
||||
],
|
||||
),
|
||||
@@ -366,8 +294,9 @@ class AnnouncementPicker extends StatefulWidget {
|
||||
final Color backgroundColor;
|
||||
final Color outlineColor;
|
||||
final List<Widget> announcements;
|
||||
final String label;
|
||||
|
||||
const AnnouncementPicker({super.key, required this.backgroundColor, required this.outlineColor, required this.announcements});
|
||||
const AnnouncementPicker({super.key, required this.backgroundColor, required this.outlineColor, required this.announcements, this.label = ""});
|
||||
|
||||
@override
|
||||
State<AnnouncementPicker> createState() => _AnnouncementPickerState();
|
||||
@@ -411,9 +340,9 @@ class _AnnouncementPickerState extends State<AnnouncementPicker> {
|
||||
color: widget.backgroundColor,
|
||||
border: Border.all(
|
||||
color: widget.outlineColor,
|
||||
width: 2
|
||||
width: 1
|
||||
),
|
||||
|
||||
borderRadius: BorderRadius.circular(8)
|
||||
|
||||
|
||||
),
|
||||
@@ -428,118 +357,26 @@ class _AnnouncementPickerState extends State<AnnouncementPicker> {
|
||||
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 2,
|
||||
color: widget.outlineColor,
|
||||
),
|
||||
|
||||
if (_currentIndex < announcementWidgets.length)
|
||||
announcementWidgets[_currentIndex + 0]
|
||||
else
|
||||
Container(
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.backgroundColor,
|
||||
border: Border.symmetric(
|
||||
vertical: BorderSide(
|
||||
color: widget.outlineColor,
|
||||
width: 2
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Container(
|
||||
height: 2,
|
||||
color: widget.outlineColor,
|
||||
),
|
||||
|
||||
if (_currentIndex + 1 < announcementWidgets.length)
|
||||
announcementWidgets[_currentIndex + 1]
|
||||
else
|
||||
Container(
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.backgroundColor,
|
||||
border: Border.symmetric(
|
||||
vertical: BorderSide(
|
||||
color: widget.outlineColor,
|
||||
width: 2
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Container(
|
||||
height: 2,
|
||||
color: widget.outlineColor,
|
||||
),
|
||||
|
||||
if (_currentIndex + 2 < announcementWidgets.length)
|
||||
announcementWidgets[_currentIndex + 2]
|
||||
else
|
||||
Container(
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.backgroundColor,
|
||||
border: Border.symmetric(
|
||||
vertical: BorderSide(
|
||||
color: widget.outlineColor,
|
||||
width: 2
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Container(
|
||||
height: 2,
|
||||
color: widget.outlineColor,
|
||||
),
|
||||
|
||||
if (_currentIndex + 3 < announcementWidgets.length)
|
||||
announcementWidgets[_currentIndex + 3]
|
||||
else
|
||||
Container(
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.backgroundColor,
|
||||
border: Border.symmetric(
|
||||
vertical: BorderSide(
|
||||
color: widget.outlineColor,
|
||||
width: 2
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Container(
|
||||
height: 2,
|
||||
color: widget.outlineColor,
|
||||
),
|
||||
|
||||
Container(
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.backgroundColor,
|
||||
border: Border.symmetric(
|
||||
vertical: BorderSide(
|
||||
color: widget.outlineColor,
|
||||
width: 2
|
||||
)
|
||||
border: Border.all(
|
||||
color: widget.outlineColor,
|
||||
width: 1
|
||||
),
|
||||
borderRadius: BorderRadius.circular(4)
|
||||
),
|
||||
|
||||
alignment: Alignment.centerRight,
|
||||
|
||||
child: Row(
|
||||
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
||||
// height: 100,
|
||||
child: Column(
|
||||
children: [
|
||||
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
|
||||
if (_currentIndex < announcementWidgets.length)
|
||||
announcementWidgets[_currentIndex + 0]
|
||||
else
|
||||
Container(
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.backgroundColor,
|
||||
border: Border.symmetric(
|
||||
@@ -549,50 +386,18 @@ class _AnnouncementPickerState extends State<AnnouncementPicker> {
|
||||
)
|
||||
),
|
||||
),
|
||||
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 4
|
||||
),
|
||||
|
||||
child: Container(
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
child: Icon(
|
||||
Icons.arrow_upward,
|
||||
color: widget.outlineColor,
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
_currentIndex = wrap(_currentIndex - 4, 0, announcementWidgets.length, increment: 4);
|
||||
setState(() {});
|
||||
print(_currentIndex);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.transparent,
|
||||
shadowColor: Colors.transparent,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
foregroundColor: Colors.transparent,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(0),
|
||||
),
|
||||
),
|
||||
child: const Text(""),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
),
|
||||
),
|
||||
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
height: 1,
|
||||
color: widget.outlineColor,
|
||||
),
|
||||
|
||||
if (_currentIndex + 1 < announcementWidgets.length)
|
||||
announcementWidgets[_currentIndex + 1]
|
||||
else
|
||||
Container(
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.backgroundColor,
|
||||
border: Border.symmetric(
|
||||
@@ -602,55 +407,200 @@ class _AnnouncementPickerState extends State<AnnouncementPicker> {
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 4
|
||||
),
|
||||
|
||||
child: Container(
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
child: Icon(
|
||||
Icons.arrow_downward,
|
||||
color: widget.outlineColor,
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
_currentIndex = wrap(_currentIndex + 4, 0, announcementWidgets.length, increment: 4);
|
||||
setState(() {});
|
||||
print(_currentIndex);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.transparent,
|
||||
shadowColor: Colors.transparent,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
foregroundColor: Colors.transparent,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(0),
|
||||
),
|
||||
),
|
||||
child: const Text(""),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
Container(
|
||||
height: 1,
|
||||
color: widget.outlineColor,
|
||||
),
|
||||
|
||||
]
|
||||
if (_currentIndex + 2 < announcementWidgets.length)
|
||||
announcementWidgets[_currentIndex + 2]
|
||||
else
|
||||
Container(
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.backgroundColor,
|
||||
border: Border.symmetric(
|
||||
vertical: BorderSide(
|
||||
color: widget.outlineColor,
|
||||
width: 2
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Container(
|
||||
height: 1,
|
||||
color: widget.outlineColor,
|
||||
),
|
||||
|
||||
if (_currentIndex + 3 < announcementWidgets.length)
|
||||
announcementWidgets[_currentIndex + 3]
|
||||
else
|
||||
Container(
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.backgroundColor,
|
||||
border: Border.symmetric(
|
||||
vertical: BorderSide(
|
||||
color: widget.outlineColor,
|
||||
width: 2
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Container(
|
||||
height: 1,
|
||||
color: widget.outlineColor,
|
||||
),
|
||||
|
||||
Container(
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.backgroundColor,
|
||||
),
|
||||
|
||||
child: Row(
|
||||
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
||||
children: [
|
||||
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
),
|
||||
child: Text(
|
||||
widget.label,
|
||||
style: ShadTheme.of(context).textTheme.h4.copyWith(
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: Colors.blueAccent.shade700,
|
||||
blurRadius: 8
|
||||
)
|
||||
],
|
||||
color: Colors.blueAccent.shade700
|
||||
)
|
||||
),
|
||||
),
|
||||
|
||||
Expanded(child: Container()),
|
||||
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.backgroundColor,
|
||||
border: Border.symmetric(
|
||||
vertical: BorderSide(
|
||||
color: widget.outlineColor,
|
||||
width: 1
|
||||
)
|
||||
),
|
||||
),
|
||||
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 4
|
||||
),
|
||||
|
||||
child: Container(
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
child: Icon(
|
||||
Icons.arrow_upward,
|
||||
color: widget.outlineColor,
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
_currentIndex = wrap(_currentIndex - 4, 0, announcementWidgets.length, increment: 4);
|
||||
setState(() {});
|
||||
print(_currentIndex);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.transparent,
|
||||
shadowColor: Colors.transparent,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
foregroundColor: Colors.transparent,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(0),
|
||||
),
|
||||
),
|
||||
child: const Text(""),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
),
|
||||
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.backgroundColor,
|
||||
border: Border.symmetric(
|
||||
vertical: BorderSide(
|
||||
color: widget.outlineColor,
|
||||
width: 1
|
||||
)
|
||||
),
|
||||
),
|
||||
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 4
|
||||
),
|
||||
|
||||
child: Container(
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
child: Icon(
|
||||
Icons.arrow_downward,
|
||||
color: widget.outlineColor,
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
_currentIndex = wrap(_currentIndex + 4, 0, announcementWidgets.length, increment: 4);
|
||||
setState(() {});
|
||||
print(_currentIndex);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.transparent,
|
||||
shadowColor: Colors.transparent,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
foregroundColor: Colors.transparent,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(0),
|
||||
),
|
||||
),
|
||||
child: const Text(""),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
),
|
||||
|
||||
]
|
||||
|
||||
),
|
||||
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
),
|
||||
Container(
|
||||
height: 2,
|
||||
color: widget.outlineColor,
|
||||
),
|
||||
|
||||
]
|
||||
@@ -669,13 +619,14 @@ class StopAnnouncementPicker extends AnnouncementPicker {
|
||||
required this.routeVariant,
|
||||
required Color backgroundColor,
|
||||
required Color outlineColor,
|
||||
String label = "Stops"
|
||||
}) : super(
|
||||
key: key,
|
||||
backgroundColor: backgroundColor,
|
||||
outlineColor: outlineColor,
|
||||
announcements: [
|
||||
for (BusRouteStop stop in routeVariant.busStops)
|
||||
_AnnouncementEntry(
|
||||
AnnouncementEntry(
|
||||
label: stop.formattedStopName,
|
||||
onPressed: () {
|
||||
LiveInformation liveInformation = LiveInformation();
|
||||
@@ -688,7 +639,8 @@ class StopAnnouncementPicker extends AnnouncementPicker {
|
||||
outlineColor: outlineColor,
|
||||
alert: LiveInformation().announcementModule.announcementCache[stop.getAudioFileName()] == null,
|
||||
)
|
||||
]
|
||||
],
|
||||
label: label
|
||||
);
|
||||
}
|
||||
|
||||
@@ -709,7 +661,7 @@ int wrap(int i, int j, int length, {int increment = -1}) {
|
||||
}
|
||||
}
|
||||
|
||||
class _AnnouncementEntry extends StatelessWidget {
|
||||
class AnnouncementEntry extends StatelessWidget {
|
||||
|
||||
final String label;
|
||||
|
||||
@@ -719,7 +671,7 @@ class _AnnouncementEntry extends StatelessWidget {
|
||||
|
||||
bool alert = false;
|
||||
|
||||
_AnnouncementEntry({super.key, required this.label, required this.onPressed, required this.index, required this.outlineColor, this.alert = false});
|
||||
AnnouncementEntry({super.key, required this.label, required this.onPressed, required this.index, required this.outlineColor, this.alert = false});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -730,12 +682,6 @@ class _AnnouncementEntry extends StatelessWidget {
|
||||
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
border: Border.symmetric(
|
||||
vertical: BorderSide(
|
||||
color: outlineColor,
|
||||
width: 2
|
||||
)
|
||||
),
|
||||
),
|
||||
|
||||
padding: const EdgeInsets.symmetric(
|
||||
@@ -760,7 +706,7 @@ class _AnnouncementEntry extends StatelessWidget {
|
||||
label,
|
||||
style: GoogleFonts.teko(
|
||||
fontSize: 25,
|
||||
color: outlineColor,
|
||||
color: Colors.white,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
||||
@@ -1228,11 +1228,11 @@ class _ConsoleState extends State<Console> {
|
||||
// TODO: implement initState
|
||||
super.initState();
|
||||
|
||||
_listenerReceipt = LiveInformation().commandModule.onCommandReceived.addListener((p0) {
|
||||
/*_listenerReceipt = LiveInformation().commandModule.onCommandReceived.addListener((p0) {
|
||||
print("Command received, updating console");
|
||||
|
||||
setState(() {});
|
||||
});
|
||||
});*/
|
||||
|
||||
}
|
||||
|
||||
@@ -1253,7 +1253,7 @@ class _ConsoleState extends State<Console> {
|
||||
Text("Command History:")
|
||||
);
|
||||
|
||||
for (int i = 0; i < LiveInformation().commandModule.commandHistory.length; i++){
|
||||
/*for (int i = 0; i < LiveInformation().commandModule.commandHistory.length; i++){
|
||||
CommandInfo command = LiveInformation().commandModule.commandHistory[i];
|
||||
|
||||
commands.add(
|
||||
@@ -1271,7 +1271,7 @@ class _ConsoleState extends State<Console> {
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
*/
|
||||
return Container(
|
||||
|
||||
decoration: BoxDecoration(
|
||||
@@ -1299,7 +1299,7 @@ class _ConsoleState extends State<Console> {
|
||||
color: Colors.white70,
|
||||
),
|
||||
|
||||
Container(
|
||||
/*Container(
|
||||
height: 50,
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: TextField(
|
||||
@@ -1315,7 +1315,7 @@ class _ConsoleState extends State<Console> {
|
||||
LiveInformation().commandModule.executeCommand(value);
|
||||
},
|
||||
),
|
||||
)
|
||||
)*/
|
||||
|
||||
],
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
|
||||
|
||||
import 'package:bus_infotainment/pages/tfl_dataset_test.dart';
|
||||
import 'package:bus_infotainment/remaster/InitialStartup.dart';
|
||||
import 'package:bus_infotainment/remaster/dashboard.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -15,13 +16,22 @@ class RemasteredApp extends StatelessWidget {
|
||||
darkTheme: ShadThemeData(
|
||||
brightness: Brightness.dark,
|
||||
colorScheme: ShadSlateColorScheme.dark(),
|
||||
// force dark mode
|
||||
),
|
||||
themeMode: ThemeMode.dark,
|
||||
|
||||
routes: {
|
||||
'/setup': (context) => InitialStartup(),
|
||||
'/': (context) => HomePage_Re(),
|
||||
'/routes': (context) => RoutePage(),
|
||||
'/enroute': (context) => EnRoutePage(),
|
||||
'/legacy': (context) => TfL_Dataset_Test(),
|
||||
'/multi': (context) => MultiModeSetup(),
|
||||
'/multi/enroute': (context) => MultiModeEnroute(),
|
||||
'/multi/login': (context) => MultiModeLogin(),
|
||||
'/multi/register': (context) => MultiModeRegister(),
|
||||
'/display': (context) => FullscreenDisplay(),
|
||||
'/multi/join': (context) => MultiModeJoin(),
|
||||
|
||||
},
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
112
lib/workaround/keepalive_realtime.dart
Normal file
112
lib/workaround/keepalive_realtime.dart
Normal file
@@ -0,0 +1,112 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:appwrite/appwrite.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
// import this package https://pub.dev/packages/web_socket_channel
|
||||
|
||||
class RealtimeKeepAliveConnection {
|
||||
RealtimeKeepAliveConnection({
|
||||
required this.channels,
|
||||
required this.domain,
|
||||
required this.client,
|
||||
this.keepAlivePingDuration = const Duration(seconds: 90),
|
||||
required this.onData,
|
||||
required this.onError,
|
||||
});
|
||||
|
||||
final List<String> channels;
|
||||
final String domain;
|
||||
final Duration keepAlivePingDuration;
|
||||
final Client client;
|
||||
final Function(RealtimeMessage) onData;
|
||||
final Function(dynamic) onError;
|
||||
|
||||
// ignore: unused_field
|
||||
StreamSubscription<dynamic>? _subscription;
|
||||
WebSocketChannel? _webSocket;
|
||||
final Stopwatch _stopwatch = Stopwatch();
|
||||
bool _keepAlive = true;
|
||||
bool _sentKeepAlivePing = false;
|
||||
int reconnectCount = 0;
|
||||
|
||||
Future initialize() async {
|
||||
await _initRealtime(
|
||||
onData: _realtimeOnData,
|
||||
onDone: _realtimeOnDone,
|
||||
onError: _realtimeOnError,
|
||||
);
|
||||
_heartbeat();
|
||||
}
|
||||
|
||||
void close() {
|
||||
_keepAlive = false;
|
||||
_subscription!.cancel();
|
||||
}
|
||||
|
||||
void _heartbeat() async {
|
||||
while (_keepAlive) {
|
||||
await Future.delayed(keepAlivePingDuration);
|
||||
if (_webSocket != null) {
|
||||
_sentKeepAlivePing = true;
|
||||
_webSocket!.sink.add("ping");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _realtimeOnData(RealtimeMessage data) {
|
||||
log("[$reconnectCount][${_stopwatch.elapsed}] onData");
|
||||
onData(data);
|
||||
}
|
||||
|
||||
void _realtimeOnDone() async {
|
||||
reconnectCount++;
|
||||
log("[$reconnectCount][${_stopwatch.elapsed}] onDone");
|
||||
if (_keepAlive) {
|
||||
if (_subscription != null) _subscription!.cancel();
|
||||
|
||||
_subscription = _subscription = await _initRealtime(
|
||||
onData: _realtimeOnData,
|
||||
onDone: _realtimeOnDone,
|
||||
onError: _realtimeOnError,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _realtimeOnError(dynamic e) {
|
||||
log("[$reconnectCount][${_stopwatch.elapsed}] onError:$e");
|
||||
onError(onError);
|
||||
}
|
||||
|
||||
Future _initRealtime({
|
||||
required Function(RealtimeMessage) onData,
|
||||
required Function() onDone,
|
||||
required Function(dynamic) onError,
|
||||
}) async {
|
||||
_stopwatch.reset();
|
||||
_stopwatch.start();
|
||||
String channelParams = channels.map((c) => "channels[]=$c").join('&');
|
||||
|
||||
String? projectId = client.config['project'];
|
||||
|
||||
final wssUrl = Uri.parse('wss://$domain/realtime?project=$projectId&$channelParams');
|
||||
_webSocket = WebSocketChannel.connect(wssUrl);
|
||||
|
||||
Realtime realtime = Realtime(client);
|
||||
RealtimeSubscription subscriptionRealTime = realtime.subscribe(channels);
|
||||
|
||||
subscriptionRealTime.stream.listen(onData, onDone: onDone, onError: onError);
|
||||
_subscription = _webSocket!.stream.listen(_handlePingMsg);
|
||||
}
|
||||
|
||||
void _handlePingMsg(dynamic response) {
|
||||
var json = jsonDecode(response);
|
||||
|
||||
if (json["type"] == "error" && _sentKeepAlivePing) {
|
||||
_sentKeepAlivePing = false;
|
||||
log("Web socket keep-alive heartbeat successful (Reconnect Count: $reconnectCount, Time alive: ${_stopwatch.elapsed})");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user