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'; import 'package:shadcn_ui/shadcn_ui.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.queueAnnouncementByInfoIndex( 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( 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(); liveInformation.announcementModule.queueAnnouncementByRouteVariant( routeVariant: liveInformation.getRouteVariant()! ); }, child: Text("Announce current destination"), ), ], ), ), ], ), ) ); } } class Speedometer extends StatefulWidget { @override State createState() => _SpeedometerState(); } class _SpeedometerState extends State { 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 announcements; final String label; const AnnouncementPicker({super.key, required this.backgroundColor, required this.outlineColor, required this.announcements, this.label = ""}); @override State createState() => _AnnouncementPickerState(); } class _AnnouncementPickerState extends State { List 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: 1 ), borderRadius: BorderRadius.circular(8) ), padding: const EdgeInsets.all(4), width: double.infinity, constraints: const BoxConstraints( maxWidth: 400 ), child: Column( children: [ Container( decoration: BoxDecoration( color: widget.backgroundColor, border: Border.all( color: widget.outlineColor, width: 1 ), borderRadius: BorderRadius.circular(4) ), // height: 100, child: Column( children: [ 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: 1, 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: 1, 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: 1, 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: 1, color: widget.outlineColor, ), Container( height: 40, decoration: BoxDecoration( color: widget.backgroundColor, ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Container( padding: const EdgeInsets.symmetric( horizontal: 8, ), child: Text( widget.label, style: ShadTheme.of(context).textTheme.h4.copyWith( shadows: [ Shadow( color: Colors.blueAccent.shade700, blurRadius: 8 ) ], color: Colors.blueAccent.shade700 ) ), ), Expanded(child: Container()), Container( width: 40, height: 40, decoration: BoxDecoration( color: widget.backgroundColor, border: Border.symmetric( vertical: BorderSide( color: widget.outlineColor, width: 1 ) ), ), 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: 1 ) ), ), 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(""), ), ) ], ), ) ), ] ), ), ], ), ), ] ), ); } } class StopAnnouncementPicker extends AnnouncementPicker { final BusRouteVariant routeVariant; StopAnnouncementPicker({ Key? key, required this.routeVariant, required Color backgroundColor, required Color outlineColor, String label = "Stops" }) : 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, ) ], label: label ); } 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, ), 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: Colors.white, ), 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"), ), ) ], ); } }