670 lines
20 KiB
Dart
670 lines
20 KiB
Dart
|
|
|
|
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:permission_handler/permission_handler.dart';
|
|
import 'package:shadcn_ui/shadcn_ui.dart';
|
|
import 'package:shared_preferences/shared_preferences.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
|
|
]);
|
|
|
|
// if (defaultTargetPlatform == TargetPlatform.android) {
|
|
// perms.add(
|
|
// await Permission.nearbyWifiDevices.isGranted
|
|
// );
|
|
// }
|
|
|
|
return !perms.contains(false);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
|
|
return Container(
|
|
|
|
padding: EdgeInsets.all(16),
|
|
|
|
alignment: Alignment.center,
|
|
|
|
|
|
|
|
child: SizedBox(
|
|
|
|
child: Column(
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
children: [
|
|
Text(
|
|
"Permissions",
|
|
style: TextStyle(
|
|
fontSize: 32,
|
|
fontWeight: FontWeight.w600,
|
|
)
|
|
),
|
|
|
|
SizedBox(
|
|
height: 16,
|
|
),
|
|
|
|
Container(
|
|
height: 210,
|
|
child: SingleChildScrollView(
|
|
scrollDirection: Axis.horizontal,
|
|
child: Row(
|
|
|
|
// mainAxisSize: MainAxisSize.min,
|
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
children: [
|
|
ShadCard(
|
|
width: 300,
|
|
height: 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,
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
if (!kIsWeb)
|
|
SizedBox(
|
|
width: 16,
|
|
),
|
|
|
|
if (!kIsWeb)
|
|
ShadCard(
|
|
width: 300,
|
|
height: 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,
|
|
);
|
|
},
|
|
)
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
if (defaultTargetPlatform == TargetPlatform.android)
|
|
SizedBox(
|
|
width: 16,
|
|
),
|
|
|
|
if (defaultTargetPlatform == TargetPlatform.android && false)
|
|
ShadCard(
|
|
width: 300,
|
|
height: double.infinity,
|
|
title: Text(
|
|
"Nearby Devices",
|
|
),
|
|
description: Text(
|
|
"Nearby Devices access is required to find nearby devices, and to establish connections with them."
|
|
),
|
|
content: Container(
|
|
child: Column(
|
|
children: [
|
|
|
|
SizedBox(
|
|
height: 4,
|
|
),
|
|
|
|
FutureBuilder(
|
|
future: Permission.nearbyWifiDevices.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.nearbyWifiDevices.request();
|
|
setState(() {
|
|
|
|
});
|
|
},
|
|
enabled: isEnabled,
|
|
backgroundColor: color,
|
|
);
|
|
},
|
|
)
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
/*SizedBox(
|
|
width: 16,
|
|
),
|
|
|
|
ShadCard(
|
|
width: 300,
|
|
height: 200,
|
|
title: Text(
|
|
"Network",
|
|
),
|
|
description: Text(
|
|
"Network access is required for commincation between devices for multi mode."
|
|
),
|
|
content: Container(
|
|
child: Column(
|
|
children: [
|
|
|
|
SizedBox(
|
|
height: 4,
|
|
),
|
|
|
|
FutureBuilder(
|
|
future: Permission.nearbyWifiDevices.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> {
|
|
|
|
bool _loadingAudio = false;
|
|
|
|
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,
|
|
),
|
|
|
|
if (!_loadingAudio)
|
|
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 {
|
|
|
|
setState(() {
|
|
_loadingAudio = true;
|
|
});
|
|
|
|
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;
|
|
|
|
if (!kIsWeb) {
|
|
|
|
// Use shared preferences to store the file location
|
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
|
|
|
prefs.setString("AnnouncementsFileLocation", result.files.single.path!);
|
|
}
|
|
|
|
// load a random announcement to ensure that the file is usable
|
|
await cache.loadAnnouncementsFromBytes(bytes, ["S_WALTHAMSTOW_CENTRAL_001.mp3"]);
|
|
|
|
setState(() {
|
|
_loadingAudio = false;
|
|
});
|
|
|
|
} else {
|
|
setState(() {
|
|
_loadingAudio = false;
|
|
});
|
|
}
|
|
|
|
|
|
},
|
|
enabled: isEnabled,
|
|
backgroundColor: color,
|
|
);
|
|
},
|
|
)
|
|
else
|
|
CircularProgressIndicator(),
|
|
|
|
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
SizedBox(
|
|
height: 16,
|
|
),
|
|
|
|
FutureBuilder(
|
|
future: _announcementsUploaded(),
|
|
builder: (context, val) {
|
|
bool isEnabled = true;
|
|
String text = "Continue";
|
|
Color color = Colors.white;
|
|
|
|
if (val.hasData) {
|
|
isEnabled = val.data!;
|
|
}
|
|
if (!isEnabled) {
|
|
text = "Complete the prerequisites to continue";
|
|
}
|
|
|
|
return ShadButton(
|
|
text: Text(text),
|
|
onPressed: () async {
|
|
|
|
LiveInformation().initTrackerModule();
|
|
|
|
showShadDialog(
|
|
context: context,
|
|
builder: (context) => ShadDialog.alert(
|
|
title: Text("You're all setup"),
|
|
description: Text("You can now continue to the application. \nTry not to annoy anyone on the bus! ;)"),
|
|
actions: [
|
|
ShadButton(
|
|
text: Text("Continue to application"),
|
|
onPressed: () {
|
|
Navigator.pushNamed(context, "/");
|
|
},
|
|
)
|
|
],
|
|
),
|
|
barrierDismissible: false
|
|
);
|
|
|
|
},
|
|
enabled: isEnabled,
|
|
backgroundColor: color,
|
|
width: double.infinity,
|
|
);
|
|
},
|
|
)
|
|
|
|
],
|
|
|
|
),
|
|
)
|
|
|
|
);
|
|
}
|
|
} |