This commit is contained in:
ImBenji
2024-02-27 16:04:12 +00:00
parent 72ef70901d
commit 2d2dcdaecb
44 changed files with 71333 additions and 27 deletions

1
.gitignore vendored
View File

@@ -41,3 +41,4 @@ app.*.map.json
/android/app/debug /android/app/debug
/android/app/profile /android/app/profile
/android/app/release /android/app/release
/assets/ibus_recordings.zip

View File

@@ -41,6 +41,7 @@ android {
targetSdkVersion flutter.targetSdkVersion targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
multiDexEnabled = true
} }
buildTypes { buildTypes {

View File

@@ -2,7 +2,10 @@
<application <application
android:label="bus_infotainment" android:label="bus_infotainment"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher"
android:usesCleartextTraffic="true"
android:networkSecurityConfig="@xml/network_security_config">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="false">127.0.0.1</domain>
</domain-config>
</network-security-config>

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.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

BIN
assets/fonts/lcd/lcddot.ttf Normal file

Binary file not shown.

View File

@@ -28,8 +28,15 @@ class AnnouncementCache extends AudioCache {
final archive = ZipDecoder().decodeBytes(bytes.buffer.asUint8List()); final archive = ZipDecoder().decodeBytes(bytes.buffer.asUint8List());
for (final file in archive) { for (final file in archive) {
if (Announcements.contains(file.name)) {
_audioCache[file.name] = file.content; String filename = file.name;
if (filename.contains("/")) {
filename = filename.split("/").last;
}
if (Announcements.contains(filename)) {
_audioCache[filename] = file.content;
print("Loaded announcement: ${filename}");
} }
} }
} }

View File

@@ -1,11 +1,21 @@
import 'dart:io';
import 'package:bus_infotainment/pages/audio_cache_test.dart'; import 'package:bus_infotainment/pages/audio_cache_test.dart';
import 'package:bus_infotainment/pages/tfl_dataset_test.dart';
import 'package:bus_infotainment/singletons/live_information.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
void main() { void main() async {
WidgetsFlutterBinding.ensureInitialized();
LiveInformation liveInformation = LiveInformation();
await liveInformation.LoadDatasets();
runApp(const MyApp()); runApp(const MyApp());
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
const MyApp({super.key}); const MyApp({super.key});
// This widget is the root of your application. // This widget is the root of your application.
@@ -14,27 +24,19 @@ class MyApp extends StatelessWidget {
return MaterialApp( return MaterialApp(
title: 'Flutter Demo', title: 'Flutter Demo',
theme: ThemeData( theme: ThemeData(
// This is the theme of your application. brightness: Brightness.light,
// /* light theme settings */
// TRY THIS: Try running your application with "flutter run". You'll see
// the application has a purple toolbar. Then, without quitting the app,
// try changing the seedColor in the colorScheme below to Colors.green
// and then invoke "hot reload" (save your changes or press the "hot
// reload" button in a Flutter-supported IDE, or press "r" if you used
// the command line to start the app).
//
// Notice that the counter didn't reset back to zero; the application
// state is not lost during the reload. To reset the state, use hot
// restart instead.
//
// This works for code too, not just values: Most code changes can be
// tested with just a hot reload.
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
), ),
darkTheme: ThemeData(
brightness: Brightness.dark,
/* dark theme settings */
),
themeMode: ThemeMode.dark,
routes: { routes: {
'/home': (context) => const MyHomePage(title: 'Flutter Demo Home Page'), '/home': (context) => const MyHomePage(title: 'Flutter Demo Home Page'),
'/': (context) => AudioCacheTest(), '/audiocachetest': (context) => AudioCacheTest(),
'/': (context) => TfL_Dataset_Test(),
}, },
); );

View File

@@ -1,6 +1,6 @@
import 'package:audioplayers/audioplayers.dart';
import 'package:bus_infotainment/audio_cache.dart'; import 'package:bus_infotainment/audio_cache.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -38,8 +38,8 @@ class AudioCacheTest extends StatelessWidget {
announcements.add( announcements.add(
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
AudioPlayer player = AudioPlayer(); // AudioPlayer player = AudioPlayer();
player.play(BytesSource(_announcementCache[key])); // player.play(BytesSource(_announcementCache[key]));
}, },
child: Text( child: Text(
key key

View File

@@ -0,0 +1,156 @@
import 'dart:async';
import 'package:bus_infotainment/singletons/live_information.dart';
import 'package:bus_infotainment/utils/delegates.dart';
import 'package:flutter/material.dart';
import 'package:text_scroll/text_scroll.dart';
class ibus_display extends StatefulWidget {
bool hasBorder = true;
ibus_display({
this.hasBorder = true
});
@override
State<ibus_display> createState() => _ibus_displayState();
}
class _ibus_displayState extends State<ibus_display> {
String topLine = "*** NO MESSAGE ***";
late final ListenerReceipt<AnnouncementQueueEntry> _receipt;
_ibus_displayState(){
LiveInformation liveInformation = LiveInformation();
_receipt = liveInformation.announcementDelegate.addListener((value) {
topLine = value.displayText;
setState(() {
});
});
topLine = liveInformation.CurrentAnnouncement;
}
Timer _timer() => Timer.periodic(Duration(seconds: 1), (timer) {
setState(() {
topLine = LiveInformation().CurrentAnnouncement;
});
});
String _padString(String input){
if (input.length < 40){
print("Input is too short");
return input;
}
String prefix = "";
String suffix = "";
for (int i = 0; i < 80; i++){
prefix += " ";
}
return prefix + input + suffix;
}
@override
void dispose() {
LiveInformation().announcementDelegate.removeListener(_receipt);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
child: FittedBox(
alignment: Alignment.center,
child: Stack(
children: [
Container(
// width: double.infinity,
// height: 100,
decoration: BoxDecoration(
color: Colors.black,
border: widget.hasBorder ? Border.all(color: Colors.grey.shade900, width: 2) : null,
),
clipBehavior: Clip.hardEdge,
child: Transform.scale(
scale: 1.3,
transformHitTests: false,
child: Transform.translate(
offset: Offset(0, 4),
child: Column(
children: [
Transform.translate(
offset: Offset(0, 5),
child: Container(
alignment: Alignment.center,
width: 32*4*3,
child: TextScroll(
_padString(topLine),
velocity: Velocity(pixelsPerSecond: Offset(120, 0)),
style: const TextStyle(
fontSize: 20,
color: Colors.orange,
fontFamily: "ibus"
),
),
),
),
Transform.translate(
offset: Offset(0, -7),
child: Text(
"Bus Stopping",
style: const TextStyle(
fontSize: 20,
color: Colors.orange,
fontFamily: "ibus",
height: 1.5
),
),
)
],
),
),
)
),
Positioned.fill(
child: Container(
decoration: BoxDecoration(
color: Colors.transparent,
border: widget.hasBorder ? Border.all(color: Colors.grey.shade900, width: 2) : null,
),
),
)
],
),
),
);
}
}

85
lib/pages/display.dart Normal file
View File

@@ -0,0 +1,85 @@
import 'package:bus_infotainment/pages/components/ibus_display.dart';
import 'package:bus_infotainment/pages/tfl_dataset_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class pages_Display extends StatefulWidget {
final TfL_Dataset_TestState _tfL_Dataset_TestState;
pages_Display(this._tfL_Dataset_TestState, {Key? key}) : super(key: key);
@override
State<pages_Display> createState() => _pages_DisplayState();
}
class _pages_DisplayState extends State<pages_Display> {
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: widget._tfL_Dataset_TestState.hideUI ? Colors.black : Theme.of(context).colorScheme.background
),
width: double.infinity,
child: Stack(
children: [
Positioned.fill(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (MediaQuery.of(context).size.width < 600)
Expanded(
child: RotatedBox(
quarterTurns: 1,
child: ibus_display(
hasBorder: false,
),
),
)
else
Expanded(
child: ibus_display(
hasBorder: false,
),
),
],
),
),
Positioned.fill(
child: Container(
alignment: Alignment.bottomRight,
child: IconButton(
icon: Icon(Icons.fullscreen),
onPressed: () {
// Hide the app bar and nav bar
widget._tfL_Dataset_TestState.setState(() {
widget._tfL_Dataset_TestState.hideUI = !widget._tfL_Dataset_TestState.hideUI;
});
setState(() {
});
// Hide the notification bar and make the app full screen and display over notch
if (widget._tfL_Dataset_TestState.hideUI) {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
} else {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
}
},
),
),
)
],
)
);
}
}

