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

View File

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