ToLaptop
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
6
android/app/src/main/res/xml/network_security_config.xml
Normal file
6
android/app/src/main/res/xml/network_security_config.xml
Normal 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>
|
||||||
BIN
assets/audio/manual_announcements/buggysafety.mp3
Normal file
BIN
assets/audio/manual_announcements/buggysafety.mp3
Normal file
Binary file not shown.
BIN
assets/audio/manual_announcements/busondiversion.mp3
Normal file
BIN
assets/audio/manual_announcements/busondiversion.mp3
Normal file
Binary file not shown.
BIN
assets/audio/manual_announcements/busterminateshere.mp3
Normal file
BIN
assets/audio/manual_announcements/busterminateshere.mp3
Normal file
Binary file not shown.
BIN
assets/audio/manual_announcements/cctvoperation.mp3
Normal file
BIN
assets/audio/manual_announcements/cctvoperation.mp3
Normal file
Binary file not shown.
BIN
assets/audio/manual_announcements/destinationchange.mp3
Normal file
BIN
assets/audio/manual_announcements/destinationchange.mp3
Normal file
Binary file not shown.
BIN
assets/audio/manual_announcements/driverchange.mp3
Normal file
BIN
assets/audio/manual_announcements/driverchange.mp3
Normal file
Binary file not shown.
BIN
assets/audio/manual_announcements/envirobell.mp3
Normal file
BIN
assets/audio/manual_announcements/envirobell.mp3
Normal file
Binary file not shown.
BIN
assets/audio/manual_announcements/facecovering.mp3
Normal file
BIN
assets/audio/manual_announcements/facecovering.mp3
Normal file
Binary file not shown.
BIN
assets/audio/manual_announcements/movedownthebus.mp3
Normal file
BIN
assets/audio/manual_announcements/movedownthebus.mp3
Normal file
Binary file not shown.
BIN
assets/audio/manual_announcements/nextstopclosed.wav
Normal file
BIN
assets/audio/manual_announcements/nextstopclosed.wav
Normal file
Binary file not shown.
BIN
assets/audio/manual_announcements/nostanding.mp3
Normal file
BIN
assets/audio/manual_announcements/nostanding.mp3
Normal file
Binary file not shown.
BIN
assets/audio/manual_announcements/readytodepart.mp3
Normal file
BIN
assets/audio/manual_announcements/readytodepart.mp3
Normal file
Binary file not shown.
BIN
assets/audio/manual_announcements/safedooropening.mp3
Normal file
BIN
assets/audio/manual_announcements/safedooropening.mp3
Normal file
Binary file not shown.
BIN
assets/audio/manual_announcements/seatsupstairs.mp3
Normal file
BIN
assets/audio/manual_announcements/seatsupstairs.mp3
Normal file
Binary file not shown.
BIN
assets/audio/manual_announcements/serviceregulation.mp3
Normal file
BIN
assets/audio/manual_announcements/serviceregulation.mp3
Normal file
Binary file not shown.
BIN
assets/audio/manual_announcements/wheelchairspace1.mp3
Normal file
BIN
assets/audio/manual_announcements/wheelchairspace1.mp3
Normal file
Binary file not shown.
BIN
assets/audio/manual_announcements/wheelchairspace2.mp3
Normal file
BIN
assets/audio/manual_announcements/wheelchairspace2.mp3
Normal file
Binary file not shown.
BIN
assets/audio/to_destination.wav
Normal file
BIN
assets/audio/to_destination.wav
Normal file
Binary file not shown.
69343
assets/datasets/bus-sequences.csv
Normal file
69343
assets/datasets/bus-sequences.csv
Normal file
File diff suppressed because it is too large
Load Diff
BIN
assets/fonts/ibus/london-buses-ibus.ttf
Normal file
BIN
assets/fonts/ibus/london-buses-ibus.ttf
Normal file
Binary file not shown.
BIN
assets/fonts/lcd/lcddot.ttf
Normal file
BIN
assets/fonts/lcd/lcddot.ttf
Normal file
Binary file not shown.
@@ -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}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
156
lib/pages/components/ibus_display.dart
Normal file
156
lib/pages/components/ibus_display.dart
Normal 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
85
lib/pages/display.dart
Normal 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
410
lib/pages/home.dart
Normal 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
397
lib/pages/routes.dart
Normal 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
16
lib/pages/settings.dart
Normal 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: [
|
||||||
|
|
||||||
|
],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
148
lib/pages/tfl_dataset_test.dart
Normal file
148
lib/pages/tfl_dataset_test.dart
Normal 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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
125
lib/tfl_datasets.dart
Normal 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;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
69
lib/utils/NameBeautify.dart
Normal file
69
lib/utils/NameBeautify.dart
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
125
lib/utils/audio wrapper.dart
Normal file
125
lib/utils/audio wrapper.dart
Normal 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
90
lib/utils/delegates.dart
Normal 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!);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"))
|
||||||
}
|
}
|
||||||
|
|||||||
82
pubspec.lock
82
pubspec.lock
@@ -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:
|
||||||
|
|||||||
20
pubspec.yaml
20
pubspec.yaml
@@ -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
|
||||||
|
|||||||
@@ -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"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user