1469 lines
37 KiB
Dart
1469 lines
37 KiB
Dart
|
|
|
|
import 'dart:convert';
|
|
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/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';
|
|
|
|
class pages_Settings extends StatefulWidget {
|
|
|
|
@override
|
|
State<pages_Settings> createState() => _pages_SettingsState();
|
|
}
|
|
|
|
class _pages_SettingsState extends State<pages_Settings> {
|
|
|
|
late final Widget _loginPage;
|
|
|
|
@override
|
|
void initState() {
|
|
// TODO: implement initState
|
|
super.initState();
|
|
|
|
if (!LiveInformation().auth.isAuthenticated()){
|
|
_loginPage = _LoginPage(
|
|
onLogin: () {
|
|
setState(() {});
|
|
},
|
|
);
|
|
} else {
|
|
_loginPage = Container(
|
|
padding: const EdgeInsets.all(8),
|
|
child: ElevatedButton(
|
|
onPressed: (){
|
|
setState(() {});
|
|
LiveInformation().auth.deleteSession();
|
|
|
|
},
|
|
|
|
// 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)
|
|
),
|
|
minimumSize: Size(double.infinity, 48)
|
|
),
|
|
|
|
child: Text(
|
|
"Sign out",
|
|
style: GoogleFonts.interTight(
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.white,
|
|
letterSpacing: 0.5
|
|
)
|
|
)
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
|
|
return Container(
|
|
|
|
child: SingleChildScrollView(
|
|
child: Column(
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
children: [
|
|
|
|
SizedBox(
|
|
height: 8
|
|
),
|
|
|
|
AnnouncementUpload(),
|
|
|
|
SizedBox(
|
|
height: 8
|
|
),
|
|
|
|
Container(
|
|
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),
|
|
|
|
child: _loginPage,
|
|
)
|
|
|
|
],
|
|
|
|
),
|
|
)
|
|
|
|
);
|
|
}
|
|
}
|
|
|
|
class AnnouncementUpload extends StatefulWidget {
|
|
|
|
Function onUploaded = () {};
|
|
|
|
AnnouncementUpload({Key? key, this.onUploaded = _defaultOnUploaded}) : super(key: key);
|
|
|
|
static void _defaultOnUploaded() {}
|
|
|
|
@override
|
|
State<AnnouncementUpload> createState() => _AnnouncementUploadState();
|
|
}
|
|
|
|
class _AnnouncementUploadState extends State<AnnouncementUpload> {
|
|
|
|
Future<void> UploadButtonPressed() async {
|
|
|
|
// Pick the file
|
|
FilePickerResult? result = await FilePicker.platform.pickFiles();
|
|
|
|
if (result != null) {
|
|
print("Got file");
|
|
|
|
AnnouncementCache cache = LiveInformation().announcementModule.announcementCache;
|
|
|
|
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(bytes, ["S_WALTHAMSTOW_CENTRAL_001.mp3"]);
|
|
|
|
print("Loaded announcements");
|
|
|
|
setState(() {
|
|
|
|
});
|
|
|
|
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) {
|
|
|
|
bool checkPassed = LiveInformation().announcementModule.announcementCache.keys.length != 0;
|
|
|
|
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: [
|
|
if (!checkPassed)
|
|
Row(
|
|
|
|
children: [
|
|
|
|
Icon(
|
|
Icons.error,
|
|
color: Colors.red,
|
|
size: 18,
|
|
),
|
|
|
|
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_box,
|
|
color: Colors.green,
|
|
size: 18,
|
|
),
|
|
|
|
SizedBox(
|
|
width: 4,
|
|
),
|
|
|
|
|
|
Transform.translate(
|
|
offset: Offset(0, 0),
|
|
child: Text(
|
|
"ANNOUNCEMENTS LOADED",
|
|
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
|
|
),
|
|
),
|
|
|
|
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,
|
|
),
|
|
|
|
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
|
|
)
|
|
)
|
|
),
|
|
),
|
|
],
|
|
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
|
|
class _LoginPage extends StatefulWidget {
|
|
|
|
_LoginType type = _LoginType.login;
|
|
|
|
final Function() onLogin;
|
|
|
|
_LoginPage({super.key, required this.onLogin, });
|
|
|
|
@override
|
|
State<_LoginPage> createState() => _LoginPageState();
|
|
}
|
|
|
|
class _LoginPageState extends State<_LoginPage> {
|
|
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
color: Color.fromRGBO(19, 19, 19, 1),
|
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 32),
|
|
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
|
|
// Login form
|
|
widget.type == _LoginType.login ?
|
|
LoginForm(
|
|
handleSubmit: (form) {
|
|
print("Login form submitted");
|
|
|
|
LiveInformation().auth.createEmailSession(
|
|
email: form.emailController.text,
|
|
password: form.passwordController.text
|
|
).then((value) {
|
|
widget.onLogin();
|
|
});
|
|
|
|
},
|
|
requestSignup: () {
|
|
setState(() {
|
|
widget.type = _LoginType.signup;
|
|
});
|
|
},
|
|
) :
|
|
SignupForm(
|
|
handleSubmit: (form) {
|
|
print("Signup form submitted");
|
|
|
|
LiveInformation().auth.createUser(
|
|
displayName: form.dispnameController.text,
|
|
username: form.usernameController.text,
|
|
email: form.emailController.text,
|
|
password: form.passwordController.text
|
|
).then((value) {
|
|
// login
|
|
LiveInformation().auth.createEmailSession(
|
|
email: form.emailController.text,
|
|
password: form.passwordController.text
|
|
).then((value) {
|
|
widget.onLogin();
|
|
});
|
|
});
|
|
|
|
},
|
|
requestSignin: () {
|
|
setState(() {
|
|
widget.type = _LoginType.login;
|
|
});
|
|
},
|
|
),
|
|
|
|
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class LoginForm extends StatefulWidget {
|
|
|
|
final Function(LoginForm) handleSubmit;
|
|
|
|
final Function() requestSignup;
|
|
|
|
/* TextControllers */
|
|
final TextEditingController emailController = TextEditingController();
|
|
|
|
final TextEditingController passwordController = TextEditingController();
|
|
|
|
|
|
LoginForm({super.key, required this.handleSubmit, required this.requestSignup});
|
|
|
|
@override
|
|
State<LoginForm> createState() => _LoginFormState();
|
|
}
|
|
|
|
class _LoginFormState extends State<LoginForm> {
|
|
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
|
|
child: Column(
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
children: [
|
|
|
|
Text(
|
|
"Sign In",
|
|
style: GoogleFonts.montserrat(
|
|
fontSize: 32,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white,
|
|
letterSpacing: -1
|
|
)
|
|
),
|
|
|
|
SizedBox(height: 20),
|
|
|
|
PW_TextField(
|
|
title: "Email",
|
|
controller: widget.emailController,
|
|
handleSubmit: (form) {
|
|
print("Signup form submitted");
|
|
widget.handleSubmit(widget);
|
|
}
|
|
),
|
|
|
|
SizedBox(height: 20),
|
|
|
|
PW_TextField(
|
|
title: "Password",
|
|
obscure: true,
|
|
controller: widget.passwordController,
|
|
handleSubmit: (form) {
|
|
print("Signup form submitted");
|
|
widget.handleSubmit(widget);
|
|
}
|
|
),
|
|
|
|
SizedBox(height: 20),
|
|
|
|
ElevatedButton(
|
|
onPressed: (){
|
|
widget.handleSubmit(widget);
|
|
},
|
|
|
|
// 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)
|
|
),
|
|
minimumSize: Size(double.infinity, 48)
|
|
),
|
|
|
|
child: Text(
|
|
"Sign in",
|
|
style: GoogleFonts.interTight(
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.white,
|
|
letterSpacing: 0.5
|
|
)
|
|
)
|
|
),
|
|
|
|
SizedBox(height: 20),
|
|
|
|
Row(
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
children: [
|
|
|
|
TextButton(
|
|
onPressed: (){
|
|
|
|
},
|
|
|
|
// Make border radius 4, background transparent, and text colour white
|
|
style: TextButton.styleFrom(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(4)
|
|
),
|
|
backgroundColor: Colors.transparent
|
|
),
|
|
|
|
child: Text(
|
|
"Forgot password?",
|
|
|
|
style: GoogleFonts.interTight(
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.w400,
|
|
color: Colors.grey.shade400,
|
|
letterSpacing: 0.5
|
|
),
|
|
|
|
)
|
|
),
|
|
|
|
Container(
|
|
width: 1,
|
|
height: 24,
|
|
margin: const EdgeInsets.symmetric(horizontal: 8),
|
|
color: Colors.grey.shade800,
|
|
),
|
|
|
|
TextButton(
|
|
onPressed: (){
|
|
widget.requestSignup();
|
|
},
|
|
|
|
// Make border radius 4, background transparent, and text colour white
|
|
style: TextButton.styleFrom(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(4)
|
|
),
|
|
backgroundColor: Colors.transparent
|
|
),
|
|
|
|
child: Text(
|
|
"Sign Up",
|
|
|
|
style: GoogleFonts.interTight(
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.w400,
|
|
color: Colors.grey.shade400,
|
|
letterSpacing: 0.5
|
|
),
|
|
|
|
)
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
|
|
|
|
],
|
|
)
|
|
|
|
);
|
|
}
|
|
}
|
|
|
|
class SignupForm extends StatelessWidget {
|
|
|
|
final Function(SignupForm) handleSubmit;
|
|
|
|
final Function() requestSignin;
|
|
|
|
/* TextControllers */
|
|
final TextEditingController dispnameController = TextEditingController();
|
|
final TextEditingController usernameController = TextEditingController();
|
|
final TextEditingController emailController = TextEditingController();
|
|
final TextEditingController passwordController = TextEditingController();
|
|
final TextEditingController confirmPasswordController = TextEditingController();
|
|
|
|
SignupForm({super.key, required this.handleSubmit, required this.requestSignin});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
|
|
child: Column(
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
children: [
|
|
|
|
Text(
|
|
"Sign Up",
|
|
style: GoogleFonts.montserrat(
|
|
fontSize: 32,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white,
|
|
letterSpacing: -1
|
|
)
|
|
),
|
|
|
|
SizedBox(height: 20),
|
|
|
|
PW_TextField(
|
|
title: "Display Name",
|
|
controller: dispnameController,
|
|
handleSubmit: (form) {
|
|
print("Signup form submitted");
|
|
handleSubmit(this);
|
|
}
|
|
),
|
|
|
|
SizedBox(height: 20),
|
|
|
|
PW_TextField(
|
|
title: "Username",
|
|
controller: usernameController,
|
|
handleSubmit: (form) {
|
|
print("Signup form submitted");
|
|
handleSubmit(this);
|
|
}
|
|
),
|
|
|
|
SizedBox(height: 20),
|
|
|
|
PW_TextField(
|
|
title: "Email",
|
|
controller: emailController,
|
|
handleSubmit: (form) {
|
|
print("Signup form submitted");
|
|
handleSubmit(this);
|
|
}
|
|
),
|
|
|
|
SizedBox(height: 20),
|
|
|
|
PW_TextField(
|
|
title: "Password",
|
|
obscure: true,
|
|
controller: passwordController,
|
|
handleSubmit: (form) {
|
|
print("Signup form submitted");
|
|
handleSubmit(this);
|
|
}
|
|
),
|
|
|
|
SizedBox(height: 20),
|
|
|
|
PW_TextField(
|
|
title: "Confirm Password",
|
|
obscure: true,
|
|
controller: confirmPasswordController,
|
|
handleSubmit: (form) {
|
|
print("Signup form submitted");
|
|
handleSubmit(this);
|
|
}
|
|
),
|
|
|
|
SizedBox(height: 20),
|
|
|
|
// Terms and conditions with hyperlink to terms and conditions, with checkbox
|
|
// use TextSpan
|
|
// use check box
|
|
|
|
Row(
|
|
children: [
|
|
Checkbox(
|
|
value: false,
|
|
onChanged: (value) {
|
|
print("Checkbox changed to $value");
|
|
},
|
|
),
|
|
Expanded(
|
|
child: RichText(
|
|
// wrap text
|
|
text: TextSpan(
|
|
children: [
|
|
TextSpan(
|
|
text: "By registering, you agree to our ",
|
|
style: GoogleFonts.interTight(
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.w400,
|
|
color: Colors.grey.shade400,
|
|
letterSpacing: 0.5,
|
|
),
|
|
),
|
|
TextSpan(
|
|
text: "Terms and Conditions",
|
|
style: GoogleFonts.interTight(
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.w400,
|
|
color: Colors.grey.shade400,
|
|
letterSpacing: 0.5,
|
|
decoration: TextDecoration.underline,
|
|
),
|
|
recognizer: new TapGestureRecognizer()
|
|
..onTap = () {
|
|
launchUrlString("https://google.co.uk/");
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
SizedBox(height: 20),
|
|
|
|
ElevatedButton(
|
|
onPressed: (){
|
|
handleSubmit(this);
|
|
},
|
|
|
|
// 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)
|
|
),
|
|
minimumSize: Size(double.infinity, 48)
|
|
),
|
|
|
|
child: Text(
|
|
"Sign up",
|
|
style: GoogleFonts.interTight(
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.white,
|
|
letterSpacing: 0.5
|
|
)
|
|
)
|
|
),
|
|
|
|
SizedBox(height: 20),
|
|
|
|
// Already have an account? Sign in
|
|
|
|
Row(
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
children: [
|
|
|
|
Text(
|
|
"Already have an account? ",
|
|
style: GoogleFonts.interTight(
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.w400,
|
|
color: Colors.grey.shade400,
|
|
letterSpacing: 0.5,
|
|
),
|
|
),
|
|
|
|
TextButton(
|
|
onPressed: (){
|
|
requestSignin();
|
|
},
|
|
|
|
// Make border radius 4, background transparent, and text colour white
|
|
style: TextButton.styleFrom(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(4)
|
|
),
|
|
backgroundColor: Colors.transparent
|
|
),
|
|
|
|
child: Text(
|
|
"Sign in",
|
|
|
|
style: GoogleFonts.interTight(
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.w400,
|
|
color: Colors.grey.shade400,
|
|
letterSpacing: 0.5
|
|
),
|
|
|
|
)
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
],
|
|
)
|
|
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
class PW_TextField extends StatelessWidget {
|
|
|
|
String title = "field";
|
|
bool obscure = false;
|
|
bool longText = false;
|
|
|
|
late TextEditingController controller;
|
|
|
|
Function(String)? handleTapOutside;
|
|
Function(String)? handleEditingComplete;
|
|
Function(String)? handleChanged;
|
|
Function(String)? handleSubmit;
|
|
|
|
PW_TextField({super.key, this.title = "field", required this.controller, this.obscure = false, this.longText = false, this.handleSubmit, this.handleTapOutside, this.handleEditingComplete, this.handleChanged});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
// TODO: implement build
|
|
return Column(
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
children: [
|
|
|
|
Text(
|
|
title,
|
|
style: GoogleFonts.interTight(
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.w400,
|
|
color: Colors.grey.shade300,
|
|
letterSpacing: 0.1
|
|
)
|
|
),
|
|
|
|
SizedBox(height: 4),
|
|
|
|
// field with uniform padding and margin
|
|
SizedBox(
|
|
|
|
child: TextField(
|
|
|
|
onEditingComplete: () {
|
|
if (handleEditingComplete != null) {
|
|
handleEditingComplete!(controller.text);
|
|
}
|
|
},
|
|
|
|
onChanged: (value) {
|
|
if (handleChanged != null) {
|
|
handleChanged!(controller.text);
|
|
}
|
|
},
|
|
|
|
onSubmitted: (value) {
|
|
if (handleSubmit != null) {
|
|
handleSubmit!(controller.text);
|
|
}
|
|
},
|
|
|
|
onTapOutside: (value) {
|
|
if (handleTapOutside != null) {
|
|
handleTapOutside!(controller.text);
|
|
}
|
|
},
|
|
|
|
decoration: InputDecoration(
|
|
contentPadding: const EdgeInsets.all(12),
|
|
isDense: true,
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(4),
|
|
|
|
),
|
|
filled: true,
|
|
// fillColor: STUDIOS_DEFAULT_BACKGROUND_COLOR,
|
|
hintText: title,
|
|
hintStyle: GoogleFonts.interTight(
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.w400,
|
|
color: Colors.grey.shade700,
|
|
letterSpacing: 0.1
|
|
),
|
|
|
|
),
|
|
|
|
// wrap text
|
|
minLines: longText ? 3 : 1,
|
|
maxLines: longText ? null : 1,
|
|
keyboardType: TextInputType.multiline,
|
|
|
|
obscureText: obscure,
|
|
|
|
style: GoogleFonts.interTight(
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.w400,
|
|
color: Colors.grey.shade300,
|
|
letterSpacing: 0.1,
|
|
),
|
|
|
|
controller: controller,
|
|
),
|
|
)
|
|
|
|
|
|
],
|
|
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
// Console widget
|
|
class Console extends StatefulWidget {
|
|
|
|
Console({super.key});
|
|
|
|
@override
|
|
State<Console> createState() => _ConsoleState();
|
|
}
|
|
|
|
class _ConsoleState extends State<Console> {
|
|
|
|
ListenerReceipt? _listenerReceipt;
|
|
|
|
@override
|
|
void initState() {
|
|
// TODO: implement initState
|
|
super.initState();
|
|
|
|
/*_listenerReceipt = LiveInformation().commandModule.onCommandReceived.addListener((p0) {
|
|
print("Command received, updating console");
|
|
|
|
setState(() {});
|
|
});*/
|
|
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
// TODO: implement dispose
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
|
|
List<Widget> commands = [];
|
|
|
|
TextEditingController commandController = TextEditingController();
|
|
|
|
commands.add(
|
|
Text("Command History:")
|
|
);
|
|
|
|
/*for (int i = 0; i < LiveInformation().commandModule.commandHistory.length; i++){
|
|
CommandInfo command = LiveInformation().commandModule.commandHistory[i];
|
|
|
|
commands.add(
|
|
TextScroll(
|
|
command.command,
|
|
style: GoogleFonts.teko(
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.w400,
|
|
color: Colors.grey.shade300,
|
|
letterSpacing: 0.1,
|
|
),
|
|
mode: TextScrollMode.bouncing,
|
|
pauseBetween: const Duration(seconds: 2),
|
|
pauseOnBounce: const Duration(seconds: 1),
|
|
)
|
|
);
|
|
}
|
|
*/
|
|
return Container(
|
|
|
|
decoration: BoxDecoration(
|
|
border: Border.all(color: Colors.white70, width: 2),
|
|
color: Colors.black,
|
|
),
|
|
|
|
child: Column(
|
|
|
|
children: [
|
|
|
|
Container(
|
|
padding: const EdgeInsets.all(8),
|
|
|
|
height: 300,
|
|
|
|
child: ListView(
|
|
children: commands,
|
|
),
|
|
),
|
|
|
|
Container(
|
|
height: 2,
|
|
width: double.infinity,
|
|
color: Colors.white70,
|
|
),
|
|
|
|
/*Container(
|
|
height: 50,
|
|
padding: const EdgeInsets.all(8),
|
|
child: TextField(
|
|
controller: commandController,
|
|
decoration: const InputDecoration(
|
|
hintText: ">",
|
|
),
|
|
style: GoogleFonts.teko(
|
|
height: 1,
|
|
),
|
|
onSubmitted: (value) {
|
|
commandController.clear();
|
|
LiveInformation().commandModule.executeCommand(value);
|
|
},
|
|
),
|
|
)*/
|
|
|
|
],
|
|
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class SettingsField<T> 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()
|
|
),
|
|
),
|
|
)
|
|
|
|
],
|
|
)
|
|
)
|
|
|
|
],
|
|
|
|
)
|
|
|
|
);
|
|
}
|
|
|
|
} |