410
lib/pages/home.dart Normal file
View File

@@ -0,0 +1,410 @@
import 'package:bus_infotainment/pages/components/ibus_display.dart';
import 'package:bus_infotainment/singletons/live_information.dart';
import 'package:flutter/material.dart';
class pages_Home extends StatelessWidget {
const pages_Home({super.key});
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
margin: const EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Text("Home Page"),
ibus_display(),
SizedBox(
height: 10,
),
_QuickAnnouncements(),
],
)
);
}
}
class _QuickAnnouncements extends StatefulWidget {
_QuickAnnouncements({super.key});
@override
State<_QuickAnnouncements> createState() => _QuickAnnouncementsState();
}
class _QuickAnnouncementsState extends State<_QuickAnnouncements> {
List<Widget> announcements = [];
int _currentIndex = 0;
_QuickAnnouncementsState() {
LiveInformation liveInformation = LiveInformation();
for (ManualAnnouncementEntry announcement in liveInformation.manualAnnouncements) {
announcements.add(
_QuickAnnouncement(announcement: announcement, index: liveInformation.manualAnnouncements.indexOf(announcement))
);
}
}
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.lightGreen.shade100,
border: Border.all(
color: Colors.black,
width: 2
),
),
padding: const EdgeInsets.all(2),
child: Column(
children: [
Container(
height: 2,
color: Colors.black,
),
if (_currentIndex < announcements.length)
announcements[_currentIndex + 0]
else
Container(
height: 50,
decoration: BoxDecoration(
color: Colors.lightGreen.shade100,
border: const Border.symmetric(
vertical: BorderSide(
color: Colors.black,
width: 2
)
),
),
),
Container(
height: 2,
color: Colors.black,
),
if (_currentIndex + 1 < announcements.length)
announcements[_currentIndex + 1]
else
Container(
height: 50,
decoration: BoxDecoration(
color: Colors.lightGreen.shade100,
border: const Border.symmetric(
vertical: BorderSide(
color: Colors.black,
width: 2
)
),
),
),
Container(
height: 2,
color: Colors.black,
),
if (_currentIndex + 2 < announcements.length)
announcements[_currentIndex + 2]
else
Container(
height: 50,
decoration: BoxDecoration(
color: Colors.lightGreen.shade100,
border: const Border.symmetric(
vertical: BorderSide(
color: Colors.black,
width: 2
)
),
),
),
Container(
height: 2,
color: Colors.black,
),
if (_currentIndex + 3 < announcements.length)
announcements[_currentIndex + 3]
else
Container(
height: 50,
decoration: BoxDecoration(
color: Colors.lightGreen.shade100,
border: const Border.symmetric(
vertical: BorderSide(
color: Colors.black,
width: 2
)
),
),
),
Container(
height: 2,
color: Colors.black,
),
Container(
height: 40,
decoration: BoxDecoration(
color: Colors.lightGreen.shade100,
border: const Border.symmetric(
vertical: BorderSide(
color: Colors.black,
width: 2
)
),
),
alignment: Alignment.centerRight,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: Colors.lightGreen.shade100,
border: const Border.symmetric(
vertical: BorderSide(
color: Colors.black,
width: 2
)
),
),
margin: const EdgeInsets.symmetric(
horizontal: 4
),
child: Container(
child: Stack(
children: [
Container(
width: 40,
height: 40,
child: Icon(
Icons.arrow_upward,
color: Colors.black,
),
),
Positioned.fill(
child: ElevatedButton(
onPressed: () {
_currentIndex = wrap(_currentIndex - 4, 0, announcements.length);
setState(() {});
print(_currentIndex);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
surfaceTintColor: Colors.transparent,
foregroundColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(0),
),
),
child: const Text(""),
),
)
],
),
)
),
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: Colors.lightGreen.shade100,
border: const Border.symmetric(
vertical: BorderSide(
color: Colors.black,
width: 2
)
),
),
margin: const EdgeInsets.symmetric(
horizontal: 4
),
child: Container(
child: Stack(
children: [
Container(
width: 40,
height: 40,
child: Icon(
Icons.arrow_downward,
color: Colors.black,
),
),
Positioned.fill(
child: ElevatedButton(
onPressed: () {
_currentIndex = wrap(_currentIndex + 4, 0, announcements.length);
setState(() {});
print(_currentIndex);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
surfaceTintColor: Colors.transparent,
foregroundColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(0),
),
),
child: const Text(""),
),
)
],
),
)
),
]
),
),
Container(
height: 2,
color: Colors.black,
),
]
),
);
}
}
int wrap(int i, int j, int length) {
return ((i - j) % length + length) % length;
}
class _QuickAnnouncement extends StatelessWidget {
final ManualAnnouncementEntry announcement;
final int index;
const _QuickAnnouncement({super.key, required this.announcement, required this.index});
@override
Widget build(BuildContext context) {
return Stack(
children: [
Container(
decoration: BoxDecoration(
color: Colors.lightGreen.shade100,
border: const Border.symmetric(
vertical: BorderSide(
color: Colors.black,
width: 2
)
),
),
padding: const EdgeInsets.all(5),
width: double.infinity,
height: 50,
child: Row(
children: [
Text(
announcement.shortName,
style: const TextStyle(
fontSize: 20,
color: Colors.black,
fontFamily: "lcd",
height: 1,
),
),
Expanded(
child: Container(
alignment: Alignment.centerRight,
child: Text(
(index+1).toString(),
style: const TextStyle(
fontSize: 20,
color: Colors.black,
fontFamily: "lcd",
height: 1,
),
),
),
)
],
)
),
Positioned.fill(
child: ElevatedButton(
onPressed: () {
LiveInformation liveInformation = LiveInformation();
liveInformation.queueAnnouncement(announcement);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
surfaceTintColor: Colors.transparent,
foregroundColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(0),
),
),
child: const Text("More"),
),
)
],
);
}
}

397
lib/pages/routes.dart Normal file
View File

@@ -0,0 +1,397 @@
import 'package:bus_infotainment/singletons/live_information.dart';
import 'package:bus_infotainment/tfl_datasets.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:text_scroll/text_scroll.dart';
class pages_Routes extends StatefulWidget {
@override
State<pages_Routes> createState() => _pages_RoutesState();
}
class _pages_RoutesState extends State<pages_Routes> {
final TextEditingController _controller = TextEditingController(text: "");
@override
Widget build(BuildContext context) {
LiveInformation liveInformation = LiveInformation();
List<Widget> routes = [];
routes.add(SizedBox(height: 10));
for (BusRoute route in liveInformation.busSequences!.routes.values) {
if (!route.routeNumber.toLowerCase().contains(_controller.text.toLowerCase())) {
continue;
}
routes.add(_Route(route, this));
routes.add(SizedBox(height: 10));
}
return Container(
color: Theme.of(context).colorScheme.background,
width: double.infinity,
// padding: const EdgeInsets.symmetric(horizontal: 10),
child: Column(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.background,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 4,
),
],
),
child: TextField(
controller: _controller,
onChanged: (String value) {
setState(() {});
},
),
),
Expanded(
child: ListView(
children: routes,
),
),
],
)
);
}
}
class _Route extends StatelessWidget {
final BusRoute route;
final _pages_RoutesState tfL_Dataset_TestState;
const _Route(this.route, this.tfL_Dataset_TestState);
@override
Widget build(BuildContext context) {
List<Widget> Variants = [];
for (BusRouteVariant variant in route.routeVariants.values) {
Variants.add(const SizedBox(height: 10));
Variants.add(_Variant(route, variant, tfL_Dataset_TestState));
}
return Container(
alignment: Alignment.center,
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 4,
),
],
color: Colors.grey.shade900,
),
margin: const EdgeInsets.symmetric(horizontal: 10),
padding: const EdgeInsets.all(10),
width: 100,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: BoxDecoration(
color: Colors.grey.shade800,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 2,
),
],
),
padding: const EdgeInsets.all(5),
child: Text(
"Route: ${route.routeNumber}",
style: GoogleFonts.montserrat(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
height: 1
),
),
),
ListView(
children: Variants,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
),
],
),
);
}
}
class _Variant extends StatelessWidget {
final BusRoute route;
final BusRouteVariant variant;
final _pages_RoutesState tfL_Dataset_TestState;
const _Variant(this.route, this.variant, this.tfL_Dataset_TestState);
@override
Widget build(BuildContext context) {
return Container(
child: Stack(
children: [
Container(
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 2,
),
],
color: Colors.grey.shade800,
),
padding: const EdgeInsets.all(5),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
"Start:",
style: GoogleFonts.montserrat(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
SizedBox(width: 5),
Expanded(
child: TextScroll(
"${variant.busStops.first.formattedStopName}",
mode: TextScrollMode.bouncing,
pauseBetween: const Duration(seconds: 2),
pauseOnBounce: const Duration(seconds: 2),
style: GoogleFonts.montserrat(
fontSize: 15,
fontWeight: FontWeight.normal,
color: Colors.white,
height: 1,
),
),
),
],
),
SizedBox(height: 5),
Row(
children: [
Text(
"End:",
style: GoogleFonts.montserrat(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
SizedBox(width: 5),
Expanded(
child: TextScroll(
"${variant.busStops.last.formattedStopName}",
mode: TextScrollMode.bouncing,
pauseBetween: const Duration(seconds: 2),
pauseOnBounce: const Duration(seconds: 2),
style: GoogleFonts.montserrat(
fontSize: 15,
fontWeight: FontWeight.normal,
color: Colors.white,
height: 1,
),
),
),
],
),
],
)
),
Positioned.fill(
child: ElevatedButton(
onPressed: () {
print("Variant: ${variant.routeVariant}");
// Open dialog
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
contentPadding: const EdgeInsets.only(
top: 15,
left: 15,
right: 15,
bottom: 8,
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Select the following route?",
style: GoogleFonts.montserrat(
fontSize: 15,
fontWeight: FontWeight.w700,
color: Colors.black,
),
),
Text(
"${route.routeNumber} to ${variant.busStops.last.formattedStopName}",
style: GoogleFonts.montserrat(
fontSize: 15,
fontWeight: FontWeight.w500,
color: Colors.black,
),
),
],
),
actionsPadding: const EdgeInsets.only(
left: 15,
right: 15,
bottom: 15,
),
actions: [
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
},
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
),
child: const Text("Cancel"),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
LiveInformation liveInformation = LiveInformation();
liveInformation.setRouteVariant(variant);
tfL_Dataset_TestState.setState(() {});
},
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
),
child: const Text("Confirm"),
),
],
);
}
);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
surfaceTintColor: Colors.transparent,
foregroundColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(0),
),
),
child: Container()
),
)
],
)
);
}
}

