From 8b8cb0412f067456b4192bc7973c0173562aa53c Mon Sep 17 00:00:00 2001 From: ImBenji <53883070+YesItsBenji@users.noreply.github.com> Date: Wed, 28 Feb 2024 18:06:22 +0000 Subject: [PATCH] tolaptop --- lib/auth/api_constants.dart | 6 +- lib/pages/components/ibus_display.dart | 2 +- lib/pages/home.dart | 178 ++++++---- lib/pages/routes.dart | 10 +- lib/singletons/live_information.dart | 458 +++++++++++++++++++++---- lib/utils/audio wrapper.dart | 2 +- lib/utils/delegates.dart | 1 + pubspec.lock | 8 + pubspec.yaml | 2 +- 9 files changed, 520 insertions(+), 147 deletions(-) diff --git a/lib/auth/api_constants.dart b/lib/auth/api_constants.dart index 4489f07..07afed2 100644 --- a/lib/auth/api_constants.dart +++ b/lib/auth/api_constants.dart @@ -5,9 +5,9 @@ class ApiConstants { static const String APPWRITE_PROJECT_ID = "65de530c1c0a7ffc0c3f"; static const String INFO_Q_DATABASE_ID = "65de5cab16717444527b"; - static const String DEST_Q_COLLECTION_ID = "65de9f2f925562a2eda8"; - static const String MANUAL_Q_COLLECTION_ID = "65de9f1b6282fd209bdb"; - static const String BUSSTOP_Q_COLLECTION_ID = "65de9ef464bfa5a0693d"; + static const String MANUAL_Q_COLLECTION_ID = "65de9f2f925562a2eda8"; + static const String INFORMATION_Q_COLLECTION_ID = "65de9f1b6282fd209bdb"; + static const String DEST_Q_COLLECTION_ID = "65de9ef464bfa5a0693d"; diff --git a/lib/pages/components/ibus_display.dart b/lib/pages/components/ibus_display.dart index 5ebd1d4..cd5dfd7 100644 --- a/lib/pages/components/ibus_display.dart +++ b/lib/pages/components/ibus_display.dart @@ -44,7 +44,7 @@ class _ibus_displayState extends State { String _padString(String input){ - if (input.length < 40){ + if (input.length < 30){ print("Input is too short"); return input; } diff --git a/lib/pages/home.dart b/lib/pages/home.dart index 3e68309..fe1705a 100644 --- a/lib/pages/home.dart +++ b/lib/pages/home.dart @@ -3,6 +3,7 @@ import 'package:bus_infotainment/pages/components/ibus_display.dart'; import 'package:bus_infotainment/singletons/live_information.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:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; @@ -109,79 +110,102 @@ class pages_Home extends StatelessWidget { outlineColor: Colors.white70, ); }, + defaultBuilder: (context) { + BusRouteVariant? routeVariant = LiveInformation().getRouteVariant(); + if (routeVariant == null) { + return Container( + color: Colors.grey.shade900, + child: Center( + child: Text( + "No route selected", + style: GoogleFonts.teko( + fontSize: 25, + color: Colors.white70 + ), + ), + ), + ); + } else { + return StopAnnouncementPicker( + routeVariant: routeVariant, + backgroundColor: Colors.grey.shade900, + outlineColor: Colors.white70, + ); + } + }, ), ), ), - Container( - - margin: EdgeInsets.all(20), - - height: 300-45, - - child: ListView( - - scrollDirection: Axis.vertical, - - children: [ - - ElevatedButton( - onPressed: () { - LiveInformation liveInformation = LiveInformation(); - liveInformation.announceRouteVariant(liveInformation.getRouteVariant()!); - }, - 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"), - ), - - ], - - ), - - ), + // 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"), + // ), + // + // ], + // + // ), + // + // ), ], ) @@ -194,7 +218,7 @@ class ManualAnnouncementPicker extends StatefulWidget { final Color backgroundColor; final Color outlineColor; - final List announcements; + final List announcements; const ManualAnnouncementPicker({super.key, required this.backgroundColor, required this.outlineColor, required this.announcements}); @@ -219,14 +243,16 @@ class _ManualAnnouncementPickerState extends State { return; } - for (InformationAnnouncementEntry announcement in widget.announcements!) { + int i = 0; + for (NamedAnnouncementQueueEntry announcement in widget.announcements!) { announcementWidgets.add( _ManualAnnouncementEntry( announcement: announcement, - index: liveInformation.manualAnnouncements.indexOf(announcement), + index: i, outlineColor: Colors.white70 ) ); + i++; } } @@ -506,10 +532,14 @@ class StopAnnouncementPicker extends ManualAnnouncementPicker { outlineColor: outlineColor, announcements: [ for (BusRouteStops stop in routeVariant.busStops) - InformationAnnouncementEntry( + ManualAnnouncementEntry( shortName: stop.formattedStopName, informationText: stop.formattedStopName, - audioSources: [] + audioSources: [ + // AudioWrapperByteSource( + // LiveInformation().announcementCache[stop.getAudioFileName()] + // ) + ] ) ] ); @@ -521,7 +551,7 @@ int wrap(int i, int j, int length) { class _ManualAnnouncementEntry extends StatelessWidget { - final InformationAnnouncementEntry announcement; + final NamedAnnouncementQueueEntry announcement; final int index; final Color outlineColor; diff --git a/lib/pages/routes.dart b/lib/pages/routes.dart index 68cac98..91cdfab 100644 --- a/lib/pages/routes.dart +++ b/lib/pages/routes.dart @@ -334,11 +334,19 @@ class _Variant extends StatelessWidget { child: const Text("Cancel"), ), ElevatedButton( - onPressed: () { + onPressed: () async { Navigator.of(context).pop(); LiveInformation liveInformation = LiveInformation(); liveInformation.setRouteVariant(variant); + + liveInformation.queueAnnouncement( + await liveInformation.getDestinationAnnouncement( + variant, + sendToServer: true, + ) + ); + tfL_Dataset_TestState.setState(() {}); }, diff --git a/lib/singletons/live_information.dart b/lib/singletons/live_information.dart index 8316411..71b732a 100644 --- a/lib/singletons/live_information.dart +++ b/lib/singletons/live_information.dart @@ -1,6 +1,7 @@ // Singleton import 'dart:async'; +import 'dart:convert'; import 'package:appwrite/appwrite.dart' as appwrite; import 'package:appwrite/models.dart' as models; @@ -10,8 +11,10 @@ import 'package:bus_infotainment/auth/auth_api.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:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:http/http.dart' as http; +import 'package:ntp/ntp.dart'; class LiveInformation { @@ -61,15 +64,45 @@ class LiveInformation { } - Timer refreshTimer() => Timer.periodic(Duration(milliseconds: 50), (timer) { + Timer refreshTimer() => Timer.periodic(const Duration(milliseconds: 100), (timer) async { + await updateNtpOffset(); _handleAnnouncementQueue(); }); + int ntpOffset = -1; + DateTime lastNtpUpdate = DateTime.now().add(const Duration(seconds: -15)); + /// updates the NTP offset from DateTime.now() + Future updateNtpOffset() async { + + // Only update the NTP offset every 10 seconds + if (DateTime.now().difference(lastNtpUpdate).inSeconds < 10) { + return; + } + + var res = await http.get(Uri.parse('http://worldtimeapi.org/api/timezone/Europe/London')); + if (res.statusCode == 200) { + var json = jsonDecode(res.body); + DateTime time = DateTime.parse(json['datetime']); + ntpOffset = time.millisecondsSinceEpoch - DateTime.now().millisecondsSinceEpoch; + lastNtpUpdate = DateTime.now(); + + } + } + + DateTime getNow() { + if (ntpOffset == -1) { + throw Exception("NTP offset not set"); + } + return DateTime.now().add(Duration(milliseconds: ntpOffset)); + } + + AudioWrapper audioPlayer = AudioWrapper(); AnnouncementCache announcementCache = AnnouncementCache(); List announcementQueue = []; - DateTime lastAnnouncement = DateTime.now(); + AnnouncementQueueEntry? lastAnnouncement; + DateTime lastAnnouncementTimeStamp = DateTime.now().toUtc(); EventDelegate announcementDelegate = EventDelegate(); String _currentAnnouncement = "*** NO MESSAGE ***"; @@ -78,18 +111,23 @@ class LiveInformation { _currentAnnouncement = value; } + bool isPlayingAnnouncement = false; void _handleAnnouncementQueue() async { - int timerInterval = 50; + int timerInterval = 100; // print("Handling announcement queue"); - if (audioPlayer.state != AudioWrapper_State.Playing) { + if (!isPlayingAnnouncement) { if (announcementQueue.isNotEmpty) { + print("Handling announcement queue"); + AnnouncementQueueEntry announcement = announcementQueue.first; + print("Queue length: ${announcementQueue.length}"); + { - DateTime now = DateTime.now(); + DateTime now = getNow(); if (announcement.scheduledTime != null) { int milisecondDifference = abs(now.millisecondsSinceEpoch - announcement.scheduledTime!.millisecondsSinceEpoch); // print("Q Difference: ${milisecondDifference}"); @@ -97,105 +135,178 @@ class LiveInformation { // Account for the time lost by the periodic timer await Future.delayed(Duration(milliseconds: timerInterval - milisecondDifference)); } else { + print("Due in: ${milisecondDifference}ms"); return; } } + } + announcementQueue.removeAt(0); + lastAnnouncement = announcement; + isPlayingAnnouncement = true; - + if (kIsWeb) { + await Future.delayed(Duration(milliseconds: 100)); } - // announcementDelegate.trigger(announcement); + print("Displaying announcement: ${announcement.displayText}"); + announcementDelegate.trigger(announcement); _currentAnnouncement = announcement.displayText; - lastAnnouncement = DateTime.now(); + lastAnnouncementTimeStamp = getNow(); - for (AudioWrapperSource source in announcement.audioSources) { + if (announcement.audioSources.isNotEmpty) { + try { + for (AudioWrapperSource source in announcement.audioSources) { - Duration? duration = await audioPlayer.play(source); - await Future.delayed(duration!); - await Future.delayed(Duration(milliseconds: 150)); + Duration? duration = await audioPlayer.play(source); + await Future.delayed(duration!); + await Future.delayed(Duration(milliseconds: 150)); + } + } finally { + audioPlayer.stop(); + + } + } else { + if (announcementQueue.isNotEmpty) { + await Future.delayed(Duration(seconds: 5)); + } } - audioPlayer.stop(); - announcementQueue.remove(announcement); - print("Queue length: ${announcementQueue.length}"); + + isPlayingAnnouncement = false; print("Popped announcement queue"); } } } - void announceRouteVariant(BusRouteVariant routeVariant) async { - if (routeVariant == null) { - return; - } + Future _getDestinationAnnouncement(BusRouteVariant routeVariant, {bool sendToServer = false}) async { + String display = "${routeVariant.busRoute.routeNumber} to ${routeVariant.busStops.last.formattedStopName}"; String audio_route = "R_${routeVariant.busRoute.routeNumber}_001.mp3"; String audio_destination = routeVariant.busStops.last.getAudioFileName(); - print("Audio file: $audio_route"); - + // Cache the audio files await announcementCache.loadAnnouncements([audio_route, audio_destination]); AudioWrapperSource source_route = AudioWrapperByteSource(announcementCache[audio_route]); AudioWrapperSource source_destination = AudioWrapperByteSource(announcementCache[audio_destination]); - queueAnnouncement(AnnouncementQueueEntry( - displayText: display, - audioSources: [source_route, AudioWrapperAssetSource("audio/to_destination.wav"), source_destination] - )); + return AnnouncementQueueEntry( + sendToServer: sendToServer, + displayText: display, + audioSources: [source_route, AudioWrapperAssetSource("audio/to_destination.wav"), source_destination] + ); + + } + + Future getDestinationAnnouncement(BusRouteVariant routeVariant, {bool sendToServer = true}) async { + return DestinationAnnouncementEntry( + routeVariant: routeVariant, + audioSources: [], + sendToServer: sendToServer, + ); } late BusSequences busSequences; BusRouteVariant? _currentRouteVariant; EventDelegate routeVariantDelegate = EventDelegate(); - void setRouteVariant(BusRouteVariant routeVariant) { + Future setRouteVariant(BusRouteVariant routeVariant) async { _currentRouteVariant = routeVariant; - announceRouteVariant(routeVariant); routeVariantDelegate.trigger(routeVariant); + + // cache all of the stop announcements + + List audioFiles = []; + + for (BusRouteStops stop in routeVariant.busStops) { + audioFiles.add(stop.getAudioFileName()); + print("Cached stop audio: ${stop.getAudioFileName()}"); + } + + await announcementCache.loadAnnouncements(audioFiles); + + } BusRouteVariant? getRouteVariant() { return _currentRouteVariant; } - void queueAnnouncement(AnnouncementQueueEntry announcement) { + void queueAnnouncement(AnnouncementQueueEntry announcement) async { // Make sure the timestamp of the announcement is after the last announcement // If so, dont queue it // If timestamp is null, then skip this check - if (announcement.timestamp != null && announcement.timestamp!.isBefore(lastAnnouncement)) { + if (announcement.timestamp != null && announcement.timestamp!.toUtc().isBefore(lastAnnouncementTimeStamp)) { print("Announcement is too old"); - print("LastAnnouncement: $lastAnnouncement"); + print("LastAnnouncement: $lastAnnouncementTimeStamp"); print("Announcement: ${announcement.timestamp}"); - int difference = announcement.timestamp!.difference(lastAnnouncement).inMilliseconds; + int difference = announcement.timestamp!.difference(lastAnnouncementTimeStamp).inMilliseconds; print("Difference: $difference"); return; } else if (announcement.timestamp == null) { print("Announcement `${announcement.displayText}` does not have timestamp"); } + + + // If there is an announcement in the queue with the same timestamp, dont queue it + if (announcementQueue.any((element) => element.timestamp == announcement.timestamp)) { + print("Announcement with same timestamp already in queue"); + return; + } + if (!announcement.sendToServer) { + + if (announcement is DestinationAnnouncementEntry) { + + BusRouteVariant routeVariant = announcement.routeVariant; + + if (getRouteVariant() != routeVariant) { + setRouteVariant(routeVariant); + } + + announcementQueue.add( + await _getDestinationAnnouncement( + routeVariant, + sendToServer: false + ) + ); + + print("Queued destination announcement: ${announcement.displayText}"); + print("Audios: ${announcement.audioSources.length}"); + return; + + } + announcementQueue.add(announcement); + print("Queued announcement: ${announcement.displayText} (no server)"); return; } final databases = appwrite.Databases(auth.client); - if (announcement is InformationAnnouncementEntry) { + print("Queuing announcement: ${announcement.displayText} (server)"); + print("Announcement type: ${announcement.runtimeType}"); + + if (announcement.runtimeType == InformationAnnouncementEntry) { + announcement as InformationAnnouncementEntry; + print("Queing to InformationAnnouncementEntry"); // 5 sedonds in the future - DateTime scheduledTime = DateTime.now().add(Duration(seconds: 5)); + DateTime scheduledTime = (await getNow()).add(Duration(seconds: 1)); + final document = databases.createDocument( documentId: appwrite.ID.unique(), databaseId: ApiConstants.INFO_Q_DATABASE_ID, - collectionId: ApiConstants.MANUAL_Q_COLLECTION_ID, + collectionId: ApiConstants.INFORMATION_Q_COLLECTION_ID, data: { "ManualAnnouncementIndex": manualAnnouncements.indexOf(announcement), "ScheduledTime": scheduledTime.toIso8601String(), @@ -203,9 +314,73 @@ class LiveInformation { } ); + print("Queued manual announcement: ${announcement.shortName} (server)"); + + } else if (announcement.runtimeType == ManualAnnouncementEntry) { + announcement as ManualAnnouncementEntry; + print("Queing to ManualAnnouncementEntry"); + + // 5 sedonds in the future + DateTime scheduledTime = (await getNow()).add(Duration(seconds: 1)); + + print("debug2"); + + List audioFileNames = []; + + for (AudioWrapperSource source in announcement.audioSources) { + + if (source is AudioWrapperByteSource) { + Uint8List? bytes = await source.bytes; + + String? filename = null; + + for (String key in announcementCache.keys) { + if (announcementCache[key] == bytes) { + filename = key; + break; + } + } + + } + + } + + final document = databases.createDocument( + documentId: appwrite.ID.unique(), + databaseId: ApiConstants.INFO_Q_DATABASE_ID, + collectionId: ApiConstants.MANUAL_Q_COLLECTION_ID, + data: { + "DisplayText": announcement.displayText, + "AudioFileNames": audioFileNames, + + "ScheduledTime": scheduledTime.toIso8601String(), + "SessionID": sessionID, + } + ); + print("Queued manual announcement: ${announcement.shortName}"); - } else if (announcement is AnnouncementQueueEntry) { + } else if (announcement.runtimeType == DestinationAnnouncementEntry) { + announcement as DestinationAnnouncementEntry; + print("Queing to DestinationAnnouncementEntry"); + + // 5 sedonds in the future + DateTime scheduledTime = (getNow()).add(Duration(seconds: 2)); + + final document = databases.createDocument( + documentId: appwrite.ID.unique(), + databaseId: ApiConstants.INFO_Q_DATABASE_ID, + collectionId: ApiConstants.DEST_Q_COLLECTION_ID, + data: { + "RouteNumber": announcement.routeVariant.busRoute.routeNumber, + "RouteVariantIndex": announcement.routeVariant.routeVariant, + + "ScheduledTime": scheduledTime.toIso8601String(), + "SessionID": sessionID, + } + ); + + print("Queued manual announcement: ${announcement.shortName} (server)"); } @@ -390,45 +565,129 @@ class LiveInformation { return; } - final databases = appwrite.Databases(auth.client); - - // Pull the manual queue - final manual_q = await databases.listDocuments( - databaseId: ApiConstants.INFO_Q_DATABASE_ID, - collectionId: ApiConstants.MANUAL_Q_COLLECTION_ID, - queries: [ - appwrite.Query.search("SessionID", sessionID), - appwrite.Query.limit(25), - appwrite.Query.offset(0), - appwrite.Query.orderDesc('\$createdAt') - ] - ); - List queue = []; - for (models.Document doc in manual_q.documents) { - int index = doc.data['ManualAnnouncementIndex']; + final databases = appwrite.Databases(auth.client); - InformationAnnouncementEntry announcement_clone = InformationAnnouncementEntry( - shortName: manualAnnouncements[index].shortName, - informationText: manualAnnouncements[index].displayText, - audioSources: manualAnnouncements[index].audioSources, - scheduledTime: doc.data["ScheduledTime"] != null ? DateTime.parse(doc.data["ScheduledTime"]) : null, - timestamp: DateTime.parse(doc.$createdAt), - sendToServer: false, + // Pull the information queue + { + final manual_q = await databases.listDocuments( + databaseId: ApiConstants.INFO_Q_DATABASE_ID, + collectionId: ApiConstants.INFORMATION_Q_COLLECTION_ID, + queries: [ + appwrite.Query.search("SessionID", sessionID), + appwrite.Query.limit(25), + appwrite.Query.offset(0), + appwrite.Query.orderDesc('\$createdAt') + ] ); - // sort the queue by timestamp, so the oldest announcements are at the front + for (models.Document doc in manual_q.documents) { + int index = doc.data['ManualAnnouncementIndex']; + InformationAnnouncementEntry announcement_clone = + InformationAnnouncementEntry( + shortName: manualAnnouncements[index].shortName, + informationText: manualAnnouncements[index].displayText, + audioSources: manualAnnouncements[index].audioSources, + scheduledTime: doc.data["ScheduledTime"] != null + ? DateTime.parse(doc.data["ScheduledTime"]) + : null, + timestamp: DateTime.parse(doc.$createdAt), + sendToServer: false, + ); + // sort the queue by timestamp, so the oldest announcements are at the front - queue.add(announcement_clone); + queue.add(announcement_clone); + } } + // Pull the manual queue + { + final manual_q = await databases.listDocuments( + databaseId: ApiConstants.INFO_Q_DATABASE_ID, + collectionId: ApiConstants.MANUAL_Q_COLLECTION_ID, + queries: [ + appwrite.Query.search("SessionID", sessionID), + appwrite.Query.limit(25), + appwrite.Query.offset(0), + appwrite.Query.orderDesc('\$createdAt') + ] + ); + + for (models.Document doc in manual_q.documents) { + + List audioSources = []; + + for (String filename in doc.data["AudioFileNames"]) { + audioSources.add(AudioWrapperByteSource(announcementCache[filename])); + } + + ManualAnnouncementEntry announcement_clone = + ManualAnnouncementEntry( + sendToServer: false, + + shortName: "", + informationText: doc.data["DisplayText"], + + audioSources: [...audioSources], + scheduledTime: doc.data["ScheduledTime"] != null + ? DateTime.parse(doc.data["ScheduledTime"]) + : null, + + ); + + // sort the queue by timestamp, so the oldest announcements are at the front + + queue.add(announcement_clone); + } + } + + // pull the destination queue + { + final dest_q = await databases.listDocuments( + databaseId: ApiConstants.INFO_Q_DATABASE_ID, + collectionId: ApiConstants.DEST_Q_COLLECTION_ID, + queries: [ + appwrite.Query.search("SessionID", sessionID), + appwrite.Query.limit(25), + appwrite.Query.offset(0), + appwrite.Query.orderDesc('\$createdAt') + ] + ); + + for (models.Document doc in dest_q.documents) { + + BusRoute? route = busSequences.routes[doc.data["RouteNumber"]]; + + BusRouteVariant? routeVariant = route!.routeVariants[doc.data["RouteVariantIndex"]]; + DestinationAnnouncementEntry announcement_clone = + DestinationAnnouncementEntry( + routeVariant: routeVariant!, + scheduledTime: doc.data["ScheduledTime"] != null + ? DateTime.parse(doc.data["ScheduledTime"]) + : null, + timestamp: DateTime.parse(doc.$createdAt), + sendToServer: false, + audioSources: [], + ); + + // sort the queue by timestamp, so the oldest announcements are at the front + + queue.add(announcement_clone); + } + } for (AnnouncementQueueEntry entry in queue) { + + // Dont queue announcements that are older than now + if (entry.scheduledTime != null && entry.scheduledTime!.isBefore(await getNow())) { + continue; + } + queueAnnouncement(entry); } @@ -436,10 +695,13 @@ class LiveInformation { + appwrite.RealtimeSubscription? information_q_subscription; appwrite.RealtimeSubscription? manual_q_subscription; + appwrite.RealtimeSubscription? destination_q_subscription; + Future setupRealtime() async { - if (manual_q_subscription != null) { + if (information_q_subscription != null) { return; } @@ -450,6 +712,15 @@ class LiveInformation { // Websocket appwrite.Realtime realtime = appwrite.Realtime(auth.client); + information_q_subscription = realtime.subscribe( + ['databases.${ApiConstants.INFO_Q_DATABASE_ID}.collections.${ApiConstants.INFORMATION_Q_COLLECTION_ID}.documents'], + ); + information_q_subscription?.stream.listen((event) { + print("Manual queue entry added"); + + pullQueue(); + }); + manual_q_subscription = realtime.subscribe( ['databases.${ApiConstants.INFO_Q_DATABASE_ID}.collections.${ApiConstants.MANUAL_Q_COLLECTION_ID}.documents'], ); @@ -459,12 +730,25 @@ class LiveInformation { pullQueue(); }); + destination_q_subscription = realtime.subscribe( + ['databases.${ApiConstants.INFO_Q_DATABASE_ID}.collections.${ApiConstants.DEST_Q_COLLECTION_ID}.documents'], + ); + destination_q_subscription?.stream.listen((event) { + print("Destination queue entry added"); + + pullQueue(); + }); + print("Subscribed to servers"); await Future.delayed(Duration(seconds: 90)); + information_q_subscription?.close(); + information_q_subscription = null; manual_q_subscription?.close(); manual_q_subscription = null; + destination_q_subscription?.close(); + destination_q_subscription = null; setupRealtime(); } @@ -483,17 +767,37 @@ class AnnouncementQueueEntry { AnnouncementQueueEntry({required this.displayText, required this.audioSources, this.sendToServer = true, this.scheduledTime, this.timestamp}); } -class ManualAnnouncementEntry extends AnnouncementQueueEntry { +class NamedAnnouncementQueueEntry extends AnnouncementQueueEntry { final String shortName; - ManualAnnouncementEntry({ + NamedAnnouncementQueueEntry({ required this.shortName, + required String displayText, + required List audioSources, + DateTime? scheduledTime, + DateTime? timestamp, + bool sendToServer = true, + }) : super( + displayText: displayText, + audioSources: audioSources, + sendToServer: sendToServer, + scheduledTime: scheduledTime, + timestamp: timestamp, + ); +} + +class ManualAnnouncementEntry extends NamedAnnouncementQueueEntry { + + + ManualAnnouncementEntry({ + required String shortName, required String informationText, required List audioSources, DateTime? scheduledTime, DateTime? timestamp, bool sendToServer = true, }) : super( + shortName: shortName, displayText: informationText, audioSources: audioSources, sendToServer: sendToServer, @@ -502,17 +806,18 @@ class ManualAnnouncementEntry extends AnnouncementQueueEntry { ); } -class InformationAnnouncementEntry extends AnnouncementQueueEntry { - final String shortName; +class InformationAnnouncementEntry extends NamedAnnouncementQueueEntry { + InformationAnnouncementEntry({ - required this.shortName, + required String shortName, required String informationText, required List audioSources, DateTime? scheduledTime, DateTime? timestamp, bool sendToServer = true, }) : super( + shortName: shortName, displayText: informationText, audioSources: audioSources, sendToServer: sendToServer, @@ -521,4 +826,25 @@ class InformationAnnouncementEntry extends AnnouncementQueueEntry { ); } +class DestinationAnnouncementEntry extends NamedAnnouncementQueueEntry { + + final BusRouteVariant routeVariant; + + DestinationAnnouncementEntry({ + required this.routeVariant, + required List audioSources, + DateTime? scheduledTime, + DateTime? timestamp, + bool sendToServer = true, + }) : super( + shortName: "Destination", + displayText: "${routeVariant.busRoute.routeNumber} to ${routeVariant.busStops.last.formattedStopName}", + audioSources: audioSources, + sendToServer: sendToServer, + scheduledTime: scheduledTime, + timestamp: timestamp, + ); + +} + var abs = (int value) => value < 0 ? -value : value; \ No newline at end of file diff --git a/lib/utils/audio wrapper.dart b/lib/utils/audio wrapper.dart index fc3e354..8b6d3f7 100644 --- a/lib/utils/audio wrapper.dart +++ b/lib/utils/audio wrapper.dart @@ -57,7 +57,7 @@ class AudioWrapper { duration = await _audioPlayer_AudioPlayer.getDuration(); } - + return duration; } diff --git a/lib/utils/delegates.dart b/lib/utils/delegates.dart index 84aa698..d6d3a73 100644 --- a/lib/utils/delegates.dart +++ b/lib/utils/delegates.dart @@ -72,6 +72,7 @@ class _DelegateBuilderState extends State> { }); }); + } @override diff --git a/pubspec.lock b/pubspec.lock index 88c8f37..66d53d1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -416,6 +416,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + ntp: + dependency: "direct main" + description: + name: ntp + sha256: "198db73e5059b334b50dbe8c626011c26576778ee9fc53f4c55c1d89d08ed2d2" + url: "https://pub.dev" + source: hosted + version: "2.0.0" package_info_plus: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7087dc3..186bcb8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -43,7 +43,7 @@ dependencies: appwrite: ^11.0.1 shared_preferences: ^2.2.2 url_launcher: ^6.2.2 - + ntp: ^2.0.0 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons.