This commit is contained in:
ImBenji
2024-05-01 19:28:51 +01:00
parent fc4d3ef898
commit c2ebac5bb5
30 changed files with 4727 additions and 403 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,7 @@ import 'package:bus_infotainment/backend/modules/announcement.dart';
import 'package:bus_infotainment/backend/modules/commands.dart'; import 'package:bus_infotainment/backend/modules/commands.dart';
import 'package:bus_infotainment/backend/modules/synced_time.dart'; import 'package:bus_infotainment/backend/modules/synced_time.dart';
import 'package:bus_infotainment/backend/modules/tracker.dart'; import 'package:bus_infotainment/backend/modules/tracker.dart';
import 'package:bus_infotainment/backend/modules/tube_info.dart';
import 'package:bus_infotainment/tfl_datasets.dart'; import 'package:bus_infotainment/tfl_datasets.dart';
import 'package:bus_infotainment/utils/audio%20wrapper.dart'; import 'package:bus_infotainment/utils/audio%20wrapper.dart';
import 'package:bus_infotainment/utils/delegates.dart'; import 'package:bus_infotainment/utils/delegates.dart';
@@ -57,6 +58,12 @@ class LiveInformation {
print("Failed to load bus sequences from TFL. Using local copy."); print("Failed to load bus sequences from TFL. Using local copy.");
} }
// Load tube stations
print("Loading tube stations from assets");
tubeStations = TubeStations.fromJson(json.decode(await rootBundle.loadString("assets/datasets/tube_stations.json")));
print("Loaded tube stations from assets");
String sessionID = "test"; String sessionID = "test";
commandModule = CommandModule(sessionID); commandModule = CommandModule(sessionID);
@@ -87,6 +94,7 @@ class LiveInformation {
late AnnouncementModule announcementModule; late AnnouncementModule announcementModule;
late SyncedTimeModule syncedTimeModule; late SyncedTimeModule syncedTimeModule;
late TrackerModule trackerModule; late TrackerModule trackerModule;
late TubeStations tubeStations;
// Important variables // Important variables
BusRouteVariant? _currentRouteVariant; BusRouteVariant? _currentRouteVariant;

View File

@@ -1,14 +1,17 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:bus_infotainment/audio_cache.dart'; import 'package:bus_infotainment/audio_cache.dart';
import 'package:bus_infotainment/backend/live_information.dart'; import 'package:bus_infotainment/backend/live_information.dart';
import 'package:bus_infotainment/backend/modules/tube_info.dart';
import 'package:bus_infotainment/tfl_datasets.dart'; import 'package:bus_infotainment/tfl_datasets.dart';
import 'package:bus_infotainment/utils/audio%20wrapper.dart'; import 'package:bus_infotainment/utils/audio%20wrapper.dart';
import 'package:bus_infotainment/utils/delegates.dart'; import 'package:bus_infotainment/utils/delegates.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'info_module.dart'; import 'info_module.dart';
@@ -33,12 +36,26 @@ class AnnouncementModule extends InfoModule {
return _bundleBytes!; return _bundleBytes!;
} else { } else {
if (kIsWeb) { // Try to load them from shared preferences
throw Exception("Cannot load bundle bytes on web"); try {
SharedPreferences prefs = await SharedPreferences.getInstance();
String fileLocation = prefs.getString("AnnouncementsFileLocation")!;
File file = File(fileLocation);
setBundleBytes(file.readAsBytesSync());
return _bundleBytes!;
} catch (e) {
throw Exception("Loading announcements from assets has been deprecated.");
} }
final bytes = await rootBundle.load(_bundleLocation);
return bytes.buffer.asUint8List();
// if (kIsWeb) {
// throw Exception("Cannot load bundle bytes on web");
// }
//
// final bytes = await rootBundle.load(_bundleLocation);
// return bytes.buffer.asUint8List();
} }
} }
@@ -274,11 +291,21 @@ class AnnouncementModule extends InfoModule {
String audioRoute = "R_${routeVariant.busRoute.routeNumber}_001.mp3"; String audioRoute = "R_${routeVariant.busRoute.routeNumber}_001.mp3";
print("Checkpoint 5"); print("Checkpoint 5");
await announcementCache.loadAnnouncementsFromBytes(await getBundleBytes(), [audioRoute, "R_RAIL_REPLACEMENT_SERVICE_001.mp3"]); await announcementCache.loadAnnouncementsFromBytes(await getBundleBytes(), [audioRoute]);
print("Checkpoint 6"); print("Checkpoint 6");
AudioWrapperSource sourceRoute = !routeNumber.toLowerCase().startsWith("ul") ? AudioWrapperSource sourceRoute = !routeNumber.toLowerCase().startsWith("ul") ?
AudioWrapperByteSource(announcementCache[audioRoute]!) : AudioWrapperByteSource(announcementCache[audioRoute]!) :
AudioWrapperByteSource(announcementCache["R_RAIL_REPLACEMENT_SERVICE_001.mp3"]!); // AudioWrapperByteSource(announcementCache["R_RAIL_REPLACEMENT_SERVICE_001.mp3"]!);
AudioWrapperAssetSource("audio/R_RAIL_REPLACEMENT_SERVICE_001.mp3");
if (routeNumber.toLowerCase().startsWith("ul")) {
TubeLine? closestLine = liveInformation.tubeStations.getClosestLine(routeVariant);
sourceRoute = closestLine?.getAudio() ?? sourceRoute;
routeNumber = closestLine?.getShortName() ?? routeNumber;
}
print("Checkpoint 6.1"); print("Checkpoint 6.1");
AudioWrapperSource sourceDestination = AudioWrapperByteSource(await routeVariant.destination!.getAudioBytes()); AudioWrapperSource sourceDestination = AudioWrapperByteSource(await routeVariant.destination!.getAudioBytes());
print("Checkpoint 7"); print("Checkpoint 7");

View File

@@ -90,7 +90,9 @@ class TrackerModule extends InfoModule {
int stopIndex = liveInformation.getRouteVariant()!.busStops.indexOf(closestStop); int stopIndex = liveInformation.getRouteVariant()!.busStops.indexOf(closestStop);
closestStop = liveInformation.getRouteVariant()!.busStops[stopIndex + 1]; int maxStops = liveInformation.getRouteVariant()!.busStops.length;
closestStop = liveInformation.getRouteVariant()!.busStops[min(stopIndex + 1, maxStops)];
print("Closest stop is now: ${closestStop.formattedStopName}"); print("Closest stop is now: ${closestStop.formattedStopName}");
} else { } else {

View File

@@ -0,0 +1,133 @@
import 'package:bus_infotainment/utils/OrdinanceSurveyUtils.dart';
import 'package:bus_infotainment/utils/audio%20wrapper.dart';
import 'package:vector_math/vector_math.dart';
import '../../tfl_datasets.dart';
class TubeStations {
List<TubeStation> stations = [];
List<TubeLine> lines = [];
TubeStations.fromJson(Map<String, dynamic> json) {
/*
Example:
"Acton Town": {
"lines": [
"District",
"Piccadilly"
],
"location": {
"lat": 51.5029984,
"lng": -0.2803455
}
},
*/
for (String stationName in json.keys) {
TubeStation station = TubeStation();
station.name = stationName;
Map<String, dynamic> stationData = json[stationName];
station.latitude = stationData["location"]["lat"];
station.longitude = stationData["location"]["lng"];
for (String lineName in stationData["lines"]) {
// check if the line already exists
TubeLine line = lines.firstWhere((element) => element.name == lineName, orElse: () => TubeLine()..name = lineName);
if (!lines.contains(line)) {
lines.add(line);
}
station.lines.add(line);
}
stations.add(station);
}
}
// Get the london underground line that is a close match to a given bus route variant
TubeLine? getClosestLine(BusRouteVariant variant) {
Map<TubeLine, int> lineMatches = {};
for (TubeLine line in lines) {
lineMatches[line] = 0;
}
for (BusRouteStop stop in variant.busStops) {
for (TubeStation station in stations) {
// get the distance between the bus stop and the tube station
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) {
for (TubeLine line in station.lines) {
lineMatches[line] = lineMatches[line]! + 1;
}
}
}
}
TubeLine? closestLine;
int closestMatch = 0;
for (TubeLine line in lineMatches.keys) {
if (lineMatches[line]! > closestMatch) {
closestLine = line;
closestMatch = lineMatches[line]!;
}
}
return closestLine;
}
}
class TubeStation {
late final String name;
late final double latitude;
late final double longitude;
List<TubeLine> lines = [];
}
class TubeLine {
late final String name;
AudioWrapperSource? getAudio() {
String lineName = name.toLowerCase().replaceAll(" ", "_");
lineName = lineName.replaceAll("and", "N");
lineName = lineName.toUpperCase();
return AudioWrapperAssetSource("audio/rail_replacement/$lineName.mp3");
}
// Get short name
// example: "District" -> "DIS", "London Overground" -> "LO", "TfL Rail" -> "TFL", "Waterloo and City" -> "W&C"
String getShortName() {
String shortName = name;
List<String> words = shortName.split(" ");
shortName = words.map((e) => e[0]).join("");
if (shortName.length < 3) {
shortName = name.substring(0, 3).toUpperCase();
}
return shortName;
}
}

View File

@@ -88,7 +88,6 @@ class MyApp extends StatelessWidget {
// '/': (context) => TfL_Dataset_Test(), // '/': (context) => TfL_Dataset_Test(),
'/': (context) => remaster.InitialStartup(), '/': (context) => remaster.InitialStartup(),
'/application': (context) => TfL_Dataset_Test(), '/application': (context) => TfL_Dataset_Test(),
'/dashboard': (context) => Dashboard(),
}, },
); );

View File

@@ -12,6 +12,7 @@ import 'package:flutter/widgets.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:shadcn_ui/shadcn_ui.dart'; import 'package:shadcn_ui/shadcn_ui.dart';
import 'package:shared_preferences/shared_preferences.dart';
class InitialStartup extends StatefulWidget { class InitialStartup extends StatefulWidget {
@@ -324,6 +325,8 @@ class _page3 extends InitialStartupPage {
class _page3State extends State<_page3> { class _page3State extends State<_page3> {
bool _loadingAudio = false;
Future<bool> _announcementsUploaded() async { Future<bool> _announcementsUploaded() async {
try { try {
Uint8List bytes = await LiveInformation().announcementModule.getBundleBytes(); Uint8List bytes = await LiveInformation().announcementModule.getBundleBytes();
@@ -393,6 +396,7 @@ class _page3State extends State<_page3> {
height: 4, height: 4,
), ),
if (!_loadingAudio)
FutureBuilder( FutureBuilder(
future: _announcementsUploaded(), future: _announcementsUploaded(),
builder: (context, val) { builder: (context, val) {
@@ -412,6 +416,10 @@ class _page3State extends State<_page3> {
text: Text(text), text: Text(text),
onPressed: () async { onPressed: () async {
setState(() {
_loadingAudio = true;
});
FilePickerResult? result = await FilePicker.platform.pickFiles( FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.custom, type: FileType.custom,
allowedExtensions: [ allowedExtensions: [
@@ -419,6 +427,8 @@ class _page3State extends State<_page3> {
] ]
); );
if (result != null) { if (result != null) {
late Uint8List bytes; late Uint8List bytes;
@@ -434,13 +444,25 @@ class _page3State extends State<_page3> {
AnnouncementCache cache = LiveInformation().announcementModule.announcementCache; AnnouncementCache cache = LiveInformation().announcementModule.announcementCache;
if (!kIsWeb) {
// Use shared preferences to store the file location
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString("AnnouncementsFileLocation", result.files.single.path!);
}
// load a random announcement to ensure that the file is usable // load a random announcement to ensure that the file is usable
await cache.loadAnnouncementsFromBytes(bytes, ["S_WALTHAMSTOW_CENTRAL_001.mp3"]); await cache.loadAnnouncementsFromBytes(bytes, ["S_WALTHAMSTOW_CENTRAL_001.mp3"]);
setState(() { setState(() {
_loadingAudio = false;
}); });
} else {
setState(() {
_loadingAudio = false;
});
} }
@@ -450,11 +472,62 @@ class _page3State extends State<_page3> {
); );
}, },
) )
else
CircularProgressIndicator(),
], ],
), ),
), ),
), ),
SizedBox(
height: 16,
),
FutureBuilder(
future: _announcementsUploaded(),
builder: (context, val) {
bool isEnabled = true;
String text = "Continue";
Color color = Colors.white;
if (val.hasData) {
isEnabled = val.data!;
}
if (!isEnabled) {
text = "Complete the prerequisites to continue";
}
return ShadButton(
text: Text(text),
onPressed: () async {
showShadDialog(
context: context,
builder: (context) => ShadDialog.alert(
title: Text("You're all setup"),
description: Text("You can now continue to the application. \nTry not to annoy anyone on the bus! ;)"),
actions: [
ShadButton(
text: Text("Continue to application"),
onPressed: () {
Navigator.pushNamed(context, "/");
},
)
],
),
barrierDismissible: false
);
},
enabled: isEnabled,
backgroundColor: color,
width: double.infinity,
);
},
)
], ],
), ),

