Alot of changes
This commit is contained in:
@@ -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<void> 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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -129,6 +129,8 @@ class CommandModule extends InfoModule {
|
||||
else if (args[0].startsWith("dest")) {
|
||||
// announce destination <RouteNumber> <RouteVariantIndex> <ScheduledTime>
|
||||
|
||||
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,
|
||||
|
||||
@@ -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(),
|
||||
|
||||
},
|
||||
);
|
||||
|
||||
@@ -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<BusRouteVariant>(
|
||||
delegate: LiveInformation().routeVariantDelegate,
|
||||
builder: (context, routeVariant) {
|
||||
print("rebuilt stop announcement picker");
|
||||
return StopAnnouncementPicker(
|
||||
routeVariant: routeVariant,
|
||||
backgroundColor: Colors.grey.shade900,
|
||||
outlineColor: Colors.white70,
|
||||
);
|
||||
},
|
||||
defaultBuilder: (context) {
|
||||
BusRouteVariant? routeVariant = LiveInformation().getRouteVariant();
|
||||
if (routeVariant == null) {
|
||||
return Container(
|
||||
color: Colors.grey.shade900,
|
||||
child: Center(
|
||||
child: Text(
|
||||
"No route selected",
|
||||
style: GoogleFonts.teko(
|
||||
fontSize: 25,
|
||||
color: Colors.white70
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
|
||||
Container(
|
||||
height: 2,
|
||||
color: Colors.white70,
|
||||
),
|
||||
|
||||
Container(
|
||||
|
||||
margin: EdgeInsets.all(10),
|
||||
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade900,
|
||||
),
|
||||
|
||||
child: DelegateBuilder<BusRouteVariant>(
|
||||
delegate: LiveInformation().routeVariantDelegate,
|
||||
builder: (context, routeVariant) {
|
||||
print("rebuilt stop announcement picker");
|
||||
return StopAnnouncementPicker(
|
||||
routeVariant: routeVariant,
|
||||
backgroundColor: Colors.grey.shade900,
|
||||
outlineColor: Colors.white70,
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
defaultBuilder: (context) {
|
||||
BusRouteVariant? routeVariant = LiveInformation().getRouteVariant();
|
||||
if (routeVariant == null) {
|
||||
return Container(
|
||||
color: Colors.grey.shade900,
|
||||
child: Center(
|
||||
child: Text(
|
||||
"No route selected",
|
||||
style: GoogleFonts.teko(
|
||||
fontSize: 25,
|
||||
color: Colors.white70
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return StopAnnouncementPicker(
|
||||
routeVariant: routeVariant,
|
||||
backgroundColor: Colors.grey.shade900,
|
||||
outlineColor: Colors.white70,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
),
|
||||
|
||||
),
|
||||
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
LiveInformation liveInformation = LiveInformation();
|
||||
final commandModule = liveInformation.commandModule;
|
||||
|
||||
// commandModule.executeCommand(
|
||||
// "announce dest"
|
||||
// );
|
||||
|
||||
liveInformation.announcementModule.queueAnnouncementByRouteVariant(
|
||||
routeVariant: liveInformation.getRouteVariant()!
|
||||
);
|
||||
|
||||
},
|
||||
child: Text("Announce current destination"),
|
||||
),
|
||||
|
||||
|
||||
// Container(
|
||||
//
|
||||
// margin: EdgeInsets.all(20),
|
||||
//
|
||||
// height: 300-45,
|
||||
//
|
||||
// child: ListView(
|
||||
//
|
||||
// scrollDirection: Axis.vertical,
|
||||
//
|
||||
// children: [
|
||||
//
|
||||
// ElevatedButton(
|
||||
// onPressed: () async {
|
||||
// LiveInformation liveInformation = LiveInformation();
|
||||
// liveInformation.queueAnnouncement(await liveInformation.getDestinationAnnouncement(liveInformation.getRouteVariant()!, sendToServer: false));
|
||||
// },
|
||||
// child: Text("Test announcement"),
|
||||
// ),
|
||||
//
|
||||
// ElevatedButton(
|
||||
// onPressed: () {
|
||||
// LiveInformation liveInformation = LiveInformation();
|
||||
// liveInformation.updateServer();
|
||||
// },
|
||||
// child: Text("Update server"),
|
||||
// ),
|
||||
//
|
||||
// SizedBox(
|
||||
//
|
||||
// width: 100,
|
||||
//
|
||||
// child: TextField(
|
||||
// onChanged: (String value) {
|
||||
// LiveInformation liveInformation = LiveInformation();
|
||||
// // liveInformation.documentID = value;
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
//
|
||||
// SizedBox(
|
||||
//
|
||||
// width: 200,
|
||||
//
|
||||
// child: TextField(
|
||||
// onSubmitted: (String value) {
|
||||
// LiveInformation liveInformation = LiveInformation();
|
||||
// liveInformation.queueAnnouncement(AnnouncementQueueEntry(
|
||||
// displayText: value,
|
||||
// audioSources: []
|
||||
// ));
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
//
|
||||
// ElevatedButton(
|
||||
// onPressed: () {
|
||||
// LiveInformation liveInformation = LiveInformation();
|
||||
// liveInformation.pullServer();
|
||||
// },
|
||||
// child: Text("Pull server"),
|
||||
// ),
|
||||
//
|
||||
// ],
|
||||
//
|
||||
// ),
|
||||
//
|
||||
// ),
|
||||
|
||||
],
|
||||
|
||||
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"),
|
||||
// ),
|
||||
//
|
||||
// ],
|
||||
//
|
||||
// ),
|
||||
//
|
||||
// ),
|
||||
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
);
|
||||
|
||||
@@ -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<InitialStartup> createState() => _InitialStartupState();
|
||||
}
|
||||
|
||||
const String Version = "0.2.0";
|
||||
// Get the current version from /assets/version.txt
|
||||
Future<String> GetVersion() async {
|
||||
return await rootBundle.loadString("assets/version.txt");
|
||||
}
|
||||
|
||||
class _InitialStartupState extends State<InitialStartup> {
|
||||
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<InitialStartup> {
|
||||
|
||||
children: [
|
||||
|
||||
PermissionsSetup(),
|
||||
|
||||
SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
@@ -79,13 +118,29 @@ class _InitialStartupState extends State<InitialStartup> {
|
||||
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
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
],
|
||||
|
||||
@@ -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<AnnouncementUpload> {
|
||||
|
||||
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<AnnouncementUpload> {
|
||||
|
||||
});
|
||||
|
||||
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<AnnouncementUpload> {
|
||||
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<AnnouncementUpload> {
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(
|
||||
height: 8,
|
||||
@@ -368,6 +412,23 @@ class _AnnouncementUploadState extends State<AnnouncementUpload> {
|
||||
),
|
||||
),
|
||||
|
||||
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<AnnouncementUpload> {
|
||||
}
|
||||
}
|
||||
|
||||
class PermissionsSetup extends StatefulWidget {
|
||||
|
||||
@override
|
||||
State<PermissionsSetup> createState() => _PermissionsSetupState();
|
||||
}
|
||||
|
||||
class _PermissionsSetupState extends State<PermissionsSetup> {
|
||||
List<bool> _hasPermissions = [];
|
||||
|
||||
bool get hasPermissions {
|
||||
return !_hasPermissions.contains(false) && _hasPermissions.length > 0;
|
||||
}
|
||||
|
||||
Future<void> 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
|
||||
|
||||
@@ -36,7 +36,7 @@ class TfL_Dataset_TestState extends State<TfL_Dataset_Test> {
|
||||
pages_Home(),
|
||||
pages_Routes(),
|
||||
pages_Display(this),
|
||||
pages_Settings(),
|
||||
pages_Settings()
|
||||
];
|
||||
}
|
||||
|
||||
@@ -48,6 +48,11 @@ class TfL_Dataset_TestState extends State<TfL_Dataset_Test> {
|
||||
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<TfL_Dataset_Test> {
|
||||
|
||||
|
||||
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<TfL_Dataset_Test> {
|
||||
|
||||
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: [
|
||||
|
||||
465
lib/remaster/InitialStartup.dart
Normal file
465
lib/remaster/InitialStartup.dart
Normal file
@@ -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<InitialStartup> createState() => _InitialStartupState();
|
||||
}
|
||||
|
||||
class _InitialStartupState extends State<InitialStartup> {
|
||||
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<bool> _allPermissionsGranted() async {
|
||||
|
||||
List<bool> 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<bool> _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,
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
],
|
||||
|
||||
),
|
||||
)
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
26
lib/remaster/RemasteredMain.dart
Normal file
26
lib/remaster/RemasteredMain.dart
Normal file
@@ -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()
|
||||
},
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
365
lib/remaster/dashboard.dart
Normal file
365
lib/remaster/dashboard.dart
Normal file
@@ -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,
|
||||
//
|
||||
// ),
|
||||
// ),
|
||||
// )
|
||||
//
|
||||
// ],
|
||||
//
|
||||
// )
|
||||
|
||||
],
|
||||
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
||||
],
|
||||
|
||||
),
|
||||
|
||||
),
|
||||
)
|
||||
|
||||
],
|
||||
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<List<dynamic>> destinationRows = const CsvToListConverter().convert(destinationsCSV);
|
||||
List<List<String>> destinationRows = CsvConverter().convert(destinationsCSV);
|
||||
destinationRows.removeAt(0);
|
||||
|
||||
print("Destination rows: ${destinationRows.length}");
|
||||
|
||||
for (int i = 0; i < destinationRows.length; i++) {
|
||||
try {
|
||||
|
||||
List<dynamic> 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<List<dynamic>> busSequenceRows = const CsvToListConverter().convert(busSequencesCSV);
|
||||
List<List<dynamic>> busSequenceRows = CsvConverter().convert(busSequencesCSV);
|
||||
busSequenceRows.removeAt(0);
|
||||
|
||||
for (int i = 0; i < busSequenceRows.length; i++) {
|
||||
@@ -61,20 +66,21 @@ class BusSequences extends InfoModule {
|
||||
{
|
||||
|
||||
List<dynamic> 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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
15
lib/utils/web_workarrounds.dart
Normal file
15
lib/utils/web_workarrounds.dart
Normal file
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user