830 lines
24 KiB
Dart
830 lines
24 KiB
Dart
|
|
|
|
import 'dart:async';
|
|
|
|
import 'package:bus_infotainment/pages/components/ibus_display.dart';
|
|
import 'package:bus_infotainment/backend/live_information.dart';
|
|
import 'package:bus_infotainment/tfl_datasets.dart';
|
|
import 'package:bus_infotainment/utils/OrdinanceSurveyUtils.dart';
|
|
import 'package:bus_infotainment/utils/audio%20wrapper.dart';
|
|
import 'package:bus_infotainment/utils/delegates.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:geolocator/geolocator.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
|
|
class pages_Home extends StatelessWidget {
|
|
const pages_Home({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
|
|
return Container(
|
|
|
|
|
|
|
|
child: SingleChildScrollView(
|
|
child: Column(
|
|
children: [
|
|
|
|
Row(
|
|
|
|
children: [
|
|
|
|
Speedometer(),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
Container(
|
|
height: 2,
|
|
color: Colors.white70,
|
|
),
|
|
|
|
SingleChildScrollView(
|
|
child: Column(
|
|
children: [
|
|
|
|
Container(
|
|
|
|
margin: EdgeInsets.all(10),
|
|
|
|
child: Container(
|
|
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey.shade900,
|
|
),
|
|
|
|
child: AnnouncementPicker(
|
|
backgroundColor: Colors.grey.shade900,
|
|
outlineColor: Colors.white70,
|
|
announcements: [
|
|
for (NamedAnnouncementQueueEntry announcement in LiveInformation().announcementModule.manualAnnouncements)
|
|
_AnnouncementEntry(
|
|
label: announcement.shortName,
|
|
index: LiveInformation().announcementModule.manualAnnouncements.indexOf(announcement),
|
|
outlineColor: Colors.white70,
|
|
onPressed: (){
|
|
LiveInformation liveInformation = LiveInformation();
|
|
liveInformation.announcementModule.queueAnnounementByInfoIndex(
|
|
infoIndex: liveInformation.announcementModule.manualAnnouncements.indexOf(announcement),
|
|
sendToServer: true
|
|
);
|
|
},
|
|
)
|
|
],
|
|
),
|
|
),
|
|
|
|
),
|
|
|
|
Container(
|
|
height: 2,
|
|
color: Colors.white70,
|
|
),
|
|
|
|
Container(
|
|
|
|
margin: EdgeInsets.all(10),
|
|
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey.shade900,
|
|
),
|
|
|
|
child: DelegateBuilder<BusRouteVariant>(
|
|
delegate: LiveInformation().routeVariantDelegate,
|
|
builder: (context, routeVariant) {
|
|
print("rebuilt stop announcement picker");
|
|
return StopAnnouncementPicker(
|
|
routeVariant: routeVariant,
|
|
backgroundColor: Colors.grey.shade900,
|
|
outlineColor: Colors.white70,
|
|
);
|
|
},
|
|
defaultBuilder: (context) {
|
|
BusRouteVariant? routeVariant = LiveInformation().getRouteVariant();
|
|
if (routeVariant == null) {
|
|
return Container(
|
|
color: Colors.grey.shade900,
|
|
child: Center(
|
|
child: Text(
|
|
"No route selected",
|
|
style: GoogleFonts.teko(
|
|
fontSize: 25,
|
|
color: Colors.white70
|
|
),
|
|
),
|
|
),
|
|
);
|
|
} else {
|
|
return StopAnnouncementPicker(
|
|
routeVariant: routeVariant,
|
|
backgroundColor: Colors.grey.shade900,
|
|
outlineColor: Colors.white70,
|
|
);
|
|
}
|
|
},
|
|
),
|
|
),
|
|
|
|
),
|
|
|
|
ElevatedButton(
|
|
onPressed: () async {
|
|
LiveInformation liveInformation = LiveInformation();
|
|
final commandModule = liveInformation.commandModule;
|
|
|
|
// commandModule.executeCommand(
|
|
// "announce dest"
|
|
// );
|
|
|
|
liveInformation.announcementModule.queueAnnouncementByRouteVariant(
|
|
routeVariant: liveInformation.getRouteVariant()!
|
|
);
|
|
|
|
},
|
|
child: Text("Announce current destination"),
|
|
),
|
|
|
|
|
|
// Container(
|
|
//
|
|
// margin: EdgeInsets.all(20),
|
|
//
|
|
// height: 300-45,
|
|
//
|
|
// child: ListView(
|
|
//
|
|
// scrollDirection: Axis.vertical,
|
|
//
|
|
// children: [
|
|
//
|
|
// ElevatedButton(
|
|
// onPressed: () async {
|
|
// LiveInformation liveInformation = LiveInformation();
|
|
// liveInformation.queueAnnouncement(await liveInformation.getDestinationAnnouncement(liveInformation.getRouteVariant()!, sendToServer: false));
|
|
// },
|
|
// child: Text("Test announcement"),
|
|
// ),
|
|
//
|
|
// ElevatedButton(
|
|
// onPressed: () {
|
|
// LiveInformation liveInformation = LiveInformation();
|
|
// liveInformation.updateServer();
|
|
// },
|
|
// child: Text("Update server"),
|
|
// ),
|
|
//
|
|
// SizedBox(
|
|
//
|
|
// width: 100,
|
|
//
|
|
// child: TextField(
|
|
// onChanged: (String value) {
|
|
// LiveInformation liveInformation = LiveInformation();
|
|
// // liveInformation.documentID = value;
|
|
// },
|
|
// ),
|
|
// ),
|
|
//
|
|
// SizedBox(
|
|
//
|
|
// width: 200,
|
|
//
|
|
// child: TextField(
|
|
// onSubmitted: (String value) {
|
|
// LiveInformation liveInformation = LiveInformation();
|
|
// liveInformation.queueAnnouncement(AnnouncementQueueEntry(
|
|
// displayText: value,
|
|
// audioSources: []
|
|
// ));
|
|
// },
|
|
// ),
|
|
// ),
|
|
//
|
|
// ElevatedButton(
|
|
// onPressed: () {
|
|
// LiveInformation liveInformation = LiveInformation();
|
|
// liveInformation.pullServer();
|
|
// },
|
|
// child: Text("Pull server"),
|
|
// ),
|
|
//
|
|
// ],
|
|
//
|
|
// ),
|
|
//
|
|
// ),
|
|
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
)
|
|
|
|
);
|
|
}
|
|
}
|
|
|
|
class Speedometer extends StatefulWidget {
|
|
|
|
@override
|
|
State<Speedometer> createState() => _SpeedometerState();
|
|
}
|
|
|
|
|
|
|
|
class _SpeedometerState extends State<Speedometer> {
|
|
|
|
double speed = 0;
|
|
double arrivalTime = 0;
|
|
|
|
BusRouteStop? nearestStop;
|
|
double speed2 = 0;
|
|
|
|
_SpeedometerState(){
|
|
|
|
|
|
|
|
}
|
|
|
|
Timer? reloadTimer;
|
|
|
|
@override
|
|
void initState() {
|
|
// TODO: implement initState
|
|
super.initState();
|
|
|
|
reloadTimer = Timer.periodic(Duration(milliseconds: 250), (timer) {
|
|
|
|
try {
|
|
Position? newPosition = LiveInformation().trackerModule.position;
|
|
|
|
speed = newPosition?.speed ?? 0;
|
|
|
|
arrivalTime -= 0.25;
|
|
arrivalTime = arrivalTime < 0 ? 0 : arrivalTime;
|
|
|
|
setState(() {
|
|
|
|
});
|
|
} catch (e) {
|
|
// print(e);
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
// TODO: implement dispose
|
|
super.dispose();
|
|
reloadTimer?.cancel();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
// TODO: implement build
|
|
return Container(
|
|
|
|
margin: EdgeInsets.all(8),
|
|
|
|
height: 74,
|
|
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
// width: 60,
|
|
height: double.infinity,
|
|
alignment: Alignment.center,
|
|
decoration: BoxDecoration(
|
|
border: Border.all(
|
|
color: Colors.white70,
|
|
width: 2
|
|
)
|
|
),
|
|
padding: const EdgeInsets.all(8),
|
|
child: Column(
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
children: [
|
|
|
|
Container(
|
|
|
|
decoration: BoxDecoration(
|
|
border: Border.all(
|
|
color: Colors.white70,
|
|
width: 2
|
|
)
|
|
),
|
|
|
|
width: 30,
|
|
height: 30,
|
|
|
|
alignment: Alignment.center,
|
|
|
|
child: Text(
|
|
"${((speed) * 2.237).toInt()}",
|
|
style: GoogleFonts.teko(
|
|
fontSize: 20,
|
|
color: Colors.white70,
|
|
height: 1
|
|
),
|
|
)
|
|
),
|
|
|
|
const SizedBox(
|
|
height: 4,
|
|
),
|
|
|
|
Text(
|
|
"MPH",
|
|
style: GoogleFonts.teko(
|
|
fontSize: 20,
|
|
color: Colors.white70,
|
|
height: 1
|
|
),
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
),
|
|
|
|
],
|
|
),
|
|
|
|
);
|
|
}
|
|
}
|
|
|
|
class AnnouncementPicker extends StatefulWidget {
|
|
|
|
final Color backgroundColor;
|
|
final Color outlineColor;
|
|
final List<Widget> announcements;
|
|
|
|
const AnnouncementPicker({super.key, required this.backgroundColor, required this.outlineColor, required this.announcements});
|
|
|
|
@override
|
|
State<AnnouncementPicker> createState() => _AnnouncementPickerState();
|
|
}
|
|
|
|
class _AnnouncementPickerState extends State<AnnouncementPicker> {
|
|
|
|
List<Widget> announcementWidgets = [];
|
|
|
|
int _currentIndex = 0;
|
|
|
|
@override
|
|
void initState() {
|
|
// TODO: implement initState
|
|
super.initState();
|
|
|
|
LiveInformation liveInformation = LiveInformation();
|
|
|
|
if (widget.announcements.isEmpty){
|
|
return;
|
|
}
|
|
|
|
int i = 0;
|
|
for (Widget announcement in widget.announcements!) {
|
|
announcementWidgets.add(
|
|
announcement
|
|
);
|
|
i++;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
|
|
|
|
return Container(
|
|
|
|
decoration: BoxDecoration(
|
|
color: widget.backgroundColor,
|
|
border: Border.all(
|
|
color: widget.outlineColor,
|
|
width: 2
|
|
),
|
|
|
|
|
|
|
|
),
|
|
|
|
padding: const EdgeInsets.all(4),
|
|
|
|
width: double.infinity,
|
|
|
|
constraints: const BoxConstraints(
|
|
maxWidth: 400
|
|
),
|
|
|
|
child: Column(
|
|
children: [
|
|
Container(
|
|
height: 2,
|
|
color: widget.outlineColor,
|
|
),
|
|
|
|
if (_currentIndex < announcementWidgets.length)
|
|
announcementWidgets[_currentIndex + 0]
|
|
else
|
|
Container(
|
|
height: 50,
|
|
decoration: BoxDecoration(
|
|
color: widget.backgroundColor,
|
|
border: Border.symmetric(
|
|
vertical: BorderSide(
|
|
color: widget.outlineColor,
|
|
width: 2
|
|
)
|
|
),
|
|
),
|
|
),
|
|
|
|
Container(
|
|
height: 2,
|
|
color: widget.outlineColor,
|
|
),
|
|
|
|
if (_currentIndex + 1 < announcementWidgets.length)
|
|
announcementWidgets[_currentIndex + 1]
|
|
else
|
|
Container(
|
|
height: 50,
|
|
decoration: BoxDecoration(
|
|
color: widget.backgroundColor,
|
|
border: Border.symmetric(
|
|
vertical: BorderSide(
|
|
color: widget.outlineColor,
|
|
width: 2
|
|
)
|
|
),
|
|
),
|
|
),
|
|
|
|
Container(
|
|
height: 2,
|
|
color: widget.outlineColor,
|
|
),
|
|
|
|
if (_currentIndex + 2 < announcementWidgets.length)
|
|
announcementWidgets[_currentIndex + 2]
|
|
else
|
|
Container(
|
|
height: 50,
|
|
decoration: BoxDecoration(
|
|
color: widget.backgroundColor,
|
|
border: Border.symmetric(
|
|
vertical: BorderSide(
|
|
color: widget.outlineColor,
|
|
width: 2
|
|
)
|
|
),
|
|
),
|
|
),
|
|
|
|
Container(
|
|
height: 2,
|
|
color: widget.outlineColor,
|
|
),
|
|
|
|
if (_currentIndex + 3 < announcementWidgets.length)
|
|
announcementWidgets[_currentIndex + 3]
|
|
else
|
|
Container(
|
|
height: 50,
|
|
decoration: BoxDecoration(
|
|
color: widget.backgroundColor,
|
|
border: Border.symmetric(
|
|
vertical: BorderSide(
|
|
color: widget.outlineColor,
|
|
width: 2
|
|
)
|
|
),
|
|
),
|
|
),
|
|
|
|
Container(
|
|
height: 2,
|
|
color: widget.outlineColor,
|
|
),
|
|
|
|
Container(
|
|
height: 40,
|
|
decoration: BoxDecoration(
|
|
color: widget.backgroundColor,
|
|
border: Border.symmetric(
|
|
vertical: BorderSide(
|
|
color: widget.outlineColor,
|
|
width: 2
|
|
)
|
|
),
|
|
),
|
|
|
|
alignment: Alignment.centerRight,
|
|
|
|
child: Row(
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
children: [
|
|
|
|
Container(
|
|
width: 40,
|
|
height: 40,
|
|
decoration: BoxDecoration(
|
|
color: widget.backgroundColor,
|
|
border: Border.symmetric(
|
|
vertical: BorderSide(
|
|
color: widget.outlineColor,
|
|
width: 2
|
|
)
|
|
),
|
|
),
|
|
|
|
margin: const EdgeInsets.symmetric(
|
|
horizontal: 4
|
|
),
|
|
|
|
child: Container(
|
|
child: Stack(
|
|
children: [
|
|
Container(
|
|
width: 40,
|
|
height: 40,
|
|
child: Icon(
|
|
Icons.arrow_upward,
|
|
color: widget.outlineColor,
|
|
),
|
|
),
|
|
Positioned.fill(
|
|
child: ElevatedButton(
|
|
onPressed: () {
|
|
_currentIndex = wrap(_currentIndex - 4, 0, announcementWidgets.length, increment: 4);
|
|
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: widget.backgroundColor,
|
|
border: Border.symmetric(
|
|
vertical: BorderSide(
|
|
color: widget.outlineColor,
|
|
width: 2
|
|
)
|
|
),
|
|
),
|
|
|
|
margin: const EdgeInsets.symmetric(
|
|
horizontal: 4
|
|
),
|
|
|
|
child: Container(
|
|
child: Stack(
|
|
children: [
|
|
Container(
|
|
width: 40,
|
|
height: 40,
|
|
child: Icon(
|
|
Icons.arrow_downward,
|
|
color: widget.outlineColor,
|
|
),
|
|
),
|
|
Positioned.fill(
|
|
child: ElevatedButton(
|
|
onPressed: () {
|
|
_currentIndex = wrap(_currentIndex + 4, 0, announcementWidgets.length, increment: 4);
|
|
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: widget.outlineColor,
|
|
),
|
|
|
|
]
|
|
),
|
|
);
|
|
|
|
|
|
}
|
|
}
|
|
|
|
class StopAnnouncementPicker extends AnnouncementPicker {
|
|
final BusRouteVariant routeVariant;
|
|
|
|
StopAnnouncementPicker({
|
|
Key? key,
|
|
required this.routeVariant,
|
|
required Color backgroundColor,
|
|
required Color outlineColor,
|
|
}) : super(
|
|
key: key,
|
|
backgroundColor: backgroundColor,
|
|
outlineColor: outlineColor,
|
|
announcements: [
|
|
for (BusRouteStop stop in routeVariant.busStops)
|
|
_AnnouncementEntry(
|
|
label: stop.formattedStopName,
|
|
onPressed: () {
|
|
LiveInformation liveInformation = LiveInformation();
|
|
liveInformation.announcementModule.queueAnnounceByAudioName(
|
|
displayText: stop.formattedStopName,
|
|
audioNames: [stop.getAudioFileName()],
|
|
);
|
|
},
|
|
index: routeVariant.busStops.indexOf(stop),
|
|
outlineColor: outlineColor,
|
|
alert: LiveInformation().announcementModule.announcementCache[stop.getAudioFileName()] == null,
|
|
)
|
|
]
|
|
);
|
|
}
|
|
|
|
int wrap(int i, int j, int length, {int increment = -1}) {
|
|
if (increment == -1) {
|
|
return ((i - j) % length + length) % length;
|
|
} else {
|
|
if (i >= length) {
|
|
return 0;
|
|
} else if (i < 0) {
|
|
double d = length / increment;
|
|
int n = d.toInt();
|
|
|
|
return (n-1) * increment;
|
|
} else {
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
|
|
class _AnnouncementEntry extends StatelessWidget {
|
|
|
|
final String label;
|
|
|
|
final Function onPressed;
|
|
final int index;
|
|
final Color outlineColor;
|
|
|
|
bool alert = false;
|
|
|
|
_AnnouncementEntry({super.key, required this.label, required this.onPressed, required this.index, required this.outlineColor, this.alert = false});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
|
|
return Stack(
|
|
children: [
|
|
Container(
|
|
|
|
decoration: BoxDecoration(
|
|
color: Colors.transparent,
|
|
border: Border.symmetric(
|
|
vertical: BorderSide(
|
|
color: outlineColor,
|
|
width: 2
|
|
)
|
|
),
|
|
),
|
|
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 10,
|
|
vertical: 5
|
|
),
|
|
|
|
width: double.infinity,
|
|
|
|
|
|
child: Row(
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Container(
|
|
|
|
constraints: const BoxConstraints(
|
|
maxWidth: 260
|
|
),
|
|
|
|
child: Text(
|
|
label,
|
|
style: GoogleFonts.teko(
|
|
fontSize: 25,
|
|
color: outlineColor,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
|
|
if (alert)
|
|
const SizedBox(
|
|
width: 4,
|
|
),
|
|
|
|
if (alert)
|
|
Icon(
|
|
Icons.error,
|
|
color: Colors.red.shade800,
|
|
size: 25,
|
|
shadows: const [
|
|
Shadow(
|
|
color: Colors.black,
|
|
blurRadius: 2,
|
|
)
|
|
],
|
|
)
|
|
],
|
|
),
|
|
Expanded(
|
|
child: Container(
|
|
|
|
alignment: Alignment.centerRight,
|
|
|
|
child: Text(
|
|
(index+1).toString(),
|
|
style: GoogleFonts.teko(
|
|
fontSize: 25,
|
|
color: outlineColor,
|
|
)
|
|
),
|
|
),
|
|
)
|
|
],
|
|
)
|
|
|
|
),
|
|
|
|
Positioned.fill(
|
|
child: ElevatedButton(
|
|
onPressed: () {
|
|
onPressed();
|
|
},
|
|
|
|
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"),
|
|
),
|
|
)
|
|
],
|
|
);
|
|
|
|
}
|
|
} |