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/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,269 +1,166 @@
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(
backgroundColor: Colors.grey.shade900,
bottomNavigationBar: ((defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.iOS)) ? null : BottomAppBar(
color: Colors.grey.shade800,
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");
},
),
],
),
),
body: Column(
children: [
Container(
margin: EdgeInsets.all(16),
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
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
),
body: Container(
padding: EdgeInsets.all(16),
child: IntrinsicWidth(
alignment: Alignment.center,
child: SizedBox(
width: double.infinity,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Bus Route:",
style: GoogleFonts.interTight(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white
"Choose mode:",
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.w600,
)
),
SizedBox(
width: 16,
height: 16,
),
Text(
"11",
style: GoogleFonts.interTight(
fontSize: 18,
fontWeight: FontWeight.normal,
color: Colors.white
)
)
],
ShadCard(
title: Text("Solo mode"),
width: double.infinity,
description: Text(
"Choose this mode if you are only using this device. (No internet required)"
),
Row(
// mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Destination:",
style: GoogleFonts.interTight(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white
)
SizedBox(
height: 4,
),
const SizedBox(
width: 16,
),
ShadButton.secondary(
onPressed: () {
Navigator.pushNamed(context, "/routes");
},
text: Text("Continue"),
)
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,
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: [
Text(
"Next Stop:",
style: GoogleFonts.interTight(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white
)
SizedBox(
height: 4,
),
const SizedBox(
width: 16,
),
Text(
"St Thomas Hospital / County Hall",
style: GoogleFonts.interTight(
fontSize: 18,
fontWeight: FontWeight.normal,
color: Colors.white
),
overflow: TextOverflow.fade,
ShadButton.secondary(
onPressed: () {
Navigator.pushNamed(context, "/application");
},
text: Text("Continue"),
)
],
),
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,
)
],
),
],
@@ -271,95 +168,343 @@ class Dashboard extends StatelessWidget {
)
),
),
// 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,
//
// ),
// ),
// )
//
// ],
//
// )
],
),
),
)
],
),
),
)
],
),
),
],
)
);
}
}
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");
}

View File

@@ -7,6 +7,7 @@
#include "generated_plugin_registrant.h"
#include <audioplayers_linux/audioplayers_linux_plugin.h>
#include <rive_common/rive_plugin.h>
#include <screen_retriever/screen_retriever_plugin.h>
#include <url_launcher_linux/url_launcher_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 =
fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin");
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 =
fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin");
screen_retriever_plugin_register_with_registrar(screen_retriever_registrar);

View File

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

View File

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

View File

@@ -50,8 +50,8 @@ dependencies:
vector_math: ^2.1.4
permission_handler: ^11.3.0
file_picker: ^8.0.0+1
shadcn_ui: ^0.3.3
modular_ui: ^0.0.5
shadcn_ui: ^0.4.1
flutter_carousel_widget: ^2.2.0
# The following adds the Cupertino Icons font to your application.
@@ -90,6 +90,9 @@ flutter:
- assets/datasets/bus-blinds.csv
- assets/audio/5-seconds-of-silence.mp3
- 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_ham.jpeg