diff --git a/lib/backend/live_information.dart b/lib/backend/live_information.dart index e919dc9..9364476 100644 --- a/lib/backend/live_information.dart +++ b/lib/backend/live_information.dart @@ -433,7 +433,7 @@ class LiveInformation { if (mode == "info") { print("Announce info command received"); - announcementModule.queueAnnounementByInfoIndex( + announcementModule.queueAnnouncementByInfoIndex( sendToServer: false, infoIndex: int.parse(commandParts[2]), scheduledTime: DateTime.fromMillisecondsSinceEpoch(int.parse(commandParts[3])), diff --git a/lib/backend/modules/announcement.dart b/lib/backend/modules/announcement.dart index fa47347..3bd921a 100644 --- a/lib/backend/modules/announcement.dart +++ b/lib/backend/modules/announcement.dart @@ -224,7 +224,7 @@ class AnnouncementModule extends InfoModule { } - void queueAnnounementByInfoIndex({ + void queueAnnouncementByInfoIndex({ int infoIndex = -1, DateTime? scheduledTime = null, bool sendToServer = true @@ -235,7 +235,7 @@ class AnnouncementModule extends InfoModule { scheduledTime ??= liveInformation.syncedTimeModule.Now().add(Duration(seconds: defaultAnnouncementDelay)); liveInformation.SendCommand("announce info $infoIndex ${scheduledTime.millisecondsSinceEpoch}"); - queueAnnounementByInfoIndex( + queueAnnouncementByInfoIndex( infoIndex: infoIndex, scheduledTime: scheduledTime, sendToServer: false diff --git a/lib/backend/modules/commands.dart b/lib/backend/modules/commands.dart index c19f1dd..a365ad5 100644 --- a/lib/backend/modules/commands.dart +++ b/lib/backend/modules/commands.dart @@ -144,7 +144,7 @@ class CommandModule extends InfoModule { } } catch (e) {} - liveInformation.announcementModule.queueAnnounementByInfoIndex( + liveInformation.announcementModule.queueAnnouncementByInfoIndex( infoIndex: InfoIndex, scheduledTime: scheduledTime, sendToServer: false diff --git a/lib/pages/components/ibus_display.dart b/lib/pages/components/ibus_display.dart index f764ce1..a5c37ee 100644 --- a/lib/pages/components/ibus_display.dart +++ b/lib/pages/components/ibus_display.dart @@ -26,6 +26,7 @@ class _ibus_displayState extends State { late final ListenerReceipt _receipt; + _ibus_displayState(){ LiveInformation liveInformation = LiveInformation(); @@ -42,8 +43,12 @@ class _ibus_displayState extends State { }); topLine = liveInformation.announcementModule.currentAnnouncement?.displayText ?? liveInformation.announcementModule.defaultText; + + } + + String _padString(String input){ if (input.length < 30){ @@ -68,6 +73,8 @@ class _ibus_displayState extends State { LiveInformation().announcementModule.onAnnouncement.removeListener(_receipt); + + super.dispose(); } @@ -157,25 +164,11 @@ class _ibus_displayState extends State { ), Transform.translate( - offset: Offset(0, -6), + offset: Offset(0, -3.5), child: Container( alignment: Alignment.center, width: 32*4*3, - child: TextScroll( - _padString(bottomLine), - velocity: Velocity(pixelsPerSecond: Offset(120, 0)), - style: const TextStyle( - fontSize: 20, - color: Colors.orange, - fontFamily: "ibus", - shadows: [ - Shadow( - color: Colors.orange, - blurRadius: 5, - ), - ], - ), - ), + child: _timeComponent(), ), ), ], @@ -200,4 +193,90 @@ class _ibus_displayState extends State { ), ); } +} + +class _timeComponent extends StatefulWidget { + + @override + State<_timeComponent> createState() => _timeComponentState(); +} + +class _timeComponentState extends State<_timeComponent> { + + late Timer timeTimer; + + String bottomLine = ""; + + @override + void initState() { + // TODO: implement initState + super.initState(); + + bottomLine = _getTime(); + + timeTimer = Timer.periodic(Duration(seconds: 1), (timer) { + if (mounted){ + setState(() { + bottomLine = _getTime(); + }); + } + }); + } + + @override + void dispose() { + // TODO: implement dispose + super.dispose(); + + timeTimer.cancel(); + } + + String _getTime(){ + // Get the current time HH:MM AM/PM + DateTime now = DateTime.now(); + + bool is24Hour = false; + + // if 16 then 4... + + String timeString = "${now.hour % 12}:${now.minute.toString().padLeft(2, "0")} ${now.hour < 12 ? "AM" : "PM"}"; + + return timeString; + } + + String _padString(String input){ + if (input.length < 30){ + print("Input is too short"); + return input; + } + + String prefix = ""; + String suffix = ""; + for (int i = 0; i < 80; i++){ + prefix += " "; + } + + input = input.replaceAll("©", "(c)"); + + return prefix + input + suffix; + } + + @override + Widget build(BuildContext context) { + return TextScroll( + _padString(bottomLine), + velocity: Velocity(pixelsPerSecond: Offset(120, 0)), + style: const TextStyle( + fontSize: 20, + color: Colors.orange, + fontFamily: "ibus", + shadows: [ + Shadow( + color: Colors.orange, + blurRadius: 5, + ), + ], + ), + ); + } } \ No newline at end of file diff --git a/lib/pages/home.dart b/lib/pages/home.dart index aa09c84..ff8951e 100644 --- a/lib/pages/home.dart +++ b/lib/pages/home.dart @@ -67,7 +67,7 @@ class pages_Home extends StatelessWidget { outlineColor: Colors.white70, onPressed: (){ LiveInformation liveInformation = LiveInformation(); - liveInformation.announcementModule.queueAnnounementByInfoIndex( + liveInformation.announcementModule.queueAnnouncementByInfoIndex( infoIndex: liveInformation.announcementModule.manualAnnouncements.indexOf(announcement), sendToServer: true ); diff --git a/lib/remaster/DashboardArc.dart b/lib/remaster/DashboardArc.dart new file mode 100644 index 0000000..aa90bd7 --- /dev/null +++ b/lib/remaster/DashboardArc.dart @@ -0,0 +1,484 @@ + +import 'package:bus_infotainment/backend/live_information.dart'; +import 'package:bus_infotainment/pages/components/ibus_display.dart'; +import 'package:bus_infotainment/remaster/dashboard.dart'; +import 'package:bus_infotainment/tfl_datasets.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_scroll_shadow/flutter_scroll_shadow.dart'; +import 'package:shadcn_ui/shadcn_ui.dart'; + +class ArcDashboard extends StatefulWidget { + + @override + State createState() => _ArcDashboardState(); +} + +class _ArcDashboardState extends State { + _closeDialogueChecker closeDialogWidget = _closeDialogueChecker(); + + @override + Widget build(BuildContext context) { + + // Force landscape mode + Future.delayed(Duration(seconds: 1), () { + SystemChrome.setPreferredOrientations([ + DeviceOrientation.landscapeRight, + DeviceOrientation.landscapeLeft, + ]); + }); + + return Scaffold( + + body: Container( + child: Row( + + children: [ + + const SizedBox( + width: 10, + ), + + Container( + padding: const EdgeInsets.symmetric( + vertical: 10, + ), + + child: IntrinsicWidth( + child: Column( + children: [ + + if (LiveInformation().roomDocumentID != null) + Tooltip( + child: ShadButton( + icon: const Icon(Icons.network_check), + width: double.infinity, + onPressed: () { + showShadSheet( + context: context, + builder: (context) { + return ShadSheet( + padding: const EdgeInsets.all(10), + content: Column( + children: [ + Text("Room ID: ${LiveInformation().roomDocumentID}"), + ], + ), + ); + } + ); + }, + ), + message: "Room information", + ), + + SizedBox( + height: 220, + child: RotatedBox( + quarterTurns: 3, + child: Column( + children: [ + ShadButton( + text: const Text("Manual Announcements"), + width: double.infinity, + borderRadius: const BorderRadius.all(Radius.circular(10)), + onPressed: () { + + List announcements = []; + + for (var announcement in LiveInformation().announcementModule.manualAnnouncements) { + + announcements.add( + ShadButton( + text: SizedBox( + width: 200-42, + child: Text(announcement.shortName), + ), + onPressed: () { + + if (closeDialogWidget.closeDialog) { + Navigator.pop(context); + } + + LiveInformation().announcementModule.queueAnnouncementByInfoIndex( + infoIndex: LiveInformation().announcementModule.manualAnnouncements.indexOf(announcement), + ); + }, + ) + ); + + } + + print(announcements.length); + + showShadSheet( + context: context, + side: ShadSheetSide.left, + + builder: (context) { + return ShadSheet( + padding: const EdgeInsets.all(0), + + content: Container( + height: MediaQuery.of(context).size.height, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: 5, + ), + Container( + padding: const EdgeInsets.symmetric( + vertical: 10, + ), + alignment: Alignment.bottomCenter, + height: double.infinity, + width: 35, + child: RotatedBox( + quarterTurns: 3, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Text( + "Manual Ann'", + style: ShadTheme.of(context).textTheme.h3 + ), + SizedBox( + width: 16, + ), + Container( + width: 1, + height: 200, + color: Colors.grey, + ), + ], + ), + + closeDialogWidget + + ], + ), + ), + ), + Container( + + // width: 220, + height: MediaQuery.of(context).size.height, + + child: Scrollbar( + thumbVisibility: true, + child: SingleChildScrollView( + reverse: true, + child: Container( + margin: const EdgeInsets.fromLTRB( + 0, + 10, + 10, + 10 + ), + child: Column( + children: announcements.reversed.toList(), + ), + ), + ), + ), + ), + ], + ), + ), + ); + } + ); + + }, + ), + ShadButton( + text: const Text("Bus Stop Announcements"), + width: double.infinity, + borderRadius: const BorderRadius.all(Radius.circular(10)), + onPressed: () { + + showShadSheet( + context: context, + side: ShadSheetSide.left, + + builder: (context) { + + List announcements = []; + + LiveInformation info = LiveInformation(); + + for (var busStop in info.getRouteVariant()!.busStops) { + + if (info.trackerModule.nearestStop == busStop) { + announcements.add( + ShadButton( + text: SizedBox( + width: 200-42, + child: Text( + "-> ${busStop.formattedStopName}", + overflow: TextOverflow.ellipsis, + ), + ), + backgroundColor: Colors.amber, + onPressed: () { + if (closeDialogWidget.closeDialog) { + Navigator.pop(context); + } + LiveInformation().announcementModule.queueAnnounceByAudioName( + displayText: busStop.formattedStopName, + audioNames: [busStop.getAudioFileName()], + ); + }, + ) + ); + } else { + announcements.add( + ShadButton( + text: SizedBox( + width: 200-42, + child: Text( + busStop.formattedStopName, + overflow: TextOverflow.ellipsis, + ), + ), + onPressed: () { + if (closeDialogWidget.closeDialog) { + Navigator.pop(context); + } + LiveInformation().announcementModule.queueAnnounceByAudioName( + displayText: busStop.formattedStopName, + audioNames: [busStop.getAudioFileName()], + ); + }, + ) + ); + } + } + + ScrollController controller = ScrollController(); + + // Scroll to the current bus stop + WidgetsBinding.instance!.addPostFrameCallback((_) { + + double offset = (info.getRouteVariant()!.busStops.indexOf(info.trackerModule.nearestStop!) * 50); + + // Offset the offset so that its in the middle of the screen + offset -= (MediaQuery.of(context).size.height / 2) - 25; + + // controller.jumpTo(offset); + }); + + return ShadSheet( + padding: const EdgeInsets.all(0), + + content: Container( + height: MediaQuery.of(context).size.height, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: 5, + ), + Container( + padding: const EdgeInsets.symmetric( + vertical: 10, + ), + alignment: Alignment.bottomCenter, + height: double.infinity, + width: 35, + child: RotatedBox( + quarterTurns: 3, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Text( + "Bus Stops", + style: ShadTheme.of(context).textTheme.h3 + ), + SizedBox( + width: 16, + ), + Container( + width: 1, + height: 200, + color: Colors.grey, + ), + ], + ), + + closeDialogWidget + + ], + ), + ), + ), + Container( + + // width: 220, + height: MediaQuery.of(context).size.height, + + child: Scrollbar( + thumbVisibility: true, + controller: controller, + child: SingleChildScrollView( + reverse: true, + controller: controller, + child: Container( + margin: const EdgeInsets.fromLTRB( + 0, + 10, + 10, + 10 + ), + child: Column( + children: announcements.reversed.toList(), + ), + ), + ), + ), + ), + ], + ), + ), + ); + } + ); + + }, + ), + ], + ) + ) + ), + + const ShadButton( + icon: Icon(Icons.stop), + width: double.infinity, + ), + + ShadButton( + // text: const Text("Announce Destination"), + icon: const Icon(Icons.location_on), + width: double.infinity, + borderRadius: const BorderRadius.all(Radius.circular(10)), + onPressed: () { + LiveInformation info = LiveInformation(); + + BusRouteVariant? routeVariant = info.getRouteVariant(); + + if (routeVariant != null) { + info.announcementModule.queueAnnouncementByRouteVariant( + routeVariant: routeVariant, + sendToServer: false + ); + } + + }, + ) + + ], + ), + ), + + ), + + Expanded( + child: Container( + + decoration: const BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.all(Radius.circular(10)), + + ), + + margin: const EdgeInsets.all(10), + padding: const EdgeInsets.all(10), + + width: double.infinity, + height: double.infinity, + + child: Stack( + children: [ + Container( + + alignment: Alignment.center, + + child: ibus_display( + hasBorder: false, + ), + + ), + Container( + + alignment: Alignment.bottomRight, + + child: ShadButton.ghost( + icon: const Icon(Icons.fullscreen), + padding: const EdgeInsets.all(8), + onPressed: () { + Navigator.pushNamed(context, '/display'); + }, + ), + + ), + Container( + + alignment: Alignment.bottomLeft, + + child: ShadButton.ghost( + icon: const Icon(Icons.arrow_back), + padding: const EdgeInsets.all(8), + onPressed: () { + Navigator.pop(context); + }, + ), + + ) + ], + ), + ), + ) + + ], + + ), + ), + + ); + + } +} + +class _closeDialogueChecker extends StatefulWidget { + + bool closeDialog = false; + + @override + State<_closeDialogueChecker> createState() => _closeDialogueCheckerState(); +} + +class _closeDialogueCheckerState extends State<_closeDialogueChecker> { + + + @override + Widget build(BuildContext context) { + // TODO: implement build + return ShadSwitch( + value: widget.closeDialog, + enabled: true, + label: const Text("Close Dialog?"), + onChanged: (value) { + widget.closeDialog = value; + setState(() { + + }); + + }, + ); + } +} \ No newline at end of file diff --git a/lib/remaster/RemasteredMain.dart b/lib/remaster/RemasteredMain.dart index 694337f..d2709bc 100644 --- a/lib/remaster/RemasteredMain.dart +++ b/lib/remaster/RemasteredMain.dart @@ -2,6 +2,7 @@ import 'package:bus_infotainment/pages/tfl_dataset_test.dart'; +import 'package:bus_infotainment/remaster/DashboardArc.dart'; import 'package:bus_infotainment/remaster/InitialStartup.dart'; import 'package:bus_infotainment/remaster/dashboard.dart'; import 'package:flutter/material.dart'; @@ -15,7 +16,12 @@ class RemasteredApp extends StatelessWidget { return ShadApp( darkTheme: ShadThemeData( brightness: Brightness.dark, - colorScheme: ShadSlateColorScheme.dark(), + colorScheme: ShadSlateColorScheme.dark( + background: Colors.grey.shade900, + primary: Colors.grey.shade50, + primaryForeground: Colors.grey.shade900, + border: Colors.grey.shade900, + ), // force dark mode ), themeMode: ThemeMode.dark, @@ -24,10 +30,10 @@ class RemasteredApp extends StatelessWidget { '/setup': (context) => InitialStartup(), '/': (context) => HomePage_Re(), '/routes': (context) => RoutePage(), - '/enroute': (context) => EnRoutePage(), + '/enroute': (context) => ArcDashboard(), '/legacy': (context) => TfL_Dataset_Test(), '/multi': (context) => MultiModeSetup(), - '/multi/enroute': (context) => MultiModeEnroute(), + '/multi/enroute': (context) => ArcDashboard(), '/multi/login': (context) => MultiModeLogin(), '/multi/register': (context) => MultiModeRegister(), '/display': (context) => FullscreenDisplay(), diff --git a/lib/remaster/dashboard.dart b/lib/remaster/dashboard.dart index a1c9b2e..310b6b4 100644 --- a/lib/remaster/dashboard.dart +++ b/lib/remaster/dashboard.dart @@ -733,7 +733,7 @@ class EasyAnnouncementPicker extends StatelessWidget { label: entry.shortName, onPressed: () { LiveInformation liveInformation = LiveInformation(); - liveInformation.announcementModule.queueAnnounementByInfoIndex( + liveInformation.announcementModule.queueAnnouncementByInfoIndex( infoIndex: liveInformation.announcementModule.manualAnnouncements.indexOf(entry), sendToServer: true ); @@ -1431,6 +1431,10 @@ class _FullscreenDisplayState extends State { @override Widget build(BuildContext context) { + // Get the current screen orientation + final Orientation orientation = MediaQuery.of(context).orientation; + + // Make the screen landscape SystemChrome.setPreferredOrientations([ DeviceOrientation.landscapeRight, @@ -1440,9 +1444,18 @@ class _FullscreenDisplayState extends State { return PopScope( onPopInvoked: (isPop) { if (isPop) { - SystemChrome.setPreferredOrientations([ + // SystemChrome.setPreferredOrientations([ + // DeviceOrientation.portraitUp, + // DeviceOrientation.portraitDown + // ]); + + // Set the orientation back to whatever it was before + SystemChrome.setPreferredOrientations(orientation == Orientation.portrait ? [ DeviceOrientation.portraitUp, DeviceOrientation.portraitDown + ] : [ + DeviceOrientation.landscapeRight, + DeviceOrientation.landscapeLeft ]); } }, @@ -1460,12 +1473,17 @@ class _FullscreenDisplayState extends State { hasBorder: false, ), ), - ShadButton.ghost( - icon: const Icon(Icons.arrow_back), - padding: const EdgeInsets.all(8), - onPressed: () { - Navigator.pop(context); - }, + Container( + + alignment: Alignment.bottomRight, + + child: ShadButton.ghost( + icon: const Icon(Icons.arrow_back), + padding: const EdgeInsets.all(8), + onPressed: () { + Navigator.pop(context); + }, + ), ) ], ), diff --git a/pubspec.lock b/pubspec.lock index 11f2fef..48a77aa 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -105,14 +105,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" - boxy: - dependency: transitive - description: - name: boxy - sha256: eaa774ff591191b86f2eb8e8eae878aec50604404b74388a27e655cf3aadf758 - url: "https://pub.dev" - source: hosted - version: "2.2.0" characters: dependency: transitive description: @@ -307,6 +299,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.19" + flutter_scroll_shadow: + dependency: "direct main" + description: + name: flutter_scroll_shadow + sha256: c0509c642c5077654301fab1fb2260adc94c82a407c60e64162974b4366e7874 + url: "https://pub.dev" + source: hosted + version: "1.2.4" flutter_shaders: dependency: transitive description: @@ -537,10 +537,10 @@ packages: dependency: transitive description: name: lucide_icons_flutter - sha256: "43b11fa16f243529c538bcb4a1af014c03c177056c0d46039bc4f665320428c6" + sha256: "6126f30f3236acd7744f8535c2756322e72e96e00f5a94c83afde4abcc917bba" url: "https://pub.dev" source: hosted - version: "1.0.8" + version: "1.0.9" matcher: dependency: transitive description: @@ -777,18 +777,18 @@ packages: dependency: transitive description: name: rive - sha256: "95690a0fb4f6e195c53b217ab3cc0e0b0f443c670adbdee9d57d636a36b82b18" + sha256: "255ab7892a77494458846cecee1376a017e64fd6b4130b51ec21424d12ffa6fe" url: "https://pub.dev" source: hosted - version: "0.13.2" + version: "0.13.4" rive_common: dependency: transitive description: name: rive_common - sha256: "3eee68fcab3e0882090cea5a8cf7acea7967f469a34a2580322575603b094435" + sha256: "3a0d95f529d52caef535d8ff32d75629ca37f7ab4707b13c83e9552a322557bc" url: "https://pub.dev" source: hosted - version: "0.4.5" + version: "0.4.8" rxdart: dependency: transitive description: @@ -809,10 +809,10 @@ packages: dependency: "direct main" description: name: shadcn_ui - sha256: df9aedbd18bd60160c1c54717eef47fe4f90422f843070ccbc32786193803f7d + sha256: d0dce618cbceea8fa96cb58d0ae84dc4fef7f1d95f8838123736ef0a88868473 url: "https://pub.dev" source: hosted - version: "0.4.1" + version: "0.4.6" shared_preferences: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 05acc91..fb6da3f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -55,7 +55,7 @@ dependencies: dart_ping: ^9.0.1 native_qr: ^0.0.3 qr_flutter: ^4.1.0 - + flutter_scroll_shadow: ^1.2.4 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons.