From 8cc40168365b248b5aed478b88943678945c4093 Mon Sep 17 00:00:00 2001 From: ImBenji Date: Sun, 14 Apr 2024 05:05:08 +0100 Subject: [PATCH] nice --- assets/audio/5-seconds-of-silence.mp3 | Bin 0 -> 41239 bytes lib/audio_cache.dart | 11 +- lib/auth/auth_api.dart | 2 +- lib/backend/live_information.dart | 13 +- lib/backend/modules/announcement.dart | 63 ++- lib/backend/modules/synced_time.dart | 19 +- lib/backend/modules/tracker.dart | 18 +- lib/main.dart | 11 +- lib/pages/audio_cache_test.dart | 2 +- lib/pages/components/ibus_display.dart | 2 +- lib/pages/home.dart | 27 +- lib/pages/initial_startup.dart | 100 ++++ lib/pages/settings.dart | 456 ++++++++++++++++- lib/pages/tfl_dataset_test.dart | 471 +++++++++--------- lib/tfl_datasets.dart | 2 +- macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 248 +++++---- pubspec.yaml | 15 +- .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 20 files changed, 1082 insertions(+), 384 deletions(-) create mode 100644 assets/audio/5-seconds-of-silence.mp3 create mode 100644 lib/pages/initial_startup.dart diff --git a/assets/audio/5-seconds-of-silence.mp3 b/assets/audio/5-seconds-of-silence.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..866abc7eef8a62ffda9e3171af1b2fb35e891504 GIT binary patch literal 41239 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
noIXPo^`xC) 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 WZQNaW_nfwK^7x-$`EUG*@;?B9P27(F literal 0 HcmV?d00001 diff --git a/lib/audio_cache.dart b/lib/audio_cache.dart index ed17ff6..450fb02 100644 --- a/lib/audio_cache.dart +++ b/lib/audio_cache.dart @@ -27,9 +27,7 @@ class AudioCache { class AnnouncementCache extends AudioCache { - String _assetLocation = "assets/ibus_recordings.zip"; - - Future loadAnnouncements(List announcements) async { + Future loadAnnouncementsFromBytes(Uint8List bytes, List announcements) async { List _announements = []; @@ -45,7 +43,7 @@ class AnnouncementCache extends AudioCache { return; } - final bytes = await rootBundle.load(_assetLocation); + // final bytes = await rootBundle.load(_assetLocation); final archive = ZipDecoder().decodeBytes(bytes.buffer.asUint8List()); for (final file in archive) { @@ -62,11 +60,11 @@ class AnnouncementCache extends AudioCache { } } - Future loadAllAnnouncements() async { + Future loadAllAnnouncementsFromBytes(Uint8List bytes) async { print("Loading all announcements."); - final bytes = await rootBundle.load(_assetLocation); + // final bytes = await rootBundle.load(_assetLocation); final archive = ZipDecoder().decodeBytes(bytes.buffer.asUint8List()); print("Done decoding zip file."); @@ -88,5 +86,4 @@ class AnnouncementCache extends AudioCache { print("Done loading all announcements."); } - } \ No newline at end of file diff --git a/lib/auth/auth_api.dart b/lib/auth/auth_api.dart index f233938..35afc2e 100644 --- a/lib/auth/auth_api.dart +++ b/lib/auth/auth_api.dart @@ -120,7 +120,7 @@ class AuthAPI extends ChangeNotifier { }) async { try { - final session = await account.createEmailSession( + final session = await account.createEmailPasswordSession( email: email, password: password, ); diff --git a/lib/backend/live_information.dart b/lib/backend/live_information.dart index b59de26..bf59652 100644 --- a/lib/backend/live_information.dart +++ b/lib/backend/live_information.dart @@ -68,11 +68,16 @@ class LiveInformation { announcementModule = AnnouncementModule(); // Tracker module is not supported on desktop - if (defaultTargetPlatform != TargetPlatform.windows || defaultTargetPlatform != TargetPlatform.linux || defaultTargetPlatform != TargetPlatform.macOS) { + if (defaultTargetPlatform != TargetPlatform.windows && defaultTargetPlatform != TargetPlatform.linux && defaultTargetPlatform != TargetPlatform.macOS) { // Tracker module is not supported on web - await Permission.location.request(); - trackerModule = TrackerModule(); + Permission.location.request().then((value) { + if (value.isGranted) { + trackerModule = TrackerModule(); + } + }); } + + print("Initialised LiveInformation"); } // Auth @@ -115,7 +120,7 @@ class LiveInformation { // Cache/Load the audio files await announcementModule .announcementCache - .loadAnnouncements(audioFiles); + .loadAnnouncementsFromBytes(await LiveInformation().announcementModule.getBundleBytes(), audioFiles); } // Public methods diff --git a/lib/backend/modules/announcement.dart b/lib/backend/modules/announcement.dart index f79525d..623c9c9 100644 --- a/lib/backend/modules/announcement.dart +++ b/lib/backend/modules/announcement.dart @@ -7,6 +7,8 @@ import 'package:bus_infotainment/backend/live_information.dart'; import 'package:bus_infotainment/tfl_datasets.dart'; import 'package:bus_infotainment/utils/audio%20wrapper.dart'; import 'package:bus_infotainment/utils/delegates.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; import 'info_module.dart'; @@ -19,6 +21,28 @@ class AnnouncementModule extends InfoModule { refreshTimer(); } + // Files + String _bundleLocation = "assets/ibus_recordings.zip"; + Uint8List? _bundleBytes; + void setBundleBytes(Uint8List bytes) { + _bundleBytes = bytes; + } + Future getBundleBytes() async { + + if (_bundleBytes != null) { + return _bundleBytes!; + } else { + + if (kIsWeb) { + throw Exception("Cannot load bundle bytes on web"); + } + + final bytes = await rootBundle.load(_bundleLocation); + return bytes.buffer.asUint8List(); + } + + } + // Queue List queue = []; AnnouncementQueueEntry? currentAnnouncement; @@ -63,23 +87,34 @@ class AnnouncementModule extends InfoModule { onAnnouncement.trigger(currentAnnouncement!); if (currentAnnouncement!.audioSources.isNotEmpty) { - try { + + 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) { + try { + await audioPlayer.loadSource(source); - await audioPlayer.loadSource(source); - - Duration? duration = await audioPlayer.play(); - await Future.delayed(duration!); - if (currentAnnouncement?.audioSources.last != source) { - await Future.delayed(const Duration(milliseconds: 100)); + Duration? duration = await audioPlayer.play(); + await Future.delayed(duration!); + if (currentAnnouncement?.audioSources.last != source) { + await Future.delayed(const Duration(milliseconds: 100)); + } + } catch (e) { + // Do nothing + // print("Error playing announcement: $e on ${currentAnnouncement?.displayText}"); + await Future.delayed(const Duration(seconds: 1)); } } - audioPlayer.stop(); + // audioPlayer.stop(); - } catch (e) { - // Do nothing - print("Error playing announcement: $e"); - } + // } catch (e) { + // // Do nothing + // print("Error playing announcement: $e on ${currentAnnouncement?.displayTex}"); + // } } else { if (queue.isNotEmpty) { await Future.delayed(const Duration(seconds: 5)); @@ -153,7 +188,7 @@ class AnnouncementModule extends InfoModule { } // Cache the announcements - await announcementCache.loadAnnouncements(audioNames); + await announcementCache.loadAnnouncementsFromBytes((await getBundleBytes())!, audioNames); List sources = []; @@ -227,7 +262,7 @@ class AnnouncementModule extends InfoModule { String audioRoute = "R_${routeVariant.busRoute.routeNumber}_001.mp3"; - await announcementCache.loadAnnouncements([audioRoute, "R_RAIL_REPLACEMENT_SERVICE_001.mp3"]); + await announcementCache.loadAnnouncementsFromBytes(await getBundleBytes(), [audioRoute, "R_RAIL_REPLACEMENT_SERVICE_001.mp3"]); AudioWrapperSource sourceRoute = !routeNumber.toLowerCase().startsWith("ul") ? AudioWrapperByteSource(announcementCache[audioRoute]!) : diff --git a/lib/backend/modules/synced_time.dart b/lib/backend/modules/synced_time.dart index 7b7ae92..101007b 100644 --- a/lib/backend/modules/synced_time.dart +++ b/lib/backend/modules/synced_time.dart @@ -15,14 +15,17 @@ class SyncedTimeModule extends InfoModule { } Timer refreshTimer() => Timer.periodic(const Duration(seconds: 10), (timer) async { - var res = await http.get(Uri.parse('http://worldtimeapi.org/api/timezone/Europe/London')); - if (res.statusCode == 200) { - var json = jsonDecode(res.body); - DateTime time = DateTime.parse(json['datetime']); - timeOffset = time.millisecondsSinceEpoch - DateTime.now().millisecondsSinceEpoch; - lastUpdate = DateTime.now(); - print("Time offset: $timeOffset"); - } else { + + try { + var res = await http.get(Uri.parse('http://worldtimeapi.org/api/timezone/Europe/London')); + if (res.statusCode == 200) { + var json = jsonDecode(res.body); + DateTime time = DateTime.parse(json['datetime']); + timeOffset = time.millisecondsSinceEpoch - DateTime.now().millisecondsSinceEpoch; + lastUpdate = DateTime.now(); + print("Time offset: $timeOffset"); + } + } catch (e) { print("Failed to get time from worldtimeapi.org"); } }); diff --git a/lib/backend/modules/tracker.dart b/lib/backend/modules/tracker.dart index 1961c3e..650433f 100644 --- a/lib/backend/modules/tracker.dart +++ b/lib/backend/modules/tracker.dart @@ -8,25 +8,29 @@ import 'package:bus_infotainment/backend/modules/info_module.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:flutter/foundation.dart'; import 'package:geolocator/geolocator.dart'; import 'package:vector_math/vector_math.dart'; class TrackerModule extends InfoModule { - + // Constructor TrackerModule() { locationStream(); - Geolocator.getLastKnownPosition().then((Position? position) { - this._position = position; - updateNearestStop(); - }); + if (!kIsWeb) + { + Geolocator.getLastKnownPosition().then((Position? position) { + this._position = position; + updateNearestStop(); + }); + } liveInformation.routeVariantDelegate.addListener((routeVariant) { print("Route variant changed"); updateNearestStop(); }); } - + // Location Tracker - will update the recorded location when the user moves. Position? _position; Position? get position => _position; @@ -45,6 +49,7 @@ class TrackerModule extends InfoModule { updateNearestStop(); }); + // Location Refresher - will update the recorded location periodically. Timer refreshTimer() => Timer.periodic(Duration(seconds: 1), (timer) async { _position = await Geolocator.getCurrentPosition(); }); @@ -52,6 +57,7 @@ class TrackerModule extends InfoModule { BusRouteStop? nearestStop; bool hasArrived = false; + Future updateNearestStop() async { if (liveInformation.getRouteVariant() == null) { return; diff --git a/lib/main.dart b/lib/main.dart index 58ed02d..1546c82 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import 'dart:io'; 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:flutter/foundation.dart'; @@ -26,9 +27,13 @@ void main() async { }); } + + LiveInformation liveInformation = LiveInformation(); await liveInformation.initialize(); + runApp(const MyApp()); + // Disalow screen to turn off on android await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); // Disalow landscape mode @@ -37,7 +42,7 @@ void main() async { DeviceOrientation.portraitDown, ]); - runApp(const MyApp()); + } class MyApp extends StatelessWidget { @@ -71,7 +76,9 @@ class MyApp extends StatelessWidget { routes: { '/home': (context) => const MyHomePage(title: 'Flutter Demo Home Page'), '/audiocachetest': (context) => AudioCacheTest(), - '/': (context) => TfL_Dataset_Test(), + // '/': (context) => TfL_Dataset_Test(), + '/': (context) => InitialStartup(), + '/application': (context) => TfL_Dataset_Test(), }, ); diff --git a/lib/pages/audio_cache_test.dart b/lib/pages/audio_cache_test.dart index 3881e24..3126d08 100644 --- a/lib/pages/audio_cache_test.dart +++ b/lib/pages/audio_cache_test.dart @@ -27,7 +27,7 @@ class AudioCacheTest extends StatelessWidget { body: Container( child: FutureBuilder( - future: _announcementCache.loadAllAnnouncements(), + future: Future.delayed(Duration(seconds: 1)), //todo builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.connectionState == ConnectionState.done) { diff --git a/lib/pages/components/ibus_display.dart b/lib/pages/components/ibus_display.dart index 99693a4..f764ce1 100644 --- a/lib/pages/components/ibus_display.dart +++ b/lib/pages/components/ibus_display.dart @@ -140,7 +140,7 @@ class _ibus_displayState extends State { width: 32*4*3, child: TextScroll( _padString(topLine), - velocity: Velocity(pixelsPerSecond: Offset(120, 0)), + velocity: Velocity(pixelsPerSecond: Offset(180, 0)), style: const TextStyle( fontSize: 20, color: Colors.orange, diff --git a/lib/pages/home.dart b/lib/pages/home.dart index 285995b..8b58eae 100644 --- a/lib/pages/home.dart +++ b/lib/pages/home.dart @@ -248,27 +248,40 @@ class _SpeedometerState extends State { } + Timer? reloadTimer; + @override void initState() { // TODO: implement initState super.initState(); - Timer.periodic(Duration(milliseconds: 250), (timer) { + reloadTimer = Timer.periodic(Duration(milliseconds: 250), (timer) { - Position? newPosition = LiveInformation().trackerModule.position; + try { + Position? newPosition = LiveInformation().trackerModule.position; - speed = newPosition?.speed ?? 0; + speed = newPosition?.speed ?? 0; - arrivalTime -= 0.25; - arrivalTime = arrivalTime < 0 ? 0 : arrivalTime; + arrivalTime -= 0.25; + arrivalTime = arrivalTime < 0 ? 0 : arrivalTime; - setState(() { + setState(() { - }); + }); + } catch (e) { + // print(e); + } }); } + @override + void dispose() { + // TODO: implement dispose + super.dispose(); + reloadTimer?.cancel(); + } + @override Widget build(BuildContext context) { // TODO: implement build diff --git a/lib/pages/initial_startup.dart b/lib/pages/initial_startup.dart new file mode 100644 index 0000000..cfdd048 --- /dev/null +++ b/lib/pages/initial_startup.dart @@ -0,0 +1,100 @@ + +import 'package:bus_infotainment/pages/settings.dart'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +class InitialStartup extends StatefulWidget { + + @override + State createState() => _InitialStartupState(); +} + +const String Version = "0.2.0"; + +class _InitialStartupState extends State { + bool AllowPassage = false; + + @override + Widget build(BuildContext context) { + // TODO: implement build + return Scaffold( + body: Container( + + alignment: Alignment.center, + + child: Column( + + mainAxisSize: MainAxisSize.min, + + children: [ + + SizedBox( + height: 8, + ), + + AnnouncementUpload( + onUploaded: () { + AllowPassage = true; + setState(() { + + }); + }, + + ), + + if (AllowPassage) + Container( + + margin: EdgeInsets.all(8), + + height: 32, + width: double.infinity, + child: ElevatedButton( + onPressed: (){ + Navigator.pushNamed(context, "/application"); + }, + + // make the corner radius 4, background color match the theme, and text colour white, fill to width of parent + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green, + // foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4) + ), + ), + + child: Text( + "Continue to application...", + style: GoogleFonts.interTight( + fontSize: 15, + fontWeight: FontWeight.w600, + color: Colors.white, + letterSpacing: 0.5 + ) + ) + ), + ), + + SizedBox( + height: 8, + ), + + Text( + "Version $Version", + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Colors.grey + ), + ) + + ], + + + + ) + + ), + ); + } +} \ No newline at end of file diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index 24bd460..23d0144 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -1,8 +1,12 @@ +import 'dart:io'; + +import 'package:bus_infotainment/audio_cache.dart'; 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/gestures.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; @@ -71,20 +75,75 @@ class _pages_SettingsState extends State { child: SingleChildScrollView( child: Column( - + + crossAxisAlignment: CrossAxisAlignment.start, + children: [ - + + SizedBox( + height: 8 + ), + + AnnouncementUpload(), + + SizedBox( + height: 8 + ), + Container( - margin: const EdgeInsets.all(8), + margin: EdgeInsets.symmetric( + horizontal: 8 + ), + child: SettingsField( + label: "Announce distance", + defaultValue: "150m", + ) + ), + + SizedBox( + height: 8 + ), + + Container( + margin: EdgeInsets.symmetric( + horizontal: 8 + ), + child: SettingsField( + label: "Announce time", + defaultValue: "10s", + ) + ), + + Container( + + margin: EdgeInsets.only( + left: 8, + top: 2 + ), + + child: Text( + "Console", + style: GoogleFonts.teko( + fontSize: 24 + ), + ), + ), + + Container( + margin: const EdgeInsets.symmetric( + horizontal: 8 + ), child: Console() ), + if (false) Container( height: 2, width: double.infinity, color: Colors.white70, ), + if (false) Container( padding: const EdgeInsets.all(8), @@ -101,6 +160,253 @@ class _pages_SettingsState extends State { } } +class AnnouncementUpload extends StatefulWidget { + + Function onUploaded = () {}; + + AnnouncementUpload({Key? key, this.onUploaded = _defaultOnUploaded}) : super(key: key); + + static void _defaultOnUploaded() {} + + @override + State createState() => _AnnouncementUploadState(); +} + +class _AnnouncementUploadState extends State { + + Future UploadButtonPressed() async { + + // Pick the file + FilePickerResult? result = await FilePicker.platform.pickFiles(); + + if (result != null) { + print("Got file"); + + AnnouncementCache cache = LiveInformation().announcementModule.announcementCache; + + LiveInformation().announcementModule.setBundleBytes(result.files[0].bytes!); + + // load a random announcement to ensure that the file is usable + await cache.loadAnnouncementsFromBytes(result.files[0].bytes!, ["S_WALTHAMSTOW_CENTRAL_001.mp3"]); + + print("Loaded announcements"); + + setState(() { + + }); + + widget.onUploaded(); + + } else { + // User canceled the picker + } + + } + + @override + Widget build(BuildContext context) { + // TODO: implement build + return Container( + decoration: BoxDecoration( + border: Border.all( + color: Colors.white70, + width: 2 + ), + ), + + margin: EdgeInsets.symmetric( + horizontal: 8 + ), + padding: EdgeInsets.all(8), + + // height: 100, + + child: Column( + + children: [ + + Row( + + 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 + Icon( + Icons.check, + color: Colors.green, + size: 18, + ), + + 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", + style: GoogleFonts.interTight( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.white70, + letterSpacing: 0.1, + ), + ), + ) + + ], + + ), + + SizedBox( + height: 8, + ), + + Text( + "Disclaimer: It is illegal to redistribute Transport for London's intellectual property. Even if it were permissible, the files are too large to be packaged into a website...", + style: GoogleFonts.interTight( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Colors.white70, + letterSpacing: 0.1, + height: 1 + ), + ), + + SizedBox( + height: 8, + ), + + Text( + "...because of these reasons, you will have to provide the announcement files yourself.", + style: GoogleFonts.interTight( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Colors.white70, + letterSpacing: 0.1, + height: 1 + ), + ), + + SizedBox( + height: 8, + ), + + Text( + "A ZIP file should be provided containg audio files with the correct naming scheme (e.g. S_WALTHAMSTOW_CENTRAL_001.mp3).", + style: GoogleFonts.interTight( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Colors.white70, + letterSpacing: 0.1, + height: 1 + ), + ), + + SizedBox( + height: 8, + ), + + Text( + "No specific folder structure is required. The files will be sorted and indexed.", + style: GoogleFonts.interTight( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Colors.white70, + letterSpacing: 0.1, + height: 1 + ), + ), + + SizedBox( + height: 8, + ), + + SizedBox( + height: 32, + width: double.infinity, + child: ElevatedButton( + onPressed: (){ + UploadButtonPressed(); + }, + + // 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( + "Upload file", + style: GoogleFonts.interTight( + fontSize: 15, + fontWeight: FontWeight.w600, + color: Colors.white, + letterSpacing: 0.5 + ) + ) + ), + ), + ], + + ) + ); + } +} + enum _LoginType { login, signup @@ -808,4 +1114,148 @@ class _ConsoleState extends State { ), ); } +} + +class SettingsField extends StatelessWidget { + + String label = "Untitled Field"; + + T defaultValue; + + SettingsField({super.key, this.label = "Untitled Field", required this.defaultValue}); + + + + @override + Widget build(BuildContext context) { + // TODO: implement build + return Container( + decoration: BoxDecoration( + border: Border.all( + color: Colors.white70, + width: 2 + ), + ), + + height: 50, + + padding: EdgeInsets.all(4), + + child: Row( + + children: [ + + SizedBox( + width: 4 + ), + + Text( + label, + style: GoogleFonts.teko( + fontSize: 25, + height: 1, + letterSpacing: 0.02, + color: Colors.white70 + ), + ), + + SizedBox( + width: 8 + ), + + Expanded( + child: Container( + + height: double.infinity, + + alignment: Alignment.center, + + decoration: BoxDecoration( + + border: Border.all( + color: Colors.white70, + width: 2 + ) + + ), + + child: Text( + defaultValue.toString(), + style: GoogleFonts.teko( + fontSize: 25, + height: 1 + ) + ) + + ), + ), + + SizedBox( + width: 4 + ), + + AspectRatio( + aspectRatio: 1, + child: Stack( + children: [ + Container( + + height: double.infinity, + width: double.infinity, + + decoration: BoxDecoration( + + border: Border.all( + color: Colors.white70, + width: 2 + ) + + ), + + child: Icon( + Icons.edit, + color: Colors.white70, + size: 20, + ) + + ), + + Container( + + padding: EdgeInsets.all(2), + + child: Positioned.fill( + child: ElevatedButton( + onPressed: () { + + }, + + style: ElevatedButton.styleFrom( + backgroundColor: Colors.transparent, + shadowColor: Colors.transparent, + surfaceTintColor: Colors.transparent, + foregroundColor: Colors.transparent, + + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(0), + ), + + ), + + child: Container() + ), + ), + ) + + ], + ) + ) + + ], + + ) + + ); + } + } \ No newline at end of file diff --git a/lib/pages/tfl_dataset_test.dart b/lib/pages/tfl_dataset_test.dart index 397d59f..556683b 100644 --- a/lib/pages/tfl_dataset_test.dart +++ b/lib/pages/tfl_dataset_test.dart @@ -42,6 +42,21 @@ class TfL_Dataset_TestState extends State { ibus_display _ibus_display = ibus_display(); + @override + void initState() { + // TODO: implement initState + super.initState(); + + Future.delayed(Duration.zero, () async { + try { + await LiveInformation().announcementModule.getBundleBytes(); + } catch (e) { + Navigator.popAndPushNamed(context, "/"); + print("Sent back to initial startup"); + } + }); + } + @override Widget build(BuildContext context) { @@ -56,7 +71,7 @@ class TfL_Dataset_TestState extends State { if (defaultTargetPlatform == TargetPlatform.android) { shouldCurve = true; - } else { + } else if (defaultTargetPlatform == TargetPlatform.windows && !kIsWeb){ rotated = _selectedIndex == 2; print("Window size: ${MediaQuery.of(context).size}"); @@ -156,295 +171,289 @@ class TfL_Dataset_TestState extends State { child: RotatedBox( quarterTurns: rotated ? 3 : 0, - child: FittedBox( + child: Container( - alignment: Alignment.topCenter, - fit: BoxFit.fitWidth, + constraints: const BoxConstraints( + maxWidth: 411.4, + maxHeight: 850.3, + ), child: Container( - constraints: const BoxConstraints( - maxWidth: 411.4, - maxHeight: 850.3, + decoration: BoxDecoration( + borderRadius: shouldCurve ? const BorderRadius.only( + bottomLeft: Radius.circular(15), + bottomRight: Radius.circular(15), + ) : null, + border: Border.all( + color: Colors.white70, + width: 2, + ), + color: Colors.grey.shade900, ), - child: Container( + margin: const EdgeInsets.all(6), - decoration: BoxDecoration( - borderRadius: shouldCurve ? const BorderRadius.only( - bottomLeft: Radius.circular(15), - bottomRight: Radius.circular(15), - ) : null, - border: Border.all( - color: Colors.white70, - width: 2, + + child: Column( + + children: [ + + if (!hideUI) + Container( + + margin: const EdgeInsets.all(6), + + child: _ibus_display, ), - color: Colors.grey.shade900, - ), - margin: const EdgeInsets.all(6), + if (!hideUI) + Container( + width: double.infinity, + height: 2, + color: Colors.white70, + ), + Expanded( + child: Container( - child: Column( + margin: const EdgeInsets.all(8), - children: [ + decoration: BoxDecoration( + border: Border.all( + color: Colors.white70, + width: 2, + ), + color: Colors.grey.shade900, + borderRadius: hideUI && shouldCurve ? const BorderRadius.only( + bottomLeft: Radius.circular(7), + bottomRight: Radius.circular(7), + ) : null, + ), - if (!hideUI) - Container( + child: ClipRRect( - margin: const EdgeInsets.all(6), - - child: _ibus_display, - ), - - if (!hideUI) - Container( - width: double.infinity, - height: 2, - color: Colors.white70, - ), - - Expanded( - child: Container( - - margin: const EdgeInsets.all(8), - - decoration: BoxDecoration( - border: Border.all( - color: Colors.white70, - width: 2, - ), - color: Colors.grey.shade900, - borderRadius: hideUI && shouldCurve ? const BorderRadius.only( - bottomLeft: Radius.circular(7), - bottomRight: Radius.circular(7), - ) : null, + // curved corners + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(7), + bottomRight: Radius.circular(7), ), - child: ClipRRect( - - // curved corners - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(7), - bottomRight: Radius.circular(7), - ), - - child: Pages[_selectedIndex], - ) - ), + child: Pages[_selectedIndex], + ) ), + ), - if (!hideUI) - Container( - width: double.infinity, - height: 2, - color: Colors.white70, - ), + if (!hideUI) + Container( + width: double.infinity, + height: 2, + color: Colors.white70, + ), - if (!hideUI) - Container( - height: 50, + if (!hideUI) + Container( + height: 50, - child: Row( + child: Row( - children: [ + children: [ - Expanded( - child: Stack( - children: [ - Container( - alignment: Alignment.center, - margin: const EdgeInsets.all(10), - child: Text( - "Home", - style: GoogleFonts.teko( - color: Colors.white70, - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ) - ), - Positioned.fill( - child: ElevatedButton( - onPressed: () { - setState(() { - _selectedIndex = 0; - }); - }, - - 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: [ - Container( + Expanded( + child: Stack( + children: [ + Container( alignment: Alignment.center, margin: const EdgeInsets.all(10), child: Text( - "Routes", + "Home", style: GoogleFonts.teko( color: Colors.white70, fontSize: 20, fontWeight: FontWeight.bold, ), ) - ), - Positioned.fill( - child: ElevatedButton( - onPressed: () { - setState(() { - _selectedIndex = 1; - }); - }, + ), + Positioned.fill( + child: ElevatedButton( + onPressed: () { + setState(() { + _selectedIndex = 0; + }); + }, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.transparent, - shadowColor: Colors.transparent, - surfaceTintColor: Colors.transparent, - foregroundColor: Colors.transparent, - - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(0), - ), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.transparent, + shadowColor: Colors.transparent, + surfaceTintColor: Colors.transparent, + foregroundColor: Colors.transparent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(0), ), - child: Container() + ), + + child: Container() + ), + ) + ], + ), + ), + + Container( + width: 2, + height: double.infinity, + color: Colors.white70, + ), + + Expanded( + child: Stack( + children: [ + Container( + alignment: Alignment.center, + margin: const EdgeInsets.all(10), + child: Text( + "Routes", + style: GoogleFonts.teko( + color: Colors.white70, + fontSize: 20, + fontWeight: FontWeight.bold, ), ) - ], - ), - ), + ), + Positioned.fill( + child: ElevatedButton( + onPressed: () { + setState(() { + _selectedIndex = 1; + }); + }, - Container( - width: 2, - height: double.infinity, - color: Colors.white70, - ), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.transparent, + shadowColor: Colors.transparent, + surfaceTintColor: Colors.transparent, + foregroundColor: Colors.transparent, - Expanded( - child: Stack( - children: [ - Container( - alignment: Alignment.center, - margin: const EdgeInsets.all(10), - child: Text( - "Display", - style: GoogleFonts.teko( - color: Colors.white70, - fontSize: 20, - fontWeight: FontWeight.bold, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(0), ), - ) + + ), + + child: Container() ), - Positioned.fill( - child: ElevatedButton( - onPressed: () { - setState(() { - _selectedIndex = 2; - }); - }, + ) + ], + ), + ), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.transparent, - shadowColor: Colors.transparent, - surfaceTintColor: Colors.transparent, - foregroundColor: Colors.transparent, + Container( + width: 2, + height: double.infinity, + color: Colors.white70, + ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(0), - ), + Expanded( + child: Stack( + children: [ + Container( + alignment: Alignment.center, + margin: const EdgeInsets.all(10), + child: Text( + "Display", + style: GoogleFonts.teko( + color: Colors.white70, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ) + ), + Positioned.fill( + child: ElevatedButton( + onPressed: () { + setState(() { + _selectedIndex = 2; + }); + }, + 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: [ - Container( - alignment: Alignment.center, - margin: const EdgeInsets.all(10), - child: Text( - "Settings", - style: GoogleFonts.teko( - color: Colors.white70, - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ) + child: Container() ), - Positioned.fill( - child: ElevatedButton( - onPressed: () { - setState(() { - _selectedIndex = 3; - }); - }, + ) + ], + ), + ), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.transparent, - shadowColor: Colors.transparent, - surfaceTintColor: Colors.transparent, - foregroundColor: Colors.transparent, + Container( + width: 2, + height: double.infinity, + color: Colors.white70, + ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(0), - ), + Expanded( + child: Stack( + children: [ + Container( + alignment: Alignment.center, + margin: const EdgeInsets.all(10), + child: Text( + "Settings", + style: GoogleFonts.teko( + color: Colors.white70, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ) + ), + Positioned.fill( + child: ElevatedButton( + onPressed: () { + setState(() { + _selectedIndex = 3; + }); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.transparent, + shadowColor: Colors.transparent, + surfaceTintColor: Colors.transparent, + foregroundColor: Colors.transparent, + + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(0), ), - child: Container() - ), - ) - ], - ), + ), + + child: Container() + ), + ) + ], ), + ), - ], + ], - ), - ) + ), + ) - ], + ], - ) - ), + ) ), ), ), diff --git a/lib/tfl_datasets.dart b/lib/tfl_datasets.dart index cacf7ac..23eee8f 100644 --- a/lib/tfl_datasets.dart +++ b/lib/tfl_datasets.dart @@ -258,7 +258,7 @@ class BusDestination { print("Audio name B: $audioNameB"); print("Audio name C: $audioNameC"); - await LiveInformation().announcementModule.announcementCache.loadAnnouncements([audioNameA, audioNameB, audioNameC]); + await LiveInformation().announcementModule.announcementCache.loadAnnouncementsFromBytes(await LiveInformation().announcementModule.getBundleBytes(), [audioNameA, audioNameB, audioNameC]); Uint8List? audioBytesA = LiveInformation().announcementModule.announcementCache[audioNameA]; Uint8List? audioBytesB = LiveInformation().announcementModule.announcementCache[audioNameB]; diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 3e8cf8a..58c12ea 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,6 +8,7 @@ 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 @@ -23,6 +24,7 @@ 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")) diff --git a/pubspec.lock b/pubspec.lock index 109d35a..d101904 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: "direct main" description: name: appwrite - sha256: "1a602cfc6122ec9f179403b44526ff60a6baac9a8f4e8578061fc5e85df67211" + sha256: "335faac24642aaf66627c21ce26a5c64bcbc3911e624c61b9dfbe0eec7be1342" url: "https://pub.dev" source: hosted - version: "11.0.1" + version: "12.0.1" archive: dependency: "direct main" description: @@ -29,66 +29,66 @@ packages: dependency: transitive description: name: audio_session - sha256: "6fdf255ed3af86535c96452c33ecff1245990bb25a605bfb1958661ccc3d467f" + sha256: a49af9981eec5d7cd73b37bacb6ee73f8143a6a9f9bd5b6021e6c346b9b6cf4e url: "https://pub.dev" source: hosted - version: "0.1.18" + version: "0.1.19" audioplayers: dependency: "direct main" description: name: audioplayers - sha256: c05c6147124cd63e725e861335a8b4d57300b80e6e92cea7c145c739223bbaef + sha256: "752039d6aa752597c98ec212e9759519061759e402e7da59a511f39d43aa07d2" url: "https://pub.dev" source: hosted - version: "5.2.1" + version: "6.0.0" audioplayers_android: dependency: transitive description: name: audioplayers_android - sha256: b00e1a0e11365d88576320ec2d8c192bc21f1afb6c0e5995d1c57ae63156acb5 + sha256: de576b890befe27175c2f511ba8b742bec83765fa97c3ce4282bba46212f58e4 url: "https://pub.dev" source: hosted - version: "4.0.3" + version: "5.0.0" audioplayers_darwin: dependency: transitive description: name: audioplayers_darwin - sha256: "3034e99a6df8d101da0f5082dcca0a2a99db62ab1d4ddb3277bed3f6f81afe08" + sha256: e507887f3ff18d8e5a10a668d7bedc28206b12e10b98347797257c6ae1019c3b url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "6.0.0" audioplayers_linux: dependency: transitive description: name: audioplayers_linux - sha256: "60787e73fefc4d2e0b9c02c69885402177e818e4e27ef087074cf27c02246c9e" + sha256: "3d3d244c90436115417f170426ce768856d8fe4dfc5ed66a049d2890acfa82f9" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "4.0.0" audioplayers_platform_interface: dependency: transitive description: name: audioplayers_platform_interface - sha256: "365c547f1bb9e77d94dd1687903a668d8f7ac3409e48e6e6a3668a1ac2982adb" + sha256: "6834dd48dfb7bc6c2404998ebdd161f79cd3774a7e6779e1348d54a3bfdcfaa5" url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "7.0.0" audioplayers_web: dependency: transitive description: name: audioplayers_web - sha256: "22cd0173e54d92bd9b2c80b1204eb1eb159ece87475ab58c9788a70ec43c2a62" + sha256: db8fc420dadf80da18e2286c18e746fb4c3b2c5adbf0c963299dde046828886d url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "5.0.0" audioplayers_windows: dependency: transitive description: name: audioplayers_windows - sha256: "9536812c9103563644ada2ef45ae523806b0745f7a78e89d1b5fb1951de90e1a" + sha256: "8605762dddba992138d476f6a0c3afd9df30ac5b96039929063eceed416795c2" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "4.0.0" boolean_selector: dependency: transitive description: @@ -145,6 +145,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.8" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32" + url: "https://pub.dev" + source: hosted + version: "0.3.4+1" crypto: dependency: transitive description: @@ -165,10 +173,10 @@ packages: dependency: "direct main" description: name: csv - sha256: "63ed2871dd6471193dffc52c0e6c76fb86269c00244d244297abbb355c84a86e" + sha256: c6aa2679b2a18cb57652920f674488d89712efaf4d3fdf2e537215b35fc19d6c url: "https://pub.dev" source: hosted - version: "5.1.1" + version: "6.0.0" cupertino_icons: dependency: "direct main" description: @@ -205,10 +213,10 @@ packages: dependency: transitive description: name: ffi - sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.2" file: dependency: transitive description: @@ -217,6 +225,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: d1d0ac3966b36dc3e66eeefb40280c17feb87fa2099c6e22e6a1fc959327bd03 + url: "https://pub.dev" + source: hosted + version: "8.0.0+1" fixnum: dependency: transitive description: @@ -234,10 +250,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "3.0.2" flutter_map: dependency: "direct main" description: @@ -246,27 +262,43 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "8cf40eebf5dec866a6d1956ad7b4f7016e6c0cc69847ab946833b7d43743809f" + url: "https://pub.dev" + source: hosted + version: "2.0.19" 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: name: flutter_web_auth_2 - sha256: "0da41e631a368e02366fc1a9b79dd8da191e700a836878bc54466fff51c07df2" + sha256: "3ea3a0cc539ca74319f4f2f7484f62742fe5b2ff9a0fca37575426d6e6f07901" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "3.1.1" flutter_web_auth_2_platform_interface: dependency: transitive description: name: flutter_web_auth_2_platform_interface - sha256: f6fa7059ff3428c19cd756c02fef8eb0147131c7e64591f9060c90b5ab84f094 + sha256: e8669e262005a8354389ba2971f0fc1c36188481234ff50d013aaf993f30f739 url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "3.1.0" flutter_web_plugins: dependency: transitive description: flutter @@ -284,18 +316,18 @@ packages: dependency: transitive description: name: geolocator_android - sha256: "136f1c97e1903366393bda514c5d9e98843418baea52899aa45edae9af8a5cd6" + sha256: f15d1536cd01b1399578f1da1eb5d566e7a718db6a3648f2c24d2e2f859f0692 url: "https://pub.dev" source: hosted - version: "4.5.2" + version: "4.5.4" geolocator_apple: dependency: transitive description: name: geolocator_apple - sha256: "2f2d4ee16c4df269e93c0e382be075cc01d5db6703c3196e4af20a634fe49ef4" + sha256: bc2aca02423ad429cb0556121f56e60360a2b7d694c8570301d06ea0c00732fd url: "https://pub.dev" source: hosted - version: "2.3.6" + version: "2.3.7" geolocator_platform_interface: dependency: transitive description: @@ -316,18 +348,18 @@ packages: dependency: transitive description: name: geolocator_windows - sha256: a92fae29779d5c6dc60e8411302f5221ade464968fe80a36d330e80a71f087af + sha256: "53da08937d07c24b0d9952eb57a3b474e29aae2abf9dd717f7e1230995f13f0e" url: "https://pub.dev" source: hosted - version: "0.2.2" + version: "0.2.3" google_fonts: dependency: "direct main" description: name: google_fonts - sha256: f0b8d115a13ecf827013ec9fc883390ccc0e87a96ed5347a3114cac177ef18e8 + sha256: b1ac0fe2832c9cc95e5e88b57d627c5e68c223b9657f4b96e1487aa9098c7b82 url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "6.2.1" html: dependency: transitive description: @@ -340,10 +372,10 @@ packages: dependency: "direct main" description: name: http - sha256: "4c3f04bfb64d3efd508d06b41b825542f08122d30bda4933fb95c069d22a4fa3" + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.2.1" http_parser: dependency: transitive description: @@ -364,18 +396,18 @@ packages: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.7.1" just_audio: dependency: "direct main" description: name: just_audio - sha256: b607cd1a43bac03d85c3aaee00448ff4a589ef2a77104e3d409889ff079bf823 + sha256: b7cb6bbf3750caa924d03f432ba401ec300fd90936b3f73a9b33d58b1e96286b url: "https://pub.dev" source: hosted - version: "0.9.36" + version: "0.9.37" just_audio_platform_interface: dependency: transitive description: @@ -404,18 +436,42 @@ packages: dependency: transitive description: name: latlong2 - sha256: "18712164760cee655bc790122b0fd8f3d5b3c36da2cb7bf94b68a197fbb0811b" + sha256: "98227922caf49e6056f91b6c56945ea1c7b166f28ffcd5fb8e72fc0b453cc8fe" url: "https://pub.dev" source: hosted - version: "0.9.0" + version: "0.9.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + url: "https://pub.dev" + source: hosted + version: "10.0.0" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + url: "https://pub.dev" + source: hosted + version: "2.0.1" lints: dependency: transitive description: name: lints - sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "3.0.0" lists: dependency: transitive description: @@ -428,34 +484,34 @@ packages: dependency: transitive description: name: logger - sha256: "6bbb9d6f7056729537a4309bda2e74e18e5d9f14302489cc1e93f33b3fe32cac" + sha256: "8c94b8c219e7e50194efc8771cd0e9f10807d8d3e219af473d89b06cc2ee4e04" url: "https://pub.dev" source: hosted - version: "2.0.2+1" + version: "2.2.0" matcher: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.0" mgrs_dart: dependency: transitive description: @@ -492,26 +548,26 @@ packages: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" path_provider: dependency: transitive description: name: path_provider - sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" + sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.4" path_provider_foundation: dependency: transitive description: @@ -548,10 +604,10 @@ packages: dependency: "direct main" description: name: permission_handler - sha256: "74e962b7fad7ff75959161bb2c0ad8fe7f2568ee82621c9c2660b751146bfe44" + sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb" url: "https://pub.dev" source: hosted - version: "11.3.0" + version: "11.3.1" permission_handler_android: dependency: transitive description: @@ -564,10 +620,10 @@ packages: dependency: transitive description: name: permission_handler_apple - sha256: bdafc6db74253abb63907f4e357302e6bb786ab41465e8635f362ee71fd8707b + sha256: e9ad66020b89ff1b63908f247c2c6f931c6e62699b756ef8b3c4569350cd8662 url: "https://pub.dev" source: hosted - version: "9.4.0" + version: "9.4.4" permission_handler_html: dependency: transitive description: @@ -580,10 +636,10 @@ packages: dependency: transitive description: name: permission_handler_platform_interface - sha256: "23dfba8447c076ab5be3dee9ceb66aad345c4a648f0cac292c77b1eb0e800b78" + sha256: "48d4fcf201a1dad93ee869ab0d4101d084f49136ec82a8a06ed9cfeacab9fd20" url: "https://pub.dev" source: hosted - version: "4.2.0" + version: "4.2.1" permission_handler_windows: dependency: transitive description: @@ -612,10 +668,10 @@ packages: dependency: transitive description: name: pointycastle - sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29" + sha256: "70fe966348fe08c34bf929582f1d8247d9d9408130723206472b4687227e4333" url: "https://pub.dev" source: hosted - version: "3.7.4" + version: "3.8.0" polylabel: dependency: transitive description: @@ -652,18 +708,18 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" + sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.3" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" + sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" shared_preferences_foundation: dependency: transitive description: @@ -692,10 +748,10 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21" + sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.3.0" shared_preferences_windows: dependency: transitive description: @@ -817,26 +873,26 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "0ecc004c62fd3ed36a2ffcbe0dd9700aee63bd7532d0b642a488b1ec310f492e" + sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e" url: "https://pub.dev" source: hosted - version: "6.2.5" + version: "6.2.6" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: d4ed0711849dd8e33eb2dd69c25db0d0d3fdc37e0a62e629fe32f57a22db2745 + sha256: "360a6ed2027f18b73c8d98e159dda67a61b7f2e0f6ec26e86c3ada33b0621775" url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.3.1" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03" + sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5" url: "https://pub.dev" source: hosted - version: "6.2.4" + version: "6.2.5" url_launcher_linux: dependency: transitive description: @@ -865,10 +921,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b + sha256: "3692a459204a33e04bc94f5fb91158faf4f2c8903281ddd82915adecdb1a901d" url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.3.0" url_launcher_windows: dependency: transitive description: @@ -881,10 +937,10 @@ packages: dependency: "direct main" description: name: uuid - sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8 + sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8" url: "https://pub.dev" source: hosted - version: "4.3.3" + version: "4.4.0" vector_math: dependency: "direct main" description: @@ -893,30 +949,38 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + url: "https://pub.dev" + source: hosted + version: "13.0.0" web: dependency: transitive description: name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "0.5.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.5" win32: dependency: transitive description: name: win32 - sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8" + sha256: "0a989dc7ca2bb51eac91e8fd00851297cfffd641aa7538b165c62637ca0eaa4a" url: "https://pub.dev" source: hosted - version: "5.2.0" + version: "5.4.0" win32_registry: dependency: transitive description: @@ -958,5 +1022,5 @@ packages: source: hosted version: "1.0.4" sdks: - dart: ">=3.2.0 <4.0.0" - flutter: ">=3.16.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.19.2" diff --git a/pubspec.yaml b/pubspec.yaml index b456929..f778d80 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -33,14 +33,14 @@ dependencies: archive: ^3.1.2 just_audio: ^0.9.36 just_audio_windows: ^0.2.0 - audioplayers: ^5.2.1 - csv: ^5.1.1 - http: any + audioplayers: ^6.0.0 + csv: ^6.0.0 + http: ^1.2.1 google_fonts: ^6.1.0 intl: any text_scroll: ^0.2.0 flutter_map: ^6.1.0 - appwrite: ^11.0.1 + appwrite: ^12.0.1 shared_preferences: ^2.2.2 url_launcher: ^6.2.2 ntp: ^2.0.0 @@ -49,6 +49,8 @@ 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 # The following adds the Cupertino Icons font to your application. @@ -64,7 +66,7 @@ dev_dependencies: # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^2.0.0 + flutter_lints: ^3.0.2 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -79,12 +81,13 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - - assets/ibus_recordings.zip +# - assets/ibus_recordings.zip - assets/datasets/bus-sequences.csv - assets/fonts/ibus/london-buses-ibus.ttf - assets/audio/manual_announcements/ - assets/audio/to_destination.wav - assets/datasets/bus-blinds.csv + - assets/audio/5-seconds-of-silence.mp3 # - 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 24fcf1e..4ba1456 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,6 +7,7 @@ #include "generated_plugin_registrant.h" #include +#include #include #include #include @@ -18,6 +19,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { AudioplayersWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); + FlutterTtsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterTtsPlugin")); GeolocatorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("GeolocatorWindows")); JustAudioWindowsPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index a9dab9a..b8b98c6 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST audioplayers_windows + flutter_tts geolocator_windows just_audio_windows permission_handler_windows