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

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/synced_time.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/utils/audio%20wrapper.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.");
}
// 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";
commandModule = CommandModule(sessionID);
@@ -87,6 +94,7 @@ class LiveInformation {
late AnnouncementModule announcementModule;
late SyncedTimeModule syncedTimeModule;
late TrackerModule trackerModule;
late TubeStations tubeStations;
// Important variables
BusRouteVariant? _currentRouteVariant;

View File

@@ -1,14 +1,17 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:bus_infotainment/audio_cache.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/utils/audio%20wrapper.dart';
import 'package:bus_infotainment/utils/delegates.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'info_module.dart';
@@ -33,12 +36,26 @@ class AnnouncementModule extends InfoModule {
return _bundleBytes!;
} else {
if (kIsWeb) {
throw Exception("Cannot load bundle bytes on web");
// Try to load them from shared preferences
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";
print("Checkpoint 5");
await announcementCache.loadAnnouncementsFromBytes(await getBundleBytes(), [audioRoute, "R_RAIL_REPLACEMENT_SERVICE_001.mp3"]);
await announcementCache.loadAnnouncementsFromBytes(await getBundleBytes(), [audioRoute]);
print("Checkpoint 6");
AudioWrapperSource sourceRoute = !routeNumber.toLowerCase().startsWith("ul") ?
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");
AudioWrapperSource sourceDestination = AudioWrapperByteSource(await routeVariant.destination!.getAudioBytes());
print("Checkpoint 7");

View File

@@ -90,7 +90,9 @@ class TrackerModule extends InfoModule {
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}");
} 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) => remaster.InitialStartup(),
'/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:permission_handler/permission_handler.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import 'package:shared_preferences/shared_preferences.dart';
class InitialStartup extends StatefulWidget {
@@ -324,6 +325,8 @@ class _page3 extends InitialStartupPage {
class _page3State extends State<_page3> {
bool _loadingAudio = false;
Future<bool> _announcementsUploaded() async {
try {
Uint8List bytes = await LiveInformation().announcementModule.getBundleBytes();
@@ -393,6 +396,7 @@ class _page3State extends State<_page3> {
height: 4,
),
if (!_loadingAudio)
FutureBuilder(
future: _announcementsUploaded(),
builder: (context, val) {
@@ -412,6 +416,10 @@ class _page3State extends State<_page3> {
text: Text(text),
onPressed: () async {
setState(() {
_loadingAudio = true;
});
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: [
@@ -419,6 +427,8 @@ class _page3State extends State<_page3> {
]
);
if (result != null) {
late Uint8List bytes;
@@ -434,13 +444,25 @@ class _page3State extends State<_page3> {
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
await cache.loadAnnouncementsFromBytes(bytes, ["S_WALTHAMSTOW_CENTRAL_001.mp3"]);
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/dashboard.dart';
import 'package:flutter/material.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
@@ -17,7 +18,11 @@ class RemasteredApp extends StatelessWidget {
),
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/tfl_datasets.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'dart:io' show Platform;
import 'package:flutter/widgets.dart';
import 'package:flutter_carousel_widget/flutter_carousel_widget.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 '../backend/modules/tube_info.dart';
Color rgb(int r, int g, int b) {
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
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
body: Container(
backgroundColor: Colors.grey.shade900,
bottomNavigationBar: ((defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.iOS)) ? null : BottomAppBar(
padding: EdgeInsets.all(16),
color: Colors.grey.shade800,
alignment: Alignment.center,
child: Row(
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");
},
),
],
),
),
child: SizedBox(
body: Column(
children: [
width: double.infinity,
Container(
child: Column(
margin: EdgeInsets.all(16),
padding: EdgeInsets.all(8),
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
decoration: BoxDecoration(
children: [
borderRadius: BorderRadius.circular(10),
color: Colors.grey.shade800
),
// 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,
//
// ),
// ),
// )
//
// ],
//
// )
],
),
),
)
],
),
),
Text(
"Choose mode:",
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.w600,
)
],
),
),
],
)
),
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");
// 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
stopName = stopName.replaceAll(' ', '_');
stopName = stopName.replaceAll('-', '_');
// convert to all caps
stopName = stopName.toUpperCase();
@@ -263,6 +276,7 @@ class BusDestination {
// Replace space with underscore
name = name.replaceAll(' ', '_');
name = name.replaceAll('-', '_');
// convert to all caps
name = name.toUpperCase();

View File

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

View File

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