16
lib/pages/settings.dart Normal file
View File

@@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
class pages_Settings extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
],
)
);
}
}

View File

@@ -0,0 +1,148 @@
import 'dart:math';
import 'package:bus_infotainment/pages/display.dart';
import 'package:bus_infotainment/pages/home.dart';
import 'package:bus_infotainment/pages/routes.dart';
import 'package:bus_infotainment/pages/settings.dart';
import 'package:bus_infotainment/singletons/live_information.dart';
import 'package:bus_infotainment/tfl_datasets.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:text_scroll/text_scroll.dart';
class TfL_Dataset_Test extends StatefulWidget {
@override
State<TfL_Dataset_Test> createState() => TfL_Dataset_TestState();
}
class TfL_Dataset_TestState extends State<TfL_Dataset_Test> {
int _selectedIndex = 0;
bool hideUI = false;
late final List<Widget> Pages;
TfL_Dataset_TestState() {
Pages = [
pages_Home(),
pages_Routes(),
pages_Display(this),
pages_Settings(),
];
}
@override
Widget build(BuildContext context) {
LiveInformation liveInformation = LiveInformation();
_selectedIndex = min(_selectedIndex, Pages.length - 1);
_selectedIndex = max(_selectedIndex, 0);
return Scaffold(
appBar: !hideUI ? AppBar(
surfaceTintColor: Colors.transparent,
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Bus Infotainment",
style: GoogleFonts.montserrat(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
Row(
children: [
Text(
"Selected: ",
style: GoogleFonts.montserrat(
fontSize: 15,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
if (liveInformation.getRouteVariant() != null)
Container(
decoration: BoxDecoration(
color: Colors.black,
),
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2),
child: Text(
"${liveInformation.getRouteVariant()!.busRoute.routeNumber} to ${liveInformation.getRouteVariant()!.busStops.last.formattedStopName}",
style: GoogleFonts.montserrat(
fontSize: 15,
fontWeight: FontWeight.w500,
color: Colors.orange.shade900,
),
),
)
else
Text(
"None",
style: GoogleFonts.montserrat(
fontSize: 15,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
],
)
],
),
) : null,
body: Pages[_selectedIndex],
bottomNavigationBar: !hideUI ? NavigationBar(
selectedIndex: _selectedIndex,
destinations: const [
NavigationDestination(
icon: Icon(Icons.home),
label: "Home",
),
NavigationDestination(
icon: Icon(Icons.bus_alert),
label: "Routes",
),
NavigationDestination(
icon: Icon(Icons.tv),
label: "Display",
),
NavigationDestination(
icon: Icon(Icons.settings),
label: "Settings",
),
],
onDestinationSelected: (int index) {
setState(() {
_selectedIndex = index;
});
},
) : null,
);
}
}

View File

@@ -1,5 +1,221 @@
// Singleton // Singleton
import 'dart:async';
import 'package:bus_infotainment/audio_cache.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/services.dart';
import 'package:http/http.dart' as http;
import 'package:just_audio/just_audio.dart';
class LiveInformation { class LiveInformation {
static final LiveInformation _singleton = LiveInformation._internal();
factory LiveInformation() {
return _singleton;
}
LiveInformation._internal();
Future<void> LoadDatasets() async {
{
// Load the bus sequences
try {
http.Response response = await http.get(Uri.parse('https://tfl.gov.uk/bus-sequences.csv'));
busSequences = BusSequences.fromCSV(response.body);
print("Loaded bus sequences from TFL");
} catch (e) {
String csv = await rootBundle.loadString("assets/datasets/bus-sequences.csv");
busSequences = BusSequences.fromCSV(csv);
print("Loaded bus sequences from assets");
}
}
refreshTimer();
}
Timer refreshTimer() => Timer.periodic(Duration(seconds: 1), (timer) {
_handleAnnouncementQueue();
});
AudioWrapper audioPlayer = AudioWrapper();
AnnouncementCache announcementCache = AnnouncementCache();
List<AnnouncementQueueEntry> announcementQueue = [];
EventDelegate<AnnouncementQueueEntry> announcementDelegate = EventDelegate();
String CurrentAnnouncement = "*** NO MESSAGE ***";
void _handleAnnouncementQueue() async {
print("Handling announcement queue");
if (audioPlayer.state != AudioWrapper_State.Playing) {
if (announcementQueue.isNotEmpty) {
AnnouncementQueueEntry announcement = announcementQueue.first;
announcementDelegate.trigger(announcement);
CurrentAnnouncement = announcement.displayText;
for (AudioWrapperSource source in announcement.audioSources) {
Duration? duration = await audioPlayer.play(source);
if (source == announcement.audioSources.last) {
announcementQueue.removeAt(0);
}
await Future.delayed(duration!);
await Future.delayed(Duration(milliseconds: 150));
}
audioPlayer.stop();
print("Popped announcement queue");
}
}
}
void announceRouteVariant(BusRouteVariant routeVariant) async {
if (routeVariant == null) {
return;
}
String display = "${routeVariant.busRoute.routeNumber} to ${routeVariant.busStops.last.formattedStopName}";
String audio_route = "R_${routeVariant.busRoute.routeNumber}_001.mp3";
String audio_destination = routeVariant.busStops.last.getAudioFileName();
print("Audio file: $audio_route");
await announcementCache.loadAnnouncements([audio_route, audio_destination]);
AudioWrapperSource source_route = AudioWrapperByteSource(announcementCache[audio_route]);
AudioWrapperSource source_destination = AudioWrapperByteSource(announcementCache[audio_destination]);
queueAnnouncement(AnnouncementQueueEntry(
displayText: display,
audioSources: [source_route, AudioWrapperAssetSource("audio/to_destination.wav"), source_destination]
));
}
late BusSequences busSequences;
BusRouteVariant? _currentRouteVariant;
void setRouteVariant(BusRouteVariant routeVariant) {
_currentRouteVariant = routeVariant;
announceRouteVariant(routeVariant);
}
BusRouteVariant? getRouteVariant() {
return _currentRouteVariant;
}
void queueAnnouncement(AnnouncementQueueEntry announcement) {
announcementQueue.add(announcement);
}
List<ManualAnnouncementEntry> manualAnnouncements = [
ManualAnnouncementEntry(
shortName: "Driver Change",
informationText: "Driver Change",
audioSources: [AudioWrapperAssetSource("audio/manual_announcements/driverchange.mp3")],
),
ManualAnnouncementEntry(
shortName: "No Standing Upr Deck",
informationText: "No standing on the upper deck",
audioSources: [AudioWrapperAssetSource("audio/manual_announcements/nostanding.mp3")],
),
ManualAnnouncementEntry(
shortName: "Face Covering",
informationText: "Please wear a face covering!",
audioSources: [AudioWrapperAssetSource("audio/manual_announcements/facecovering.mp3")],
),
ManualAnnouncementEntry(
shortName: "Seats Upstairs",
informationText: "Seats are available upstairs",
audioSources: [AudioWrapperAssetSource("audio/manual_announcements/seatsupstairs.mp3")],
),
ManualAnnouncementEntry(
shortName: "Bus Terminates Here",
informationText: "Bus terminates here. Please take your belongings with you",
audioSources: [AudioWrapperAssetSource("audio/manual_announcements/busterminateshere.mp3")],
),
ManualAnnouncementEntry(
shortName: "Bus On Diversion",
informationText: "Bus on diversion. Please listen for further announcements",
audioSources: [AudioWrapperAssetSource("audio/manual_announcements/busondiversion.mp3")],
),
ManualAnnouncementEntry(
shortName: "Destination Change",
informationText: "Destination Changed - please listen for further instructions",
audioSources: [AudioWrapperAssetSource("audio/manual_announcements/destinationchange.mp3")],
),
ManualAnnouncementEntry(
shortName: "Wheelchair Space",
informationText: "Wheelchair space requested",
audioSources: [AudioWrapperAssetSource("audio/manual_announcements/wheelchairspace1.mp3")],
),
ManualAnnouncementEntry(
shortName: "Move Down The Bus",
informationText: "Please move down the bus",
audioSources: [AudioWrapperAssetSource("audio/manual_announcements/movedownthebus.mp3")],
),
ManualAnnouncementEntry(
shortName: "Next Stop Closed",
informationText: "The next bus stop is closed",
audioSources: [AudioWrapperAssetSource("audio/manual_announcements/nextstopclosed.wav")],
),
ManualAnnouncementEntry(
shortName: "CCTV In Operation",
informationText: "CCTV is in operation on this bus",
audioSources: [AudioWrapperAssetSource("audio/manual_announcements/cctvoperation.mp3")],
),
ManualAnnouncementEntry(
shortName: "Safe Door Opening",
informationText: "Driver will open the doors when it is safe to do so",
audioSources: [AudioWrapperAssetSource("audio/manual_announcements/safedooropening.mp3")],
),
ManualAnnouncementEntry(
shortName: "Buggy Safety",
informationText: "For your child's safety, please remain with your buggy",
audioSources: [AudioWrapperAssetSource("audio/manual_announcements/buggysafety.mp3")],
),
ManualAnnouncementEntry(
shortName: "Wheelchair Space 2",
informationText: "Wheelchair priority space required",
audioSources: [AudioWrapperAssetSource("audio/manual_announcements/wheelchairspace2.mp3")],
),
ManualAnnouncementEntry(
shortName: "Service Regulation",
informationText: "Regulating service - please listen for further information",
audioSources: [AudioWrapperAssetSource("audio/manual_announcements/serviceregulation.mp3")],
),
ManualAnnouncementEntry(
shortName: "Bus Ready To Depart",
informationText: "This bus is ready to depart",
audioSources: [AudioWrapperAssetSource("audio/manual_announcements/readytodepart.mp3")],
),
];
}
class AnnouncementQueueEntry {
final String displayText;
final List<AudioWrapperSource> audioSources;
AnnouncementQueueEntry({required this.displayText, required this.audioSources});
}
class ManualAnnouncementEntry extends AnnouncementQueueEntry {
final String shortName;
ManualAnnouncementEntry({required this.shortName, required String informationText, required List<AudioWrapperSource> audioSources}) : super(displayText: informationText, audioSources: audioSources);
} }

125
lib/tfl_datasets.dart Normal file
View File

@@ -0,0 +1,125 @@
import 'dart:typed_data';
import 'package:bus_infotainment/audio_cache.dart';
import 'package:bus_infotainment/utils/NameBeautify.dart';
import 'package:csv/csv.dart';
class BusSequences {
Map<String, BusRoute> routes = {};
BusSequences.fromCSV(String csv) {
List<List<dynamic>> rowsAsListOfValues = const CsvToListConverter().convert(csv);
rowsAsListOfValues.removeAt(0);
for (int i = 0; i < rowsAsListOfValues.length; i++) {
try {
List<dynamic> entries = rowsAsListOfValues[i];
String routeNumber = entries[0].toString();
BusRoute route = routes.containsKey(routeNumber) ? routes[routeNumber]! : BusRoute(routeNumber: routeNumber);
int routeVariant = entries[1];
BusRouteVariant variant = route.routeVariants.containsKey(routeVariant) ? route.routeVariants[routeVariant]! : BusRouteVariant(routeVariant: routeVariant, busRoute: route);
BusRouteStops stop = BusRouteStops();
stop.stopName = entries[6].toString();
stop.stopCode = entries[4].toString();
stop.easting = entries[7];
stop.northing = entries[8];
variant.busStops.add(stop);
route.routeVariants[routeVariant] = variant;
routes[routeNumber] = route;
} catch (e) {
// print("Error parsing bus sequence: $e");
}
}
}
}
class BusRoute {
String routeNumber = "";
AnnouncementCache? announcementCache;
Map<int, BusRouteVariant> routeVariants = {};
BusRoute({
this.routeNumber = "-1",
this.announcementCache,
});
}
class BusRouteVariant {
int routeVariant = -1;
List<BusRouteStops> busStops = [];
late BusRoute busRoute;
BusRouteVariant({
this.routeVariant = -1,
required this.busRoute,
});
}
class BusRouteStops {
String stopName = "";
int easting = -1;
int northing = -1;
String stopCode = "";
String get formattedStopName {
return NameBeautify.beautifyStopName(stopName);
}
String getAudioFileName() {
// Convert the stop name to all caps
String stopName = this.stopName.toUpperCase();
stopName = NameBeautify.beautifyStopName(stopName);
// replace & with N
stopName = stopName.replaceAll('&', 'N');
stopName = stopName.replaceAll('/', '');
stopName = stopName.replaceAll('\'', '');
stopName = stopName.replaceAll(' ', ' ');
// Replace space with underscore
stopName = stopName.replaceAll(' ', '_');
// convert to all caps
stopName = stopName.toUpperCase();
stopName = "S_${stopName}_001.mp3";
return stopName;
}
}

View File

@@ -0,0 +1,69 @@
import 'dart:math';
import 'package:intl/intl.dart';
class NameBeautify {
static final Map<String, String> Longify = {
"ctr": "Centre",
"stn": "Station",
"tn": "Town",
};
static String beautifyStopName(String label) {
String stopName = label.toUpperCase();
// remove <>
stopName = stopName.replaceAll("<>", "");
// remove any parathesese pairs as well as the contents (), [], {}, <>, ><
stopName = stopName.replaceAll(RegExp(r'\(.*\)'), '');
stopName = stopName.replaceAll(RegExp(r'\[.*\]'), '');
stopName = stopName.replaceAll(RegExp(r'\{.*\}'), '');
// stopName = stopName.replaceAll(RegExp(r'\<.*\>'), '');
stopName = stopName.replaceAll(RegExp(r'\>.*\<'), '');
// remove any special characters except & and /
stopName = stopName.replaceAll(RegExp(r'[^a-zA-Z0-9&/ ]'), '');
// remove any double spaces
stopName = stopName.replaceAll(RegExp(r' '), ' ');
// remove any spaces at the start or end of the string
stopName = stopName.trim();
// replace any short words with their long form
for (String phrase in Longify.keys) {
stopName = stopName.replaceAll(RegExp(phrase, caseSensitive: false), Longify[phrase]!);
}
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) {}
return stopName;
}
static String getShortTime(){
// return the HH:MM with AM and PM and make sure that the hour is 12 hour format and it always double digits. IE 01, 02 etc
DateTime now = DateTime.now();
String formatted = DateFormat('hh:mm a').format(now);
return formatted;
}
static String getLongTime() {
DateTime now = DateTime.now();
String formattedTime = DateFormat('HH:mm:ss dd.MM.yyyy').format(now);
return formattedTime;
}
}

View File

@@ -0,0 +1,125 @@
import 'package:audioplayers/audioplayers.dart' as audioplayers;
import 'package:flutter/foundation.dart';
import 'package:just_audio/just_audio.dart' as justaudio;
enum AudioWrapper_State {
Playing,
NotPlaying
}
class AudioWrapper {
audioplayers.AudioPlayer _audioPlayer_AudioPlayer = audioplayers.AudioPlayer();
justaudio.AudioPlayer _justAudio_AudioPlayer = justaudio.AudioPlayer();
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);
} else {
throw Exception("Unknown source type");
}
}
audioplayers.Source _convertSource_AudioPlayers(AudioWrapperSource source){
if (source is AudioWrapperByteSource){
return audioplayers.BytesSource(source.bytes);
} else if (source is AudioWrapperAssetSource){
return audioplayers.AssetSource(source.assetPath);
} else {
throw Exception("Unknown source type");
}
}
Future<Duration?> play(AudioWrapperSource source) async {
if (kIsWeb) {
// Use just_audio
justaudio.AudioSource audioSource = _convertSource_JustAudio(source);
Duration? duration = await _justAudio_AudioPlayer.setAudioSource(audioSource);
_justAudio_AudioPlayer.play();
return duration;
} else {
// Use audioplayers
audioplayers.Source audioSource = _convertSource_AudioPlayers(source);
await _audioPlayer_AudioPlayer.play(audioSource);
return await _audioPlayer_AudioPlayer.getDuration();
}
}
void stop(){
if (kIsWeb) {
_justAudio_AudioPlayer.stop();
} else {
_audioPlayer_AudioPlayer.stop();
}
}
AudioWrapper_State get state {
if (kIsWeb) {
if (_justAudio_AudioPlayer.playing){
return AudioWrapper_State.Playing;
} else {
return AudioWrapper_State.NotPlaying;
}
} else {
if (_audioPlayer_AudioPlayer.state == audioplayers.PlayerState.playing){
return AudioWrapper_State.Playing;
} else {
return AudioWrapper_State.NotPlaying;
}
}
}
}
class AudioWrapperSource {
}
class AudioWrapperByteSource extends AudioWrapperSource {
Uint8List bytes = Uint8List(0);
AudioWrapperByteSource(Uint8List bytes){
this.bytes = bytes;
}
}
class AudioWrapperAssetSource extends AudioWrapperSource {
String assetPath = "";
AudioWrapperAssetSource(String assetPath){
this.assetPath = assetPath;
}
}
class _ByteSource extends justaudio.StreamAudioSource {
final List<int> bytes;
_ByteSource(this.bytes);
@override
Future<justaudio.StreamAudioResponse> request([int? start, int? end]) async {
start ??= 0;
end ??= bytes.length;
return justaudio.StreamAudioResponse(
sourceLength: bytes.length,
contentLength: end - start,
offset: start,
stream: Stream.value(bytes.sublist(start, end)),
contentType: 'audio/mpeg',
);
}
}

90
lib/utils/delegates.dart Normal file
View File

@@ -0,0 +1,90 @@
import 'package:flutter/cupertino.dart';
/// Event system
class ListenerReceipt<T> {
Function(T) listener;
ListenerReceipt(this.listener);
}
class EventDelegate<T> {
final List<ListenerReceipt<T>> _receipts = [];
ListenerReceipt<T> addListener(Function(T) listener) {
final receipt = ListenerReceipt(listener);
_receipts.add(receipt);
return receipt;
}
void removeListener(ListenerReceipt<T> receipt) {
_receipts.remove(receipt);
print("removed listener");
}
void trigger(T event) {
print("triggering event");
for (var receipt in _receipts) {
print("triggering listener");
try {
receipt.listener(event);
} catch (e) {
print("Error in listener: $e");
removeListener(receipt);
}
}
}
}
// flutter integration
class DelegateBuilder<T> extends StatefulWidget {
final EventDelegate<T> delegate;
final Widget Function(BuildContext, T) builder;
final Widget Function(BuildContext)? defaultBuilder;
DelegateBuilder({required this.delegate, required this.builder, this.defaultBuilder}) : super(key: UniqueKey())
{
print("created delegate builder widget");
}
@override
State<StatefulWidget> createState() => _DelegateBuilderState<T>();
}
class _DelegateBuilderState<T> extends State<DelegateBuilder<T>> {
late ListenerReceipt<T> _receipt;
T? lastEvent;
@override
void initState() {
super.initState();
print("init delegate builder widget");
_receipt = widget.delegate.addListener((event) {
lastEvent = event;
print("triggered");
setState(() {
});
});
}
@override
void dispose() {
super.dispose();
widget.delegate.removeListener(_receipt);
print("disposed");
}
@override
Widget build(BuildContext context) {
print("rebuilt");
print("Valid: ${lastEvent != null}");
return lastEvent == null ? widget.defaultBuilder == null ? Container() : widget.defaultBuilder!(context) : widget.builder(context, lastEvent!);
}
}