View File

@@ -2,6 +2,7 @@
import 'package:bus_infotainment/remaster/InitialStartup.dart'; import 'package:bus_infotainment/remaster/InitialStartup.dart';
import 'package:bus_infotainment/remaster/dashboard.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:shadcn_ui/shadcn_ui.dart'; import 'package:shadcn_ui/shadcn_ui.dart';
@@ -17,7 +18,11 @@ class RemasteredApp extends StatelessWidget {
), ),
routes: { routes: {
'/': (context) => InitialStartup() '/setup': (context) => InitialStartup(),
'/': (context) => HomePage_Re(),
'/routes': (context) => RoutePage(),
'/enroute': (context) => EnRoutePage(),
}, },
); );

View File

@@ -1,365 +1,510 @@
import 'package:bus_infotainment/backend/live_information.dart';
import 'package:bus_infotainment/pages/components/ibus_display.dart'; import 'package:bus_infotainment/pages/components/ibus_display.dart';
import 'package:bus_infotainment/tfl_datasets.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'dart:io' show Platform; import 'dart:io' show Platform;
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_carousel_widget/flutter_carousel_widget.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import 'package:text_scroll/text_scroll.dart'; import 'package:text_scroll/text_scroll.dart';
import '../backend/modules/tube_info.dart';
Color rgb(int r, int g, int b) { Color rgb(int r, int g, int b) {
return Color.fromRGBO(r, g, b, 1); return Color.fromRGBO(r, g, b, 1);
} }
class Dashboard extends StatelessWidget { class HomePage_Re extends StatefulWidget {
@override
State<HomePage_Re> createState() => _HomePage_ReState();
}
class _HomePage_ReState extends State<HomePage_Re> {
Future<bool> _shouldRedirectToSetup() async {
List<bool> perms = [];
perms.addAll([
await Permission.manageExternalStorage.isGranted,
await Permission.location.isGranted
]);
bool shouldRedirectA = !perms.contains(false);
bool shouldRedirectB = true;
try {
Uint8List bytes = await LiveInformation().announcementModule.getBundleBytes();
shouldRedirectB = true;
} catch (e) {
print("Failed to load bundle");
shouldRedirectB = false;
}
print("Should redirect to setup: ${shouldRedirectA || shouldRedirectB}");
print("Permissions: $shouldRedirectA");
print("Bundle: $shouldRedirectB");
print("Permissions_indv: $perms");
return !shouldRedirectA || !shouldRedirectB;
}
@override
void initState() {
super.initState();
_shouldRedirectToSetup().then((value) {
if (value) {
Navigator.pushNamed(context, "/setup");
}
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// TODO: implement build // TODO: implement build
return Scaffold( return Scaffold(
body: Container(
backgroundColor: Colors.grey.shade900, padding: EdgeInsets.all(16),
bottomNavigationBar: ((defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.iOS)) ? null : BottomAppBar(
color: Colors.grey.shade800, alignment: Alignment.center,
child: Row( child: SizedBox(
children: [
IconButton(
icon: Icon(Icons.home),
onPressed: () {
Navigator.pushNamed(context, "/dashboard");
},
),
IconButton(
icon: Icon(Icons.search),
onPressed: () {
Navigator.pushNamed(context, "/search");
},
),
IconButton(
icon: Icon(Icons.settings),
onPressed: () {
Navigator.pushNamed(context, "/settings");
},
),
],
),
),
body: Column( width: double.infinity,
children: [
Container( child: Column(
margin: EdgeInsets.all(16), mainAxisSize: MainAxisSize.min,
padding: EdgeInsets.all(8), crossAxisAlignment: CrossAxisAlignment.start,
decoration: BoxDecoration( children: [
borderRadius: BorderRadius.circular(10), Text(
"Choose mode:",
color: Colors.grey.shade800 style: TextStyle(
fontSize: 32,
), fontWeight: FontWeight.w600,
// height: 100,
child: ibus_display()
),
Expanded(
child: Row(
children: [
if (false)
if (!(defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.iOS))
NavigationRail(
selectedIndex: 0,
groupAlignment: 1,
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text("Dashboard"),
),
NavigationRailDestination(
icon: Icon(Icons.search),
label: Text("Routes")
),
NavigationRailDestination(
icon: Icon(Icons.settings),
label: Text("Settings")
)
],
),
Expanded(
child: Container(
child: Column(
children: [
Container(
padding: EdgeInsets.symmetric(
horizontal: 16
),
child: IntrinsicHeight(
child: Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colors.grey.shade800
),
padding: EdgeInsets.all(16),
child: IntrinsicWidth(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Bus Route:",
style: GoogleFonts.interTight(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white
)
),
SizedBox(
width: 16,
),
Text(
"11",
style: GoogleFonts.interTight(
fontSize: 18,
fontWeight: FontWeight.normal,
color: Colors.white
)
)
],
),
Row(
// mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Destination:",
style: GoogleFonts.interTight(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white
)
),
const SizedBox(
width: 16,
),
Text(
"Fullham Broadway",
style: GoogleFonts.interTight(
fontSize: 18,
fontWeight: FontWeight.normal,
color: Colors.white
)
)
],
),
const SizedBox(
height: 8,
),
Row(
// mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Next Stop:",
style: GoogleFonts.interTight(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white
)
),
const SizedBox(
width: 16,
),
Text(
"St Thomas Hospital / County Hall",
style: GoogleFonts.interTight(
fontSize: 18,
fontWeight: FontWeight.normal,
color: Colors.white
),
overflow: TextOverflow.fade,
)
],
),
Row(
// mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Last Stop:",
style: GoogleFonts.interTight(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white
),
overflow: TextOverflow.fade,
),
const SizedBox(
width: 16,
),
Text(
"Fullham Town Hall",
style: GoogleFonts.interTight(
fontSize: 18,
fontWeight: FontWeight.normal,
color: Colors.white
),
overflow: TextOverflow.fade,
)
],
),
],
),
)
),
),
// SizedBox(
// width: 16,
// ),
//
// Column(
//
// children: [
//
// SizedBox(
//
// width: 150,
//
// child: FloatingActionButton(
// onPressed: () {
//
// },
//
// backgroundColor: Colors.red,
//
// child: Text(
// "Bus Stopping",
// style: GoogleFonts.interTight(
// fontSize: 18,
// fontWeight: FontWeight.bold,
// color: Colors.white
// )
//
// ),
// ),
// ),
//
// SizedBox(
// height: 16
// ),
//
// SizedBox(
//
// width: 150,
// height: 100,
//
// child: FloatingActionButton(
// onPressed: () {
//
// },
//
// backgroundColor: Colors.grey.shade600,
//
// child: Text(
// "Acknowledge Bus Stop",
// style: GoogleFonts.interTight(
// fontSize: 18,
// fontWeight: FontWeight.bold,
// color: Colors.white
// ),
// textAlign: TextAlign.center,
//
// ),
// ),
// )
//
// ],
//
// )
],
),
),
)
],
),
),
) )
),
],
),
),
],
)
SizedBox(
height: 16,
),
ShadCard(
title: Text("Solo mode"),
width: double.infinity,
description: Text(
"Choose this mode if you are only using this device. (No internet required)"
),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: 4,
),
ShadButton.secondary(
onPressed: () {
Navigator.pushNamed(context, "/routes");
},
text: Text("Continue"),
)
],
),
),
SizedBox(
height: 16,
),
ShadCard(
title: Text("Multi mode"),
width: double.infinity,
description: Text(
"Choose this mode if you are using multiple devices. (Internet required)"
),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: 4,
),
ShadButton.secondary(
onPressed: () {
Navigator.pushNamed(context, "/application");
},
text: Text("Continue"),
)
],
),
)
],
),
)
),
); );
} }
} }
class RoutePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.all(16),
alignment: Alignment.center,
child: SizedBox(
width: double.infinity,
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Routes",
style: ShadTheme.of(context).textTheme.h1,
),
Text(
"Nearby routes",
style: ShadTheme.of(context).textTheme.h4,
),
ExpandableCarousel(
options: CarouselOptions(
),
items: [
ShadCard(
title: Text("Route 34"),
content: ShadSelect(
options: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text("Choose a variant"),
)
],
selectedOptionBuilder: (BuildContext context, value) {
return Text("Choose a variant");
},
),
padding: EdgeInsets.all(10),
)
],
),
Divider(),
RouteSearch()
],
),
)
),
);
}
List<Widget> _getNearbyRoutes() {
final variants = [
"Walthamstow Central to Barnet Church",
"Walthamstow Central to Barnet Church",
];
List<Widget> widgets = [];
for (int i = 0; i < variants.length; i++) {
widgets.add(
ShadOption(
value: i,
child: Text(variants[i]!),
)
);
}
return widgets;
}
}
class RouteSearch extends StatefulWidget {
@override
State<RouteSearch> createState() => _RouteSearchState();
}
class _RouteSearchState extends State<RouteSearch> {
TextEditingController controller = TextEditingController();
@override
Widget build(BuildContext context) {
List<Widget> routes = [];
for (BusRoute route in LiveInformation().busSequences.routes.values.toList()) {
if (controller.text.isNotEmpty && !route.routeNumber.toLowerCase().contains(controller.text.toLowerCase())) {
continue;
}
routes.add(RouteCard(route: route));
}
return Expanded(
child: Column(
children: [
ShadInput(
placeholder: Text("Search for a route..."),
controller: controller,
onChanged: (value) {
setState(() {
});
},
),
SizedBox(
height: 4,
),
Expanded(
child: GridView.count(
crossAxisCount: 3,
children: [
...routes
],
),
)
],
),
);
}
}
class RouteCard extends StatelessWidget {
BusRoute route;
RouteCard({required this.route});
@override
Widget build(BuildContext context) {
// TODO: implement build
Map<String, Widget> variants = {};
for (BusRouteVariant variant in route.routeVariants.values) {
String variantLabel = "${variant.busStops.first.formattedStopName} -> ${variant.busStops.last.formattedStopName}";
variants[variantLabel] = ShadOption(
value: variant.routeVariant.toString(),
child: Text(variantLabel),
);
}
return AspectRatio(
aspectRatio: 1,
child: Container(
child: ShadButton.secondary(
text: Text(
"Route \n ${route.routeNumber}",
style: ShadTheme.of(context).textTheme.h3
),
padding: EdgeInsets.all(8),
width: 105,
height: 105,
onPressed: () {
showShadSheet(
side: ShadSheetSide.bottom,
context: context,
builder: (context) {
List<Widget> variantWidgets = [];
for (BusRouteVariant variant in route.routeVariants.values) {
String variantLabel = "${variant.busStops.first.formattedStopName} -> ${variant.busStops.last.formattedStopName}";
variantWidgets.add(
ShadButton.outline(
text: SizedBox(
width: 800-490,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("${variant.busStops.first.formattedStopName} ->"),
SizedBox(
height: 2,
),
Text(variant.busStops.last.formattedStopName)
],
),
),
width: double.infinity,
height: 50,
padding: EdgeInsets.all(8),
onPressed: () async {
LiveInformation liveInformation = LiveInformation();
await liveInformation.setRouteVariant(variant);
liveInformation.announcementModule.queueAnnouncementByRouteVariant(routeVariant: variant);
Navigator.pushNamed(context, "/enroute");
},
)
);
variantWidgets.add(SizedBox(
height: 4,
));
}
return ShadSheet(
title: Text("Route ${route.routeNumber} - Variants"),
content: Container(
width: 2000,
alignment: Alignment.center,
child: Column(
children: [
...variantWidgets
],
),
),
padding: EdgeInsets.all(8),
);
}
);
},
),
),
);
}
}
class EnRoutePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
body: Column(
children: [
Container(
padding: EdgeInsets.all(8),
child: ibus_display()
),
Divider(
height: 1,
),
ShadButton(
text: Text("Route scanner"),
onPressed: () {
LiveInformation liveInformation = LiveInformation();
TubeLine? line = liveInformation.tubeStations.getClosestLine(liveInformation.getRouteVariant()!);
ShadToaster.of(context).show(
ShadToast(
title: Text("Closest line"),
description: Text(line == null ? "No line found" : line.name),
duration: Duration(seconds: 5),
)
);
},
),
ShadButton(
text: Text("dest"),
onPressed: () {
LiveInformation liveInformation = LiveInformation();
liveInformation.announcementModule.queueAnnouncementByRouteVariant(routeVariant: liveInformation.getRouteVariant()!);
},
)
],
),
);
}
}
/*
ShadSelectFormField<String>(
options: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text("Choose a variant"),
),
...variants.values
],
selectedOptionBuilder: (context, value) => value ==
'none'
? const Text('Select a verified email to display')
: Text(variants.keys.toList()[int.parse(value)-1]!),
placeholder: Text("Choose a variant"),
),
*/

