This commit is contained in:
ImBenji
2024-05-17 17:38:37 +01:00
6 changed files with 5734 additions and 358 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -39,7 +39,7 @@ class LiveInformation {
// By default, load the bus sequences from the assets
print("Loading bus sequences from assets");
busSequences = BusSequences.fromCSV(
await rootBundle.loadString("assets/datasets/bus-blinds.csv"),
await rootBundle.loadString("assets/datasets/destinations.json"),
await rootBundle.loadString("assets/datasets/bus-sequences.csv")
);
print("Loaded bus sequences from assets");
@@ -49,7 +49,7 @@ class LiveInformation {
http.Response response = await http.get(Uri.parse('https://tfl.gov.uk/bus-sequences.csv'));
busSequences = BusSequences.fromCSV(
await rootBundle.loadString("assets/datasets/bus-blinds.csv"),
await rootBundle.loadString("assets/datasets/destinations.json"),
response.body
);
@@ -294,7 +294,7 @@ class LiveInformation {
print("Created room with code $roomCode");
}
Future<void> JoinRoom(String roomCode) async {
Future<void> joinRoom(String roomCode) async {
print("Joining room with code $roomCode");
// Disable host mode
@@ -375,6 +375,45 @@ class LiveInformation {
print("Joined room with code $roomCode");
}
Future<void> leaveRoom() async {
if (roomCode == null) {
throw Exception("Not in a room");
}
if (isHost) {
// Access the database
final client = auth.client;
final databases = appwrite.Databases(client);
// Remove any existing documents
final existingDocuments = await databases.listDocuments(
databaseId: "6633e85400036415ab0f",
collectionId: "6633e85d0020f52f3771",
queries: [
appwrite.Query.search("SessionID", roomCode!)
]
);
for (var document in existingDocuments.documents) {
await databases.deleteDocument(
databaseId: "6633e85400036415ab0f",
collectionId: "6633e85d0020f52f3771",
documentId: document.$id
);
}
}
roomCode = null;
roomDocumentID = null;
isHost = false;
_keepAliveConnection?.close();
_keepAliveConnection = null;
// Reset stuff
setRouteVariant(null);
}
String? lastCommand;
Future<void> ServerListener(appwrite.RealtimeMessage response) async {
print("Session update");

View File

@@ -175,7 +175,7 @@ class AnnouncementModule extends InfoModule {
bool sendToServer = true
}) async {
if (sendToServer) {
if (sendToServer && _shouldSendToServer()) {
scheduledTime ??= liveInformation.syncedTimeModule.Now().add(Duration(seconds: defaultAnnouncementDelay));
@@ -230,7 +230,7 @@ class AnnouncementModule extends InfoModule {
bool sendToServer = true
}) {
if (sendToServer) {
if (sendToServer && _shouldSendToServer()) {
scheduledTime ??= liveInformation.syncedTimeModule.Now().add(Duration(seconds: defaultAnnouncementDelay));
@@ -260,7 +260,7 @@ class AnnouncementModule extends InfoModule {
bool sendToServer = true
}) async {
if (sendToServer) {
if (sendToServer && _shouldSendToServer()) {
scheduledTime ??= liveInformation.syncedTimeModule.Now().add(Duration(seconds: defaultAnnouncementDelay));
@@ -324,6 +324,14 @@ class AnnouncementModule extends InfoModule {
queue.add(announcement);
}
// Server check
bool _shouldSendToServer() {
bool condition = liveInformation.roomCode != null;
print("Should send to server? " + (condition.toString()));
return condition;
}
// Constants
final List<NamedAnnouncementQueueEntry> manualAnnouncements = [

View File

@@ -231,7 +231,7 @@ class RoutePage extends StatelessWidget {
],
),
if (!kIsWeb)
Text(
"Nearby routes",
style: ShadTheme.of(context).textTheme.h4,
@@ -315,23 +315,23 @@ class _RouteSearchState extends State<RouteSearch> {
return Expanded(
child: Column(
children: [
ShadInput(
placeholder: const Text("Search for a route..."),
controller: controller,
onChanged: (value) {
setState(() {
});
},
),
const SizedBox(
height: 4,
),
Expanded(
child: Scrollbar(
interactive: true,
@@ -347,7 +347,7 @@ class _RouteSearchState extends State<RouteSearch> {
),
),
)
],
),
);
@@ -649,11 +649,23 @@ class _dash extends StatelessWidget {
],
),
),
)
),
// SizedBox(
// height: 8,
// ),
SizedBox(
height: 8,
),
Container(
padding: const EdgeInsets.all(8),
child: ShadButton(
text: const Text("Fullscreen display"),
onPressed: () {
Navigator.pushNamed(context, "/display");
},
icon: const Icon(Icons.fullscreen),
width: double.infinity,
),
),
//
// ShadCard(
// title: Text("Stop announcements"),
@@ -961,199 +973,83 @@ class _MultiModeEnrouteState extends State<MultiModeEnroute> {
LiveInformation liveInformation = LiveInformation();
return Scaffold(
return PopScope(
canPop: false,
onPopInvoked: (didPop) {
body: Column(
children: [
if (didPop){
print("Compensating for pop");
liveInformation.leaveRoom();
return;
}
Container(
padding: const EdgeInsets.all(16),
height: 200,
child: ShadCard(
title: liveInformation.isHost ? const Text("Hosting group") : const Text("Joined group"),
border: Border.all(
color: Colors.amber,
width: 1
),
padding: const EdgeInsets.all(16),
width: double.infinity,
description: liveInformation.isHost ? const Text(
"You are hosting a group. \nShare the room code with others to join"
) : const Text(
"You have joined a group."
),
content: Column(
children: [
const SizedBox(
height: 4,
),
FutureBuilder(
future: Future.delayed(const Duration(seconds: 1)),
builder: (context, snapshot) {
return Row(
children: [
Expanded(
child: ShadButton(
text: Text(
liveInformation.roomCode!,
),
icon: const Icon(Icons.copy),
padding: const EdgeInsets.all(8),
onPressed: () {
Clipboard.setData(ClipboardData(text: liveInformation.roomCode!));
ShadToaster.of(context).show(
const ShadToast(
title: Text("Copied to clipboard"),
description: Text("Room code copied to clipboard"),
duration: Duration(seconds: 5),
)
);
},
),
),
ShadButton(
icon: const Icon(Icons.qr_code),
onPressed: () {
showShadDialog(
context: context,
builder: (context) {
return ShadDialog(
title: const Text("QR Code"),
content: Container(
width: 200,
height: 225,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
QrImageView(
data: liveInformation.roomCode!,
size: 200,
backgroundColor: Colors.white,
),
const SizedBox(
height: 8,
),
Text("Scan QR code to join the group")
],
),
),
actions: [
ShadButton(
text: const Text("Close"),
onPressed: () {
Navigator.pop(context);
},
)
],
);
}
);
},
)
],
);
// return;
// Ask the user to confirm if they want to leave the group
showShadDialog(
context: context,
builder: (context) {
return ShadDialog(
title: const Text("Leave group?"),
content: const Text("Are you sure you want to leave the group?"),
actions: [
ShadButton(
text: const Text("Leave"),
onPressed: () {
liveInformation.leaveRoom();
Navigator.pop(context);
Navigator.pop(context);
},
),
ShadButton(
text: const Text("Cancel"),
onPressed: () {
Navigator.pop(context);
},
)
],
);
}
);
},
child: Scaffold(
body: Column(
children: [
const Divider(
height: 1,
),
Container(
padding: EdgeInsets.all(8),
child: ibus_display()
),
const Divider(
height: 1,
),
Container(
padding: EdgeInsets.all(8),
child: Text(
"* Swipe left and right below for more options!",
textAlign: TextAlign.center,
),
),
),
const Divider(
height: 1,
),
Container(
padding: EdgeInsets.all(8),
child: ibus_display()
),
const Divider(
height: 1,
),
Container(
padding: EdgeInsets.all(8),
child: Text(
"* Swipe left and right below for more options!",
textAlign: TextAlign.center,
const Divider(
height: 1,
),
),
const Divider(
height: 1,
),
SizedBox(
height: 16,
),
SizedBox(
height: 16,
),
Expanded(
child: FlutterCarousel(
items: [
Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.white,
width: 1
),
borderRadius: BorderRadius.circular(8)
),
padding: const EdgeInsets.all(4),
margin: const EdgeInsets.only(
left: 16,
right: 16,
),
width: double.infinity,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.only(
top: 4,
left: 8,
right: 8,
bottom: 4
),
child: Text(
"Nearby routes",
style: ShadTheme.of(context).textTheme.h4,
),
),
if (!kIsWeb)
Expanded(
child: Scrollbar(
interactive: true,
radius: const Radius.circular(8),
thickness: 8,
thumbVisibility: true,
child: GridView.count(
crossAxisCount: 3,
children: [
..._getNearbyRoutes(multiMode: true)
],
shrinkWrap: true,
),
),
)
],
)
),
Container(
Expanded(
child: FlutterCarousel(
items: [
Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.white,
@@ -1166,160 +1062,321 @@ class _MultiModeEnrouteState extends State<MultiModeEnroute> {
left: 16,
right: 16,
),
width: double.infinity,
child: Expanded(child: RouteSearch(multiMode: true,))
),
Container(
decoration: BoxDecoration(
border: Border.all(
width: 1,
color: Colors.white
),
borderRadius: BorderRadius.all(Radius.circular(8))
),
margin: const EdgeInsets.only(
left: 16,
right: 16,
),
padding: EdgeInsets.all(8),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
EasyAnnouncementPicker(
announcements: LiveInformation().announcementModule.manualAnnouncements,
title: "Manual",
outlineColor: ShadTheme.of(context).colorScheme.secondary
),
if (liveInformation.getRouteVariant() != null)
SizedBox(
height: 16,
if (!kIsWeb)
Container(
padding: const EdgeInsets.only(
top: 4,
left: 8,
right: 8,
bottom: 4
),
if (liveInformation.getRouteVariant() != null)
Container(
child: StopAnnouncementPicker(
routeVariant: LiveInformation().getRouteVariant()!,
backgroundColor: Colors.transparent,
outlineColor: ShadTheme.of(context).colorScheme.secondary,
label: "Bus Stops",
)
child: Text(
"Nearby routes",
style: ShadTheme.of(context).textTheme.h4,
),
),
if (!kIsWeb)
Expanded(
child: Scrollbar(
interactive: true,
radius: const Radius.circular(8),
thickness: 8,
thumbVisibility: true,
child: GridView.count(
crossAxisCount: 3,
children: [
..._getNearbyRoutes(multiMode: true)
],
shrinkWrap: true,
),
),
)
],
)
),
Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.white,
width: 1
),
borderRadius: BorderRadius.circular(8)
),
padding: const EdgeInsets.all(4),
margin: const EdgeInsets.only(
left: 16,
right: 16,
),
child: Expanded(child: RouteSearch(multiMode: true,))
),
Container(
decoration: BoxDecoration(
border: Border.all(
width: 1,
color: Colors.white
),
borderRadius: BorderRadius.all(Radius.circular(8))
),
margin: const EdgeInsets.only(
left: 16,
right: 16,
),
padding: EdgeInsets.all(8),
child: SingleChildScrollView(
child: Column(
children: [
EasyAnnouncementPicker(
announcements: LiveInformation().announcementModule.manualAnnouncements,
title: "Manual",
outlineColor: ShadTheme.of(context).colorScheme.secondary
),
if (liveInformation.getRouteVariant() != null)
SizedBox(
height: 16,
),
if (liveInformation.getRouteVariant() != null)
Container(
child: StopAnnouncementPicker(
routeVariant: LiveInformation().getRouteVariant()!,
backgroundColor: Colors.transparent,
outlineColor: ShadTheme.of(context).colorScheme.secondary,
label: "Bus Stops",
)
)
],
),
),
),
),
Container(
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: ShadTheme.of(context).colorScheme.primary,
width: 1
)
),
margin: const EdgeInsets.only(
left: 16,
right: 16,
),
padding: const EdgeInsets.all(4),
child: Container(
Container(
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(4),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: ShadTheme.of(context).colorScheme.primary,
width: 1
)
),
child: Column(
children: [
margin: const EdgeInsets.only(
left: 16,
right: 16,
),
padding: const EdgeInsets.all(4),
child: Container(
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: ShadTheme.of(context).colorScheme.primary,
width: 1
)
),
child: Column(
children: [
if (!kIsWeb)
AnnouncementEntry(
label: "Display next stop",
index: 0,
outlineColor: ShadTheme.of(context).colorScheme.primary,
onPressed: () {
LiveInformation liveInformation = LiveInformation();
TrackerModule trackerModule = liveInformation.trackerModule;
BusRouteStop? stop = trackerModule.nearestStop;
if (stop != null) {
liveInformation.announcementModule.queueAnnounceByAudioName(displayText: stop.formattedStopName);
} else {
ShadToaster.of(context).show(
const ShadToast(
title: Text("No bus stop found"),
description: Text("No bus stop found nearby"),
duration: Duration(seconds: 5),
)
);
}
},
),
if (!kIsWeb)
Container(
height: 1,
color: ShadTheme.of(context).colorScheme.primary,
),
if (!kIsWeb)
AnnouncementEntry(
label: "Display next stop",
index: 0,
label: "Announce destination",
index: 1,
outlineColor: ShadTheme.of(context).colorScheme.primary,
onPressed: () {
LiveInformation liveInformation = LiveInformation();
TrackerModule trackerModule = liveInformation.trackerModule;
BusRouteStop? stop = trackerModule.nearestStop;
if (stop != null) {
liveInformation.announcementModule.queueAnnounceByAudioName(displayText: stop.formattedStopName);
} else {
ShadToaster.of(context).show(
const ShadToast(
title: Text("No bus stop found"),
description: Text("No bus stop found nearby"),
duration: Duration(seconds: 5),
)
);
}
liveInformation.announcementModule.queueAnnouncementByRouteVariant(
routeVariant:
liveInformation.getRouteVariant()!
);
},
),
if (!kIsWeb)
Container(
height: 1,
color: ShadTheme.of(context).colorScheme.primary,
),
AnnouncementEntry(
label: "Announce destination",
index: 1,
outlineColor: ShadTheme.of(context).colorScheme.primary,
onPressed: () {
LiveInformation liveInformation = LiveInformation();
liveInformation.announcementModule.queueAnnouncementByRouteVariant(
routeVariant:
liveInformation.getRouteVariant()!
);
},
),
Container(
height: 1,
color: ShadTheme.of(context).colorScheme.primary,
),
],
Container(
height: 1,
color: ShadTheme.of(context).colorScheme.primary,
),
],
),
),
),
],
options: CarouselOptions(
showIndicator: false,
viewportFraction: 1,
height: double.infinity,
enableInfiniteScroll: true
),
],
options: CarouselOptions(
showIndicator: false,
viewportFraction: 1,
height: double.infinity,
enableInfiniteScroll: true
),
),
),
Container(
padding: const EdgeInsets.all(8),
child: ShadButton(
text: const Text("Fullscreen display"),
onPressed: () {
Navigator.pushNamed(context, "/display");
},
icon: const Icon(Icons.fullscreen),
width: double.infinity,
Container(
padding: const EdgeInsets.all(8),
child: ShadButton(
text: const Text("Fullscreen display"),
onPressed: () {
Navigator.pushNamed(context, "/display");
},
icon: const Icon(Icons.fullscreen),
width: double.infinity,
),
),
const Divider(
height: 1,
),
),
const Divider(
height: 1,
),
NavigationBar()
],
),
Container(
padding: const EdgeInsets.all(16),
// height: 200,
child: ShadCard(
title: liveInformation.isHost ? const Text("Currently hosting group") : const Text("Successfully joined group"),
border: Border.all(
color: Colors.amber,
width: 1
),
padding: const EdgeInsets.all(16),
width: double.infinity,
description: liveInformation.isHost ? const Text(
"You are hosting a group. \nShare the room code with others to join"
) : const Text(
"You have joined a group."
),
content: Column(
children: [
const SizedBox(
height: 4,
),
FutureBuilder(
future: Future.delayed(const Duration(seconds: 1)),
builder: (context, snapshot) {
return Row(
children: [
Expanded(
child: ShadButton(
text: Text(
liveInformation.roomCode!,
),
icon: const Icon(Icons.copy),
padding: const EdgeInsets.all(8),
onPressed: () {
Clipboard.setData(ClipboardData(text: liveInformation.roomCode!));
ShadToaster.of(context).show(
const ShadToast(
title: Text("Copied to clipboard"),
description: Text("Room code copied to clipboard"),
duration: Duration(seconds: 5),
)
);
},
),
),
ShadButton(
icon: const Icon(Icons.qr_code),
onPressed: () {
showShadDialog(
context: context,
builder: (context) {
return ShadDialog(
title: const Text("QR Code"),
content: Container(
width: 200,
height: 225,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
QrImageView(
data: liveInformation.roomCode!,
size: 200,
backgroundColor: Colors.white,
),
const SizedBox(
height: 8,
),
Text("Scan QR code to join the group")
],
),
),
actions: [
ShadButton(
text: const Text("Close"),
onPressed: () {
Navigator.pop(context);
},
)
],
);
}
);
},
)
],
);
},
),
],
),
),
),
const Divider(
height: 1,
),
NavigationBar()
],
),
),
);
}
@@ -1377,7 +1434,7 @@ class _MultiModeJoinState extends State<MultiModeJoin> {
liveInformation.setRouteVariant(null);
await liveInformation.JoinRoom(controller.text);
await liveInformation.joinRoom(controller.text);
Navigator.popAndPushNamed(context, "/multi/enroute");
@@ -1426,7 +1483,7 @@ class FullscreenDisplay extends StatefulWidget {
class _FullscreenDisplayState extends State<FullscreenDisplay> {
@override
Widget build(BuildContext context) {
@@ -1460,12 +1517,12 @@ class _FullscreenDisplayState extends State<FullscreenDisplay> {
}
},
child: Scaffold(
body: Container(
color: Colors.black,
alignment: Alignment.center,
child: Row(
children: [
Expanded(
@@ -1487,9 +1544,9 @@ class _FullscreenDisplayState extends State<FullscreenDisplay> {
)
],
),
),
),
);
}
@@ -1634,17 +1691,17 @@ class MultiModeRegister extends StatelessWidget {
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Register",
style: ShadTheme.of(context).textTheme.h2,
),
ShadForm(
key: formKey,
child: Column(
@@ -1703,7 +1760,7 @@ class MultiModeRegister extends StatelessWidget {
],
),
),
ShadButton(
text: const Text("Register"),
width: double.infinity,
@@ -1711,7 +1768,7 @@ class MultiModeRegister extends StatelessWidget {
if (formKey.currentState!.validate()) {
formKey.currentState!.save();
print("Logging in...");
LiveInformation liveInformation = LiveInformation();
await liveInformation.auth.createUser(
displayName: formKey.currentState!.value["email"],
@@ -1738,7 +1795,7 @@ class MultiModeRegister extends StatelessWidget {
}
},
),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
@@ -1752,9 +1809,9 @@ class MultiModeRegister extends StatelessWidget {
)
],
)
],
),
),
@@ -1773,6 +1830,11 @@ class MultiModeRegister extends StatelessWidget {
}
class NavigationBar extends StatefulWidget {
final Widget? content;
NavigationBar({this.content = null});
@override
State<NavigationBar> createState() => _NavigationBarState();
}
@@ -1825,7 +1887,7 @@ List<Widget> _getNearbyRoutes({bool multiMode = false}) {
if (kDebugMode) {
currentVector = OSGrid.toNorthingEasting(51.583781262560926, -0.020359583104595073);
// currentVector = OSGrid.toNorthingEasting(51.583781262560926, -0.020359583104595073);
}
for (BusRoute route in busSequences.routes.values) {

View File

@@ -1,5 +1,6 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:bus_infotainment/audio_cache.dart';
@@ -16,28 +17,24 @@ class BusSequences extends InfoModule {
Map<String, BusDestination> destinations = {};
BusSequences.fromCSV(String destinationsCSV, String busSequencesCSV) {
BusSequences.fromCSV(String destinationsJson, String busSequencesCSV) {
// Init the bus destinations
List<List<String>> destinationRows = CsvConverter().convert(destinationsCSV);
destinationRows.removeAt(0);
Map<String, dynamic> destinationData = jsonDecode(destinationsJson);
print("Destination rows: ${destinationRows.length}");
print("Destination rows: ${destinationData.length}");
for (int i = 0; i < destinationRows.length; i++) {
for (String destinationName in destinationData.keys) {
try {
Map<String, dynamic> destinationDetails = destinationData[destinationName];
List<dynamic> entries = destinationRows[i];
// print("Parsing destination row $i: $entries");
String blind = destinationName;
String routeNumber = entries[0].toString();
BusRoute route = routes.containsKey(routeNumber) ? routes[routeNumber]! : BusRoute(routeNumber: routeNumber);
String blind = entries[1].toString();
double lat = double.parse(entries[2].toString());
double long = double.parse(entries[3].toString());
List<String> location = destinationDetails['Location'].split(', ');
double lat = double.parse(location[0]);
double long = double.parse(location[1]);
Vector2 grid = OSGrid.toNorthingEasting(lat, long);
@@ -46,11 +43,10 @@ class BusSequences extends InfoModule {
destination.easting = grid.x;
destination.northing = grid.y;
route.destinations.add(destination);
routes[routeNumber] = route;
destinations[blind] = destination;
} catch (e) {}
} catch (e) {
print("Error parsing destination: $e");
}
}
print("Loaded ${destinations.length} destinations");

View File

@@ -97,6 +97,7 @@ flutter:
- assets/datasets/tube_stations.json
- assets/audio/rail_replacement/
- assets/audio/R_SPECIAL_SERVICE_001.mp3
- assets/datasets/destinations.json
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg