From fc4d3ef89877ba82483f1a7fcbb8335a7b3b4be3 Mon Sep 17 00:00:00 2001 From: ImBenji Date: Wed, 1 May 2024 12:29:59 +0100 Subject: [PATCH] Alot of changes --- Dockerfile | 61 ++- android/app/build.gradle | 2 +- android/app/src/main/AndroidManifest.xml | 2 + assets/version.txt | 1 + lib/backend/live_information.dart | 26 +- lib/backend/modules/announcement.dart | 30 +- lib/backend/modules/commands.dart | 4 + lib/main.dart | 12 +- lib/pages/home.dart | 376 +++++++------- lib/pages/initial_startup.dart | 71 ++- lib/pages/settings.dart | 332 ++++++++++--- lib/pages/tfl_dataset_test.dart | 130 +++-- lib/remaster/InitialStartup.dart | 465 ++++++++++++++++++ lib/remaster/RemasteredMain.dart | 26 + lib/remaster/dashboard.dart | 365 ++++++++++++++ lib/tfl_datasets.dart | 31 +- lib/utils/audio wrapper.dart | 19 +- lib/utils/web_workarrounds.dart | 15 + macos/Flutter/GeneratedPluginRegistrant.swift | 4 +- pubspec.lock | 197 ++++++-- pubspec.yaml | 8 +- .../flutter/generated_plugin_registrant.cc | 6 +- windows/flutter/generated_plugins.cmake | 2 +- 23 files changed, 1755 insertions(+), 430 deletions(-) create mode 100644 assets/version.txt create mode 100644 lib/remaster/InitialStartup.dart create mode 100644 lib/remaster/RemasteredMain.dart create mode 100644 lib/remaster/dashboard.dart create mode 100644 lib/utils/web_workarrounds.dart diff --git a/Dockerfile b/Dockerfile index 6c6b147..2bb3d9e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,36 +1,47 @@ -# Install Operating system and dependencies -FROM ubuntu:20.04 +# Environemnt to install flutter and build web +FROM debian:latest AS build-env -ENV DEBIAN_FRONTEND noninteractive +# install all needed stuff +RUN apt-get update +RUN apt-get install -y curl git unzip -RUN apt-get update -RUN apt-get install -y curl git wget unzip libgconf-2-4 gdb libstdc++6 libglu1-mesa fonts-droid-fallback lib32stdc++6 python3 -RUN apt-get clean +# define variables +ARG FLUTTER_SDK=/usr/local/flutter +ARG FLUTTER_VERSION=3.19.5 +ARG APP=/app/ -# download Flutter SDK from Flutter Github repo -RUN git clone https://github.com/flutter/flutter.git /usr/local/flutter +#clone flutter +RUN git clone https://github.com/flutter/flutter.git $FLUTTER_SDK +# change dir to current flutter folder and make a checkout to the specific version +RUN cd $FLUTTER_SDK && git fetch && git checkout $FLUTTER_VERSION -# Set flutter environment path -ENV PATH="/usr/local/flutter/bin:/usr/local/flutter/bin/cache/dart-sdk/bin:${PATH}" +# setup the flutter path as an enviromental variable +ENV PATH="$FLUTTER_SDK/bin:$FLUTTER_SDK/bin/cache/dart-sdk/bin:${PATH}" -# Run flutter doctor -RUN flutter doctor +# Start to run Flutter commands +# doctor to see if all was installes ok +RUN flutter doctor -v -# Enable flutter web -RUN flutter channel master -RUN flutter upgrade -RUN flutter config --enable-web +# create folder to copy source code +RUN mkdir $APP +# copy source code to folder +COPY . $APP +# stup new folder as the working directory +WORKDIR $APP -# Copy files to container and build -RUN mkdir /app/ -COPY . /app/ -WORKDIR /app/ +# Run build: 1 - clean, 2 - pub get, 3 - build web +RUN flutter clean +RUN flutter pub get RUN flutter build web -# Record the exposed port -EXPOSE 5000 +# once heare the app will be compiled and ready to deploy -# make server startup script executable and start the web server -RUN ["chmod", "+x", "/app/server/server.sh"] +# use nginx to deploy +FROM nginx:1.25.2-alpine -ENTRYPOINT [ "/app/server/server.sh"] \ No newline at end of file +# copy the info of the builded web app to nginx +COPY --from=build-env /app/build/web /usr/share/nginx/html + +# Expose and run nginx +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index 988d667..13f2359 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -25,7 +25,7 @@ if (flutterVersionName == null) { android { namespace "com.imbenji.bus_infotainment" compileSdkVersion 34 - ndkVersion flutter.ndkVersion + ndkVersion "25.1.8937393" compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 90fc3f8..8d040ad 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -35,5 +35,7 @@ + + diff --git a/assets/version.txt b/assets/version.txt new file mode 100644 index 0000000..d183d4a --- /dev/null +++ b/assets/version.txt @@ -0,0 +1 @@ +0.16.0 \ No newline at end of file diff --git a/lib/backend/live_information.dart b/lib/backend/live_information.dart index bf59652..e598910 100644 --- a/lib/backend/live_information.dart +++ b/lib/backend/live_information.dart @@ -67,19 +67,17 @@ class LiveInformation { syncedTimeModule = SyncedTimeModule(); announcementModule = AnnouncementModule(); - // Tracker module is not supported on desktop - if (defaultTargetPlatform != TargetPlatform.windows && defaultTargetPlatform != TargetPlatform.linux && defaultTargetPlatform != TargetPlatform.macOS) { - // Tracker module is not supported on web - Permission.location.request().then((value) { - if (value.isGranted) { - trackerModule = TrackerModule(); - } - }); - } + initTrackerModule(); print("Initialised LiveInformation"); } + Future initTrackerModule() async { + if (await Permission.location.isGranted) { + trackerModule = TrackerModule(); + } + } + // Auth AuthAPI auth = AuthAPI(); @@ -120,7 +118,15 @@ class LiveInformation { // Cache/Load the audio files await announcementModule .announcementCache - .loadAnnouncementsFromBytes(await LiveInformation().announcementModule.getBundleBytes(), audioFiles); + .loadAnnouncementsFromBytes(await LiveInformation().announcementModule.getBundleBytes(), [ + ...audioFiles, + if (!routeVariant.busRoute.routeNumber.toLowerCase().startsWith("ul")) + "R_${routeVariant.busRoute.routeNumber}_001.mp3" + else + "R_RAIL_REPLACEMENT_SERVICE_001.mp3", + + ] + ); } // Public methods diff --git a/lib/backend/modules/announcement.dart b/lib/backend/modules/announcement.dart index 623c9c9..93da25e 100644 --- a/lib/backend/modules/announcement.dart +++ b/lib/backend/modules/announcement.dart @@ -88,10 +88,10 @@ class AnnouncementModule extends InfoModule { if (currentAnnouncement!.audioSources.isNotEmpty) { - audioPlayer.loadSource(AudioWrapperAssetSource("assets/audio/5-seconds-of-silence.mp3")); - audioPlayer.play(); - await Future.delayed(const Duration(milliseconds: 300)); - audioPlayer.stop(); + // audioPlayer.loadSource(AudioWrapperAssetSource("assets/audio/5-seconds-of-silence.mp3")); + // audioPlayer.play(); + // await Future.delayed(const Duration(milliseconds: 300)); + // audioPlayer.stop(); // try { for (AudioWrapperSource source in currentAnnouncement!.audioSources) { @@ -100,6 +100,8 @@ class AnnouncementModule extends InfoModule { Duration? duration = await audioPlayer.play(); await Future.delayed(duration!); + audioPlayer.stop(); + // await Future.delayed(const Duration(milliseconds: 100)); if (currentAnnouncement?.audioSources.last != source) { await Future.delayed(const Duration(milliseconds: 100)); } @@ -256,25 +258,37 @@ class AnnouncementModule extends InfoModule { ); return; } + print("Checkpoint 4"); + + print(routeVariant); + print("Checkpoint 4.1"); + String routeNumber = routeVariant.busRoute.routeNumber; - String destination = routeVariant.destination!.destination; + print("Checkpoint 4.2"); + + String destination = routeVariant.destination?.destination ?? "NullPointerException"; + print("Destination: $destination"); + print("Checkpoint 4.3"); + String audioRoute = "R_${routeVariant.busRoute.routeNumber}_001.mp3"; - + print("Checkpoint 5"); await announcementCache.loadAnnouncementsFromBytes(await getBundleBytes(), [audioRoute, "R_RAIL_REPLACEMENT_SERVICE_001.mp3"]); - + print("Checkpoint 6"); AudioWrapperSource sourceRoute = !routeNumber.toLowerCase().startsWith("ul") ? AudioWrapperByteSource(announcementCache[audioRoute]!) : AudioWrapperByteSource(announcementCache["R_RAIL_REPLACEMENT_SERVICE_001.mp3"]!); + print("Checkpoint 6.1"); AudioWrapperSource sourceDestination = AudioWrapperByteSource(await routeVariant.destination!.getAudioBytes()); - + print("Checkpoint 7"); AnnouncementQueueEntry announcement = AnnouncementQueueEntry( displayText: "$routeNumber to $destination", audioSources: [sourceRoute, AudioWrapperAssetSource("audio/to_destination.wav"), sourceDestination], scheduledTime: scheduledTime ); + print("Checkpoint 8"); queue.add(announcement); } diff --git a/lib/backend/modules/commands.dart b/lib/backend/modules/commands.dart index 516ec20..5be6b25 100644 --- a/lib/backend/modules/commands.dart +++ b/lib/backend/modules/commands.dart @@ -129,6 +129,8 @@ class CommandModule extends InfoModule { else if (args[0].startsWith("dest")) { // announce destination + print("Checkpoint 1"); + String routeNumber = args[1]; int routeVariantIndex = int.parse(args[2]); @@ -139,9 +141,11 @@ class CommandModule extends InfoModule { } } catch (e) {} + print("Checkpoint 2"); BusRoute route = LiveInformation().busSequences.routes[routeNumber]!; BusRouteVariant routeVariant = route.routeVariants.values.toList()[routeVariantIndex]; + print("Checkpoint 3"); liveInformation.announcementModule.queueAnnouncementByRouteVariant( routeVariant: routeVariant, scheduledTime: scheduledTime, diff --git a/lib/main.dart b/lib/main.dart index 1546c82..a301b5d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,10 +4,13 @@ import 'package:bus_infotainment/pages/audio_cache_test.dart'; import 'package:bus_infotainment/pages/initial_startup.dart'; import 'package:bus_infotainment/pages/tfl_dataset_test.dart'; import 'package:bus_infotainment/backend/live_information.dart'; +import 'package:bus_infotainment/remaster/RemasteredMain.dart'; +import 'package:bus_infotainment/remaster/dashboard.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:window_manager/window_manager.dart'; +import 'package:bus_infotainment/remaster/InitialStartup.dart' as remaster; void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -55,14 +58,20 @@ class MyApp extends StatelessWidget { // Permission.location.onGrantedCallback(() => null).request(); + if (true) { + return RemasteredApp(); + } + print("Window size: ${MediaQuery.of(context).size}"); return MaterialApp( title: 'Flutter Demo', theme: ThemeData( + useMaterial3: true, brightness: Brightness.light, /* light theme settings */ ), darkTheme: ThemeData( + useMaterial3: true, brightness: Brightness.dark, // colorScheme: ColorScheme.dark(), colorScheme: ColorScheme.fromSeed( @@ -77,8 +86,9 @@ class MyApp extends StatelessWidget { '/home': (context) => const MyHomePage(title: 'Flutter Demo Home Page'), '/audiocachetest': (context) => AudioCacheTest(), // '/': (context) => TfL_Dataset_Test(), - '/': (context) => InitialStartup(), + '/': (context) => remaster.InitialStartup(), '/application': (context) => TfL_Dataset_Test(), + '/dashboard': (context) => Dashboard(), }, ); diff --git a/lib/pages/home.dart b/lib/pages/home.dart index 8b58eae..7945bed 100644 --- a/lib/pages/home.dart +++ b/lib/pages/home.dart @@ -22,204 +22,206 @@ class pages_Home extends StatelessWidget { - child: Column( - children: [ - - Row( - - children: [ - - Speedometer(), - - ], - - ), - - Container( - height: 2, - color: Colors.white70, - ), - - SingleChildScrollView( - child: Column( + child: SingleChildScrollView( + child: Column( + children: [ + + Row( + 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 - ); - }, - ) - ], + + 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( - 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 { + + 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(); - 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"), - // ), - // - // ], - // - // ), - // - // ), - - ], + + 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"), + // ), + // + // ], + // + // ), + // + // ), + + ], + ), ), - ), - ], + ], + ), ) ); diff --git a/lib/pages/initial_startup.dart b/lib/pages/initial_startup.dart index cfdd048..7761f5e 100644 --- a/lib/pages/initial_startup.dart +++ b/lib/pages/initial_startup.dart @@ -1,7 +1,12 @@ +import 'dart:convert'; + +import 'package:bus_infotainment/backend/live_information.dart'; import 'package:bus_infotainment/pages/settings.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:shared_preferences/shared_preferences.dart'; class InitialStartup extends StatefulWidget { @@ -9,11 +14,43 @@ class InitialStartup extends StatefulWidget { State createState() => _InitialStartupState(); } -const String Version = "0.2.0"; +// Get the current version from /assets/version.txt +Future GetVersion() async { + return await rootBundle.loadString("assets/version.txt"); +} class _InitialStartupState extends State { bool AllowPassage = false; + + @override + void initState() { + // TODO: implement initState + super.initState(); + + SharedPreferences.getInstance().then((prefs) { + String? base64String = prefs.getString("announcements"); + + if (base64String != null) { + print("Found previois announcement file"); + Uint8List ByteList = base64Decode(base64String); + + LiveInformation().announcementModule.setBundleBytes(ByteList); + + Navigator.pushNamed(context, "/application"); + + + + } else { + print("No previous announcement file found"); + } + + }); + + + + } + @override Widget build(BuildContext context) { // TODO: implement build @@ -28,6 +65,8 @@ class _InitialStartupState extends State { children: [ + PermissionsSetup(), + SizedBox( height: 8, ), @@ -79,13 +118,29 @@ class _InitialStartupState extends State { height: 8, ), - Text( - "Version $Version", - style: GoogleFonts.inter( - fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.grey - ), + FutureBuilder( + future: GetVersion(), + builder: (context, snapshot){ + if (snapshot.hasData){ + return Text( + "Version ${snapshot.data}", + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Colors.grey + ), + ); + } else { + return Text( + "Version -.-.-", + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Colors.grey + ), + ); + } + }, ) ], diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index 23d0144..1b86d23 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -1,5 +1,6 @@ +import 'dart:convert'; import 'dart:io'; import 'package:bus_infotainment/audio_cache.dart'; @@ -7,9 +8,12 @@ import 'package:bus_infotainment/backend/live_information.dart'; import 'package:bus_infotainment/backend/modules/commands.dart'; import 'package:bus_infotainment/utils/delegates.dart'; import 'package:file_picker/file_picker.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:text_scroll/text_scroll.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -184,10 +188,24 @@ class _AnnouncementUploadState extends State { AnnouncementCache cache = LiveInformation().announcementModule.announcementCache; - LiveInformation().announcementModule.setBundleBytes(result.files[0].bytes!); + late Uint8List bytes; + + if (kIsWeb) { + bytes = result.files.single.bytes!; + } else { + File file = File(result.files.single.path!); + + bytes = file.readAsBytesSync(); + } + + + LiveInformation().announcementModule.setBundleBytes(bytes); + + + // load a random announcement to ensure that the file is usable - await cache.loadAnnouncementsFromBytes(result.files[0].bytes!, ["S_WALTHAMSTOW_CENTRAL_001.mp3"]); + await cache.loadAnnouncementsFromBytes(bytes, ["S_WALTHAMSTOW_CENTRAL_001.mp3"]); print("Loaded announcements"); @@ -195,17 +213,60 @@ class _AnnouncementUploadState extends State { }); + if (!kIsWeb) { + + // Use shared preferences to store the file location + SharedPreferences prefs = await SharedPreferences.getInstance(); + + prefs.setString("AnnouncementsFileLocation", result.files.single.path!); + } + widget.onUploaded(); } else { // User canceled the picker } + } + + @override + void initState() { + // TODO: implement initState + super.initState(); + + if (!kIsWeb) { + SharedPreferences.getInstance().then((prefs) async { + String FileLocation = prefs.getString("AnnouncementsFileLocation")!; + + File file = File(FileLocation); + + Uint8List bytes = file.readAsBytesSync(); + + LiveInformation().announcementModule.setBundleBytes(bytes); + + AnnouncementCache cache = LiveInformation().announcementModule.announcementCache; + await cache.loadAnnouncementsFromBytes(bytes, ["S_WALTHAMSTOW_CENTRAL_001.mp3"]); + + setState(() { + + }); + + print("Loaded announcemends from SharedPrefs"); + + widget.onUploaded(); + + }); + } + + + } @override Widget build(BuildContext context) { - // TODO: implement build + + bool checkPassed = LiveInformation().announcementModule.announcementCache.keys.length != 0; + return Container( decoration: BoxDecoration( border: Border.all( @@ -224,77 +285,61 @@ class _AnnouncementUploadState extends State { child: Column( children: [ + if (!checkPassed) + Row( - Row( + children: [ - children: [ - - Icon( - Icons.error, - color: Colors.red, - size: 18, - ), - - SizedBox( - width: 4, - ), - - Transform.translate( - offset: Offset(0, 0), - child: Text( - "IMPORTANT", - style: GoogleFonts.interTight( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.white70, - letterSpacing: 0.1, - ), - ), - ) - - ], - - ), - - Row( - - children: [ - - if (LiveInformation().announcementModule.announcementCache.keys.length == 0) Icon( Icons.error, color: Colors.red, size: 18, - ) - else + ), + + SizedBox( + width: 4, + ), + + + Transform.translate( + offset: Offset(0, 0), + child: Text( + "NO ANNOUNCEMENTS LOADED", + style: GoogleFonts.interTight( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.white70, + letterSpacing: 0.1, + ), + ), + ) + + + + + ], + + ) + else + Row( + + children: [ + Icon( - Icons.check, + Icons.check_box, color: Colors.green, size: 18, ), - SizedBox( - width: 4, - ), + SizedBox( + width: 4, + ), + - if (LiveInformation().announcementModule.announcementCache.keys.length == 0) Transform.translate( offset: Offset(0, 0), child: Text( - "No announcements", - style: GoogleFonts.interTight( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.white70, - letterSpacing: 0.1, - ), - ), - ) - else - Transform.translate( - offset: Offset(0, 0), - child: Text( - "Announcements loaded successfully", + "ANNOUNCEMENTS LOADED", style: GoogleFonts.interTight( fontSize: 18, fontWeight: FontWeight.bold, @@ -303,10 +348,9 @@ class _AnnouncementUploadState extends State { ), ), ) + ], - ], - - ), + ), SizedBox( height: 8, @@ -368,6 +412,23 @@ class _AnnouncementUploadState extends State { ), ), + if (kIsWeb) + SizedBox( + height: 8, + ), + + if (kIsWeb) + Text( + "Announcements uploaded on web are not persistent, and will need to be re-uploaded each time the site is loaded.", + style: GoogleFonts.interTight( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Colors.white70, + letterSpacing: 0.1, + height: 1 + ), + ), + SizedBox( height: 8, ), @@ -407,6 +468,153 @@ class _AnnouncementUploadState extends State { } } +class PermissionsSetup extends StatefulWidget { + + @override + State createState() => _PermissionsSetupState(); +} + +class _PermissionsSetupState extends State { + List _hasPermissions = []; + + bool get hasPermissions { + return !_hasPermissions.contains(false) && _hasPermissions.length > 0; + } + + Future requestPermission() async { + _hasPermissions = []; + + print("Requesting location permission"); + if (!await Permission.location.isGranted){ + PermissionStatus locationStatus = await Permission.location.request(); + if (locationStatus.isGranted) { + _hasPermissions.add(true); + LiveInformation().initTrackerModule(); + } else { + _hasPermissions.add(false); + } + } else { + _hasPermissions.add(true); + } + print("Gotten result for location permission"); + + if (!kIsWeb){ + print("Requesting storage permissions"); + PermissionStatus fileStatus = await Permission.manageExternalStorage.request(); + + if (fileStatus.isGranted) { + _hasPermissions.add(true); + } else { + _hasPermissions.add(false); + } + } else { + _hasPermissions.add(true); + } + + print("Permissions: $_hasPermissions"); + + setState(() { + + }); + + + + } + + @override + Widget build(BuildContext context) { + + return Container( + decoration: BoxDecoration( + border: Border.all( + color: Colors.white70, + width: 2 + ), + ), + + margin: EdgeInsets.symmetric( + horizontal: 8 + ), + padding: EdgeInsets.all(8), + + child: Column( + + children: [ + + Row( + + children: [ + + Icon( + hasPermissions ? Icons.check_box : Icons.error, + color: hasPermissions ? Colors.green : Colors.red, + size: 18, + ), + + SizedBox( + width: 4, + ), + + Transform.translate( + offset: Offset(0, 0), + child: Text( + hasPermissions ? "PERMISSIONS GRANTED" : "MISSING PERMISSIONS", + style: GoogleFonts.interTight( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.white70, + letterSpacing: 0.1, + ), + ), + ), + + + + ], + + ), + + SizedBox( + height: 8, + ), + + SizedBox( + height: 32, + width: double.infinity, + child: ElevatedButton( + onPressed: (){ + requestPermission(); + }, + + // make the corner radius 4, background color match the theme, and text colour white, fill to width of parent + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.primary, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4) + ), + ), + + child: Text( + "Request permissions", + style: GoogleFonts.interTight( + fontSize: 15, + fontWeight: FontWeight.w600, + color: Colors.white, + letterSpacing: 0.5 + ) + ) + ), + ) + + ], + + ), + + ); + } +} + enum _LoginType { login, signup diff --git a/lib/pages/tfl_dataset_test.dart b/lib/pages/tfl_dataset_test.dart index 556683b..0978a54 100644 --- a/lib/pages/tfl_dataset_test.dart +++ b/lib/pages/tfl_dataset_test.dart @@ -36,7 +36,7 @@ class TfL_Dataset_TestState extends State { pages_Home(), pages_Routes(), pages_Display(this), - pages_Settings(), + pages_Settings() ]; } @@ -48,6 +48,11 @@ class TfL_Dataset_TestState extends State { super.initState(); Future.delayed(Duration.zero, () async { + + // if in debug mode skip all this + if (kDebugMode) + return; + try { await LiveInformation().announcementModule.getBundleBytes(); } catch (e) { @@ -90,80 +95,6 @@ class TfL_Dataset_TestState extends State { return Scaffold( - - // appBar: !hideUI ? AppBar( - // - // surfaceTintColor: Colors.transparent, - // - // title: Container( - // - // child: Column( - // - // crossAxisAlignment: CrossAxisAlignment.start, - // - // children: [ - // - // Text( - // "Bus Infotainment", - // style: GoogleFonts.teko( - // fontSize: 25, - // fontWeight: FontWeight.bold, - // color: Colors.white, - // height: 1, - // ), - // ), - // - // Row( - // - // children: [ - // - // Text( - // "Selected: ", - // style: GoogleFonts.teko( - // fontSize: 20, - // fontWeight: FontWeight.w600, - // color: Colors.white, - // height: 1, - // ), - // ), - // - // 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: 20, - // fontWeight: FontWeight.w500, - // color: Colors.orange.shade900, - // ), - // ), - // - // ) - // else - // Text( - // "None", - // style: GoogleFonts.teko( - // fontSize: 20, - // fontWeight: FontWeight.w500, - // color: Colors.white, - // ), - // ), - // ], - // - // ) - // - // ], - // - // ), - // ), - // ) : null, body: Container( width: double.infinity, @@ -259,6 +190,55 @@ class TfL_Dataset_TestState extends State { children: [ + if (false) + Expanded( + child: Stack( + children: [ + Container( + alignment: Alignment.center, + margin: const EdgeInsets.all(10), + child: Text( + "Dashboard", + style: GoogleFonts.teko( + color: Colors.white70, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ) + ), + Positioned.fill( + child: ElevatedButton( + onPressed: () { + setState(() { + Navigator.pushNamed(context, "/dashboard"); + }); + }, + + style: ElevatedButton.styleFrom( + backgroundColor: Colors.transparent, + shadowColor: Colors.transparent, + surfaceTintColor: Colors.transparent, + foregroundColor: Colors.transparent, + + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(0), + ), + + ), + + child: Container() + ), + ) + ], + ), + ), + + Container( + width: 2, + height: double.infinity, + color: Colors.white70, + ), + Expanded( child: Stack( children: [ diff --git a/lib/remaster/InitialStartup.dart b/lib/remaster/InitialStartup.dart new file mode 100644 index 0000000..5282013 --- /dev/null +++ b/lib/remaster/InitialStartup.dart @@ -0,0 +1,465 @@ + + +import 'dart:io'; + +import 'package:bus_infotainment/audio_cache.dart'; +import 'package:bus_infotainment/backend/live_information.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:shadcn_ui/shadcn_ui.dart'; + +class InitialStartup extends StatefulWidget { + + @override + State createState() => _InitialStartupState(); +} + +class _InitialStartupState extends State { + int _page = 0; + + @override + Widget build(BuildContext context) { + + if (_page == 0 && !kIsWeb) { + _page = 1; + } + + return Scaffold( + + body: [ + _page1(this), + _page2(this), + _page3(this) + ][_page], + + ); + + } + + void setPage(int page) { + setState(() { + _page = page; + }); + } +} + +abstract class InitialStartupPage extends StatefulWidget { + + _InitialStartupState parent; + + InitialStartupPage(this.parent); + +} + +// Cookies page - only for web +class _page1 extends InitialStartupPage { + + _page1(super.parent); + + @override + State<_page1> createState() => _page1State(); +} + +class _page1State extends State<_page1> { + @override + Widget build(BuildContext context) { + // TODO: implement build + return Container( + + padding: EdgeInsets.all(32), + + alignment: Alignment.center, + + child: Column( + + mainAxisSize: MainAxisSize.min, + + children: [ + + Text( + "Cookies", + style: TextStyle( + fontSize: 32, + fontWeight: FontWeight.w600, + ) + ), + + Text( + "This website uses first-party cookies for the storage of user data. These cookies are necessary for the functioning of the site and help us provide you with a better browsing experience. By using this website, you consent to the use of these cookies.", + textAlign: TextAlign.center, + ), + + SizedBox( + height: 8, + ), + + ShadButton( + onPressed: () { + widget.parent.setPage(1); + }, + text: Text( + "I understand and agree" + ), + ) + + + + ], + + ) + + ); + } +} + +// Permission request page +class _page2 extends InitialStartupPage { + + _page2(super.parent); + + @override + State<_page2> createState() => _page2State(); +} + +class _page2State extends State<_page2> { + + Future _allPermissionsGranted() async { + + List perms = []; + + perms.addAll([ + await Permission.manageExternalStorage.isGranted, + await Permission.location.isGranted + ]); + + return !perms.contains(false); + } + + @override + Widget build(BuildContext context) { + + return Container( + + padding: EdgeInsets.all(16), + + alignment: Alignment.center, + + + + child: SizedBox( + + width: double.infinity, + + child: Column( + + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + + children: [ + + Text( + "Permissions", + style: TextStyle( + fontSize: 32, + fontWeight: FontWeight.w600, + ) + ), + + SizedBox( + height: 16, + ), + + ShadCard( + width: double.infinity, + title: Text( + "Location", + ), + description: Text( + "Your location is required for automatically updating your nearest bus stop." + ), + content: Container( + child: Column( + children: [ + + SizedBox( + height: 4, + ), + + FutureBuilder( + future: Permission.location.isGranted, + builder: (context, val) { + bool isEnabled = true; + String text = "Request permission"; + Color color = Colors.white; + + if (val.hasData) { + isEnabled = !val.data!; + } + if (!isEnabled) { + text = "Permission granted!"; + color = Colors.green.shade400; + } + + return ShadButton( + text: Text(text), + onPressed: () async { + await Permission.location.request(); + setState(() { + + }); + }, + enabled: isEnabled, + backgroundColor: color, + ); + }, + ), + ], + ), + ), + ), + + SizedBox( + height: 16, + ), + + ShadCard( + width: double.infinity, + title: Text( + "Storage", + ), + description: Text( + "Storage access is required to access recorded announcements." + ), + content: Container( + child: Column( + children: [ + + SizedBox( + height: 4, + ), + + FutureBuilder( + future: Permission.manageExternalStorage.isGranted, + builder: (context, val) { + bool isEnabled = true; + String text = "Request permission"; + Color color = Colors.white; + + if (val.hasData) { + isEnabled = !val.data!; + } + if (!isEnabled) { + text = "Permission granted!"; + color = Colors.green.shade400; + } + + return ShadButton( + text: Text(text), + onPressed: () async { + await Permission.manageExternalStorage.request(); + setState(() { + + }); + }, + enabled: isEnabled, + backgroundColor: color, + ); + }, + ) + ], + ), + ), + ), + + SizedBox( + height: 16, + ), + + FutureBuilder( + future: _allPermissionsGranted(), + builder: (context, val) { + bool isEnabled = true; + String text = "Continue"; + Color color = Colors.white; + + if (val.hasData) { + isEnabled = val.data!; + } + if (!isEnabled) { + text = "Grant all permissions before continuing"; + } + + return ShadButton( + text: Text(text), + onPressed: () async { + widget.parent.setPage(2); + }, + enabled: isEnabled, + backgroundColor: color, + width: double.infinity, + ); + }, + ) + + ], + ), + ) + + ); + + } +} + +class _page3 extends InitialStartupPage { + _page3(super.parent); + + @override + State<_page3> createState() => _page3State(); +} + +class _page3State extends State<_page3> { + + Future _announcementsUploaded() async { + try { + Uint8List bytes = await LiveInformation().announcementModule.getBundleBytes(); + return true; + } catch (e) { + return false; + } + + } + + @override + Widget build(BuildContext context) { + // TODO: implement build + return Container( + + padding: EdgeInsets.all(16), + + alignment: Alignment.center, + + child: SizedBox( + + width: double.infinity, + + child: Column( + + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + + children: [ + + Text( + "Prerequisites", + style: TextStyle( + fontSize: 32, + fontWeight: FontWeight.w600, + ) + ), + + SizedBox( + height: 16, + ), + + ShadCard( + width: double.infinity, + title: Text( + "Announcement files", + ), + description: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "You are required to upload the London iBus announcement files. These files can be acquire by submitting a request to TfL under the Freedom of Information Act (2000)" + ), + SizedBox( + height: 4, + ), + Text( + "Please upload a zip file." + ) + ], + ), + content: Container( + child: Column( + children: [ + + SizedBox( + height: 4, + ), + + FutureBuilder( + future: _announcementsUploaded(), + builder: (context, val) { + bool isEnabled = true; + String text = "Upload file"; + Color color = Colors.white; + + if (val.hasData) { + isEnabled = !val.data!; + } + if (!isEnabled) { + text = "File uploaded!"; + color = Colors.green.shade400; + } + + return ShadButton( + text: Text(text), + onPressed: () async { + + FilePickerResult? result = await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: [ + "zip" + ] + ); + + if (result != null) { + late Uint8List bytes; + + if (kIsWeb) { + bytes = result.files.single.bytes!; + } else { + File file = File(result.files.single.path!); + + bytes = file.readAsBytesSync(); + } + + LiveInformation().announcementModule.setBundleBytes(bytes); + + AnnouncementCache cache = LiveInformation().announcementModule.announcementCache; + + // load a random announcement to ensure that the file is usable + await cache.loadAnnouncementsFromBytes(bytes, ["S_WALTHAMSTOW_CENTRAL_001.mp3"]); + + setState(() { + + }); + + } + + + }, + enabled: isEnabled, + backgroundColor: color, + ); + }, + ) + ], + ), + ), + ), + + ], + + ), + ) + + ); + } +} \ No newline at end of file diff --git a/lib/remaster/RemasteredMain.dart b/lib/remaster/RemasteredMain.dart new file mode 100644 index 0000000..6ef19bc --- /dev/null +++ b/lib/remaster/RemasteredMain.dart @@ -0,0 +1,26 @@ + + + +import 'package:bus_infotainment/remaster/InitialStartup.dart'; +import 'package:flutter/material.dart'; +import 'package:shadcn_ui/shadcn_ui.dart'; + +class RemasteredApp extends StatelessWidget { + + @override + Widget build(BuildContext context) { + // TODO: implement build + return ShadApp( + darkTheme: ShadThemeData( + brightness: Brightness.dark, + colorScheme: ShadSlateColorScheme.dark(), + ), + + routes: { + '/': (context) => InitialStartup() + }, + + ); + } + +} \ No newline at end of file diff --git a/lib/remaster/dashboard.dart b/lib/remaster/dashboard.dart new file mode 100644 index 0000000..5d47c60 --- /dev/null +++ b/lib/remaster/dashboard.dart @@ -0,0 +1,365 @@ + +import 'package:bus_infotainment/pages/components/ibus_display.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'dart:io' show Platform; + +import 'package:flutter/widgets.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:text_scroll/text_scroll.dart'; + +Color rgb(int r, int g, int b) { + return Color.fromRGBO(r, g, b, 1); +} + +class Dashboard extends StatelessWidget { + + @override + Widget build(BuildContext context) { + // TODO: implement build + return Scaffold( + + backgroundColor: Colors.grey.shade900, + + bottomNavigationBar: ((defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.iOS)) ? null : BottomAppBar( + + color: Colors.grey.shade800, + + child: Row( + children: [ + IconButton( + icon: Icon(Icons.home), + onPressed: () { + Navigator.pushNamed(context, "/dashboard"); + }, + ), + IconButton( + icon: Icon(Icons.search), + onPressed: () { + Navigator.pushNamed(context, "/search"); + }, + ), + IconButton( + icon: Icon(Icons.settings), + onPressed: () { + Navigator.pushNamed(context, "/settings"); + }, + ), + ], + ), + ), + + body: Column( + children: [ + + Container( + + margin: EdgeInsets.all(16), + padding: EdgeInsets.all(8), + + decoration: BoxDecoration( + + borderRadius: BorderRadius.circular(10), + + color: Colors.grey.shade800 + + ), + + // height: 100, + + child: ibus_display() + ), + + Expanded( + child: Row( + + children: [ + + if (false) + if (!(defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.iOS)) + NavigationRail( + + selectedIndex: 0, + + groupAlignment: 1, + + destinations: [ + + NavigationRailDestination( + icon: Icon(Icons.home), + label: Text("Dashboard"), + ), + + NavigationRailDestination( + icon: Icon(Icons.search), + label: Text("Routes") + ), + + NavigationRailDestination( + icon: Icon(Icons.settings), + label: Text("Settings") + ) + + ], + + ), + + Expanded( + child: Container( + + child: Column( + + children: [ + + Container( + + padding: EdgeInsets.symmetric( + horizontal: 16 + ), + + child: IntrinsicHeight( + child: Row( + + children: [ + + Expanded( + child: Container( + + decoration: BoxDecoration( + + borderRadius: BorderRadius.circular(10), + + color: Colors.grey.shade800 + + ), + + padding: EdgeInsets.all(16), + + child: IntrinsicWidth( + child: Column( + + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + + children: [ + + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + + children: [ + Text( + "Bus Route:", + style: GoogleFonts.interTight( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.white + ) + ), + + SizedBox( + width: 16, + ), + + Text( + "11", + style: GoogleFonts.interTight( + fontSize: 18, + fontWeight: FontWeight.normal, + color: Colors.white + ) + ) + ], + ), + Row( + // mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Destination:", + style: GoogleFonts.interTight( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.white + ) + ), + + const SizedBox( + width: 16, + ), + + Text( + "Fullham Broadway", + style: GoogleFonts.interTight( + fontSize: 18, + fontWeight: FontWeight.normal, + color: Colors.white + ) + ) + ], + ), + + const SizedBox( + height: 8, + ), + + Row( + // mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Next Stop:", + style: GoogleFonts.interTight( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.white + ) + ), + + const SizedBox( + width: 16, + ), + + Text( + "St Thomas Hospital / County Hall", + style: GoogleFonts.interTight( + fontSize: 18, + fontWeight: FontWeight.normal, + color: Colors.white + ), + overflow: TextOverflow.fade, + ) + ], + ), + + Row( + // mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Last Stop:", + style: GoogleFonts.interTight( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.white + ), + overflow: TextOverflow.fade, + ), + + const SizedBox( + width: 16, + ), + + Text( + "Fullham Town Hall", + style: GoogleFonts.interTight( + fontSize: 18, + fontWeight: FontWeight.normal, + color: Colors.white + ), + overflow: TextOverflow.fade, + ) + ], + ), + + + + ], + + ), + ) + + ), + ), + + // SizedBox( + // width: 16, + // ), + // + // Column( + // + // children: [ + // + // SizedBox( + // + // width: 150, + // + // child: FloatingActionButton( + // onPressed: () { + // + // }, + // + // backgroundColor: Colors.red, + // + // child: Text( + // "Bus Stopping", + // style: GoogleFonts.interTight( + // fontSize: 18, + // fontWeight: FontWeight.bold, + // color: Colors.white + // ) + // + // ), + // ), + // ), + // + // SizedBox( + // height: 16 + // ), + // + // SizedBox( + // + // width: 150, + // height: 100, + // + // child: FloatingActionButton( + // onPressed: () { + // + // }, + // + // backgroundColor: Colors.grey.shade600, + // + // child: Text( + // "Acknowledge Bus Stop", + // style: GoogleFonts.interTight( + // fontSize: 18, + // fontWeight: FontWeight.bold, + // color: Colors.white + // ), + // textAlign: TextAlign.center, + // + // ), + // ), + // ) + // + // ], + // + // ) + + ], + + ), + ), + ) + + + + ], + + ), + + ), + ) + + ], + + ), + ), + ], + ) + + ); + } + +} \ No newline at end of file diff --git a/lib/tfl_datasets.dart b/lib/tfl_datasets.dart index 23eee8f..13a35c2 100644 --- a/lib/tfl_datasets.dart +++ b/lib/tfl_datasets.dart @@ -7,7 +7,7 @@ import 'package:bus_infotainment/backend/live_information.dart'; import 'package:bus_infotainment/backend/modules/info_module.dart'; import 'package:bus_infotainment/utils/NameBeautify.dart'; import 'package:bus_infotainment/utils/OrdinanceSurveyUtils.dart'; -import 'package:csv/csv.dart'; +import 'package:fast_csv/csv_converter.dart'; import 'package:vector_math/vector_math.dart'; class BusSequences extends InfoModule { @@ -19,13 +19,16 @@ class BusSequences extends InfoModule { BusSequences.fromCSV(String destinationsCSV, String busSequencesCSV) { // Init the bus destinations - List> destinationRows = const CsvToListConverter().convert(destinationsCSV); + List> destinationRows = CsvConverter().convert(destinationsCSV); destinationRows.removeAt(0); + print("Destination rows: ${destinationRows.length}"); + for (int i = 0; i < destinationRows.length; i++) { try { List entries = destinationRows[i]; + // print("Parsing destination row $i: $entries"); String routeNumber = entries[0].toString(); @@ -50,9 +53,11 @@ class BusSequences extends InfoModule { } catch (e) {} } + print("Loaded ${destinations.length} destinations"); + // Init the bus routes - List> busSequenceRows = const CsvToListConverter().convert(busSequencesCSV); + List> busSequenceRows = CsvConverter().convert(busSequencesCSV); busSequenceRows.removeAt(0); for (int i = 0; i < busSequenceRows.length; i++) { @@ -61,20 +66,21 @@ class BusSequences extends InfoModule { { List entries = busSequenceRows[i]; + // print("Parsing bus sequence row $i: $entries"); String routeNumber = entries[0].toString(); BusRoute route = routes.containsKey(routeNumber) ? routes[routeNumber]! : BusRoute(routeNumber: routeNumber); - int routeVariant = entries[1]; + int routeVariant = int.parse(entries[1]); BusRouteStop stop = BusRouteStop(); stop.stopName = entries[6].toString(); stop.stopCode = entries[4].toString(); - stop.easting = entries[7]; - stop.northing = entries[8]; - stop.heading = entries[9] != "" ? entries[9] : -1; + stop.easting = int.parse(entries[7]); + stop.northing = int.parse(entries[8]); + stop.heading = int.parse(entries[9] != "" ? entries[9] : "-1"); BusRouteVariant variant = route.routeVariants.containsKey(routeVariant) ? route.routeVariants[routeVariant]! : BusRouteVariant(routeVariant: routeVariant, busRoute: route); @@ -90,6 +96,8 @@ class BusSequences extends InfoModule { } } + print("Loaded ${routes.length} routes"); + } } @@ -164,6 +172,9 @@ class BusRouteVariant { } + print("Nearest destination A: $nearestDestinationA, distance: $nearestDistanceA"); + print("Nearest destination B: $nearestDestinationB, distance: $nearestDistanceB"); + // Choose the nearest destination if (nearestDistanceA < nearestDistanceB) { _destination = nearestDestinationA; @@ -172,8 +183,14 @@ class BusRouteVariant { } } + return _destination; } + + @override + String toString() { + return 'BusRouteVariant{routeVariant: $routeVariant, busRoute: ${busRoute.routeNumber}, busStops: ${busStops.length}, destinations: ${busRoute.destinations.length}}'; + } } class BusRouteStop { diff --git a/lib/utils/audio wrapper.dart b/lib/utils/audio wrapper.dart index e4c0699..0dea43b 100644 --- a/lib/utils/audio wrapper.dart +++ b/lib/utils/audio wrapper.dart @@ -25,7 +25,7 @@ class AudioWrapper { print("AudioWrapper mode: $mode"); - // mode = AudioWrapper_Mode.Mobile; + mode = AudioWrapper_Mode.Mobile; } justaudio.AudioSource _convertSource_JustAudio(AudioWrapperSource source){ @@ -126,11 +126,11 @@ class AudioWrapper { } catch (e) {} _justAudio_AudioPlayer = justaudio.AudioPlayer(); } else if (mode == AudioWrapper_Mode.Mobile) { - _audioPlayer_AudioPlayer.stop(); + // _audioPlayer_AudioPlayer.stop(); try { - _audioPlayer_AudioPlayer.dispose(); + // _audioPlayer_AudioPlayer.dispose(); } catch (e) {} - _audioPlayer_AudioPlayer = audioplayers.AudioPlayer(); + // _audioPlayer_AudioPlayer = audioplayers.AudioPlayer(); } } @@ -142,12 +142,13 @@ class AudioWrapper { return AudioWrapper_State.NotPlaying; } } else { - if (_audioPlayer_AudioPlayer.state == audioplayers.PlayerState.playing){ - return AudioWrapper_State.Playing; - } else { - return AudioWrapper_State.NotPlaying; - } + // if (_audioPlayer_AudioPlayer.state == audioplayers.PlayerState.playing){ + // return AudioWrapper_State.Playing; + // } else { + // return AudioWrapper_State.NotPlaying; + // } } + return AudioWrapper_State.NotPlaying; } } diff --git a/lib/utils/web_workarrounds.dart b/lib/utils/web_workarrounds.dart new file mode 100644 index 0000000..faf340a --- /dev/null +++ b/lib/utils/web_workarrounds.dart @@ -0,0 +1,15 @@ + +import 'package:flutter/foundation.dart'; + +String WW_AssetSource(String location) { + if (kIsWeb) { + + // remove the first character if it is a '/' + if (location.startsWith('/')) { + location = location.substring(1); + } + + return "assets/$location"; + } + return location; +} \ No newline at end of file diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 58c12ea..22bd301 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,12 +8,12 @@ import Foundation import audio_session import audioplayers_darwin import device_info_plus -import flutter_tts import flutter_web_auth_2 import geolocator_apple import just_audio import package_info_plus import path_provider_foundation +import rive_common import screen_retriever import shared_preferences_foundation import url_launcher_macos @@ -24,12 +24,12 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) - FlutterTtsPlugin.register(with: registry.registrar(forPlugin: "FlutterTtsPlugin")) FlutterWebAuth2Plugin.register(with: registry.registrar(forPlugin: "FlutterWebAuth2Plugin")) GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + RivePlugin.register(with: registry.registrar(forPlugin: "RivePlugin")) ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) diff --git a/pubspec.lock b/pubspec.lock index d101904..a0272de 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.4.10" + args: + dependency: transitive + description: + name: args + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + url: "https://pub.dev" + source: hosted + version: "2.5.0" async: dependency: transitive description: @@ -97,6 +105,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + boxy: + dependency: transitive + description: + name: boxy + sha256: eaa774ff591191b86f2eb8e8eae878aec50604404b74388a27e655cf3aadf758 + url: "https://pub.dev" + source: hosted + version: "2.2.0" characters: dependency: transitive description: @@ -169,22 +185,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" - csv: - dependency: "direct main" - description: - name: csv - sha256: c6aa2679b2a18cb57652920f674488d89712efaf4d3fdf2e537215b35fc19d6c - url: "https://pub.dev" - source: hosted - version: "6.0.0" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "1.0.8" device_info_plus: dependency: transitive description: @@ -209,6 +217,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + fast_csv: + dependency: "direct main" + description: + name: fast_csv + sha256: "2165aa6b76daa0de168a712a157a78e60fd786257228935dd9d2809fe2ef7d8a" + url: "https://pub.dev" + source: hosted + version: "0.2.11" ffi: dependency: transitive description: @@ -229,10 +245,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: d1d0ac3966b36dc3e66eeefb40280c17feb87fa2099c6e22e6a1fc959327bd03 + sha256: b6283d7387310ad83bc4f3bc245b75d223a032ae6eba275afcd585de2b9a1476 url: "https://pub.dev" source: hosted - version: "8.0.0+1" + version: "8.0.1" fixnum: dependency: transitive description: @@ -246,6 +262,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_animate: + dependency: transitive + description: + name: flutter_animate + sha256: "7c8a6594a9252dad30cc2ef16e33270b6248c4dedc3b3d06c86c4f3f4dc05ae5" + url: "https://pub.dev" + source: hosted + version: "4.5.0" flutter_lints: dependency: "direct dev" description: @@ -254,6 +278,11 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" + flutter_localizations: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" flutter_map: dependency: "direct main" description: @@ -270,19 +299,27 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.19" + flutter_shaders: + dependency: transitive + description: + name: flutter_shaders + sha256: "02750b545c01ff4d8e9bbe8f27a7731aa3778402506c67daa1de7f5fc3f4befe" + url: "https://pub.dev" + source: hosted + version: "0.1.2" + flutter_svg: + dependency: transitive + description: + name: flutter_svg + sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" + url: "https://pub.dev" + source: hosted + version: "2.0.10+1" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" - flutter_tts: - dependency: "direct main" - description: - name: flutter_tts - sha256: aed2a00c48c43af043ed81145fd8503ddd793dafa7088ab137dbef81a703e53d - url: "https://pub.dev" - source: hosted - version: "4.0.2" flutter_web_auth_2: dependency: transitive description: @@ -360,6 +397,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.2.1" + graphs: + dependency: transitive + description: + name: graphs + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + url: "https://pub.dev" + source: hosted + version: "2.3.1" html: dependency: transitive description: @@ -388,10 +433,10 @@ packages: dependency: "direct main" description: name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" url: "https://pub.dev" source: hosted - version: "0.19.0" + version: "0.18.1" js: dependency: transitive description: @@ -488,6 +533,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + lucide_icons_flutter: + dependency: transitive + description: + name: lucide_icons_flutter + sha256: "43b11fa16f243529c538bcb4a1af014c03c177056c0d46039bc4f665320428c6" + url: "https://pub.dev" + source: hosted + version: "1.0.8" matcher: dependency: transitive description: @@ -520,6 +573,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + modular_ui: + dependency: "direct main" + description: + name: modular_ui + sha256: c7bfa2d509f6b55f45833ea5a46c7b40030c2a3c8fb26f5211eab440235ee99f + url: "https://pub.dev" + source: hosted + version: "0.0.5" ntp: dependency: "direct main" description: @@ -552,6 +613,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.0" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf + url: "https://pub.dev" + source: hosted + version: "1.0.1" path_provider: dependency: transitive description: @@ -648,6 +717,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + url: "https://pub.dev" + source: hosted + version: "6.0.2" platform: dependency: transitive description: @@ -668,10 +745,10 @@ packages: dependency: transitive description: name: pointycastle - sha256: "70fe966348fe08c34bf929582f1d8247d9d9408130723206472b4687227e4333" + sha256: "79fbafed02cfdbe85ef3fd06c7f4bc2cbcba0177e61b765264853d4253b21744" url: "https://pub.dev" source: hosted - version: "3.8.0" + version: "3.9.0" polylabel: dependency: transitive description: @@ -688,6 +765,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + rive: + dependency: transitive + description: + name: rive + sha256: ec44b6cf7341e21727c4b0e762f4ac82f9a45f7e52df3ebad2d1289a726fbaaf + url: "https://pub.dev" + source: hosted + version: "0.13.1" + rive_common: + dependency: transitive + description: + name: rive_common + sha256: "0f070bc0e764c570abd8b34d744ef30d1292bd4051f2e0951bbda755875fce6a" + url: "https://pub.dev" + source: hosted + version: "0.3.3" rxdart: dependency: transitive description: @@ -704,6 +797,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.9" + shadcn_ui: + dependency: "direct main" + description: + name: shadcn_ui + sha256: "06c25555efbabe6d1d6acd6397bff4940e931d53ac3448e3a4f6a2287ab6798d" + url: "https://pub.dev" + source: hosted + version: "0.3.3" shared_preferences: dependency: "direct main" description: @@ -837,6 +938,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.0" + two_dimensional_scrollables: + dependency: transitive + description: + name: two_dimensional_scrollables + sha256: c972f327282149a9018eb629ea848fdbc1acf6eda0969d86317c97ce8e4efa78 + url: "https://pub.dev" + source: hosted + version: "0.2.1" typed_data: dependency: transitive description: @@ -921,10 +1030,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "3692a459204a33e04bc94f5fb91158faf4f2c8903281ddd82915adecdb1a901d" + sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.3.1" url_launcher_windows: dependency: transitive description: @@ -941,6 +1050,30 @@ packages: url: "https://pub.dev" source: hosted version: "4.4.0" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" + url: "https://pub.dev" + source: hosted + version: "1.1.11+1" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da + url: "https://pub.dev" + source: hosted + version: "1.1.11+1" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" + url: "https://pub.dev" + source: hosted + version: "1.1.11+1" vector_math: dependency: "direct main" description: @@ -985,10 +1118,10 @@ packages: dependency: transitive description: name: win32_registry - sha256: "41fd8a189940d8696b1b810efb9abcf60827b6cbfab90b0c43e8439e3a39d85a" + sha256: "10589e0d7f4e053f2c61023a31c9ce01146656a70b7b7f0828c0b46d7da2a9bb" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.1.3" window_manager: dependency: "direct main" description: @@ -1021,6 +1154,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.dev" + source: hosted + version: "6.5.0" sdks: dart: ">=3.3.0 <4.0.0" flutter: ">=3.19.2" diff --git a/pubspec.yaml b/pubspec.yaml index f778d80..e0f0776 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,9 +34,9 @@ dependencies: just_audio: ^0.9.36 just_audio_windows: ^0.2.0 audioplayers: ^6.0.0 - csv: ^6.0.0 + fast_csv: ^0.2.11 http: ^1.2.1 - google_fonts: ^6.1.0 + google_fonts: ^6.2.1 intl: any text_scroll: ^0.2.0 flutter_map: ^6.1.0 @@ -49,8 +49,9 @@ dependencies: geolocator: ^11.0.0 vector_math: ^2.1.4 permission_handler: ^11.3.0 - flutter_tts: ^4.0.2 file_picker: ^8.0.0+1 + shadcn_ui: ^0.3.3 + modular_ui: ^0.0.5 # The following adds the Cupertino Icons font to your application. @@ -88,6 +89,7 @@ flutter: - assets/audio/to_destination.wav - assets/datasets/bus-blinds.csv - assets/audio/5-seconds-of-silence.mp3 + - assets/version.txt # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 4ba1456..6461cbe 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,10 +7,10 @@ #include "generated_plugin_registrant.h" #include -#include #include #include #include +#include #include #include #include @@ -19,14 +19,14 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { AudioplayersWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); - FlutterTtsPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("FlutterTtsPlugin")); GeolocatorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("GeolocatorWindows")); JustAudioWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("JustAudioWindowsPlugin")); PermissionHandlerWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); + RivePluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("RivePlugin")); ScreenRetrieverPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ScreenRetrieverPlugin")); UrlLauncherWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index b8b98c6..f1afbb2 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,10 +4,10 @@ list(APPEND FLUTTER_PLUGIN_LIST audioplayers_windows - flutter_tts geolocator_windows just_audio_windows permission_handler_windows + rive_common screen_retriever url_launcher_windows window_manager