View File

@@ -98,6 +98,18 @@ class BusSequences extends InfoModule {
print("Loaded ${routes.length} routes"); print("Loaded ${routes.length} routes");
// Remove any empty routes
List<String> emptyRoutes = [];
for (String routeNumber in routes.keys) {
if (routes[routeNumber]!.routeVariants.isEmpty) {
emptyRoutes.add(routeNumber);
}
}
for (String routeNumber in emptyRoutes) {
routes.remove(routeNumber);
}
} }
} }
@@ -225,6 +237,7 @@ class BusRouteStop {
// Replace space with underscore // Replace space with underscore
stopName = stopName.replaceAll(' ', '_'); stopName = stopName.replaceAll(' ', '_');
stopName = stopName.replaceAll('-', '_');
// convert to all caps // convert to all caps
stopName = stopName.toUpperCase(); stopName = stopName.toUpperCase();
@@ -263,6 +276,7 @@ class BusDestination {
// Replace space with underscore // Replace space with underscore
name = name.replaceAll(' ', '_'); name = name.replaceAll(' ', '_');
name = name.replaceAll('-', '_');
// convert to all caps // convert to all caps
name = name.toUpperCase(); name = name.toUpperCase();

View File

@@ -27,8 +27,8 @@ class NameBeautify {
stopName = stopName.replaceAll(RegExp(r'\>.*\<'), ''); stopName = stopName.replaceAll(RegExp(r'\>.*\<'), '');
// remove any special characters except & and / // remove any special characters except & and / and -
stopName = stopName.replaceAll(RegExp(r'[^a-zA-Z0-9&/ ]'), ''); stopName = stopName.replaceAll(RegExp(r'[^a-zA-Z0-9&\/\- ]'), '');
// remove any double spaces // remove any double spaces
stopName = stopName.replaceAll(RegExp(r' '), ' '); stopName = stopName.replaceAll(RegExp(r' '), ' ');
@@ -41,12 +41,17 @@ class NameBeautify {
stopName = stopName.replaceAll(RegExp(phrase, caseSensitive: false), Longify[phrase]!); stopName = stopName.replaceAll(RegExp(phrase, caseSensitive: false), Longify[phrase]!);
} }
// remove any spaces at the start or end of the string
stopName = stopName.trim();
stopName = stopName.replaceAll(' ', ' ');
stopName = stopName.toLowerCase(); stopName = stopName.toLowerCase();
// Capitalify the first letter of each word // Capitalify the first letter of each word
try { try {
stopName = stopName.split(' ').map((word) => word[0].toUpperCase() + word.substring(1)).join(' '); stopName = stopName.split(' ').map((word) => word[0].toUpperCase() + word.substring(1)).join(' ');
} catch (e) {} } catch (e) {
print("Error capitalifying stop name: $stopName");
}
return stopName; return stopName;

View File

@@ -25,14 +25,14 @@ class AudioWrapper {
print("AudioWrapper mode: $mode"); print("AudioWrapper mode: $mode");
mode = AudioWrapper_Mode.Mobile; mode = AudioWrapper_Mode.Web;
} }
justaudio.AudioSource _convertSource_JustAudio(AudioWrapperSource source){ justaudio.AudioSource _convertSource_JustAudio(AudioWrapperSource source){
if (source is AudioWrapperByteSource){ if (source is AudioWrapperByteSource){
return _ByteSource(source.bytes); return _ByteSource(source.bytes);
} else if (source is AudioWrapperAssetSource){ } else if (source is AudioWrapperAssetSource){
return justaudio.AudioSource.asset(source.assetPath); return justaudio.AudioSource.asset("assets/" + source.assetPath);
} else { } else {
throw Exception("Unknown source type"); throw Exception("Unknown source type");
} }

View File

@@ -7,6 +7,7 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <audioplayers_linux/audioplayers_linux_plugin.h> #include <audioplayers_linux/audioplayers_linux_plugin.h>
#include <rive_common/rive_plugin.h>
#include <screen_retriever/screen_retriever_plugin.h> #include <screen_retriever/screen_retriever_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h> #include <url_launcher_linux/url_launcher_plugin.h>
#include <window_manager/window_manager_plugin.h> #include <window_manager/window_manager_plugin.h>
@@ -16,6 +17,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar = g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin");
audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar); audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar);
g_autoptr(FlPluginRegistrar) rive_common_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "RivePlugin");
rive_plugin_register_with_registrar(rive_common_registrar);
g_autoptr(FlPluginRegistrar) screen_retriever_registrar = g_autoptr(FlPluginRegistrar) screen_retriever_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin");
screen_retriever_plugin_register_with_registrar(screen_retriever_registrar); screen_retriever_plugin_register_with_registrar(screen_retriever_registrar);

