From 675c42d2da34377ba69f179bd7539024799379ab Mon Sep 17 00:00:00 2001 From: ImBenji <53883070+YesItsBenji@users.noreply.github.com> Date: Wed, 28 Feb 2024 04:56:50 +0000 Subject: [PATCH] to laptptop --- .gitignore | 5 +- Dockerfile | 36 + android/app/build.gradle | 2 +- lib/auth/api_constants.dart | 31 + lib/auth/auth_api.dart | 171 +++++ lib/main.dart | 9 +- lib/pages/components/ibus_display.dart | 21 +- lib/pages/home.dart | 283 +++++--- lib/pages/settings.dart | 617 +++++++++++++++++- lib/pages/tfl_dataset_test.dart | 97 +-- lib/singletons/live_information.dart | 260 +++++++- linux/flutter/generated_plugin_registrant.cc | 8 + linux/flutter/generated_plugins.cmake | 2 + macos/Flutter/GeneratedPluginRegistrant.swift | 12 + pubspec.lock | 326 ++++++++- pubspec.yaml | 4 + server/server.sh | 17 + .../flutter/generated_plugin_registrant.cc | 6 + windows/flutter/generated_plugins.cmake | 2 + 19 files changed, 1758 insertions(+), 151 deletions(-) create mode 100644 Dockerfile create mode 100644 lib/auth/api_constants.dart create mode 100644 lib/auth/auth_api.dart create mode 100644 server/server.sh diff --git a/.gitignore b/.gitignore index f524aa7..41d7fcd 100644 --- a/.gitignore +++ b/.gitignore @@ -41,4 +41,7 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release -/assets/ibus_recordings.zip + +# Ignore any file with the name "ibus_recordings.zip" +ibus_recordings.zip +bus_infotainment.rar diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6c6b147 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,36 @@ +# Install Operating system and dependencies +FROM ubuntu:20.04 + +ENV DEBIAN_FRONTEND noninteractive + +RUN apt-get update +RUN apt-get install -y curl git wget unzip libgconf-2-4 gdb libstdc++6 libglu1-mesa fonts-droid-fallback lib32stdc++6 python3 +RUN apt-get clean + +# download Flutter SDK from Flutter Github repo +RUN git clone https://github.com/flutter/flutter.git /usr/local/flutter + +# Set flutter environment path +ENV PATH="/usr/local/flutter/bin:/usr/local/flutter/bin/cache/dart-sdk/bin:${PATH}" + +# Run flutter doctor +RUN flutter doctor + +# Enable flutter web +RUN flutter channel master +RUN flutter upgrade +RUN flutter config --enable-web + +# Copy files to container and build +RUN mkdir /app/ +COPY . /app/ +WORKDIR /app/ +RUN flutter build web + +# Record the exposed port +EXPOSE 5000 + +# make server startup script executable and start the web server +RUN ["chmod", "+x", "/app/server/server.sh"] + +ENTRYPOINT [ "/app/server/server.sh"] \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index c8251f2..988d667 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -24,7 +24,7 @@ if (flutterVersionName == null) { android { namespace "com.imbenji.bus_infotainment" - compileSdkVersion flutter.compileSdkVersion + compileSdkVersion 34 ndkVersion flutter.ndkVersion compileOptions { diff --git a/lib/auth/api_constants.dart b/lib/auth/api_constants.dart new file mode 100644 index 0000000..4489f07 --- /dev/null +++ b/lib/auth/api_constants.dart @@ -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"}"; + } + } + + +} \ No newline at end of file diff --git a/lib/auth/auth_api.dart b/lib/auth/auth_api.dart new file mode 100644 index 0000000..f233938 --- /dev/null +++ b/lib/auth/auth_api.dart @@ -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 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 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 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 _onLogin = EventDelegate(); + EventDelegate get onLogin => _onLogin; + +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index faaafaf..a7149a2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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, diff --git a/lib/pages/components/ibus_display.dart b/lib/pages/components/ibus_display.dart index 4f49763..5ebd1d4 100644 --- a/lib/pages/components/ibus_display.dart +++ b/lib/pages/components/ibus_display.dart @@ -28,21 +28,20 @@ class _ibus_displayState extends State { 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 { style: const TextStyle( fontSize: 20, color: Colors.orange, - fontFamily: "ibus" + fontFamily: "ibus", + shadows: [ + Shadow( + color: Colors.orange, + blurRadius: 5, + ), + ], ), ), ), diff --git a/lib/pages/home.dart b/lib/pages/home.dart index 1e11d49..de06a1f 100644 --- a/lib/pages/home.dart +++ b/lib/pages/home.dart @@ -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, + ) ), ), - ), - ) - ], + ) + ], + ), ) ), diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index e16cbc1..3614015 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -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 createState() => _pages_SettingsState(); +} + +class _pages_SettingsState extends State { + + @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 createState() => _LoginFormState(); +} + +class _LoginFormState extends State { + + + @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, + ), + ) + + + ], + + ); + } + } \ No newline at end of file diff --git a/lib/pages/tfl_dataset_test.dart b/lib/pages/tfl_dataset_test.dart index 58a395f..cf70193 100644 --- a/lib/pages/tfl_dataset_test.dart +++ b/lib/pages/tfl_dataset_test.dart @@ -48,68 +48,73 @@ class TfL_Dataset_TestState extends State { 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], diff --git a/lib/singletons/live_information.dart b/lib/singletons/live_information.dart index 92e4465..02cef0a 100644 --- a/lib/singletons/live_information.dart +++ b/lib/singletons/live_information.dart @@ -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 LoadDatasets() async { + Future 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 announcementQueue = []; + DateTime lastAnnouncement = DateTime.now(); EventDelegate 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 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 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 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 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 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 audioSources}) : super(displayText: informationText, audioSources: audioSources); + ManualAnnouncementEntry({ + required this.shortName, + required String informationText, + required List audioSources, + DateTime? timestamp, + bool sendToServer = true, + }) : super( + displayText: informationText, + audioSources: audioSources, + sendToServer: sendToServer, + timestamp: timestamp, + ); } \ No newline at end of file diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 1830e5c..1da8b5b 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -7,9 +7,17 @@ #include "generated_plugin_registrant.h" #include +#include +#include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin"); audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); + g_autoptr(FlPluginRegistrar) window_to_front_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "WindowToFrontPlugin"); + window_to_front_plugin_register_with_registrar(window_to_front_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index e9abb91..69df1f4 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -4,6 +4,8 @@ list(APPEND FLUTTER_PLUGIN_LIST audioplayers_linux + url_launcher_linux + window_to_front ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 62e11a9..85b576f 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,12 +7,24 @@ import Foundation import audio_session import audioplayers_darwin +import device_info_plus +import flutter_web_auth_2 import just_audio +import package_info_plus import path_provider_foundation +import shared_preferences_foundation +import url_launcher_macos +import window_to_front func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) + DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) + FlutterWebAuth2Plugin.register(with: registry.registrar(forPlugin: "FlutterWebAuth2Plugin")) JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + WindowToFrontPlugin.register(with: registry.registrar(forPlugin: "WindowToFrontPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index c93484e..88c8f37 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + appwrite: + dependency: "direct main" + description: + name: appwrite + sha256: "1a602cfc6122ec9f179403b44526ff60a6baac9a8f4e8578061fc5e85df67211" + url: "https://pub.dev" + source: hosted + version: "11.0.1" archive: dependency: "direct main" description: @@ -97,6 +105,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + charcode: + dependency: transitive + description: + name: charcode + sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 + url: "https://pub.dev" + source: hosted + version: "1.3.1" clock: dependency: transitive description: @@ -121,6 +137,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.1" + cookie_jar: + dependency: transitive + description: + name: cookie_jar + sha256: a6ac027d3ed6ed756bfce8f3ff60cb479e266f3b0fdabd6242b804b6765e52de + url: "https://pub.dev" + source: hosted + version: "4.0.8" crypto: dependency: transitive description: @@ -129,6 +153,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + csslib: + dependency: transitive + description: + name: csslib + sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" + url: "https://pub.dev" + source: hosted + version: "1.0.0" csv: dependency: "direct main" description: @@ -145,6 +177,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.6" + device_info_plus: + dependency: transitive + description: + name: device_info_plus + sha256: "77f757b789ff68e4eaf9c56d1752309bd9f7ad557cb105b938a7f8eb89e59110" + url: "https://pub.dev" + source: hosted + version: "9.1.2" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64 + url: "https://pub.dev" + source: hosted + version: "7.0.0" fake_async: dependency: transitive description: @@ -190,11 +238,35 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + flutter_map: + dependency: "direct main" + description: + name: flutter_map + sha256: cda8d72135b697f519287258b5294a57ce2f2a5ebf234f0e406aad4dc14c9399 + url: "https://pub.dev" + source: hosted + version: "6.1.0" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_auth_2: + dependency: transitive + description: + name: flutter_web_auth_2 + sha256: "0da41e631a368e02366fc1a9b79dd8da191e700a836878bc54466fff51c07df2" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + flutter_web_auth_2_platform_interface: + dependency: transitive + description: + name: flutter_web_auth_2_platform_interface + sha256: f6fa7059ff3428c19cd756c02fef8eb0147131c7e64591f9060c90b5ab84f094 + url: "https://pub.dev" + source: hosted + version: "2.1.4" flutter_web_plugins: dependency: transitive description: flutter @@ -208,14 +280,22 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.0" + html: + dependency: transitive + description: + name: html + sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" + url: "https://pub.dev" + source: hosted + version: "0.15.4" http: dependency: "direct main" description: name: http - sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba + sha256: "4c3f04bfb64d3efd508d06b41b825542f08122d30bda4933fb95c069d22a4fa3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.0.0" http_parser: dependency: transitive description: @@ -272,6 +352,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.0" + latlong2: + dependency: transitive + description: + name: latlong2 + sha256: "18712164760cee655bc790122b0fd8f3d5b3c36da2cb7bf94b68a197fbb0811b" + url: "https://pub.dev" + source: hosted + version: "0.9.0" lints: dependency: transitive description: @@ -280,6 +368,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + lists: + dependency: transitive + description: + name: lists + sha256: "4ca5c19ae4350de036a7e996cdd1ee39c93ac0a2b840f4915459b7d0a7d4ab27" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + logger: + dependency: transitive + description: + name: logger + sha256: "6bbb9d6f7056729537a4309bda2e74e18e5d9f14302489cc1e93f33b3fe32cac" + url: "https://pub.dev" + source: hosted + version: "2.0.2+1" matcher: dependency: transitive description: @@ -304,6 +408,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + mgrs_dart: + dependency: transitive + description: + name: mgrs_dart + sha256: fb89ae62f05fa0bb90f70c31fc870bcbcfd516c843fb554452ab3396f78586f7 + url: "https://pub.dev" + source: hosted + version: "2.0.0" + package_info_plus: + dependency: transitive + description: + name: package_info_plus + sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017" + url: "https://pub.dev" + source: hosted + version: "4.2.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" + url: "https://pub.dev" + source: hosted + version: "2.0.1" path: dependency: transitive description: @@ -384,6 +512,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.7.4" + polylabel: + dependency: transitive + description: + name: polylabel + sha256: "41b9099afb2aa6c1730bdd8a0fab1400d287694ec7615dd8516935fa3144214b" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + proj4dart: + dependency: transitive + description: + name: proj4dart + sha256: c8a659ac9b6864aa47c171e78d41bbe6f5e1d7bd790a5814249e6b68bc44324e + url: "https://pub.dev" + source: hosted + version: "2.1.0" rxdart: dependency: transitive description: @@ -392,6 +536,62 @@ packages: url: "https://pub.dev" source: hosted version: "0.27.7" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" + url: "https://pub.dev" + source: hosted + version: "2.3.5" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" + url: "https://pub.dev" + source: hosted + version: "2.3.2" sky_engine: dependency: transitive description: flutter @@ -477,6 +677,94 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + unicode: + dependency: transitive + description: + name: unicode + sha256: "0f69e46593d65245774d4f17125c6084d2c20b4e473a983f6e21b7d7762218f1" + url: "https://pub.dev" + source: hosted + version: "0.3.1" + universal_html: + dependency: transitive + description: + name: universal_html + sha256: "56536254004e24d9d8cfdb7dbbf09b74cf8df96729f38a2f5c238163e3d58971" + url: "https://pub.dev" + source: hosted + version: "2.2.4" + universal_io: + dependency: transitive + description: + name: universal_io + sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: "0ecc004c62fd3ed36a2ffcbe0dd9700aee63bd7532d0b642a488b1ec310f492e" + url: "https://pub.dev" + source: hosted + version: "6.2.5" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: d4ed0711849dd8e33eb2dd69c25db0d0d3fdc37e0a62e629fe32f57a22db2745 + url: "https://pub.dev" + source: hosted + version: "6.3.0" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03" + url: "https://pub.dev" + source: hosted + version: "6.2.4" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 + url: "https://pub.dev" + source: hosted + version: "3.1.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 + url: "https://pub.dev" + source: hosted + version: "3.1.0" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b + url: "https://pub.dev" + source: hosted + version: "2.2.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 + url: "https://pub.dev" + source: hosted + version: "3.1.1" uuid: dependency: transitive description: @@ -501,6 +789,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.0" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + url: "https://pub.dev" + source: hosted + version: "2.4.0" win32: dependency: transitive description: @@ -509,6 +805,30 @@ packages: url: "https://pub.dev" source: hosted version: "5.2.0" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "41fd8a189940d8696b1b810efb9abcf60827b6cbfab90b0c43e8439e3a39d85a" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + window_to_front: + dependency: transitive + description: + name: window_to_front + sha256: "7aef379752b7190c10479e12b5fd7c0b9d92adc96817d9e96c59937929512aee" + url: "https://pub.dev" + source: hosted + version: "0.0.3" + wkt_parser: + dependency: transitive + description: + name: wkt_parser + sha256: "8a555fc60de3116c00aad67891bcab20f81a958e4219cc106e3c037aa3937f13" + url: "https://pub.dev" + source: hosted + version: "2.0.0" xdg_directories: dependency: transitive description: @@ -519,4 +839,4 @@ packages: version: "1.0.4" sdks: dart: ">=3.2.0 <4.0.0" - flutter: ">=3.10.0" + flutter: ">=3.16.0" diff --git a/pubspec.yaml b/pubspec.yaml index 6cd24ad..7087dc3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,6 +39,10 @@ dependencies: google_fonts: ^6.1.0 intl: any text_scroll: ^0.2.0 + flutter_map: ^6.1.0 + appwrite: ^11.0.1 + shared_preferences: ^2.2.2 + url_launcher: ^6.2.2 # The following adds the Cupertino Icons font to your application. diff --git a/server/server.sh b/server/server.sh new file mode 100644 index 0000000..5f5bd36 --- /dev/null +++ b/server/server.sh @@ -0,0 +1,17 @@ +sh + +#!/bin/bash + +# Set the port +PORT=5000 + +# Stop any program currently running on the set port +echo 'preparing port' $PORT '...' +fuser -k 5000/tcp + +# switch directories +cd build/web/ + +# Start the server +echo 'Server starting on port' $PORT '...' +python3 -m http.server $PORT \ No newline at end of file diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index af15f08..4d83633 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -8,10 +8,16 @@ #include #include +#include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { AudioplayersWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); JustAudioWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("JustAudioWindowsPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); + WindowToFrontPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("WindowToFrontPlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index a87969d..40b0733 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -5,6 +5,8 @@ list(APPEND FLUTTER_PLUGIN_LIST audioplayers_windows just_audio_windows + url_launcher_windows + window_to_front ) list(APPEND FLUTTER_FFI_PLUGIN_LIST