to laptptop
This commit is contained in:
31
lib/auth/api_constants.dart
Normal file
31
lib/auth/api_constants.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
class ApiConstants {
|
||||
|
||||
static const String APPWRITE_ENDPOINT = "https://cloud.imbenji.net/v1";
|
||||
static const String APPWRITE_PROJECT_ID = "65de530c1c0a7ffc0c3f";
|
||||
|
||||
static const String INFO_Q_DATABASE_ID = "65de5cab16717444527b";
|
||||
static const String DEST_Q_COLLECTION_ID = "65de9f2f925562a2eda8";
|
||||
static const String MANUAL_Q_COLLECTION_ID = "65de9f1b6282fd209bdb";
|
||||
static const String BUSSTOP_Q_COLLECTION_ID = "65de9ef464bfa5a0693d";
|
||||
|
||||
|
||||
|
||||
|
||||
// function to convert date time to something that looks like: Today at 12:00 PM or Yesterday at 12:00 PM or 12/31/2021 at 12:00 PM, make sure to always use double digits for the time so 01 not 1
|
||||
static String formatDateTime(DateTime dateTime) {
|
||||
DateTime now = DateTime.now();
|
||||
DateTime today = DateTime(now.year, now.month, now.day);
|
||||
DateTime yesterday = DateTime(now.year, now.month, now.day - 1);
|
||||
|
||||
if (dateTime.isAfter(today)) {
|
||||
return "Today at ${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')} ${dateTime.hour > 12 ? "PM" : "AM"}";
|
||||
} else if (dateTime.isAfter(yesterday)) {
|
||||
return "Yesterday at ${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')} ${dateTime.hour > 12 ? "PM" : "AM"}";
|
||||
} else {
|
||||
return "${dateTime.month.toString().padLeft(2, '0')}/${dateTime.day.toString().padLeft(2, '0')}/${dateTime.year.toString().padLeft(2, '0')} at ${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')} ${dateTime.hour > 12 ? "PM" : "AM"}";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
171
lib/auth/auth_api.dart
Normal file
171
lib/auth/auth_api.dart
Normal file
@@ -0,0 +1,171 @@
|
||||
|
||||
|
||||
|
||||
|
||||
import 'package:appwrite/appwrite.dart' as appwrite;
|
||||
import 'package:appwrite/models.dart' as models;
|
||||
import 'package:bus_infotainment/auth/api_constants.dart';
|
||||
import 'package:bus_infotainment/utils/delegates.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
enum AuthStatus {
|
||||
UNINITIALIZED,
|
||||
AUTHENTICATED,
|
||||
UNAUTHENTICATED,
|
||||
}
|
||||
|
||||
class AuthAPI extends ChangeNotifier {
|
||||
|
||||
appwrite.Client client = appwrite.Client();
|
||||
late final appwrite.Account account;
|
||||
|
||||
late models.User _currentUser;
|
||||
|
||||
AuthStatus _status = AuthStatus.UNINITIALIZED;
|
||||
|
||||
// late UserInfo _userInfo;
|
||||
|
||||
// Getter methods
|
||||
models.User get currentUser => _currentUser;
|
||||
// UserInfo get userInfo => _userInfo;
|
||||
AuthStatus get status => _status;
|
||||
String? get username => _currentUser.name;
|
||||
String? get email => _currentUser.email;
|
||||
String? get userID => _currentUser.$id;
|
||||
|
||||
AuthAPI() {
|
||||
init();
|
||||
loadUser();
|
||||
}
|
||||
|
||||
init() {
|
||||
client
|
||||
.setEndpoint(ApiConstants.APPWRITE_ENDPOINT)
|
||||
.setProject(ApiConstants.APPWRITE_PROJECT_ID)
|
||||
.setSelfSigned();
|
||||
account = appwrite.Account(client);
|
||||
}
|
||||
|
||||
loadUser() async {
|
||||
try {
|
||||
|
||||
{
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
String? username = prefs.getString("APPWRITE_SESSIONID_USERNAME");
|
||||
String? password = prefs.getString("APPWRITE_SESSIONID_PASSWORD");
|
||||
if (username != null && password != null) {
|
||||
await createEmailSession(email: username, password: password);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
final user = await account.get();
|
||||
_status = AuthStatus.AUTHENTICATED;
|
||||
// _userInfo = await UserInfo.getFromId(this, userID!);
|
||||
_currentUser = user;
|
||||
} catch (e) {
|
||||
_status = AuthStatus.UNAUTHENTICATED;
|
||||
} finally {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
Future<models.User> createUser({
|
||||
required String displayName,
|
||||
required String username,
|
||||
required String email,
|
||||
required String password,
|
||||
}) async {
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
final user = await account.create(
|
||||
userId: appwrite.ID.unique(),
|
||||
email: email,
|
||||
password: password,
|
||||
name: username,
|
||||
);
|
||||
|
||||
final appwrite.Databases databases = appwrite.Databases(client);
|
||||
|
||||
// await databases.createDocument(
|
||||
// databaseId: ApiConstants.SOCIALS_DATABASE_ID,
|
||||
// collectionId: ApiConstants.USERS_COLLECTION_ID,
|
||||
// documentId: user!.$id,
|
||||
// data: {
|
||||
// "username": username,
|
||||
// "display_name": displayName,
|
||||
// "bio": "",
|
||||
// "status": "",
|
||||
// "profilepicture_id": "default",
|
||||
// "profilebanner_id": "default",
|
||||
// "last_activity": DateTime.now().toIso8601String(),
|
||||
// "guild_ids": [],
|
||||
//
|
||||
// }
|
||||
// );
|
||||
|
||||
return user;
|
||||
} finally {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/* Login */
|
||||
Future<models.Session> createEmailSession({
|
||||
required String email,
|
||||
required String password,
|
||||
}) async {
|
||||
|
||||
try {
|
||||
final session = await account.createEmailSession(
|
||||
email: email,
|
||||
password: password,
|
||||
);
|
||||
_currentUser = await account.get();
|
||||
_status = AuthStatus.AUTHENTICATED;
|
||||
// _userInfo = await UserInfo.getFromId(this, userID!);
|
||||
// _userInfo.username = _currentUser.name!;
|
||||
// _userInfo.pushChanges(this);
|
||||
|
||||
// store session in shared preferences
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
prefs.setString("APPWRITE_SESSIONID_USERNAME", email);
|
||||
prefs.setString("APPWRITE_SESSIONID_PASSWORD", password);
|
||||
|
||||
return session;
|
||||
} finally {
|
||||
onLogin.trigger(1);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/* Logout */
|
||||
Future<void> deleteSession() async {
|
||||
try {
|
||||
await account.deleteSession(sessionId: "current");
|
||||
|
||||
{
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
prefs.remove("APPWRITE_SESSIONID_USERNAME");
|
||||
prefs.remove("APPWRITE_SESSIONID_PASSWORD");
|
||||
}
|
||||
|
||||
_status = AuthStatus.UNAUTHENTICATED;
|
||||
} finally {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
bool isAuthenticated() {
|
||||
return _status == AuthStatus.AUTHENTICATED;
|
||||
}
|
||||
|
||||
// Events
|
||||
|
||||
EventDelegate<int> _onLogin = EventDelegate();
|
||||
EventDelegate get onLogin => _onLogin;
|
||||
|
||||
}
|
||||
@@ -9,7 +9,7 @@ void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
LiveInformation liveInformation = LiveInformation();
|
||||
await liveInformation.LoadDatasets();
|
||||
await liveInformation.Initialize();
|
||||
|
||||
runApp(const MyApp());
|
||||
}
|
||||
@@ -29,7 +29,12 @@ class MyApp extends StatelessWidget {
|
||||
),
|
||||
darkTheme: ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
/* dark theme settings */
|
||||
// colorScheme: ColorScheme.dark(),
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: Colors.blue,
|
||||
brightness: Brightness.dark
|
||||
|
||||
)
|
||||
),
|
||||
themeMode: ThemeMode.dark,
|
||||
|
||||
|
||||
@@ -28,21 +28,20 @@ class _ibus_displayState extends State<ibus_display> {
|
||||
|
||||
LiveInformation liveInformation = LiveInformation();
|
||||
_receipt = liveInformation.announcementDelegate.addListener((value) {
|
||||
|
||||
if (topLine == value.displayText){
|
||||
return;
|
||||
}
|
||||
|
||||
topLine = value.displayText;
|
||||
setState(() {
|
||||
|
||||
});
|
||||
});
|
||||
topLine = liveInformation.CurrentAnnouncement;
|
||||
topLine = liveInformation.currentAnnouncement;
|
||||
|
||||
}
|
||||
|
||||
Timer _timer() => Timer.periodic(Duration(seconds: 1), (timer) {
|
||||
setState(() {
|
||||
topLine = LiveInformation().CurrentAnnouncement;
|
||||
});
|
||||
});
|
||||
|
||||
String _padString(String input){
|
||||
|
||||
if (input.length < 40){
|
||||
@@ -115,7 +114,13 @@ class _ibus_displayState extends State<ibus_display> {
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
color: Colors.orange,
|
||||
fontFamily: "ibus"
|
||||
fontFamily: "ibus",
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: Colors.orange,
|
||||
blurRadius: 5,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -13,31 +13,138 @@ class pages_Home extends StatelessWidget {
|
||||
|
||||
return Container(
|
||||
|
||||
width: double.infinity,
|
||||
|
||||
margin: const EdgeInsets.all(10),
|
||||
|
||||
child: Column(
|
||||
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
||||
children: [
|
||||
|
||||
// Text("Home Page"),
|
||||
|
||||
ibus_display(),
|
||||
|
||||
SizedBox(
|
||||
height: 10,
|
||||
Container(
|
||||
height: 2,
|
||||
color: Colors.white70,
|
||||
),
|
||||
|
||||
_QuickAnnouncements_IBUS(),
|
||||
Container(
|
||||
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade900,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.3),
|
||||
blurRadius: 2,
|
||||
spreadRadius: 4
|
||||
)
|
||||
]
|
||||
),
|
||||
|
||||
margin: EdgeInsets.all(20),
|
||||
|
||||
child: ibus_display(),
|
||||
|
||||
SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
|
||||
QuickAnnouncement(),
|
||||
Container(
|
||||
height: 2,
|
||||
color: Colors.white70,
|
||||
),
|
||||
|
||||
Container(
|
||||
|
||||
margin: EdgeInsets.all(20),
|
||||
|
||||
child: Container(
|
||||
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade900,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.3),
|
||||
blurRadius: 2,
|
||||
spreadRadius: 4
|
||||
)
|
||||
]
|
||||
),
|
||||
|
||||
child: _QuickAnnouncements_IBUS(
|
||||
backgroundColor: Colors.grey.shade900,
|
||||
outlineColor: Colors.white70,
|
||||
),
|
||||
),
|
||||
|
||||
),
|
||||
|
||||
Container(
|
||||
height: 2,
|
||||
color: Colors.white70,
|
||||
),
|
||||
|
||||
Container(
|
||||
|
||||
margin: EdgeInsets.all(20),
|
||||
|
||||
height: 300-45,
|
||||
|
||||
child: ListView(
|
||||
|
||||
scrollDirection: Axis.vertical,
|
||||
|
||||
children: [
|
||||
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
LiveInformation liveInformation = LiveInformation();
|
||||
liveInformation.announceRouteVariant(liveInformation.getRouteVariant()!);
|
||||
},
|
||||
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"),
|
||||
),
|
||||
|
||||
],
|
||||
|
||||
),
|
||||
|
||||
),
|
||||
|
||||
],
|
||||
)
|
||||
@@ -48,8 +155,10 @@ class pages_Home extends StatelessWidget {
|
||||
|
||||
class _QuickAnnouncements_IBUS extends StatefulWidget {
|
||||
|
||||
final Color backgroundColor;
|
||||
final Color outlineColor;
|
||||
|
||||
_QuickAnnouncements_IBUS({super.key});
|
||||
_QuickAnnouncements_IBUS({super.key, required this.backgroundColor, required this.outlineColor});
|
||||
|
||||
@override
|
||||
State<_QuickAnnouncements_IBUS> createState() => _QuickAnnouncementsState_IBUS();
|
||||
@@ -61,16 +170,23 @@ class _QuickAnnouncementsState_IBUS extends State<_QuickAnnouncements_IBUS> {
|
||||
|
||||
int _currentIndex = 0;
|
||||
|
||||
|
||||
_QuickAnnouncementsState_IBUS() {
|
||||
LiveInformation liveInformation = LiveInformation();
|
||||
|
||||
for (ManualAnnouncementEntry announcement in liveInformation.manualAnnouncements) {
|
||||
announcements.add(
|
||||
_QuickAnnouncement_IBUS(announcement: announcement, index: liveInformation.manualAnnouncements.indexOf(announcement))
|
||||
_QuickAnnouncement_IBUS(
|
||||
announcement: announcement,
|
||||
index: liveInformation.manualAnnouncements.indexOf(announcement),
|
||||
outlineColor: Colors.white70
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -78,9 +194,9 @@ class _QuickAnnouncementsState_IBUS extends State<_QuickAnnouncements_IBUS> {
|
||||
return Container(
|
||||
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.lightGreen.shade100,
|
||||
color: widget.backgroundColor,
|
||||
border: Border.all(
|
||||
color: Colors.black,
|
||||
color: widget.outlineColor,
|
||||
width: 2
|
||||
),
|
||||
|
||||
@@ -88,13 +204,19 @@ class _QuickAnnouncementsState_IBUS extends State<_QuickAnnouncements_IBUS> {
|
||||
|
||||
),
|
||||
|
||||
padding: const EdgeInsets.all(2),
|
||||
padding: const EdgeInsets.all(4),
|
||||
|
||||
width: double.infinity,
|
||||
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 400
|
||||
),
|
||||
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 2,
|
||||
color: Colors.black,
|
||||
color: widget.outlineColor,
|
||||
),
|
||||
|
||||
if (_currentIndex < announcements.length)
|
||||
@@ -103,10 +225,10 @@ class _QuickAnnouncementsState_IBUS extends State<_QuickAnnouncements_IBUS> {
|
||||
Container(
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.lightGreen.shade100,
|
||||
border: const Border.symmetric(
|
||||
color: widget.backgroundColor,
|
||||
border: Border.symmetric(
|
||||
vertical: BorderSide(
|
||||
color: Colors.black,
|
||||
color: widget.outlineColor,
|
||||
width: 2
|
||||
)
|
||||
),
|
||||
@@ -115,7 +237,7 @@ class _QuickAnnouncementsState_IBUS extends State<_QuickAnnouncements_IBUS> {
|
||||
|
||||
Container(
|
||||
height: 2,
|
||||
color: Colors.black,
|
||||
color: widget.outlineColor,
|
||||
),
|
||||
|
||||
if (_currentIndex + 1 < announcements.length)
|
||||
@@ -124,10 +246,10 @@ class _QuickAnnouncementsState_IBUS extends State<_QuickAnnouncements_IBUS> {
|
||||
Container(
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.lightGreen.shade100,
|
||||
border: const Border.symmetric(
|
||||
color: widget.backgroundColor,
|
||||
border: Border.symmetric(
|
||||
vertical: BorderSide(
|
||||
color: Colors.black,
|
||||
color: widget.outlineColor,
|
||||
width: 2
|
||||
)
|
||||
),
|
||||
@@ -136,7 +258,7 @@ class _QuickAnnouncementsState_IBUS extends State<_QuickAnnouncements_IBUS> {
|
||||
|
||||
Container(
|
||||
height: 2,
|
||||
color: Colors.black,
|
||||
color: widget.outlineColor,
|
||||
),
|
||||
|
||||
if (_currentIndex + 2 < announcements.length)
|
||||
@@ -145,10 +267,10 @@ class _QuickAnnouncementsState_IBUS extends State<_QuickAnnouncements_IBUS> {
|
||||
Container(
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.lightGreen.shade100,
|
||||
border: const Border.symmetric(
|
||||
color: widget.backgroundColor,
|
||||
border: Border.symmetric(
|
||||
vertical: BorderSide(
|
||||
color: Colors.black,
|
||||
color: widget.outlineColor,
|
||||
width: 2
|
||||
)
|
||||
),
|
||||
@@ -157,7 +279,7 @@ class _QuickAnnouncementsState_IBUS extends State<_QuickAnnouncements_IBUS> {
|
||||
|
||||
Container(
|
||||
height: 2,
|
||||
color: Colors.black,
|
||||
color: widget.outlineColor,
|
||||
),
|
||||
|
||||
if (_currentIndex + 3 < announcements.length)
|
||||
@@ -166,10 +288,10 @@ class _QuickAnnouncementsState_IBUS extends State<_QuickAnnouncements_IBUS> {
|
||||
Container(
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.lightGreen.shade100,
|
||||
border: const Border.symmetric(
|
||||
color: widget.backgroundColor,
|
||||
border: Border.symmetric(
|
||||
vertical: BorderSide(
|
||||
color: Colors.black,
|
||||
color: widget.outlineColor,
|
||||
width: 2
|
||||
)
|
||||
),
|
||||
@@ -178,16 +300,16 @@ class _QuickAnnouncementsState_IBUS extends State<_QuickAnnouncements_IBUS> {
|
||||
|
||||
Container(
|
||||
height: 2,
|
||||
color: Colors.black,
|
||||
color: widget.outlineColor,
|
||||
),
|
||||
|
||||
Container(
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.lightGreen.shade100,
|
||||
border: const Border.symmetric(
|
||||
color: widget.backgroundColor,
|
||||
border: Border.symmetric(
|
||||
vertical: BorderSide(
|
||||
color: Colors.black,
|
||||
color: widget.outlineColor,
|
||||
width: 2
|
||||
)
|
||||
),
|
||||
@@ -205,10 +327,10 @@ class _QuickAnnouncementsState_IBUS extends State<_QuickAnnouncements_IBUS> {
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.lightGreen.shade100,
|
||||
border: const Border.symmetric(
|
||||
color: widget.backgroundColor,
|
||||
border: Border.symmetric(
|
||||
vertical: BorderSide(
|
||||
color: Colors.black,
|
||||
color: widget.outlineColor,
|
||||
width: 2
|
||||
)
|
||||
),
|
||||
@@ -226,7 +348,7 @@ class _QuickAnnouncementsState_IBUS extends State<_QuickAnnouncements_IBUS> {
|
||||
height: 40,
|
||||
child: Icon(
|
||||
Icons.arrow_upward,
|
||||
color: Colors.black,
|
||||
color: widget.outlineColor,
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
@@ -258,10 +380,10 @@ class _QuickAnnouncementsState_IBUS extends State<_QuickAnnouncements_IBUS> {
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.lightGreen.shade100,
|
||||
border: const Border.symmetric(
|
||||
color: widget.backgroundColor,
|
||||
border: Border.symmetric(
|
||||
vertical: BorderSide(
|
||||
color: Colors.black,
|
||||
color: widget.outlineColor,
|
||||
width: 2
|
||||
)
|
||||
),
|
||||
@@ -279,7 +401,7 @@ class _QuickAnnouncementsState_IBUS extends State<_QuickAnnouncements_IBUS> {
|
||||
height: 40,
|
||||
child: Icon(
|
||||
Icons.arrow_downward,
|
||||
color: Colors.black,
|
||||
color: widget.outlineColor,
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
@@ -314,7 +436,7 @@ class _QuickAnnouncementsState_IBUS extends State<_QuickAnnouncements_IBUS> {
|
||||
),
|
||||
Container(
|
||||
height: 2,
|
||||
color: Colors.black,
|
||||
color: widget.outlineColor,
|
||||
),
|
||||
|
||||
]
|
||||
@@ -333,8 +455,9 @@ class _QuickAnnouncement_IBUS extends StatelessWidget {
|
||||
|
||||
final ManualAnnouncementEntry announcement;
|
||||
final int index;
|
||||
final Color outlineColor;
|
||||
|
||||
const _QuickAnnouncement_IBUS({super.key, required this.announcement, required this.index});
|
||||
_QuickAnnouncement_IBUS({super.key, required this.announcement, required this.index, required this.outlineColor});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -344,48 +467,52 @@ class _QuickAnnouncement_IBUS extends StatelessWidget {
|
||||
Container(
|
||||
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.lightGreen.shade100,
|
||||
border: const Border.symmetric(
|
||||
color: Colors.transparent,
|
||||
border: Border.symmetric(
|
||||
vertical: BorderSide(
|
||||
color: Colors.black,
|
||||
color: outlineColor,
|
||||
width: 2
|
||||
)
|
||||
),
|
||||
),
|
||||
|
||||
padding: const EdgeInsets.all(5),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 5
|
||||
),
|
||||
|
||||
width: double.infinity,
|
||||
height: 50,
|
||||
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
announcement.shortName,
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
color: Colors.black,
|
||||
fontFamily: "lcd",
|
||||
height: 1,
|
||||
|
||||
child: Transform.translate(
|
||||
|
||||
offset: Offset(0, 4),
|
||||
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
announcement.shortName,
|
||||
style: GoogleFonts.teko(
|
||||
fontSize: 25,
|
||||
color: outlineColor,
|
||||
)
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
Expanded(
|
||||
child: Container(
|
||||
|
||||
alignment: Alignment.centerRight,
|
||||
alignment: Alignment.centerRight,
|
||||
|
||||
child: Text(
|
||||
(index+1).toString(),
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
color: Colors.black,
|
||||
fontFamily: "lcd",
|
||||
height: 1,
|
||||
child: Text(
|
||||
(index+1).toString(),
|
||||
style: GoogleFonts.teko(
|
||||
fontSize: 25,
|
||||
color: outlineColor,
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
),
|
||||
|
||||
@@ -1,16 +1,629 @@
|
||||
|
||||
|
||||
import 'package:bus_infotainment/singletons/live_information.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.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> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
if (LiveInformation().auth.isAuthenticated()){
|
||||
return Container(
|
||||
|
||||
|
||||
|
||||
);
|
||||
} else {
|
||||
return _LoginPage(
|
||||
onLogin: () {
|
||||
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
|
||||
|
||||
class pages_Settings extends StatelessWidget {
|
||||
@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,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
],
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -48,68 +48,73 @@ class TfL_Dataset_TestState extends State<TfL_Dataset_Test> {
|
||||
|
||||
surfaceTintColor: Colors.transparent,
|
||||
|
||||
title: Column(
|
||||
title: Container(
|
||||
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
child: Column(
|
||||
|
||||
children: [
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
||||
Text(
|
||||
"Bus Infotainment",
|
||||
style: GoogleFonts.montserrat(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
children: [
|
||||
|
||||
Row(
|
||||
|
||||
children: [
|
||||
|
||||
Text(
|
||||
"Selected: ",
|
||||
style: GoogleFonts.montserrat(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
Text(
|
||||
"Bus Infotainment",
|
||||
style: GoogleFonts.teko(
|
||||
fontSize: 25,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
|
||||
if (liveInformation.getRouteVariant() != null)
|
||||
Container(
|
||||
Row(
|
||||
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black,
|
||||
children: [
|
||||
|
||||
Text(
|
||||
"Selected: ",
|
||||
style: GoogleFonts.teko(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2),
|
||||
if (liveInformation.getRouteVariant() != null)
|
||||
Container(
|
||||
|
||||
child: Text(
|
||||
"${liveInformation.getRouteVariant()!.busRoute.routeNumber} to ${liveInformation.getRouteVariant()!.busStops.last.formattedStopName}",
|
||||
style: GoogleFonts.montserrat(
|
||||
fontSize: 15,
|
||||
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.orange.shade900,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
)
|
||||
else
|
||||
Text(
|
||||
"None",
|
||||
style: GoogleFonts.montserrat(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
],
|
||||
],
|
||||
|
||||
),
|
||||
),
|
||||
) : null,
|
||||
body: Pages[_selectedIndex],
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
// Singleton
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appwrite/appwrite.dart' as appwrite;
|
||||
import 'package:appwrite/models.dart' as models;
|
||||
import 'package:bus_infotainment/audio_cache.dart';
|
||||
import 'package:bus_infotainment/auth/api_constants.dart';
|
||||
import 'package:bus_infotainment/auth/auth_api.dart';
|
||||
import 'package:bus_infotainment/tfl_datasets.dart';
|
||||
import 'package:bus_infotainment/utils/audio%20wrapper.dart';
|
||||
import 'package:bus_infotainment/utils/delegates.dart';
|
||||
@@ -19,7 +23,7 @@ class LiveInformation {
|
||||
|
||||
LiveInformation._internal();
|
||||
|
||||
Future<void> LoadDatasets() async {
|
||||
Future<void> Initialize() async {
|
||||
|
||||
{
|
||||
// Load the bus sequences
|
||||
@@ -41,6 +45,16 @@ class LiveInformation {
|
||||
print("Loaded bus sequences from assets");
|
||||
}
|
||||
|
||||
if (auth.isAuthenticated()){
|
||||
print("Auth is authenticated");
|
||||
setupRealtime();
|
||||
} else {
|
||||
print("Auth is not authenticated");
|
||||
auth.onLogin.addListener((value) {
|
||||
setupRealtime();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
refreshTimer();
|
||||
@@ -54,28 +68,33 @@ class LiveInformation {
|
||||
AudioWrapper audioPlayer = AudioWrapper();
|
||||
AnnouncementCache announcementCache = AnnouncementCache();
|
||||
List<AnnouncementQueueEntry> announcementQueue = [];
|
||||
DateTime lastAnnouncement = DateTime.now();
|
||||
EventDelegate<AnnouncementQueueEntry> announcementDelegate = EventDelegate();
|
||||
String CurrentAnnouncement = "*** NO MESSAGE ***";
|
||||
String _currentAnnouncement = "*** NO MESSAGE ***";
|
||||
|
||||
String get currentAnnouncement => _currentAnnouncement;
|
||||
void set currentAnnouncement(String value) {
|
||||
_currentAnnouncement = value;
|
||||
}
|
||||
|
||||
void _handleAnnouncementQueue() async {
|
||||
print("Handling announcement queue");
|
||||
if (audioPlayer.state != AudioWrapper_State.Playing) {
|
||||
if (announcementQueue.isNotEmpty) {
|
||||
AnnouncementQueueEntry announcement = announcementQueue.first;
|
||||
announcementDelegate.trigger(announcement);
|
||||
CurrentAnnouncement = announcement.displayText;
|
||||
|
||||
_currentAnnouncement = announcement.displayText;
|
||||
|
||||
lastAnnouncement = DateTime.now();
|
||||
|
||||
|
||||
for (AudioWrapperSource source in announcement.audioSources) {
|
||||
|
||||
Duration? duration = await audioPlayer.play(source);
|
||||
if (source == announcement.audioSources.last) {
|
||||
announcementQueue.removeAt(0);
|
||||
}
|
||||
await Future.delayed(duration!);
|
||||
await Future.delayed(Duration(milliseconds: 150));
|
||||
}
|
||||
audioPlayer.stop();
|
||||
announcementQueue.removeAt(0);
|
||||
print("Popped announcement queue");
|
||||
}
|
||||
}
|
||||
@@ -119,6 +138,38 @@ class LiveInformation {
|
||||
|
||||
void queueAnnouncement(AnnouncementQueueEntry announcement) {
|
||||
announcementQueue.add(announcement);
|
||||
|
||||
// Make sure the timestamp of the announcement is after the last announcement
|
||||
// If so, dont queue it
|
||||
// If timestamp is null, then skip this check
|
||||
if (announcement.timestamp != null && announcement.timestamp!.isBefore(lastAnnouncement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!announcement.sendToServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
final databases = appwrite.Databases(auth.client);
|
||||
|
||||
if (announcement is ManualAnnouncementEntry) {
|
||||
|
||||
final document = databases.createDocument(
|
||||
documentId: appwrite.ID.unique(),
|
||||
databaseId: ApiConstants.INFO_Q_DATABASE_ID,
|
||||
collectionId: ApiConstants.MANUAL_Q_COLLECTION_ID,
|
||||
data: {
|
||||
"ManualAnnouncementIndex": manualAnnouncements.indexOf(announcement),
|
||||
"SessionID": sessionID,
|
||||
}
|
||||
);
|
||||
|
||||
print("Queued manual announcement: ${announcement.shortName}");
|
||||
|
||||
} else if (announcement is AnnouncementQueueEntry) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
List<ManualAnnouncementEntry> manualAnnouncements = [
|
||||
@@ -204,17 +255,206 @@ class LiveInformation {
|
||||
),
|
||||
];
|
||||
|
||||
AuthAPI auth = AuthAPI();
|
||||
String sessionID = "65de648aa7f44684ecce";
|
||||
void updateServer() async {
|
||||
|
||||
|
||||
|
||||
final databases = appwrite.Databases(auth.client);
|
||||
|
||||
// final document = databases.updateDocument(
|
||||
// databaseId: ApiConstants.INFO_DATABASE_ID,
|
||||
// collectionId: ApiConstants.INFO_COLLECTION_ID,
|
||||
// documentId: documentID,
|
||||
// data: {
|
||||
// "Display": _currentAnnouncement,
|
||||
// }
|
||||
// );
|
||||
|
||||
print("Updated server with announcement: $_currentAnnouncement");
|
||||
|
||||
}
|
||||
void pullServer() async {
|
||||
|
||||
if (auth.status == AuthStatus.UNAUTHENTICATED) {
|
||||
return;
|
||||
}
|
||||
|
||||
final databases = appwrite.Databases(auth.client);
|
||||
|
||||
// final document = await databases.getDocument(
|
||||
// databaseId: ApiConstants.INFO_DATABASE_ID,
|
||||
// collectionId: ApiConstants.INFO_COLLECTION_ID,
|
||||
// documentId: documentID,
|
||||
// );
|
||||
|
||||
// queueAnnouncement(AnnouncementQueueEntry(
|
||||
// displayText: document.data['Display'],
|
||||
// audioSources: [],
|
||||
// sendToServer: false, // Don't send this back to the server, else we'll get an infinite loop
|
||||
// ));
|
||||
|
||||
// print("Pulled announcement from server: ${document.data['Display']}");
|
||||
}
|
||||
|
||||
bool purgeRunning = false;
|
||||
Future<void> deleteAllManualQueueEntries() async {
|
||||
|
||||
if (purgeRunning) {
|
||||
return;
|
||||
}
|
||||
purgeRunning = true;
|
||||
|
||||
final databases = appwrite.Databases(auth.client);
|
||||
int offset = 0;
|
||||
const int limit = 25; // Maximum number of documents that can be fetched at once
|
||||
|
||||
while (true) {
|
||||
// Fetch a page of documents from the manual queue collection
|
||||
print("Deleting manual queue entries");
|
||||
final manual_q = await databases.listDocuments(
|
||||
databaseId: ApiConstants.INFO_Q_DATABASE_ID,
|
||||
collectionId: ApiConstants.MANUAL_Q_COLLECTION_ID,
|
||||
queries: [
|
||||
appwrite.Query.search("SessionID", sessionID),
|
||||
appwrite.Query.limit(limit),
|
||||
appwrite.Query.offset(offset),
|
||||
appwrite.Query.orderDesc('\$createdAt')
|
||||
]
|
||||
);
|
||||
|
||||
// If there are no documents in the fetched page, break the loop
|
||||
if (manual_q.documents.isEmpty) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Delete each document in the fetched page
|
||||
for (models.Document doc in manual_q.documents) {
|
||||
await databases.deleteDocument(
|
||||
databaseId: ApiConstants.INFO_Q_DATABASE_ID,
|
||||
collectionId: ApiConstants.MANUAL_Q_COLLECTION_ID,
|
||||
documentId: doc.$id,
|
||||
);
|
||||
}
|
||||
|
||||
// Go to the next page
|
||||
offset += limit;
|
||||
}
|
||||
|
||||
print("Deleted all manual queue entries");
|
||||
}
|
||||
|
||||
void pullQueue() async {
|
||||
|
||||
if (auth.status == AuthStatus.UNAUTHENTICATED) {
|
||||
return;
|
||||
}
|
||||
|
||||
final databases = appwrite.Databases(auth.client);
|
||||
|
||||
// Pull the manual queue
|
||||
final manual_q = await databases.listDocuments(
|
||||
databaseId: ApiConstants.INFO_Q_DATABASE_ID,
|
||||
collectionId: ApiConstants.MANUAL_Q_COLLECTION_ID,
|
||||
queries: [
|
||||
appwrite.Query.search("SessionID", sessionID),
|
||||
appwrite.Query.limit(25),
|
||||
appwrite.Query.offset(0),
|
||||
appwrite.Query.orderDesc('\$createdAt')
|
||||
]
|
||||
);
|
||||
|
||||
List<AnnouncementQueueEntry> queue = [];
|
||||
|
||||
for (models.Document doc in manual_q.documents) {
|
||||
int index = doc.data['ManualAnnouncementIndex'];
|
||||
|
||||
ManualAnnouncementEntry announcement_clone = ManualAnnouncementEntry(
|
||||
shortName: manualAnnouncements[index].shortName,
|
||||
informationText: manualAnnouncements[index].displayText,
|
||||
audioSources: manualAnnouncements[index].audioSources,
|
||||
timestamp: DateTime.parse(doc.$createdAt),
|
||||
sendToServer: false,
|
||||
);
|
||||
|
||||
// sort the queue by timestamp, so the oldest announcements are at the front
|
||||
|
||||
|
||||
|
||||
queue.add(announcement_clone);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
for (AnnouncementQueueEntry entry in queue) {
|
||||
queueAnnouncement(entry);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
appwrite.RealtimeSubscription? manual_q_subscription;
|
||||
Future<void> setupRealtime() async {
|
||||
|
||||
if (manual_q_subscription != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// await deleteAllManualQueueEntries(); //todo
|
||||
|
||||
print("Setting up realtime");
|
||||
|
||||
// Websocket
|
||||
appwrite.Realtime realtime = appwrite.Realtime(auth.client);
|
||||
|
||||
manual_q_subscription = realtime.subscribe(
|
||||
['databases.${ApiConstants.INFO_Q_DATABASE_ID}.collections.${ApiConstants.MANUAL_Q_COLLECTION_ID}.documents'],
|
||||
);
|
||||
manual_q_subscription?.stream.listen((event) {
|
||||
print("Manual queue entry added");
|
||||
|
||||
pullQueue();
|
||||
});
|
||||
|
||||
print("Subscribed to servers");
|
||||
|
||||
await Future.delayed(Duration(seconds: 90));
|
||||
|
||||
manual_q_subscription?.close();
|
||||
manual_q_subscription = null;
|
||||
setupRealtime();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
class AnnouncementQueueEntry {
|
||||
final String displayText;
|
||||
final List<AudioWrapperSource> audioSources;
|
||||
bool sendToServer = true;
|
||||
DateTime? timestamp;
|
||||
|
||||
AnnouncementQueueEntry({required this.displayText, required this.audioSources});
|
||||
AnnouncementQueueEntry({required this.displayText, required this.audioSources, this.sendToServer = true, this.timestamp});
|
||||
}
|
||||
|
||||
class ManualAnnouncementEntry extends AnnouncementQueueEntry {
|
||||
final String shortName;
|
||||
|
||||
ManualAnnouncementEntry({required this.shortName, required String informationText, required List<AudioWrapperSource> audioSources}) : super(displayText: informationText, audioSources: audioSources);
|
||||
ManualAnnouncementEntry({
|
||||
required this.shortName,
|
||||
required String informationText,
|
||||
required List<AudioWrapperSource> audioSources,
|
||||
DateTime? timestamp,
|
||||
bool sendToServer = true,
|
||||
}) : super(
|
||||
displayText: informationText,
|
||||
audioSources: audioSources,
|
||||
sendToServer: sendToServer,
|
||||
timestamp: timestamp,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user