View File

@@ -4,6 +4,7 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_linux audioplayers_linux
rive_common
screen_retriever screen_retriever
url_launcher_linux url_launcher_linux
window_manager window_manager

View File

@@ -5,18 +5,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: appwrite name: appwrite
sha256: "335faac24642aaf66627c21ce26a5c64bcbc3911e624c61b9dfbe0eec7be1342" sha256: d95476adb1bbb72ae9eb31adffb9205b5cfe24abc4f483982eadceb396d433fb
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "12.0.1" version: "12.0.3"
archive: archive:
dependency: "direct main" dependency: "direct main"
description: description:
name: archive name: archive
sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d" sha256: "0763b45fa9294197a2885c8567927e2830ade852e5c896fd4ab7e0e348d0f373"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.4.10" version: "3.5.0"
args: args:
dependency: transitive dependency: transitive
description: description:
@@ -145,14 +145,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.18.0" version: "1.18.0"
convert:
dependency: transitive
description:
name: convert
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
cookie_jar: cookie_jar:
dependency: transitive dependency: transitive
description: description:
@@ -197,10 +189,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: device_info_plus name: device_info_plus
sha256: "77f757b789ff68e4eaf9c56d1752309bd9f7ad557cb105b938a7f8eb89e59110" sha256: eead12d1a1ed83d8283ab4c2f3fca23ac4082f29f25f29dff0f758f57d06ec91
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.1.2" version: "10.1.0"
device_info_plus_platform_interface: device_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -245,10 +237,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: file_picker name: file_picker
sha256: b6283d7387310ad83bc4f3bc245b75d223a032ae6eba275afcd585de2b9a1476 sha256: "29c90806ac5f5fb896547720b73b17ee9aed9bba540dc5d91fe29f8c5745b10a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.0.1" version: "8.0.3"
fixnum: fixnum:
dependency: transitive dependency: transitive
description: description:
@@ -270,6 +262,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.5.0" version: "4.5.0"
flutter_carousel_widget:
dependency: "direct main"
description:
name: flutter_carousel_widget
sha256: "37b9e55e4cafffe358152b016db24153e756152aa07c4214cfe6ee902cd69a01"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
flutter_lints: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
@@ -437,14 +437,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.18.1" version: "0.18.1"
js:
dependency: transitive
description:
name: js
sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
url: "https://pub.dev"
source: hosted
version: "0.7.1"
just_audio: just_audio:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -473,10 +465,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: just_audio_windows name: just_audio_windows
sha256: "7b8801f3987e98a2002cd23b5600b2daf162248ff1413266fb44c84448c1c0d3" sha256: "48ab2dec79cf6097550602fe07b1a644f341450e138dc8fdc23e42ce0ed2d928"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.0" version: "0.2.1"
latlong2: latlong2:
dependency: transitive dependency: transitive
description: description:
@@ -573,14 +565,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.0" version: "2.0.0"
modular_ui:
dependency: "direct main"
description:
name: modular_ui
sha256: c7bfa2d509f6b55f45833ea5a46c7b40030c2a3c8fb26f5211eab440235ee99f
url: "https://pub.dev"
source: hosted
version: "0.0.5"
ntp: ntp:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -593,18 +577,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: package_info_plus name: package_info_plus
sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017" sha256: b93d8b4d624b4ea19b0a5a208b2d6eff06004bc3ce74c06040b120eeadd00ce0
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.2.0" version: "8.0.0"
package_info_plus_platform_interface: package_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: package_info_plus_platform_interface name: package_info_plus_platform_interface
sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" sha256: f49918f3433a3146047372f9d4f1f847511f2acd5cd030e1f44fe5a50036b70e
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "3.0.0"
path: path:
dependency: transitive dependency: transitive
description: description:
@@ -741,14 +725,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.8" version: "2.1.8"
pointycastle:
dependency: transitive
description:
name: pointycastle
sha256: "79fbafed02cfdbe85ef3fd06c7f4bc2cbcba0177e61b765264853d4253b21744"
url: "https://pub.dev"
source: hosted
version: "3.9.0"
polylabel: polylabel:
dependency: transitive dependency: transitive
description: description:
@@ -769,18 +745,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: rive name: rive
sha256: ec44b6cf7341e21727c4b0e762f4ac82f9a45f7e52df3ebad2d1289a726fbaaf sha256: "95690a0fb4f6e195c53b217ab3cc0e0b0f443c670adbdee9d57d636a36b82b18"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.13.1" version: "0.13.2"
rive_common: rive_common:
dependency: transitive dependency: transitive
description: description:
name: rive_common name: rive_common
sha256: "0f070bc0e764c570abd8b34d744ef30d1292bd4051f2e0951bbda755875fce6a" sha256: "3eee68fcab3e0882090cea5a8cf7acea7967f469a34a2580322575603b094435"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.3" version: "0.4.5"
rxdart: rxdart:
dependency: transitive dependency: transitive
description: description:
@@ -801,10 +777,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: shadcn_ui name: shadcn_ui
sha256: "06c25555efbabe6d1d6acd6397bff4940e931d53ac3448e3a4f6a2287ab6798d" sha256: df9aedbd18bd60160c1c54717eef47fe4f90422f843070ccbc32786193803f7d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.3" version: "0.4.1"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1110,10 +1086,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: "0a989dc7ca2bb51eac91e8fd00851297cfffd641aa7538b165c62637ca0eaa4a" sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.4.0" version: "5.5.0"
win32_registry: win32_registry:
dependency: transitive dependency: transitive
description: description:

View File

@@ -50,8 +50,8 @@ dependencies:
vector_math: ^2.1.4 vector_math: ^2.1.4
permission_handler: ^11.3.0 permission_handler: ^11.3.0
file_picker: ^8.0.0+1 file_picker: ^8.0.0+1
shadcn_ui: ^0.3.3 shadcn_ui: ^0.4.1
modular_ui: ^0.0.5 flutter_carousel_widget: ^2.2.0
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
@@ -90,6 +90,9 @@ flutter:
- assets/datasets/bus-blinds.csv - assets/datasets/bus-blinds.csv
- assets/audio/5-seconds-of-silence.mp3 - assets/audio/5-seconds-of-silence.mp3
- assets/version.txt - assets/version.txt
- assets/audio/R_RAIL_REPLACEMENT_SERVICE_001.mp3
- assets/datasets/tube_stations.json
- assets/audio/rail_replacement/
# - images/a_dot_burr.jpeg # - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg # - images/a_dot_ham.jpeg