View File

@@ -5,10 +5,14 @@
import FlutterMacOS import FlutterMacOS
import Foundation import Foundation
import audio_session
import audioplayers_darwin import audioplayers_darwin
import just_audio
import path_provider_foundation import path_provider_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
} }

View File

@@ -17,6 +17,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.11.0" version: "2.11.0"
audio_session:
dependency: transitive
description:
name: audio_session
sha256: "6fdf255ed3af86535c96452c33ecff1245990bb25a605bfb1958661ccc3d467f"
url: "https://pub.dev"
source: hosted
version: "0.1.18"
audioplayers: audioplayers:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -121,6 +129,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.3" version: "3.0.3"
csv:
dependency: "direct main"
description:
name: csv
sha256: "63ed2871dd6471193dffc52c0e6c76fb86269c00244d244297abbb355c84a86e"
url: "https://pub.dev"
source: hosted
version: "5.1.1"
cupertino_icons: cupertino_icons:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -184,8 +200,16 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
google_fonts:
dependency: "direct main"
description:
name: google_fonts
sha256: f0b8d115a13ecf827013ec9fc883390ccc0e87a96ed5347a3114cac177ef18e8
url: "https://pub.dev"
source: hosted
version: "6.1.0"
http: http:
dependency: transitive dependency: "direct main"
description: description:
name: http name: http
sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba
@@ -200,6 +224,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.2" version: "4.0.2"
intl:
dependency: "direct main"
description:
name: intl
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
url: "https://pub.dev"
source: hosted
version: "0.19.0"
js: js:
dependency: transitive dependency: transitive
description: description:
@@ -208,6 +240,38 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.7" version: "0.6.7"
just_audio:
dependency: "direct main"
description:
name: just_audio
sha256: b607cd1a43bac03d85c3aaee00448ff4a589ef2a77104e3d409889ff079bf823
url: "https://pub.dev"
source: hosted
version: "0.9.36"
just_audio_platform_interface:
dependency: transitive
description:
name: just_audio_platform_interface
sha256: c3dee0014248c97c91fe6299edb73dc4d6c6930a2f4f713579cd692d9e47f4a1
url: "https://pub.dev"
source: hosted
version: "4.2.2"
just_audio_web:
dependency: transitive
description:
name: just_audio_web
sha256: "134356b0fe3d898293102b33b5fd618831ffdc72bb7a1b726140abdf22772b70"
url: "https://pub.dev"
source: hosted
version: "0.4.9"
just_audio_windows:
dependency: "direct main"
description:
name: just_audio_windows
sha256: "7b8801f3987e98a2002cd23b5600b2daf162248ff1413266fb44c84448c1c0d3"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
lints: lints:
dependency: transitive dependency: transitive
description: description:
@@ -320,6 +384,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.7.4" version: "3.7.4"
rxdart:
dependency: transitive
description:
name: rxdart
sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb"
url: "https://pub.dev"
source: hosted
version: "0.27.7"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@@ -389,6 +461,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.1" version: "0.6.1"
text_scroll:
dependency: "direct main"
description:
name: text_scroll
sha256: "7869d86a6fdd725dee56bdd150216a99f0372b82fbfcac319214dbd5f36e1908"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:

View File

@@ -31,7 +31,14 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
archive: ^3.1.2 archive: ^3.1.2
just_audio: ^0.9.36
just_audio_windows: ^0.2.0
audioplayers: ^5.2.1 audioplayers: ^5.2.1
csv: ^5.1.1
http: any
google_fonts: ^6.1.0
intl: any
text_scroll: ^0.2.0
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
@@ -63,6 +70,10 @@ flutter:
# To add assets to your application, add an assets section, like this: # To add assets to your application, add an assets section, like this:
assets: assets:
- assets/ibus_recordings.zip - assets/ibus_recordings.zip
- assets/datasets/bus-sequences.csv
- assets/fonts/ibus/london-buses-ibus.ttf
- assets/audio/manual_announcements/
- assets/audio/to_destination.wav
# - images/a_dot_burr.jpeg # - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg # - images/a_dot_ham.jpeg
@@ -78,7 +89,14 @@ flutter:
# "family" key with the font family name, and a "fonts" key with a # "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For # list giving the asset and other descriptors for the font. For
# example: # example:
# fonts: fonts:
- family: "ibus"
fonts:
- asset: assets/fonts/ibus/london-buses-ibus.ttf
- family: "lcd"
fonts:
- asset: assets/fonts/lcd/lcddot.ttf
# - family: Schyler # - family: Schyler
# fonts: # fonts:
# - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Regular.ttf

View File

@@ -7,8 +7,11 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <audioplayers_windows/audioplayers_windows_plugin.h> #include <audioplayers_windows/audioplayers_windows_plugin.h>
#include <just_audio_windows/just_audio_windows_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
AudioplayersWindowsPluginRegisterWithRegistrar( AudioplayersWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin"));
JustAudioWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("JustAudioWindowsPlugin"));
} }

View File

@@ -4,6 +4,7 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_windows audioplayers_windows
just_audio_windows
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST