From 639faddfc8a5a912ee8b79683839cc943d422f59 Mon Sep 17 00:00:00 2001
From: ImBenji <53883070+YesItsBenji@users.noreply.github.com>
Date: Mon, 20 May 2024 09:06:38 +0100
Subject: [PATCH] desktop push
---
android/app/src/main/AndroidManifest.xml | 3 +-
lib/audio_cache.dart | 7 +-
lib/backend/live_information.dart | 455 +++++---
lib/backend/modules/announcement.dart | 34 +-
lib/backend/modules/networking.dart | 196 ++++
lib/main.dart | 38 +-
lib/pages/components/ibus_display.dart | 6 +-
lib/pages/display.dart | 5 -
lib/remaster/DashboardArc.dart | 752 +++++++------
lib/remaster/InitialStartup.dart | 247 +++--
lib/remaster/RemasteredMain.dart | 40 +-
lib/remaster/SearchArc.dart | 346 ++++++
lib/remaster/WebSocketTest.dart | 189 ++++
lib/remaster/dashboard.dart | 995 +++++++++++-------
lib/utils/audio wrapper.dart | 2 +-
lib/utils/web_socket_server.dart | 38 +
macos/Flutter/GeneratedPluginRegistrant.swift | 2 +
pubspec.lock | 88 ++
pubspec.yaml | 8 +-
19 files changed, 2455 insertions(+), 996 deletions(-)
create mode 100644 lib/backend/modules/networking.dart
create mode 100644 lib/remaster/SearchArc.dart
create mode 100644 lib/remaster/WebSocketTest.dart
create mode 100644 lib/utils/web_socket_server.dart
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index ed02135..b02eddf 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -42,5 +42,6 @@
-
+
+
diff --git a/lib/audio_cache.dart b/lib/audio_cache.dart
index 450fb02..2bc0fa2 100644
--- a/lib/audio_cache.dart
+++ b/lib/audio_cache.dart
@@ -15,9 +15,8 @@ class AudioCache {
Uint8List? operator [](String key) {
// ignore case
- key = key.toLowerCase();
for (var k in _audioCache.keys) {
- if (k.toLowerCase() == key) {
+ if (k.toLowerCase() == key.toLowerCase()) {
return _audioCache[k];
}
}
@@ -35,7 +34,7 @@ class AnnouncementCache extends AudioCache {
// remove any announcements that are already loaded
for (var announcement in announcements) {
if (!_audioCache.containsKey(announcement.toLowerCase())) {
- _announements.add(announcement);
+ _announements.add(announcement.toLowerCase());
}
}
@@ -53,7 +52,7 @@ class AnnouncementCache extends AudioCache {
filename = filename.split("/").last;
}
- if (_announements.contains(filename)) {
+ if (_announements.contains(filename.toLowerCase())) {
_audioCache[filename.toLowerCase()] = file.content;
print("Loaded announcement: $filename");
}
diff --git a/lib/backend/live_information.dart b/lib/backend/live_information.dart
index e15dc45..22e7f42 100644
--- a/lib/backend/live_information.dart
+++ b/lib/backend/live_information.dart
@@ -10,6 +10,7 @@ import 'package:bus_infotainment/auth/api_constants.dart';
import 'package:bus_infotainment/auth/auth_api.dart';
import 'package:bus_infotainment/backend/modules/announcement.dart';
import 'package:bus_infotainment/backend/modules/commands.dart';
+import 'package:bus_infotainment/backend/modules/networking.dart';
import 'package:bus_infotainment/backend/modules/synced_time.dart';
import 'package:bus_infotainment/backend/modules/tracker.dart';
import 'package:bus_infotainment/backend/modules/tube_info.dart';
@@ -75,6 +76,8 @@ class LiveInformation {
if (!auth.isAuthenticated()) {
auth.loadAnonymousUser();
}
+
+ networkingModule = NetworkingModule();
}
Future initTrackerModule() async {
@@ -90,9 +93,12 @@ class LiveInformation {
String? roomCode;
String? roomDocumentID;
bool isHost = false;
+ bool inRoom = false;
appwrite.RealtimeSubscription? _subscription;
RealtimeKeepAliveConnection? _keepAliveConnection; // This is a workaround for a bug in the appwrite SDK
+ // Local room stuff
+ ListenerReceipt? _listenerReciept;
// Modules
// late CommandModule commandModule; This needs to be deprecated
@@ -101,6 +107,7 @@ class LiveInformation {
late SyncedTimeModule syncedTimeModule;
late TrackerModule trackerModule;
late TubeStations tubeStations;
+ late NetworkingModule networkingModule;
// Important variables
BusRouteVariant? _currentRouteVariant;
@@ -114,7 +121,7 @@ class LiveInformation {
- Future setRouteVariant(BusRouteVariant? routeVariant) async {
+ Future setRouteVariant(BusRouteVariant? routeVariant, {bool sendToServer = false}) async {
if (routeVariant == null) {
_currentRouteVariant = null;
@@ -160,6 +167,11 @@ class LiveInformation {
print("Failed to update route on server");
}
+ }
+ if (inRoom && sendToServer) {
+
+ SendCommand("setroute ${routeVariant.busRoute.routeNumber} ${routeVariant.busRoute.routeVariants.values.toList().indexOf(routeVariant)}");
+
}
Continuation:
@@ -200,12 +212,13 @@ class LiveInformation {
return _currentRouteVariant;
}
- Future setRouteVariantQuery(String routeNumber, int routeVariantIndex) async {
+ Future setRouteVariantQuery(String routeNumber, int routeVariantIndex, {bool sendToServer = false}) async {
BusRoute route = busSequences.routes[routeNumber]!;
BusRouteVariant routeVariant = route.routeVariants.values.toList()[routeVariantIndex];
await setRouteVariant(
- routeVariant
+ routeVariant,
+ sendToServer: sendToServer
);
}
@@ -213,174 +226,225 @@ class LiveInformation {
// Multi device support
Future createRoom(String roomCode) async {
- print("Creating room with code $roomCode");
- // Update the room code
- this.roomCode = roomCode;
-
- // Enable host mode
- isHost = true;
-
- // Access the database
- final client = auth.client;
- final databases = appwrite.Databases(client);
-
- // Remove any existing documents
- final existingDocuments = await databases.listDocuments(
- databaseId: "6633e85400036415ab0f",
- collectionId: "6633e85d0020f52f3771",
- queries: [
- appwrite.Query.search("SessionID", roomCode)
- ]
- );
- for (var document in existingDocuments.documents) {
- await databases.deleteDocument(
- databaseId: "6633e85400036415ab0f",
- collectionId: "6633e85d0020f52f3771",
- documentId: document.$id
+ {
+ // Local Room
+ await networkingModule.startWebSocketServer();
+ inRoom = true;
+ _listenerReciept = networkingModule.onMessageReceived?.addListener(
+ (p0) {
+ print("Received local command: $p0");
+ ExecuteCommand(p0);
+ }
);
}
- // Create the document
- final document = await databases.createDocument(
- databaseId: "6633e85400036415ab0f",
- collectionId: "6633e85d0020f52f3771",
- documentId: appwrite.ID.unique(),
- data: {
- "SessionID": roomCode,
- "LastUpdater": auth.userID,
- }
- );
+ {
+ // Cloud Room
+ print("Creating room with code $roomCode");
- // Listen for changes
- // { Breaks due to bug in appwrite
- // final realtime = appwrite.Realtime(client);
- //
- // if (_subscription != null) {
- // _subscription!.close();
- // }
- //
- // _subscription = realtime.subscribe(
- // ['databases.6633e85400036415ab0f.collections.6633e85d0020f52f3771.documents.${document.$id}']
- // );
- // _subscription!.stream.listen(ServerListener);
- // }
- // Listen for changes
- if (_keepAliveConnection != null) {
- try {
- _keepAliveConnection!.close();
- } catch (e) {
- print("Failed to close connection... oh well");
+ // Update the room code
+ this.roomCode = roomCode;
+
+ // Enable host mode
+ isHost = true;
+ inRoom = true;
+ // Access the database
+ final client = auth.client;
+ final databases = appwrite.Databases(client);
+
+ // Remove any existing documents
+ final existingDocuments = await databases.listDocuments(
+ databaseId: "6633e85400036415ab0f",
+ collectionId: "6633e85d0020f52f3771",
+ queries: [
+ appwrite.Query.search("SessionID", roomCode)
+ ]
+ );
+ for (var document in existingDocuments.documents) {
+ await databases.deleteDocument(
+ databaseId: "6633e85400036415ab0f",
+ collectionId: "6633e85d0020f52f3771",
+ documentId: document.$id
+ );
}
+
+ // Create the document
+ final document = await databases.createDocument(
+ databaseId: "6633e85400036415ab0f",
+ collectionId: "6633e85d0020f52f3771",
+ documentId: appwrite.ID.unique(),
+ data: {
+ "SessionID": roomCode,
+ "LastUpdater": auth.userID,
+ }
+ );
+
+ // Listen for changes
+ if (_keepAliveConnection != null) {
+ try {
+ _keepAliveConnection!.close();
+ } catch (e) {
+ print("Failed to close connection... oh well");
+ }
+ }
+
+ String APPWRITE_ENDPOINT_URL = "https://cloud.appwrite.io/v1";
+ String domain = APPWRITE_ENDPOINT_URL.replaceAll("https://", "").trim();
+ _keepAliveConnection = RealtimeKeepAliveConnection(
+ channels: ['databases.6633e85400036415ab0f.collections.6633e85d0020f52f3771.documents.${document.$id}'],
+ onData: ServerListener,
+ domain: domain,
+ client: auth.client,
+ onError: (e) {
+ print("Workarround Error: $e");
+ },
+ );
+ _keepAliveConnection!.initialize();
+
+
+ // Update the room document ID
+ roomDocumentID = document.$id;
+
+ print("Created room with code $roomCode");
}
- String APPWRITE_ENDPOINT_URL = "https://cloud.appwrite.io/v1";
- String domain = APPWRITE_ENDPOINT_URL.replaceAll("https://", "").trim();
- _keepAliveConnection = RealtimeKeepAliveConnection(
- channels: ['databases.6633e85400036415ab0f.collections.6633e85d0020f52f3771.documents.${document.$id}'],
- onData: ServerListener,
- domain: domain,
- client: auth.client,
- onError: (e) {
- print("Workarround Error: $e");
- },
- );
- _keepAliveConnection!.initialize();
-
-
- // Update the room document ID
- roomDocumentID = document.$id;
-
- print("Created room with code $roomCode");
}
- Future joinRoom(String roomCode) async {
- print("Joining room with code $roomCode");
+ Future joinRoom(String infoJson) async {
- // Disable host mode
- isHost = false;
+ try {
+ {
+ // sync
+ String routeNumber = jsonDecode(infoJson)["sync"]["route"];
+ int routeVariantIndex = jsonDecode(infoJson)["sync"]["routeVariant"];
- // Update the room code
- this.roomCode = roomCode;
+ setRouteVariantQuery(routeNumber, routeVariantIndex);
- // Access the database
- final client = auth.client;
- final databases = appwrite.Databases(client);
-
- // Get the document
- final response = await databases.listDocuments(
- databaseId: "6633e85400036415ab0f",
- collectionId: "6633e85d0020f52f3771",
- queries: [
- appwrite.Query.search("SessionID", roomCode)
- ]
- );
-
- if (response.documents.isEmpty) {
- throw Exception("Room not found");
+ LiveInformation().announcementModule.queueAnnouncementByRouteVariant(routeVariant: _currentRouteVariant!, sendToServer: false);
+ }
+ } catch (e) {
+ print("Failed to sync route");
}
- final document = response.documents.first;
+ {
+ // Local Room
- // Listen for changes
- // {
- // final realtime = appwrite.Realtime(client);
- //
- // if (_subscription != null) {
- // _subscription!.close();
- // }
- //
- // _subscription = realtime.subscribe([
- // 'databases.6633e85400036415ab0f.collections.6633e85d0020f52f3771.documents.${document.$id}'
- // ]);
- //
- // _subscription!.stream.listen(ServerListener);
- // }
- // Listen for changes
- if (_keepAliveConnection != null) {
- try {
- _keepAliveConnection!.close();
- } catch (e) {
- print("Failed to close connection... oh well");
+ String host = jsonDecode(infoJson)["local"]["host"];
+
+ if (await networkingModule.connectToWebSocketServer(host)){
+ print("Connected to local room at $host");
+
+ _listenerReciept = networkingModule.onMessageReceived?.addListener(
+ (p0) {
+ print("Received local command: $p0");
+ ExecuteCommand(p0);
+ }
+ );
+ inRoom = true;
+ return; // We dont need to connect to the cloud room if we are connected to the local room.
+ } else {
+ print("Failed to connect to local room at $host");
+ print("Falling back to cloud room");
}
}
- String APPWRITE_ENDPOINT_URL = "https://cloud.appwrite.io/v1";
- String domain = APPWRITE_ENDPOINT_URL.replaceAll("https://", "").trim();
- _keepAliveConnection = RealtimeKeepAliveConnection(
- channels: ['databases.6633e85400036415ab0f.collections.6633e85d0020f52f3771.documents.${document.$id}'],
- onData: ServerListener,
- domain: domain,
- client: auth.client,
- onError: (e) {
- print("Workarround Error: $e");
- },
- );
- _keepAliveConnection!.initialize();
+ {
+ // Cloud Room
- // Update the room document ID
- roomDocumentID = document.$id;
+ String roomCode = jsonDecode(infoJson)["cloud"]["roomCode"];
- // Get the current route
- try {
- String routeNumber = document.data["CurrentRoute"];
- int routeVariantIndex = document.data["CurrentRouteVariant"];
+ print("Joining cloud room with code $roomCode");
- await setRouteVariantQuery(routeNumber, routeVariantIndex);
- print("Set route to $routeNumber $routeVariantIndex");
- } catch (e) {
- print("Failed to set route");
+ // Disable host mode
+ isHost = false;
+
+ // Update the room code
+ this.roomCode = roomCode;
+
+ // Access the database
+ final client = auth.client;
+ final databases = appwrite.Databases(client);
+
+ // Get the document
+ final response = await databases.listDocuments(
+ databaseId: "6633e85400036415ab0f",
+ collectionId: "6633e85d0020f52f3771",
+ queries: [
+ appwrite.Query.search("SessionID", roomCode)
+ ]
+ );
+
+ if (response.documents.isEmpty) {
+ throw Exception("Room not found");
+ }
+
+ final document = response.documents.first;
+
+ // Listen for changes
+ if (_keepAliveConnection != null) {
+ try {
+ _keepAliveConnection!.close();
+ } catch (e) {
+ print("Failed to close connection... oh well");
+ }
+ }
+
+ String APPWRITE_ENDPOINT_URL = "https://cloud.appwrite.io/v1";
+ String domain = APPWRITE_ENDPOINT_URL.replaceAll("https://", "").trim();
+ _keepAliveConnection = RealtimeKeepAliveConnection(
+ channels: ['databases.6633e85400036415ab0f.collections.6633e85d0020f52f3771.documents.${document.$id}'],
+ onData: ServerListener,
+ domain: domain,
+ client: auth.client,
+ onError: (e) {
+ print("Workarround Error: $e");
+ },
+ );
+ _keepAliveConnection!.initialize();
+
+ // Update the room document ID
+ roomDocumentID = document.$id;
+
+ // Get the current route
+ try {
+ String routeNumber = document.data["CurrentRoute"];
+ int routeVariantIndex = document.data["CurrentRouteVariant"];
+
+ await setRouteVariantQuery(routeNumber, routeVariantIndex);
+ print("Set route to $routeNumber $routeVariantIndex");
+ } catch (e) {
+ print("Failed to set route");
+ }
+ inRoom = true;
+ print("Joined cloud room with code $roomCode");
}
- print("Joined room with code $roomCode");
}
Future leaveRoom() async {
- if (roomCode == null) {
- throw Exception("Not in a room");
+ if (!inRoom) {
+ return;
}
+ {
+ // Local Room
+ networkingModule.stopWebSocketServer();
+ inRoom = false;
+ networkingModule.onMessageReceived?.removeListener(_listenerReciept!);
+ }
+
+ {
+ // Cloud Room
+ if (_keepAliveConnection != null) {
+ _keepAliveConnection!.close();
+ _keepAliveConnection = null;
+ }
+ }
+
+ inRoom = false;
+
if (isHost) {
// Access the database
final client = auth.client;
@@ -406,7 +470,7 @@ class LiveInformation {
roomCode = null;
roomDocumentID = null;
isHost = false;
-
+ inRoom = false;
_keepAliveConnection?.close();
_keepAliveConnection = null;
@@ -414,6 +478,42 @@ class LiveInformation {
setRouteVariant(null);
}
+ String generateRoomInfo() {
+
+ // Room Info Example
+ /*
+ {
+ "cloud": {
+ "roomCode": "6633e85d0020f52f3771"
+ },
+ "local":
+ {
+ "host": "ws://192.168.0.123:8080"
+ },
+ "sync":
+ {
+ "route": "W11",
+ "routeVariant": 1
+ }
+ }
+ */
+
+ return jsonEncode({
+ "cloud": {
+ "roomCode": roomCode,
+ },
+ "local": {
+ "host": "ws://${networkingModule.localIP}:8080"
+ },
+ if (_currentRouteVariant != null)
+ "sync": {
+ "route": _currentRouteVariant!.busRoute.routeNumber,
+ "routeVariant": _currentRouteVariant!.busRoute.routeVariants.values.toList().indexOf(_currentRouteVariant!),
+ }
+ });
+
+ }
+
String? lastCommand;
Future ServerListener(appwrite.RealtimeMessage response) async {
print("Session update");
@@ -454,8 +554,11 @@ class LiveInformation {
// Execute the command
List commands = response.payload["Commands"].cast();
- String? command = commands.last;
+ ExecuteCommand(commands.last);
+ }
+
+ void ExecuteCommand(String command) {
if (command == lastCommand) {
return;
}
@@ -516,6 +619,12 @@ class LiveInformation {
);
}
+ } else if (commandName == "setroute") {
+ print("Set route command received");
+ String routeNumber = commandParts[1];
+ int routeVariantIndex = int.parse(commandParts[2]);
+
+ setRouteVariantQuery(routeNumber, routeVariantIndex, sendToServer: false);
}
}
@@ -527,34 +636,48 @@ class LiveInformation {
Future SendCommand(String command) async {
- final client = auth.client;
- final databases = appwrite.Databases(client);
+ {
+ // Local Commands
- final response = await databases.listDocuments(
- databaseId: "6633e85400036415ab0f",
- collectionId: "6633e85d0020f52f3771",
- queries: [
- appwrite.Query.search("SessionID", roomCode!)
- ]
- );
+ networkingModule.sendMessage(command);
+ }
- List pastCommands = [];
- response.documents.first.data["Commands"].forEach((element) {
- pastCommands.add(element);
- });
+ {
+ // Cloud Commands
+
+ final client = auth.client;
+ final databases = appwrite.Databases(client);
+
+ final response = await databases.listDocuments(
+ databaseId: "6633e85400036415ab0f",
+ collectionId: "6633e85d0020f52f3771",
+ queries: [
+ appwrite.Query.search("SessionID", roomCode!)
+ ]
+ );
+
+ List pastCommands = [];
+
+ response.documents.first.data["Commands"].forEach((element) {
+ pastCommands.add(element);
+ });
+
+ pastCommands.add(command);
+
+ final document = await databases.updateDocument(
+ databaseId: "6633e85400036415ab0f",
+ collectionId: "6633e85d0020f52f3771",
+ documentId: roomDocumentID!,
+ data: {
+ "Commands": pastCommands,
+ "LastUpdater": auth.userID,
+ }
+ );
+ }
+
- pastCommands.add(command);
- final document = await databases.updateDocument(
- databaseId: "6633e85400036415ab0f",
- collectionId: "6633e85d0020f52f3771",
- documentId: roomDocumentID!,
- data: {
- "Commands": pastCommands,
- "LastUpdater": auth.userID,
- }
- );
}
List _splitCommand(String command) {
diff --git a/lib/backend/modules/announcement.dart b/lib/backend/modules/announcement.dart
index 6bbff65..53a580e 100644
--- a/lib/backend/modules/announcement.dart
+++ b/lib/backend/modules/announcement.dart
@@ -27,7 +27,7 @@ class AnnouncementModule extends InfoModule {
// Files
String _bundleLocation = "assets/ibus_recordings.zip";
Uint8List? _bundleBytes;
- void setBundleBytes(Uint8List bytes) {
+ void setBundleBytes(Uint8List? bytes) {
_bundleBytes = bytes;
}
Future getBundleBytes() async {
@@ -35,7 +35,6 @@ class AnnouncementModule extends InfoModule {
if (_bundleBytes != null) {
return _bundleBytes!;
} else {
-
// Try to load them from shared preferences
try {
SharedPreferences prefs = await SharedPreferences.getInstance();
@@ -47,17 +46,7 @@ class AnnouncementModule extends InfoModule {
} catch (e) {
throw Exception("Loading announcements from assets has been deprecated.");
}
-
-
-
- // if (kIsWeb) {
- // throw Exception("Cannot load bundle bytes on web");
- // }
- //
- // final bytes = await rootBundle.load(_bundleLocation);
- // return bytes.buffer.asUint8List();
}
-
}
// Queue
@@ -165,7 +154,14 @@ class AnnouncementModule extends InfoModule {
}
// Configuration
- int get defaultAnnouncementDelay => liveInformation.auth.isAuthenticated() ? 1 : 0;
+ Duration get defaultAnnouncementDelay {
+ if (liveInformation.inRoom) {
+ return Duration(milliseconds: 500);
+ } else {
+ print("Not in room");
+ return Duration.zero;
+ }
+ }
// Methods
Future queueAnnounceByAudioName({
@@ -177,7 +173,9 @@ class AnnouncementModule extends InfoModule {
if (sendToServer && _shouldSendToServer()) {
- scheduledTime ??= liveInformation.syncedTimeModule.Now().add(Duration(seconds: defaultAnnouncementDelay));
+
+
+ scheduledTime ??= liveInformation.syncedTimeModule.Now().add(defaultAnnouncementDelay);
String audioNamesString = "";
@@ -232,7 +230,7 @@ class AnnouncementModule extends InfoModule {
if (sendToServer && _shouldSendToServer()) {
- scheduledTime ??= liveInformation.syncedTimeModule.Now().add(Duration(seconds: defaultAnnouncementDelay));
+ scheduledTime ??= liveInformation.syncedTimeModule.Now().add(defaultAnnouncementDelay);
liveInformation.SendCommand("announce info $infoIndex ${scheduledTime.millisecondsSinceEpoch}");
queueAnnouncementByInfoIndex(
@@ -262,7 +260,9 @@ class AnnouncementModule extends InfoModule {
if (sendToServer && _shouldSendToServer()) {
- scheduledTime ??= liveInformation.syncedTimeModule.Now().add(Duration(seconds: defaultAnnouncementDelay));
+ print("Sending route announcement to server");
+
+ scheduledTime ??= liveInformation.syncedTimeModule.Now().add(defaultAnnouncementDelay);
String routeNumber = routeVariant.busRoute.routeNumber;
int routeVariantIndex = routeVariant.busRoute.routeVariants.values.toList().indexOf(routeVariant);
@@ -326,7 +326,7 @@ class AnnouncementModule extends InfoModule {
// Server check
bool _shouldSendToServer() {
- bool condition = liveInformation.roomCode != null;
+ bool condition = liveInformation.inRoom;
print("Should send to server? " + (condition.toString()));
return condition;
diff --git a/lib/backend/modules/networking.dart b/lib/backend/modules/networking.dart
new file mode 100644
index 0000000..7512dd4
--- /dev/null
+++ b/lib/backend/modules/networking.dart
@@ -0,0 +1,196 @@
+import 'dart:async';
+import 'dart:io';
+
+import 'package:flutter/foundation.dart';
+import 'package:network_info_plus/network_info_plus.dart';
+import 'package:shelf/shelf.dart';
+import 'package:shelf/shelf_io.dart' as io;
+import 'package:shelf_web_socket/shelf_web_socket.dart';
+import 'package:web_socket_channel/web_socket_channel.dart';
+import 'package:bus_infotainment/backend/modules/info_module.dart';
+import 'package:bus_infotainment/utils/delegates.dart';
+
+class NetworkingModule extends InfoModule {
+ // Host websocket server
+ String host = "ws://0.0.0.0:8080";
+ HttpServer? _server;
+ WebSocketChannel? _channel;
+
+ // Store connected WebSocket channels
+ final List _connectedClients = [];
+
+ EventDelegate? onMessageReceived = EventDelegate();
+
+ NetworkingModule() {
+ _refresh();
+ refreshTimer();
+ }
+
+ Future startWebSocketServer() async {
+ try {
+ var handler = webSocketHandler((WebSocketChannel webSocket) {
+ _connectedClients.add(webSocket); // Add the client to the list
+ print('Client connected: ${webSocket}'); // Log client connection
+
+ webSocket.stream.listen((message) {
+ // Handle messages from the client here
+ print('Received message: $message');
+
+ // Forward message to all clients except the sender
+ for (var client in _connectedClients) {
+ if (client != webSocket) {
+ client.sink.add(message);
+ }
+ }
+
+ _onMessageReceived(message);
+ }, onDone: () {
+ _connectedClients.remove(webSocket); // Remove client on disconnect
+ print('Client disconnected: ${webSocket}'); // Log client disconnection
+ });
+ });
+
+ _server = await io.serve(handler, InternetAddress.anyIPv4, 8080);
+ print('WebSocket server started at ${_server?.address.address}:${_server?.port}');
+
+ return true;
+ } catch (e) {
+ print('Failed to start WebSocket server: $e');
+ return false;
+ }
+ }
+
+ bool stopWebSocketServer() {
+ if (_server == null) {
+ throw Exception('WebSocket server is not running');
+ }
+
+ try {
+ for (var client in _connectedClients) {
+ client.sink.close();
+ }
+ _connectedClients.clear();
+ _server?.close(force: true);
+ _server = null;
+ print('WebSocket server stopped');
+ return true;
+ } catch (e) {
+ print('Failed to stop WebSocket server: $e');
+ return false;
+ }
+ }
+
+ Future connectToWebSocketServer(String url) async {
+ try {
+ _channel = await WebSocketChannel.connect(Uri.parse(url));
+ _channel?.stream.listen((message) {
+ // Handle messages from the server here
+ print('Received message from server: $message');
+ _onMessageReceived(message);
+ });
+
+ print('Connected to WebSocket server at $url');
+ return true;
+ } catch (e) {
+ print('Failed to connect to WebSocket server: $e');
+ return false;
+ }
+ }
+
+ bool disconnectFromWebSocketServer() {
+ if (_channel == null) {
+ throw Exception('No active WebSocket connection');
+ }
+
+ try {
+ _channel?.sink.close();
+ _channel = null;
+ print('Disconnected from WebSocket server');
+ return true;
+ } catch (e) {
+ print('Failed to disconnect from WebSocket server: $e');
+ return false;
+ }
+ }
+
+ bool sendMessage(String message) {
+
+ // If hosting a server, send message to all clients
+ if (_server != null) {
+ return sendMessageToClients(message);
+ }
+
+ if (_channel == null) {
+ throw Exception('No active WebSocket connection');
+ }
+
+ try {
+ _channel?.sink.add(message);
+ print('Sent message: $message');
+ return true;
+ } catch (e) {
+ print('Failed to send message: $e');
+ return false;
+ }
+ }
+
+ bool sendMessageToClients(String message) {
+ if (_connectedClients.isEmpty) {
+ print('No clients connected');
+ return false;
+ }
+
+ try {
+ for (var client in _connectedClients) {
+ client.sink.add(message);
+ }
+ print('Sent message to all clients: $message');
+ return true;
+ } catch (e) {
+ print('Failed to send message to clients: $e');
+ return false;
+ }
+ }
+
+ void _onMessageReceived(String message) {
+ // Notify all listeners that a message has been received.
+ onMessageReceived?.trigger(message);
+ }
+
+ // Useful boilerplate
+ String _localIP = "";
+ String get localIP => _localIP;
+
+ Timer refreshTimer() => Timer.periodic(const Duration(seconds: 10), (timer) {
+ if (kIsWeb) return;
+ _refresh();
+ });
+
+ Future _refresh() async {
+ print("Refreshing network info...");
+ {
+ // Update the local IP address
+
+ // First try NetworkInfo
+ _localIP = (await NetworkInfo().getWifiIP()) ?? "";
+
+ // If null, try NetworkInterface
+ // Only look for ethernet. Wifi would have been found by NetworkInfo
+ if (_localIP.isEmpty) {
+ for (var interface in await NetworkInterface.list()) {
+ if (!interface.name.toLowerCase().contains("eth") || interface.name.contains(" ")) {
+ continue;
+ }
+
+ for (var addr in interface.addresses) {
+ print('Interface ${interface.name} has address ${addr.address}');
+ if (addr.type == InternetAddressType.IPv4 && !addr.isLoopback) {
+ _localIP = addr.address;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/lib/main.dart b/lib/main.dart
index 093ba11..9b1c38e 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -9,8 +9,10 @@ import 'package:bus_infotainment/remaster/dashboard.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
+import 'package:shelf/shelf.dart';
import 'package:window_manager/window_manager.dart';
import 'package:bus_infotainment/remaster/InitialStartup.dart' as remaster;
+import 'package:shelf/shelf_io.dart' as shelf_io;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
@@ -19,10 +21,11 @@ void main() async {
await windowManager.ensureInitialized();
WindowOptions options = WindowOptions(
- size: Size(411.4, 850.3),
+ title: 'Bus Infotainment',
+
);
- windowManager.setAspectRatio(411.4 / 850.3);
+ // windowManager.setAspectRatio(411.4 / 850.3);
await windowManager.waitUntilReadyToShow(options, () async {
await windowManager.show();
@@ -30,22 +33,31 @@ void main() async {
});
}
-
+ // {
+ // // Web server test
+ //
+ // var handler = const Pipeline().addMiddleware(logRequests()).addHandler((Request request) {
+ // return Response.ok('Hello, world!');
+ // });
+ // var server = await shelf_io.serve(handler, '0.0.0.0', 8080);
+ // server.autoCompress = true;
+ //
+ // print('Serving at http://${server.address.host}:${server.port}');
+ //
+ // // get the IP address
+ // for (var interface in await NetworkInterface.list()) {
+ // for (var addr in interface.addresses) {
+ // print('Interface ${interface.name} has address ${addr.address}');
+ // print('Try http://${addr.address}:${server.port}');
+ // }
+ // }
+ //
+ // }
LiveInformation liveInformation = LiveInformation();
await liveInformation.initialize();
runApp(const MyApp());
-
- // Disalow screen to turn off on android
- await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
- // Disalow landscape mode
- await SystemChrome.setPreferredOrientations([
- DeviceOrientation.portraitUp,
- DeviceOrientation.portraitDown,
- ]);
-
-
}
class MyApp extends StatelessWidget {
diff --git a/lib/pages/components/ibus_display.dart b/lib/pages/components/ibus_display.dart
index a5c37ee..b3891a9 100644
--- a/lib/pages/components/ibus_display.dart
+++ b/lib/pages/components/ibus_display.dart
@@ -52,7 +52,7 @@ class _ibus_displayState extends State {
String _padString(String input){
if (input.length < 30){
- print("Input is too short");
+ // print("Input is too short");
return input;
}
@@ -241,12 +241,14 @@ class _timeComponentState extends State<_timeComponent> {
String timeString = "${now.hour % 12}:${now.minute.toString().padLeft(2, "0")} ${now.hour < 12 ? "AM" : "PM"}";
+ timeString = timeString.replaceAll("0:", "12:");
+
return timeString;
}
String _padString(String input){
if (input.length < 30){
- print("Input is too short");
+ // print("Input is too short");
return input;
}
diff --git a/lib/pages/display.dart b/lib/pages/display.dart
index 144fd60..6e89a23 100644
--- a/lib/pages/display.dart
+++ b/lib/pages/display.dart
@@ -74,11 +74,6 @@ class _pages_DisplayState extends State {
});
// Hide the notification bar and make the app full screen and display over notch
- if (widget._tfL_Dataset_TestState.hideUI) {
- SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
- } else {
- SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
- }
},
),
diff --git a/lib/remaster/DashboardArc.dart b/lib/remaster/DashboardArc.dart
index aa90bd7..3190f34 100644
--- a/lib/remaster/DashboardArc.dart
+++ b/lib/remaster/DashboardArc.dart
@@ -3,9 +3,12 @@ import 'package:bus_infotainment/backend/live_information.dart';
import 'package:bus_infotainment/pages/components/ibus_display.dart';
import 'package:bus_infotainment/remaster/dashboard.dart';
import 'package:bus_infotainment/tfl_datasets.dart';
+import 'package:bus_infotainment/utils/delegates.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_scroll_shadow/flutter_scroll_shadow.dart';
+import 'package:network_info_plus/network_info_plus.dart';
+import 'package:qr_flutter/qr_flutter.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
class ArcDashboard extends StatefulWidget {
@@ -17,260 +20,207 @@ class ArcDashboard extends StatefulWidget {
class _ArcDashboardState extends State {
_closeDialogueChecker closeDialogWidget = _closeDialogueChecker();
+ late ListenerReceipt onRouteVariantChange;
+
+ @override
+ void initState() {
+ // TODO: implement initState
+ super.initState();
+
+ onRouteVariantChange = LiveInformation().routeVariantDelegate.addListener((value) {
+ print("Route variant changed");
+ setState(() {
+
+ });
+ });
+ }
+
+ @override
+ void dispose() {
+ // TODO: implement dispose
+ super.dispose();
+
+ LiveInformation().routeVariantDelegate.removeListener(onRouteVariantChange);
+ }
+
@override
Widget build(BuildContext context) {
+ return PopScope(
- // Force landscape mode
- Future.delayed(Duration(seconds: 1), () {
- SystemChrome.setPreferredOrientations([
- DeviceOrientation.landscapeRight,
- DeviceOrientation.landscapeLeft,
- ]);
- });
+ onPopInvoked: (isPop) {
- return Scaffold(
+ try {
+ LiveInformation().leaveRoom();
+ print("Left room");
+ } catch (e) {
+ print("Error leaving room: $e");
+ }
- body: Container(
- child: Row(
- children: [
+ },
- const SizedBox(
- width: 10,
- ),
+ child: Scaffold(
- Container(
- padding: const EdgeInsets.symmetric(
- vertical: 10,
+ body: Container(
+ child: Row(
+
+ children: [
+
+ const SizedBox(
+ width: 10,
),
- child: IntrinsicWidth(
- child: Column(
- children: [
+ Container(
+ padding: const EdgeInsets.symmetric(
+ vertical: 10,
+ ),
- if (LiveInformation().roomDocumentID != null)
- Tooltip(
- child: ShadButton(
- icon: const Icon(Icons.network_check),
+ child: IntrinsicWidth(
+ child: Column(
+ children: [
+
+ ShadButton.outline(
+ icon: const Icon(Icons.menu),
width: double.infinity,
+ border: Border.all(
+ width: 2,
+ color: Colors.grey.shade400,
+ ),
+ padding: const EdgeInsets.all(0),
+ borderRadius: const BorderRadius.all(Radius.circular(10)),
onPressed: () {
+ bool multiMode = ModalRoute.of(context)!.settings.name!.contains("multi");
+
showShadSheet(
context: context,
+ side: ShadSheetSide.left,
+
builder: (context) {
return ShadSheet(
- padding: const EdgeInsets.all(10),
- content: Column(
- children: [
- Text("Room ID: ${LiveInformation().roomDocumentID}"),
- ],
+ padding: const EdgeInsets.all(0),
+ content: Container(
+ width: 225,
+ height: MediaQuery.of(context).size.height,
+ padding: const EdgeInsets.all(10),
+ child: Column(
+ children: [
+
+ Expanded(
+ child: Column(
+ children: [
+
+ ShadButton(
+ text: Text("Rooom Information"),
+ onPressed: () {
+ Navigator.pop(context);
+ showShadSheet(
+ context: context,
+ side: ShadSheetSide.left,
+ builder: (context) {
+ return ShadSheet(
+ padding: const EdgeInsets.all(10),
+ content: Column(
+ children: [
+ Text("Room ID: ${LiveInformation().roomDocumentID}"),
+ Text("IP Address: ${LiveInformation().networkingModule.localIP}"),
+ QrImageView(
+ data: LiveInformation().generateRoomInfo(),
+ size: 270,
+ backgroundColor: Colors.white,
+ ),
+ ShadButton(
+ text: Text("Copy Room Info"),
+ onPressed: () {
+ Clipboard.setData(ClipboardData(text: LiveInformation().generateRoomInfo()));
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text("Copied room info to clipboard"),
+ )
+ );
+ },
+ )
+ ],
+
+ ),
+ );
+ }
+ );
+ },
+ )
+
+ ],
+ )
+ ),
+
+ if (!multiMode)
+ ShadButton(
+ text: Text("Return to route selection"),
+ onPressed: () {
+ Navigator.pop(context);
+ Navigator.pop(context);
+ },
+ )
+ else
+ ShadButton(
+ text: Text("Route selection"),
+ onPressed: () {
+ Navigator.popAndPushNamed(context, '/multi/routes');
+ },
+ )
+ ],
+ ),
),
);
}
);
+
},
),
- message: "Room information",
- ),
- SizedBox(
- height: 220,
- child: RotatedBox(
- quarterTurns: 3,
- child: Column(
- children: [
- ShadButton(
- text: const Text("Manual Announcements"),
- width: double.infinity,
- borderRadius: const BorderRadius.all(Radius.circular(10)),
- onPressed: () {
+ SizedBox(
+ height: 220,
+ child: RotatedBox(
+ quarterTurns: 3,
+ child: Column(
+ children: [
+ ShadButton(
+ text: const Text("Manual Announcements"),
+ width: double.infinity,
+ borderRadius: const BorderRadius.all(Radius.circular(10)),
+ onPressed: () {
- List announcements = [];
+ List announcements = [];
- for (var announcement in LiveInformation().announcementModule.manualAnnouncements) {
+ for (var announcement in LiveInformation().announcementModule.manualAnnouncements) {
- announcements.add(
- ShadButton(
- text: SizedBox(
- width: 200-42,
- child: Text(announcement.shortName),
- ),
- onPressed: () {
-
- if (closeDialogWidget.closeDialog) {
- Navigator.pop(context);
- }
-
- LiveInformation().announcementModule.queueAnnouncementByInfoIndex(
- infoIndex: LiveInformation().announcementModule.manualAnnouncements.indexOf(announcement),
- );
- },
- )
- );
-
- }
-
- print(announcements.length);
-
- showShadSheet(
- context: context,
- side: ShadSheetSide.left,
-
- builder: (context) {
- return ShadSheet(
- padding: const EdgeInsets.all(0),
-
- content: Container(
- height: MediaQuery.of(context).size.height,
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- SizedBox(
- width: 5,
- ),
- Container(
- padding: const EdgeInsets.symmetric(
- vertical: 10,
- ),
- alignment: Alignment.bottomCenter,
- height: double.infinity,
- width: 35,
- child: RotatedBox(
- quarterTurns: 3,
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Row(
- children: [
- Text(
- "Manual Ann'",
- style: ShadTheme.of(context).textTheme.h3
- ),
- SizedBox(
- width: 16,
- ),
- Container(
- width: 1,
- height: 200,
- color: Colors.grey,
- ),
- ],
- ),
-
- closeDialogWidget
-
- ],
- ),
- ),
- ),
- Container(
-
- // width: 220,
- height: MediaQuery.of(context).size.height,
-
- child: Scrollbar(
- thumbVisibility: true,
- child: SingleChildScrollView(
- reverse: true,
- child: Container(
- margin: const EdgeInsets.fromLTRB(
- 0,
- 10,
- 10,
- 10
- ),
- child: Column(
- children: announcements.reversed.toList(),
- ),
- ),
- ),
- ),
- ),
- ],
+ announcements.add(
+ ShadButton(
+ text: SizedBox(
+ width: 200-42,
+ child: Text(announcement.shortName),
),
- ),
+ onPressed: () {
+
+ if (closeDialogWidget.closeDialog) {
+ Navigator.pop(context);
+ }
+
+ LiveInformation().announcementModule.queueAnnouncementByInfoIndex(
+ infoIndex: LiveInformation().announcementModule.manualAnnouncements.indexOf(announcement),
+ );
+ },
+ )
);
+
}
- );
- },
- ),
- ShadButton(
- text: const Text("Bus Stop Announcements"),
- width: double.infinity,
- borderRadius: const BorderRadius.all(Radius.circular(10)),
- onPressed: () {
+ print(announcements.length);
- showShadSheet(
+ showShadSheet(
context: context,
side: ShadSheetSide.left,
builder: (context) {
-
- List announcements = [];
-
- LiveInformation info = LiveInformation();
-
- for (var busStop in info.getRouteVariant()!.busStops) {
-
- if (info.trackerModule.nearestStop == busStop) {
- announcements.add(
- ShadButton(
- text: SizedBox(
- width: 200-42,
- child: Text(
- "-> ${busStop.formattedStopName}",
- overflow: TextOverflow.ellipsis,
- ),
- ),
- backgroundColor: Colors.amber,
- onPressed: () {
- if (closeDialogWidget.closeDialog) {
- Navigator.pop(context);
- }
- LiveInformation().announcementModule.queueAnnounceByAudioName(
- displayText: busStop.formattedStopName,
- audioNames: [busStop.getAudioFileName()],
- );
- },
- )
- );
- } else {
- announcements.add(
- ShadButton(
- text: SizedBox(
- width: 200-42,
- child: Text(
- busStop.formattedStopName,
- overflow: TextOverflow.ellipsis,
- ),
- ),
- onPressed: () {
- if (closeDialogWidget.closeDialog) {
- Navigator.pop(context);
- }
- LiveInformation().announcementModule.queueAnnounceByAudioName(
- displayText: busStop.formattedStopName,
- audioNames: [busStop.getAudioFileName()],
- );
- },
- )
- );
- }
- }
-
- ScrollController controller = ScrollController();
-
- // Scroll to the current bus stop
- WidgetsBinding.instance!.addPostFrameCallback((_) {
-
- double offset = (info.getRouteVariant()!.busStops.indexOf(info.trackerModule.nearestStop!) * 50);
-
- // Offset the offset so that its in the middle of the screen
- offset -= (MediaQuery.of(context).size.height / 2) - 25;
-
- // controller.jumpTo(offset);
- });
-
return ShadSheet(
padding: const EdgeInsets.all(0),
@@ -297,8 +247,8 @@ class _ArcDashboardState extends State {
Row(
children: [
Text(
- "Bus Stops",
- style: ShadTheme.of(context).textTheme.h3
+ "Manual Ann'",
+ style: ShadTheme.of(context).textTheme.h3
),
SizedBox(
width: 16,
@@ -324,16 +274,14 @@ class _ArcDashboardState extends State {
child: Scrollbar(
thumbVisibility: true,
- controller: controller,
child: SingleChildScrollView(
reverse: true,
- controller: controller,
child: Container(
margin: const EdgeInsets.fromLTRB(
- 0,
- 10,
- 10,
- 10
+ 0,
+ 10,
+ 10,
+ 10
),
child: Column(
children: announcements.reversed.toList(),
@@ -347,108 +295,272 @@ class _ArcDashboardState extends State {
),
);
}
- );
+ );
- },
- ),
- ],
+ },
+ ),
+ ShadButton(
+ text: const Text("Bus Stop Announcements"),
+ enabled: LiveInformation().getRouteVariant() != null,
+ width: double.infinity,
+ borderRadius: const BorderRadius.all(Radius.circular(10)),
+ onPressed: () {
+
+ showShadSheet(
+ context: context,
+ side: ShadSheetSide.left,
+
+ builder: (context) {
+
+ List announcements = [];
+
+ LiveInformation info = LiveInformation();
+
+ for (var busStop in info.getRouteVariant()!.busStops) {
+
+ if (info.trackerModule.nearestStop == busStop) {
+ announcements.add(
+ ShadButton(
+ text: SizedBox(
+ width: 200-42,
+ child: Text(
+ "-> ${busStop.formattedStopName}",
+ overflow: TextOverflow.ellipsis,
+ ),
+ ),
+ backgroundColor: Colors.amber,
+ onPressed: () {
+ if (closeDialogWidget.closeDialog) {
+ Navigator.pop(context);
+ }
+ LiveInformation().announcementModule.queueAnnounceByAudioName(
+ displayText: busStop.formattedStopName,
+ audioNames: [busStop.getAudioFileName()],
+ );
+ },
+ )
+ );
+ } else {
+ announcements.add(
+ ShadButton(
+ text: SizedBox(
+ width: 200-42,
+ child: Text(
+ busStop.formattedStopName,
+ overflow: TextOverflow.ellipsis,
+ ),
+ ),
+ onPressed: () {
+ if (closeDialogWidget.closeDialog) {
+ Navigator.pop(context);
+ }
+ LiveInformation().announcementModule.queueAnnounceByAudioName(
+ displayText: busStop.formattedStopName,
+ audioNames: [busStop.getAudioFileName()],
+ );
+ },
+ )
+ );
+ }
+ }
+
+ ScrollController controller = ScrollController();
+
+ // Scroll to the current bus stop
+ WidgetsBinding.instance!.addPostFrameCallback((_) {
+
+ double offset = (info.getRouteVariant()!.busStops.indexOf(info.trackerModule.nearestStop!) * 50);
+
+ // Offset the offset so that its in the middle of the screen
+ offset -= (MediaQuery.of(context).size.height / 2) - 25;
+
+ // controller.jumpTo(offset);
+ });
+
+ return ShadSheet(
+ padding: const EdgeInsets.all(0),
+
+ content: Container(
+ height: MediaQuery.of(context).size.height,
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ SizedBox(
+ width: 5,
+ ),
+ Container(
+ padding: const EdgeInsets.symmetric(
+ vertical: 10,
+ ),
+ alignment: Alignment.bottomCenter,
+ height: double.infinity,
+ width: 35,
+ child: RotatedBox(
+ quarterTurns: 3,
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Row(
+ children: [
+ Text(
+ "Bus Stops",
+ style: ShadTheme.of(context).textTheme.h3
+ ),
+ SizedBox(
+ width: 16,
+ ),
+ Container(
+ width: 1,
+ height: 200,
+ color: Colors.grey,
+ ),
+ ],
+ ),
+
+ closeDialogWidget
+
+ ],
+ ),
+ ),
+ ),
+ Container(
+
+ // width: 220,
+ height: MediaQuery.of(context).size.height,
+
+ child: Scrollbar(
+ thumbVisibility: true,
+ controller: controller,
+ child: SingleChildScrollView(
+ reverse: true,
+ controller: controller,
+ child: Container(
+ margin: const EdgeInsets.fromLTRB(
+ 0,
+ 10,
+ 10,
+ 10
+ ),
+ child: Column(
+ children: announcements.reversed.toList(),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+ );
+
+ },
+ ),
+ ],
+ )
)
+ ),
+
+ const ShadButton(
+ icon: Icon(Icons.stop),
+ width: double.infinity,
+ borderRadius: BorderRadius.all(Radius.circular(10)),
+ ),
+
+ ShadButton(
+ // text: const Text("Announce Destination"),
+ icon: const Icon(Icons.bus_alert),
+ width: double.infinity,
+ borderRadius: const BorderRadius.all(Radius.circular(10)),
+ onPressed: () {
+ LiveInformation info = LiveInformation();
+
+ BusRouteVariant? routeVariant = info.getRouteVariant();
+
+ if (routeVariant != null) {
+ info.announcementModule.queueAnnouncementByRouteVariant(
+ routeVariant: routeVariant,
+ sendToServer: ModalRoute.of(context)!.settings.name!.contains("multi")
+ );
+ }
+
+ },
+ ),
+
+
+
+ ],
+ ),
+ ),
+
+ ),
+
+ Expanded(
+ child: Container(
+
+ decoration: const BoxDecoration(
+ color: Colors.black,
+ borderRadius: BorderRadius.all(Radius.circular(10)),
+
+ ),
+
+ margin: const EdgeInsets.all(10),
+ padding: const EdgeInsets.all(10),
+
+ width: double.infinity,
+ height: double.infinity,
+
+ child: Stack(
+ children: [
+ Container(
+
+ alignment: Alignment.center,
+
+ child: ibus_display(
+ hasBorder: false,
+ ),
+
+ ),
+ Container(
+
+ alignment: Alignment.bottomRight,
+
+ child: ShadButton.ghost(
+ icon: const Icon(Icons.fullscreen),
+ padding: const EdgeInsets.all(8),
+ onPressed: () {
+ Navigator.pushNamed(context, '/display');
+ },
+ ),
+
+ ),
+ Container(
+
+ alignment: Alignment.bottomLeft,
+
+ child: ShadButton.ghost(
+ icon: const Icon(Icons.arrow_back),
+ padding: const EdgeInsets.all(8),
+ onPressed: () {
+ Navigator.popUntil(context, (route) {
+ return route.settings.name == '/multi' || route.settings.name == '/routes';
+ });
+ },
+ ),
+
)
- ),
-
- const ShadButton(
- icon: Icon(Icons.stop),
- width: double.infinity,
- ),
-
- ShadButton(
- // text: const Text("Announce Destination"),
- icon: const Icon(Icons.location_on),
- width: double.infinity,
- borderRadius: const BorderRadius.all(Radius.circular(10)),
- onPressed: () {
- LiveInformation info = LiveInformation();
-
- BusRouteVariant? routeVariant = info.getRouteVariant();
-
- if (routeVariant != null) {
- info.announcementModule.queueAnnouncementByRouteVariant(
- routeVariant: routeVariant,
- sendToServer: false
- );
- }
-
- },
- )
-
- ],
+ ],
+ ),
),
- ),
+ )
- ),
-
- Expanded(
- child: Container(
-
- decoration: const BoxDecoration(
- color: Colors.black,
- borderRadius: BorderRadius.all(Radius.circular(10)),
-
- ),
-
- margin: const EdgeInsets.all(10),
- padding: const EdgeInsets.all(10),
-
- width: double.infinity,
- height: double.infinity,
-
- child: Stack(
- children: [
- Container(
-
- alignment: Alignment.center,
-
- child: ibus_display(
- hasBorder: false,
- ),
-
- ),
- Container(
-
- alignment: Alignment.bottomRight,
-
- child: ShadButton.ghost(
- icon: const Icon(Icons.fullscreen),
- padding: const EdgeInsets.all(8),
- onPressed: () {
- Navigator.pushNamed(context, '/display');
- },
- ),
-
- ),
- Container(
-
- alignment: Alignment.bottomLeft,
-
- child: ShadButton.ghost(
- icon: const Icon(Icons.arrow_back),
- padding: const EdgeInsets.all(8),
- onPressed: () {
- Navigator.pop(context);
- },
- ),
-
- )
- ],
- ),
- ),
- )
-
- ],
+ ],
+ ),
),
- ),
+ ),
);
}
diff --git a/lib/remaster/InitialStartup.dart b/lib/remaster/InitialStartup.dart
index 6cb0b1c..e3fc045 100644
--- a/lib/remaster/InitialStartup.dart
+++ b/lib/remaster/InitialStartup.dart
@@ -154,7 +154,7 @@ class _page2State extends State<_page2> {
child: SizedBox(
- width: double.infinity,
+ // width: double.infinity,
child: Column(
@@ -162,7 +162,6 @@ class _page2State extends State<_page2> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
-
Text(
"Permissions",
style: TextStyle(
@@ -175,105 +174,172 @@ class _page2State extends State<_page2> {
height: 16,
),
- ShadCard(
- width: double.infinity,
- title: Text(
- "Location",
- ),
- description: Text(
- "Your location is required for automatically updating your nearest bus stop."
- ),
- content: Container(
- child: Column(
- children: [
+ SingleChildScrollView(
+ scrollDirection: Axis.horizontal,
+ child: Row(
- SizedBox(
- height: 4,
+ // mainAxisSize: MainAxisSize.min,
+ // crossAxisAlignment: CrossAxisAlignment.start,
+
+ children: [
+ ShadCard(
+ width: 300,
+ height: 200,
+ title: Text(
+ "Location",
),
-
- FutureBuilder(
- future: Permission.location.isGranted,
- builder: (context, val) {
- bool isEnabled = true;
- String text = "Request permission";
- Color color = Colors.white;
-
- if (val.hasData) {
- isEnabled = !val.data!;
- }
- if (!isEnabled) {
- text = "Permission granted!";
- color = Colors.green.shade400;
- }
-
- return ShadButton(
- text: Text(text),
- onPressed: () async {
- await Permission.location.request();
- setState(() {
-
- });
- },
- enabled: isEnabled,
- backgroundColor: color,
- );
- },
+ description: Text(
+ "Your location is required for automatically updating your nearest bus stop."
),
- ],
- ),
- ),
- ),
+ content: Container(
+ child: Column(
+ children: [
- SizedBox(
- height: 16,
- ),
+ SizedBox(
+ height: 4,
+ ),
- ShadCard(
- width: double.infinity,
- title: Text(
- "Storage",
- ),
- description: Text(
- "Storage access is required to access recorded announcements."
- ),
- content: Container(
- child: Column(
- children: [
+ FutureBuilder(
+ future: Permission.location.isGranted,
+ builder: (context, val) {
+ bool isEnabled = true;
+ String text = "Request permission";
+ Color color = Colors.white;
- SizedBox(
- height: 4,
+ if (val.hasData) {
+ isEnabled = !val.data!;
+ }
+ if (!isEnabled) {
+ text = "Permission granted!";
+ color = Colors.green.shade400;
+ }
+
+ return ShadButton(
+ text: Text(text),
+ onPressed: () async {
+ await Permission.location.request();
+ setState(() {
+
+ });
+ },
+ enabled: isEnabled,
+ backgroundColor: color,
+ );
+ },
+ ),
+ ],
+ ),
),
+ ),
- FutureBuilder(
- future: Permission.manageExternalStorage.isGranted,
- builder: (context, val) {
- bool isEnabled = true;
- String text = "Request permission";
- Color color = Colors.white;
+ SizedBox(
+ width: 16,
+ ),
- if (val.hasData) {
- isEnabled = !val.data!;
- }
- if (!isEnabled) {
- text = "Permission granted!";
- color = Colors.green.shade400;
- }
+ ShadCard(
+ width: 300,
+ height: 200,
+ title: Text(
+ "Storage",
+ ),
+ description: Text(
+ "Storage access is required to access recorded announcements."
+ ),
+ content: Container(
+ child: Column(
+ children: [
- return ShadButton(
- text: Text(text),
- onPressed: () async {
- await Permission.manageExternalStorage.request();
- setState(() {
+ SizedBox(
+ height: 4,
+ ),
- });
- },
- enabled: isEnabled,
- backgroundColor: color,
- );
- },
- )
- ],
- ),
+ FutureBuilder(
+ future: Permission.manageExternalStorage.isGranted,
+ builder: (context, val) {
+ bool isEnabled = true;
+ String text = "Request permission";
+ Color color = Colors.white;
+
+ if (val.hasData) {
+ isEnabled = !val.data!;
+ }
+ if (!isEnabled) {
+ text = "Permission granted!";
+ color = Colors.green.shade400;
+ }
+
+ return ShadButton(
+ text: Text(text),
+ onPressed: () async {
+ await Permission.manageExternalStorage.request();
+ setState(() {
+
+ });
+ },
+ enabled: isEnabled,
+ backgroundColor: color,
+ );
+ },
+ )
+ ],
+ ),
+ ),
+ ),
+
+ SizedBox(
+ width: 16,
+ ),
+
+ ShadCard(
+ width: 300,
+ height: 200,
+ title: Text(
+ "Network",
+ ),
+ description: Text(
+ "Network access is required for commincation between devices for multi mode."
+ ),
+ content: Container(
+ child: Column(
+ children: [
+
+ SizedBox(
+ height: 4,
+ ),
+
+ FutureBuilder(
+ future: Permission.nearbyWifiDevices.isGranted,
+ builder: (context, val) {
+ bool isEnabled = true;
+ String text = "Request permission";
+ Color color = Colors.white;
+
+ if (val.hasData) {
+ isEnabled = !val.data!;
+ }
+ if (!isEnabled) {
+ text = "Permission granted!";
+ color = Colors.green.shade400;
+ }
+
+ return ShadButton(
+ text: Text(text),
+ onPressed: () async {
+ await Permission.manageExternalStorage.request();
+ setState(() {
+
+ });
+ },
+ enabled: isEnabled,
+ backgroundColor: color,
+ );
+ },
+ )
+ ],
+ ),
+ ),
+ ),
+ ],
),
),
@@ -306,7 +372,6 @@ class _page2State extends State<_page2> {
);
},
)
-
],
),
)
diff --git a/lib/remaster/RemasteredMain.dart b/lib/remaster/RemasteredMain.dart
index d2709bc..75eeb75 100644
--- a/lib/remaster/RemasteredMain.dart
+++ b/lib/remaster/RemasteredMain.dart
@@ -4,15 +4,40 @@
import 'package:bus_infotainment/pages/tfl_dataset_test.dart';
import 'package:bus_infotainment/remaster/DashboardArc.dart';
import 'package:bus_infotainment/remaster/InitialStartup.dart';
+import 'package:bus_infotainment/remaster/SearchArc.dart';
import 'package:bus_infotainment/remaster/dashboard.dart';
import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
+import 'package:window_manager/window_manager.dart';
+
+import 'WebSocketTest.dart';
class RemasteredApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
- // TODO: implement build
+
+ // Force landscape mode
+ SystemChrome.setPreferredOrientations([
+ DeviceOrientation.landscapeLeft,
+ DeviceOrientation.landscapeRight,
+ ]);
+
+ // Hide navigation bar and status bar
+ SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive, overlays: [
+ SystemUiOverlay.bottom,
+ SystemUiOverlay.top,
+ ]);
+
+ // Hide the gesture navigation bar
+ SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky, overlays: [
+ SystemUiOverlay.bottom,
+ SystemUiOverlay.top,
+ ]);
+
+
+
return ShadApp(
darkTheme: ShadThemeData(
brightness: Brightness.dark,
@@ -20,16 +45,22 @@ class RemasteredApp extends StatelessWidget {
background: Colors.grey.shade900,
primary: Colors.grey.shade50,
primaryForeground: Colors.grey.shade900,
- border: Colors.grey.shade900,
+ border: Colors.grey.shade400,
+ input: Colors.grey.shade400,
),
- // force dark mode
),
themeMode: ThemeMode.dark,
+ // remove debug banner
+ debugShowCheckedModeBanner: false,
+
routes: {
'/setup': (context) => InitialStartup(),
'/': (context) => HomePage_Re(),
- '/routes': (context) => RoutePage(),
+
+ '/routes': (context) => SearchArc(),
+ '/multi/routes': (context) => RoutePage(),
+
'/enroute': (context) => ArcDashboard(),
'/legacy': (context) => TfL_Dataset_Test(),
'/multi': (context) => MultiModeSetup(),
@@ -38,6 +69,7 @@ class RemasteredApp extends StatelessWidget {
'/multi/register': (context) => MultiModeRegister(),
'/display': (context) => FullscreenDisplay(),
'/multi/join': (context) => MultiModeJoin(),
+ '/websocket': (context) => WebSocketWidget(),
},
diff --git a/lib/remaster/SearchArc.dart b/lib/remaster/SearchArc.dart
new file mode 100644
index 0000000..1d6130b
--- /dev/null
+++ b/lib/remaster/SearchArc.dart
@@ -0,0 +1,346 @@
+
+
+import 'package:bus_infotainment/backend/live_information.dart';
+import 'package:bus_infotainment/remaster/dashboard.dart';
+import 'package:bus_infotainment/tfl_datasets.dart';
+import 'package:bus_infotainment/utils/OrdinanceSurveyUtils.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart' hide NavigationBar;
+import 'package:flutter/widgets.dart';
+import 'package:geolocator/geolocator.dart';
+import 'package:shadcn_ui/shadcn_ui.dart';
+import 'package:vector_math/vector_math.dart' hide Colors;
+
+import '../backend/modules/tube_info.dart';
+
+class SearchArc extends StatefulWidget {
+
+ @override
+ State createState() => _SearchArcState();
+}
+
+class _SearchArcState extends State {
+ TextEditingController searchController = TextEditingController();
+
+ @override
+ Widget build(BuildContext context) {
+
+ List nearbyRoutes = _getNearbyRoutes(context);
+ List pastRoutes = _getPastRoutes(context);
+
+ List routeCards = [];
+
+ if (searchController.text.isNotEmpty) {
+ // Detect if the search query is a route number
+ // Examples of route numbers: 1, A1, 1A, A1A ...
+ // If it isnt a route number, it is a stop name
+ // Examples of stop names: "Euston Station", "Baker Street", "Kings Cross"
+ bool containsNumber = RegExp(r'\d').hasMatch(searchController.text);
+
+ List searchResults = [];
+
+ // Loop through all bus routes
+ for (BusRoute route in LiveInformation().busSequences.routes.values) {
+ if (containsNumber) {
+ if (route.routeNumber.contains(searchController.text) && !searchResults.contains(route)) {
+ routeCards.add(_getRouteCard(context, route));
+ searchResults.add(route);
+ }
+ } else {
+ for (BusRouteVariant variant in route.routeVariants.values) {
+ for (BusRouteStop stop in variant.busStops) {
+ if (stop.formattedStopName.toLowerCase().contains(
+ searchController.text.toLowerCase()) && !searchResults.contains(route)) {
+ routeCards.add(_getRouteCard(context, route));
+ searchResults.add(route);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (routeCards.isEmpty){
+ routeCards.add(
+ Text("No results found", style: ShadTheme.of(context).textTheme.h3,)
+ );
+ }
+
+ }
+
+ return Scaffold(
+ body: Row(
+ children: [
+ Expanded(
+ child: Container(
+ padding: EdgeInsets.fromLTRB(32, 16, 32, 0),
+ height: MediaQuery.of(context).size.height,
+ child: Column(
+ mainAxisSize: MainAxisSize.max,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ ShadInput(
+ placeholder: Text("Search for route or stop (Can be laggy)"),
+ controller: searchController,
+ onChanged: (value) {
+ setState(() {
+ // Update search results
+ });
+ },
+ ),
+ SizedBox(height: 8),
+ if (routeCards.isNotEmpty)
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ "Search results",
+ style: ShadTheme.of(context).textTheme.h3,
+ ),
+ SizedBox(height: 10),
+ Expanded(
+ child: GridView.extent(
+ shrinkWrap: true,
+ maxCrossAxisExtent: 100,
+ scrollDirection: Axis.vertical,
+ crossAxisSpacing: 8,
+ mainAxisSpacing: 8,
+ children: routeCards,
+ ),
+ ),
+ ],
+ ),
+ )
+ else
+ Expanded(
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Text(
+ "Nearby routes",
+ style: ShadTheme.of(context).textTheme.h3,
+ ),
+ SizedBox(height: 10),
+ Expanded(
+ child: GridView.extent(
+ shrinkWrap: true,
+ maxCrossAxisExtent: 100,
+ scrollDirection: Axis.vertical,
+ crossAxisSpacing: 8,
+ mainAxisSpacing: 8,
+ children: _getNearbyRoutes(context, multiMode: true),
+ ),
+ ),
+ ],
+ ),
+ )
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+
+ Container(
+ width: 2,
+ color: Colors.grey.shade300,
+ ),
+
+ RotatedBox(
+ quarterTurns: 3,
+ child: NavigationBar(),
+ )
+ ],
+ ),
+ );
+ }
+}
+
+List _getNearbyRoutes(context, {bool multiMode = false}) {
+
+ print("Getting nearby routes");
+
+ LiveInformation liveInformation = LiveInformation();
+ BusSequences busSequences = liveInformation.busSequences;
+
+ List nearbyRoutes = [];
+
+ Position? currentLocation = liveInformation.trackerModule.position;
+
+ Vector2 currentVector = Vector2(0, 0);
+
+ if (currentLocation == null && !kDebugMode) {
+ return [];
+ } else if (currentLocation != null){
+ currentVector = OSGrid.toNorthingEasting(currentLocation!.latitude, currentLocation.longitude);
+ }
+
+
+
+ if (kDebugMode) {
+ currentVector = OSGrid.toNorthingEasting(51.583781262560926, -0.020359583104595073);
+ }
+
+ for (BusRoute route in busSequences.routes.values) {
+ for (BusRouteVariant variant in route.routeVariants.values) {
+ for (BusRouteStop stop in variant.busStops) {
+
+ Vector2 stopVector = Vector2(stop.easting.toDouble(), stop.northing.toDouble());
+
+ double distance = currentVector.distanceTo(stopVector);
+
+ if (distance < 1000) {
+ nearbyRoutes.add(route);
+ break;
+ }
+ }
+ if (nearbyRoutes.contains(route)) {
+ break;
+ }
+ }
+ if (nearbyRoutes.contains(route)) {
+ continue;
+ }
+ }
+
+ List routeCards = [];
+
+ for (BusRoute route in nearbyRoutes) {
+ routeCards.add(_getRouteCard(context, route, multiMode: multiMode));
+ }
+
+ return routeCards;
+
+}
+
+Widget _getRouteCard(context, BusRoute route, {bool multiMode = false}) {
+
+ String rr = "";
+
+ if (route.routeNumber.toLowerCase().startsWith("ul")) {
+
+ rr = "Rail replacement";
+
+ TubeLine? line = LiveInformation().tubeStations.getClosestLine(route.routeVariants.values.first);
+
+ rr = line?.name ?? rr;
+
+ if (!["London Overground", "DLR", "Rail replacement", "Elizabeth Line"].contains(rr)) {
+ rr += " line";
+ }
+ if (rr == "Hammersmith and City line") {
+ rr = "Hammersmith & City";
+ }
+
+ }
+
+ return ElevatedButton(
+ onPressed: () {
+ showShadSheet(
+ side: ShadSheetSide.right,
+ context: context,
+ builder: (context) {
+
+ List variantWidgets = [];
+
+ for (BusRouteVariant variant in route.routeVariants.values) {
+ variantWidgets.add(
+ ShadButton.outline(
+ text: SizedBox(
+
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text("${variant.busStops.first.formattedStopName} ->"),
+ const SizedBox(
+ height: 2,
+ ),
+ Text(variant.busStops.last.formattedStopName)
+ ],
+ ),
+ ),
+ width: double.infinity,
+ height: 50,
+ padding: const EdgeInsets.all(8),
+ onPressed: () async {
+ LiveInformation liveInformation = LiveInformation();
+ await liveInformation.setRouteVariant(variant);
+
+ if (!multiMode) {
+ Navigator.popAndPushNamed(context, "/enroute");
+ } else {
+ Navigator.popAndPushNamed(context, "/multi/enroute");
+ }
+
+ },
+ )
+ );
+
+ variantWidgets.add(const SizedBox(
+ height: 4,
+ ));
+ }
+
+ return ShadSheet(
+ title: Text("Route ${route.routeNumber} - Variants"),
+
+ content: Container(
+ width: 300,
+ height: MediaQuery.of(context).size.height - 52,
+ child: Scrollbar(
+ thumbVisibility: true,
+ child: ListView(
+ shrinkWrap: true,
+ children: [
+ ...variantWidgets
+ ],
+ ),
+ ),
+ ),
+ padding: const EdgeInsets.all(8),
+ );
+
+
+ }
+
+
+ );
+ },
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+
+ Text(
+ "Route \n ${route.routeNumber}",
+ style: ShadTheme.of(context).textTheme.h4.copyWith(
+ height: 1.1
+ ),
+ textAlign: TextAlign.center,
+ ),
+ if (route.routeNumber.toLowerCase().startsWith("ul"))
+ Text(rr, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w400, height: 1), textAlign: TextAlign.center,)
+
+
+ ],
+ ),
+
+ style: ElevatedButton.styleFrom(
+ padding: EdgeInsets.all(10),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(10),
+ ),
+ ),
+ );;
+}
+
+List _getPastRoutes(context) {
+ return [];
+}
\ No newline at end of file
diff --git a/lib/remaster/WebSocketTest.dart b/lib/remaster/WebSocketTest.dart
new file mode 100644
index 0000000..8430d20
--- /dev/null
+++ b/lib/remaster/WebSocketTest.dart
@@ -0,0 +1,189 @@
+import 'dart:async';
+import 'dart:isolate';
+import 'dart:io';
+
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:web_socket_channel/web_socket_channel.dart';
+import 'package:web_socket_channel/status.dart' as status;
+import 'package:network_info_plus/network_info_plus.dart';
+import 'package:flutter/foundation.dart' show kIsWeb;
+
+import '../utils/web_socket_server.dart';
+
+class WebSocketWidget extends StatefulWidget {
+ @override
+ _WebSocketWidgetState createState() => _WebSocketWidgetState();
+}
+
+class _WebSocketWidgetState extends State {
+ WebSocketChannel? _channel;
+ TextEditingController _controller = TextEditingController();
+ TextEditingController _urlController = TextEditingController(text: 'ws://localhost:8080');
+ List _messages = [];
+ Isolate? _serverIsolate;
+ bool _isServerRunning = false;
+ String? _localIpAddress;
+
+ @override
+ void initState() {
+ super.initState();
+ _getLocalIpAddress().then((ip) {
+ setState(() {
+ _localIpAddress = ip;
+ _urlController.text = 'ws://$_localIpAddress:8080';
+ });
+ });
+ }
+
+ Future _getLocalIpAddress() async {
+ if (kIsWeb) {
+ return '127.0.0.1'; // Web does not support getting the local IP address
+ }
+
+ if (Platform.isAndroid || Platform.isIOS) {
+ final info = NetworkInfo();
+ String? ip = await info.getWifiIP();
+ return ip ?? '127.0.0.1'; // Fallback to localhost if no IP is found
+ }
+
+ for (var interface in await NetworkInterface.list()) {
+ for (var addr in interface.addresses) {
+ if (addr.type == InternetAddressType.IPv4 && !addr.isLoopback) {
+ return addr.address;
+ }
+ }
+ }
+ return '127.0.0.1'; // Fallback to localhost if no IP is found
+ }
+
+ void _startServer() async {
+ final receivePort = ReceivePort();
+ _serverIsolate = await Isolate.spawn(webSocketServer, receivePort.sendPort);
+ receivePort.listen((message) {
+ print(message);
+ setState(() {
+ _isServerRunning = true;
+ });
+ });
+ }
+
+ void _stopServer() {
+ _serverIsolate?.kill(priority: Isolate.immediate);
+ setState(() {
+ _isServerRunning = false;
+ });
+ }
+
+ void _joinWebSocket() {
+ if (_channel != null) {
+ _channel!.sink.close();
+ }
+ setState(() {
+ _channel = WebSocketChannel.connect(Uri.parse(_urlController.text));
+ _channel!.stream.listen((message) {
+ setState(() {
+ _messages.add(message);
+ });
+ });
+ });
+ }
+
+ void _sendMessage(String message) {
+ if (_channel != null) {
+ _channel!.sink.add(message);
+ setState(() {
+ _messages.add('You: $message'); // Display the sent message
+ });
+ }
+ }
+
+ void _closeConnection() {
+ if (_channel != null) {
+ _channel!.sink.close(status.goingAway);
+ setState(() {
+ _channel = null;
+ _messages.clear();
+ });
+ }
+ }
+
+ @override
+ void dispose() {
+ _closeConnection();
+ _stopServer();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+
+ // force portrait mode
+ SystemChrome.setPreferredOrientations([
+ DeviceOrientation.portraitUp,
+ DeviceOrientation.portraitDown,
+ ]);
+
+ return Scaffold(
+ appBar: AppBar(
+ title: Text('WebSocket Demo'),
+ actions: [
+ IconButton(
+ icon: Icon(Icons.close),
+ onPressed: _closeConnection,
+ ),
+ ],
+ ),
+ body: Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Column(
+ children: [
+ if (!_isServerRunning)
+ ElevatedButton(
+ onPressed: _startServer,
+ child: Text('Start WebSocket Server'),
+ )
+ else
+ ElevatedButton(
+ onPressed: _stopServer,
+ child: Text('Stop WebSocket Server'),
+ ),
+ SizedBox(height: 10),
+ if (_localIpAddress != null)
+ Text('Server URL: ws://$_localIpAddress:8080'),
+ SizedBox(height: 10),
+ TextField(
+ controller: _urlController,
+ decoration: InputDecoration(labelText: 'WebSocket URL'),
+ onSubmitted: (value) => _joinWebSocket(),
+ ),
+ SizedBox(height: 10),
+ ElevatedButton(
+ onPressed: _joinWebSocket,
+ child: Text('Join WebSocket'),
+ ),
+ SizedBox(height: 10),
+ Expanded(
+ child: ListView.builder(
+ itemCount: _messages.length,
+ itemBuilder: (context, index) {
+ return ListTile(
+ title: Text(_messages[index]),
+ );
+ },
+ ),
+ ),
+ TextField(
+ controller: _controller,
+ decoration: InputDecoration(labelText: 'Send a message'),
+ onSubmitted: (text) {
+ _sendMessage(text);
+ _controller.clear();
+ },
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/remaster/dashboard.dart b/lib/remaster/dashboard.dart
index 2547c3e..8ecad6f 100644
--- a/lib/remaster/dashboard.dart
+++ b/lib/remaster/dashboard.dart
@@ -22,6 +22,7 @@ import 'package:flutter_carousel_widget/flutter_carousel_widget.dart';
import 'package:geolocator/geolocator.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
+import 'package:shared_preferences/shared_preferences.dart';
import 'package:uuid/uuid.dart';
import 'package:vector_math/vector_math.dart' hide Colors;
@@ -65,7 +66,7 @@ class _HomePage_ReState extends State {
print("Bundle: $shouldRedirectB");
print("Permissions_indv: $perms");
- return !shouldRedirectA || !shouldRedirectB;
+ return (!shouldRedirectA || !shouldRedirectB);
}
@@ -84,105 +85,261 @@ class _HomePage_ReState extends State {
@override
Widget build(BuildContext context) {
+
return Scaffold(
body: Container(
padding: const EdgeInsets.all(16),
-
alignment: Alignment.center,
- child: SizedBox(
+ child: Column(
- width: double.infinity,
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
- child: Column(
+ children: [
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
+ Text(
+ "Choose mode:",
+ style: ShadTheme.of(context).textTheme.h1.copyWith(),
+ ),
- children: [
+ SizedBox(
+ height: 16,
+ ),
- const Text(
- "Choose mode:",
- style: TextStyle(
- fontSize: 32,
- fontWeight: FontWeight.w600,
- )
- ),
-
- const SizedBox(
- height: 16,
- ),
-
- ShadCard(
- title: const Text("Solo mode"),
- width: double.infinity,
- description: const Text(
- "Choose this mode if you are only using this device. (No internet required)"
- ),
- content: Column(
-
- crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisSize: MainAxisSize.min,
-
- children: [
-
- const SizedBox(
- height: 4,
+ SingleChildScrollView(
+ scrollDirection: Axis.horizontal,
+ child: Row(
+
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+
+ children: [
+
+ ShadCard(
+ title: const Text("Solo mode"),
+ width: 300,
+ description: const Text(
+ "Choose this mode if you are only using this device. (No internet required)"
),
-
- ShadButton.secondary(
- onPressed: () {
- Navigator.pushNamed(context, "/routes");
- },
- text: const Text("Continue"),
- )
-
- ],
- ),
- ),
-
- const SizedBox(
- height: 16,
- ),
-
- ShadCard(
- title: const Text("Multi mode"),
- width: double.infinity,
- description: const Text(
- "Choose this mode if you are using multiple devices. (Internet required)"
- ),
- content: Column(
-
- crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisSize: MainAxisSize.min,
-
- children: [
-
- const SizedBox(
- height: 4,
+ content: Column(
+
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
+
+ children: [
+
+ const SizedBox(
+ height: 4,
+ ),
+
+ ShadButton.secondary(
+ onPressed: () {
+ Navigator.pushNamed(context, "/routes");
+ },
+ text: const Text("Continue"),
+ )
+
+ ],
),
+ ),
+
+ SizedBox(
+ width: 16,
+ ),
+
+ ShadCard(
+ title: const Text("Multi mode"),
+ width: 300,
+ description: const Text(
+ "Choose this mode if you are using multiple devices. (Internet required)"
+ ),
+ content: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ const SizedBox(
+ height: 4,
+ ),
+ ShadButton.secondary(
+ onPressed: () {
+ Navigator.pushNamed(context, "/multi");
+ },
+ text: const Text("Continue"),
+ )
+ ],
+ ),
+ ),
+
+ SizedBox(
+ width: 16,
+ ),
+
+ ShadCard(
+ title: const Text("Setup"),
+ width: 300,
+ description: const Text(
+ "This button is only for debug mode. If you see this button in production, please contact support."
+ ),
+ content: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ const SizedBox(
+ height: 4,
+ ),
+ ShadButton.secondary(
+ onPressed: () async {
+ LiveInformation().announcementModule.setBundleBytes(null);
+ SharedPreferences prefs = await SharedPreferences.getInstance();
+ prefs.remove("AnnouncementsFileLocation");
+ Navigator.pushNamed(context, "/setup");
+ },
+ text: const Text("Continue"),
+ )
+ ],
+ ),
+ ),
- ShadButton.secondary(
- onPressed: () {
- Navigator.pushNamed(context, "/multi");
- },
- text: const Text("Continue"),
- )
+ ShadCard(
+ title: const Text("Websocket test"),
+ width: 300,
+ description: const Text(
+ "This button is only for debug mode. If you see this button in production, please contact support."
+ ),
+ content: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ const SizedBox(
+ height: 4,
+ ),
+ ShadButton.secondary(
+ onPressed: () {
+ Navigator.pushNamed(context, "/websocket");
+ },
+ text: const Text("Continue"),
+ )
+ ],
+ ),
+ )
+
+ ],
+ ),
+ )
- ],
- ),
- )
-
-
-
- ],
-
- ),
- )
+ ],
+ ),
),
);
+
+ // return Scaffold(
+ // body: Container(
+ //
+ // padding: const EdgeInsets.all(16),
+ //
+ // alignment: Alignment.center,
+ //
+ // child: SizedBox(
+ //
+ // // width: double.infinity,
+ // height: double.infinity,
+ //
+ // child: Column(
+ // children: [
+ //
+ // const Text(
+ // "Choose mode:",
+ // style: TextStyle(
+ // fontSize: 32,
+ // fontWeight: FontWeight.w600,
+ // )
+ // ),
+ //
+ // Row(
+ //
+ // mainAxisSize: MainAxisSize.min,
+ // crossAxisAlignment: CrossAxisAlignment.start,
+ //
+ // children: [
+ //
+ // Text("Test"),
+ //
+ // ShadCard(
+ // title: const Text("Solo mode"),
+ // width: double.infinity,
+ // description: const Text(
+ // "Choose this mode if you are only using this device. (No internet required)"
+ // ),
+ // content: Column(
+ //
+ // crossAxisAlignment: CrossAxisAlignment.start,
+ // mainAxisSize: MainAxisSize.min,
+ //
+ // children: [
+ //
+ // const SizedBox(
+ // height: 4,
+ // ),
+ //
+ // ShadButton.secondary(
+ // onPressed: () {
+ // Navigator.pushNamed(context, "/routes");
+ // },
+ // text: const Text("Continue"),
+ // )
+ //
+ // ],
+ // ),
+ // ),
+ //
+ // const SizedBox(
+ // width: 16,
+ // ),
+ //
+ // ShadCard(
+ // title: const Text("Multi mode"),
+ // width: double.infinity,
+ // description: const Text(
+ // "Choose this mode if you are using multiple devices. (Internet required)"
+ // ),
+ // content: Column(
+ //
+ // crossAxisAlignment: CrossAxisAlignment.start,
+ // mainAxisSize: MainAxisSize.min,
+ //
+ // children: [
+ //
+ // const SizedBox(
+ // height: 4,
+ // ),
+ //
+ // ShadButton.secondary(
+ // onPressed: () {
+ // Navigator.pushNamed(context, "/multi");
+ // },
+ // text: const Text("Continue"),
+ // )
+ //
+ // ],
+ // ),
+ // )
+ //
+ //
+ //
+ // ],
+ //
+ // ),
+ // ],
+ // ),
+ // )
+ //
+ // ),
+ // );
}
}
@@ -194,92 +351,210 @@ class RoutePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
-
-
return Scaffold(
- body: Column(
+ body: Row(
+
children: [
- Expanded(
- child: Container(
- padding: const EdgeInsets.all(16),
+ Container(
+ height: double.infinity,
- alignment: Alignment.center,
+ child: Row(
- child: SizedBox(
+ crossAxisAlignment: CrossAxisAlignment.end,
- width: double.infinity,
+ children: [
- child: Column(
-
- mainAxisSize: MainAxisSize.max,
- crossAxisAlignment: CrossAxisAlignment.start,
-
- children: [
-
- Row(
- children: [
-
- Text(
- "Routes",
- style: ShadTheme.of(context).textTheme.h1.copyWith(),
- ),
-
- Expanded(
- child: Container(),
- ),
-
- ],
+ Container(
+ padding: const EdgeInsets.fromLTRB(
+ 8,
+ 8,
+ 0,
+ 8
+ ),
+ child: RotatedBox(
+ quarterTurns: 3,
+ child: Text(
+ "Routes - Nearby",
+ style: ShadTheme.of(context).textTheme.h3.copyWith(
+ height: 0.8
),
- if (!kIsWeb)
- Text(
- "Nearby routes",
- style: ShadTheme.of(context).textTheme.h4,
+ ),
+ ),
+ ),
+
+ Container(
+ width: 230,
+ child: Scrollbar(
+ thumbVisibility: true,
+ child: GridView.count(
+ crossAxisCount: 2,
+ padding: const EdgeInsets.fromLTRB(
+ 4,
+ 4,
+ 4,
+ 4
),
- if (!kIsWeb)
- FlutterCarousel(
- options: CarouselOptions(
- // height: 130,
- viewportFraction: 0.33,
- aspectRatio: 3 / 1,
- enableInfiniteScroll: true,
- initialPage: 1,
- autoPlay: true,
- autoPlayInterval: const Duration(seconds: 2),
- pauseAutoPlayOnTouch: true,
- pauseAutoPlayOnManualNavigate: true,
- showIndicator: false,
- slideIndicator: const CircularSlideIndicator(),
- autoPlayAnimationDuration: const Duration(milliseconds: 800),
- autoPlayCurve: Curves.bounceOut,
-
-
- ),
- items: [
- ..._getNearbyRoutes()
- ],
- ),
-
- const Divider(),
-
- RouteSearch(multiMode: false)
-
-
- ],
-
+ shrinkWrap: true,
+ children: [
+ ..._getNearbyRoutes(
+ multiMode: ModalRoute.of(context)!.settings.name!.contains("multi")
+ )
+ ],
+ ),
),
)
-
+ ],
),
),
- const Divider(
- height: 1,
+
+ Container(
+ width: 2,
+ height: double.infinity,
+ color: Colors.grey.shade400,
),
- NavigationBar()
+
+ Expanded(
+ child: Container(
+ height: double.infinity,
+ child: Row(
+
+ crossAxisAlignment: CrossAxisAlignment.end,
+
+ children: [
+
+ Container(
+ padding: const EdgeInsets.fromLTRB(
+ 8,
+ 8,
+ 8,
+ 8
+ ),
+ child: RotatedBox(
+ quarterTurns: 3,
+ child: Text(
+ "Routes - All",
+ style: ShadTheme.of(context).textTheme.h3.copyWith(
+ height: 0.8
+ ),
+ ),
+ ),
+ ),
+
+ Expanded(
+ child: RouteSearch(
+ multiMode: ModalRoute.of(context)!.settings.name!.contains("multi"),
+ ),
+ ),
+
+ Container(
+ width: 2,
+ height: double.infinity,
+ color: Colors.grey.shade400,
+ ),
+
+ RotatedBox(
+ quarterTurns: 3,
+ child: NavigationBar()
+ )
+
+ ],
+ ),
+ ),
+ )
+
+
+
],
+
),
);
+
+ // return Scaffold(
+ // body: Column(
+ // children: [
+ // Expanded(
+ // child: Container(
+ //
+ // padding: const EdgeInsets.all(16),
+ //
+ // alignment: Alignment.center,
+ //
+ // child: SizedBox(
+ //
+ // width: double.infinity,
+ //
+ // child: Column(
+ //
+ // mainAxisSize: MainAxisSize.max,
+ // crossAxisAlignment: CrossAxisAlignment.start,
+ //
+ // children: [
+ //
+ // Row(
+ // children: [
+ //
+ // Text(
+ // "Routes",
+ // style: ShadTheme.of(context).textTheme.h1.copyWith(),
+ // ),
+ //
+ // Expanded(
+ // child: Container(),
+ // ),
+ //
+ // ],
+ // ),
+ // if (!kIsWeb)
+ // Text(
+ // "Nearby routes",
+ // style: ShadTheme.of(context).textTheme.h4,
+ // ),
+ // if (!kIsWeb)
+ // FlutterCarousel(
+ // options: CarouselOptions(
+ // // height: 130,
+ // viewportFraction: 0.33,
+ // aspectRatio: 3 / 1,
+ // enableInfiniteScroll: true,
+ // initialPage: 1,
+ // autoPlay: true,
+ // autoPlayInterval: const Duration(seconds: 2),
+ // pauseAutoPlayOnTouch: true,
+ // pauseAutoPlayOnManualNavigate: true,
+ // showIndicator: false,
+ // slideIndicator: const CircularSlideIndicator(),
+ // autoPlayAnimationDuration: const Duration(milliseconds: 800),
+ // autoPlayCurve: Curves.bounceOut,
+ //
+ //
+ // ),
+ // items: [
+ // ..._getNearbyRoutes()
+ // ],
+ // ),
+ //
+ // const Divider(),
+ //
+ // RouteSearch(multiMode: false)
+ //
+ //
+ // ],
+ //
+ // ),
+ // )
+ //
+ // ),
+ // ),
+ // const Divider(
+ // height: 1,
+ // ),
+ // NavigationBar()
+ // ],
+ // ),
+ // );
+
}
@@ -318,34 +593,42 @@ class _RouteSearchState extends State {
children: [
- ShadInput(
- placeholder: const Text("Search for a route..."),
- controller: controller,
- onChanged: (value) {
- setState(() {
+ Padding(
+ padding: const EdgeInsets.fromLTRB(
+ 4,
+ 4,
+ 4,
+ 0
+ ),
+ child: ShadInput(
+ placeholder: const Text("Search for a route..."),
+ controller: controller,
+ onChanged: (value) {
+ setState(() {
- });
- },
- ),
-
- const SizedBox(
- height: 4,
+ });
+ },
+ ),
),
Expanded(
child: Scrollbar(
- interactive: true,
- radius: const Radius.circular(8),
- thickness: 8,
thumbVisibility: true,
- child: GridView.count(
- crossAxisCount: 3,
+ trackVisibility: true,
+ scrollbarOrientation: ScrollbarOrientation.bottom,
+ child: GridView.extent(
+ // padding: const EdgeInsets.all(4),
+ scrollDirection: Axis.vertical,
+ maxCrossAxisExtent: 120,
children: [
...routes
],
- shrinkWrap: true,
),
),
+ ),
+
+ SizedBox(
+ height: 4,
)
],
@@ -397,111 +680,100 @@ class RouteCard extends StatelessWidget {
- return AspectRatio(
- aspectRatio: 1,
- child: Container(
- child: ShadButton.secondary(
- text: Column(
- children: [
- Text(
- "Route \n ${route.routeNumber}",
- style: ShadTheme.of(context).textTheme.h3.copyWith(
- height: 1.1
- )
- ),
- if (route.routeNumber.toLowerCase().startsWith("ul"))
- Text(rr, style: const TextStyle(fontSize: 8))
- ],
+ return ShadButton.secondary(
+ text: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Text(
+ "Route \n ${route.routeNumber}",
+ style: ShadTheme.of(context).textTheme.h3.copyWith(
+ height: 1.1
+ )
),
- padding: const EdgeInsets.all(8),
- width: 105,
- height: 105,
+ if (route.routeNumber.toLowerCase().startsWith("ul"))
+ Text(rr, style: const TextStyle(fontSize: 8))
+ ],
+ ),
+ padding: const EdgeInsets.all(8),
+ width: 100,
+ height: 100,
+ size: ShadButtonSize.icon,
- onPressed: () {
- showShadSheet(
- side: ShadSheetSide.bottom,
- context: context,
- builder: (context) {
+ onPressed: () {
+ showShadSheet(
+ side: ShadSheetSide.right,
+ context: context,
+ builder: (context) {
- List variantWidgets = [];
+ List variantWidgets = [];
- for (BusRouteVariant variant in route.routeVariants.values) {
- variantWidgets.add(
- ShadButton.outline(
- text: SizedBox(
- width: 800-490,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text("${variant.busStops.first.formattedStopName} ->"),
- const SizedBox(
- height: 2,
- ),
- Text(variant.busStops.last.formattedStopName)
- ],
+ for (BusRouteVariant variant in route.routeVariants.values) {
+ variantWidgets.add(
+ ShadButton.outline(
+ text: SizedBox(
+ width: 800-490,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text("${variant.busStops.first.formattedStopName} ->"),
+ const SizedBox(
+ height: 2,
),
- ),
- width: double.infinity,
- height: 50,
- padding: const EdgeInsets.all(8),
- onPressed: () async {
- LiveInformation liveInformation = LiveInformation();
- await liveInformation.setRouteVariant(variant);
-
- if (!multiMode) {
- Navigator.popAndPushNamed(context, "/enroute");
- } else {
- // Navigator.popAndPushNamed(context, "/multi/enroute");
- Navigator.pop(context);
- ShadToaster.of(context).show(
- ShadToast(
- title: Text("Route selected"),
- description: Text("Set route to ${variant.busRoute.routeNumber} - ${variant.busStops.first.formattedStopName} -> ${variant.busStops.last.formattedStopName}"),
- duration: Duration(seconds: 5),
- )
- );
- }
-
- },
- )
- );
-
- variantWidgets.add(const SizedBox(
- height: 4,
- ));
- }
-
- return ShadSheet(
- title: Text("Route ${route.routeNumber} - Variants"),
-
- content: Container(
- width: 2000,
- constraints: const BoxConstraints(
- maxHeight: 400
- ),
- alignment: Alignment.center,
- child: Scrollbar(
- thumbVisibility: true,
- child: SingleChildScrollView(
- child: Column(
- children: [
- ...variantWidgets
- ],
- ),
+ Text(variant.busStops.last.formattedStopName)
+ ],
),
),
+ width: double.infinity,
+ height: 50,
+ padding: const EdgeInsets.all(8),
+ onPressed: () async {
+ LiveInformation liveInformation = LiveInformation();
+ await liveInformation.setRouteVariant(variant);
+
+ if (!multiMode) {
+ Navigator.popAndPushNamed(context, "/enroute");
+ } else {
+ Navigator.popAndPushNamed(context, "/multi/enroute");
+ }
+
+ },
+ )
+ );
+
+ variantWidgets.add(const SizedBox(
+ height: 4,
+ ));
+ }
+
+ return ShadSheet(
+ title: Text("Route ${route.routeNumber} - Variants"),
+
+ content: Container(
+ width: 350,
+ constraints: const BoxConstraints(
+ maxHeight: 400
+ ),
+ alignment: Alignment.center,
+ child: Scrollbar(
+ thumbVisibility: true,
+ child: SingleChildScrollView(
+ child: Column(
+ children: [
+ ...variantWidgets
+ ],
+ ),
),
- padding: const EdgeInsets.all(8),
- );
+ ),
+ ),
+ padding: const EdgeInsets.all(8),
+ );
- }
+ }
- );
- },
- ),
- ),
+ );
+ },
);
}
@@ -826,89 +1098,96 @@ class _MultiModeSetupState extends State {
children: [
- const Text(
+ Text(
"Multi mode options:",
- style: TextStyle(
- fontSize: 32,
- fontWeight: FontWeight.w600,
- )
+ style: ShadTheme.of(context).textTheme.h1.copyWith(),
),
const SizedBox(
height: 16,
),
- ShadCard(
- title: const Text("Host a group"),
- width: double.infinity,
- description: const Text(
- ""
- ),
- content: Column(
+ Row(
- crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisSize: MainAxisSize.min,
+ mainAxisSize: MainAxisSize.min,
- children: [
+ children: [
- const SizedBox(
- height: 4,
+ ShadCard(
+ title: const Text("Host a group"),
+ width: 300,
+ description: const Text(
+ ""
),
+ content: Column(
- ShadButton.secondary(
- onPressed: () async {
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
- LiveInformation liveInformation = LiveInformation();
+ children: [
- Future.delayed(Duration.zero, () {
- print("At time of loading: ${liveInformation.auth.status}");
+ const SizedBox(
+ height: 4,
+ ),
- if (liveInformation.auth.status != AuthStatus.AUTHENTICATED) {
- Navigator.popAndPushNamed(context, "/multi/login");
- }
- });
- await liveInformation.createRoom(liveInformation.auth.userID!);
+ ShadButton.secondary(
+ onPressed: () async {
+
+ LiveInformation liveInformation = LiveInformation();
+
+ Future.delayed(Duration.zero, () {
+ print("At time of loading: ${liveInformation.auth.status}");
+
+ if (liveInformation.auth.status != AuthStatus.AUTHENTICATED) {
+ Navigator.popAndPushNamed(context, "/multi/login");
+ }
+ });
+ await liveInformation.createRoom(liveInformation.auth.userID!);
- Navigator.pushNamed(context, "/multi/enroute");
- },
- text: const Text("Continue"),
- )
+ Navigator.pushNamed(context, "/multi/enroute");
+ },
+ text: const Text("Continue"),
+ )
- ],
- ),
- ),
-
- const SizedBox(
- height: 16,
- ),
-
- ShadCard(
- title: const Text("Join existing group"),
- width: double.infinity,
- description: const Text(
- ""
- ),
- content: Column(
-
- crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisSize: MainAxisSize.min,
-
- children: [
-
- const SizedBox(
- height: 4,
+ ],
),
+ ),
- ShadButton.secondary(
- onPressed: () {
- Navigator.pushNamed(context, "/multi/join");
- },
- text: const Text("Continue"),
- )
+ const SizedBox(
+ width: 16,
+ ),
+
+ ShadCard(
+ title: const Text("Join existing group"),
+ width: 300,
+ description: const Text(
+ ""
+ ),
+ content: Column(
+
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
+
+ children: [
+
+ const SizedBox(
+ height: 4,
+ ),
+
+ ShadButton.secondary(
+ onPressed: () {
+ Navigator.pushNamed(context, "/multi/join");
+ },
+ text: const Text("Continue"),
+ )
+
+ ],
+ ),
+ )
+
+ ],
- ],
- ),
)
],
@@ -1491,63 +1770,37 @@ class _FullscreenDisplayState extends State {
// Get the current screen orientation
final Orientation orientation = MediaQuery.of(context).orientation;
+ return Scaffold(
- // Make the screen landscape
- SystemChrome.setPreferredOrientations([
- DeviceOrientation.landscapeRight,
- DeviceOrientation.landscapeLeft
- ]);
+ body: Container(
- return PopScope(
- onPopInvoked: (isPop) {
- if (isPop) {
- // SystemChrome.setPreferredOrientations([
- // DeviceOrientation.portraitUp,
- // DeviceOrientation.portraitDown
- // ]);
+ color: Colors.black,
+ alignment: Alignment.center,
- // Set the orientation back to whatever it was before
- SystemChrome.setPreferredOrientations(orientation == Orientation.portrait ? [
- DeviceOrientation.portraitUp,
- DeviceOrientation.portraitDown
- ] : [
- DeviceOrientation.landscapeRight,
- DeviceOrientation.landscapeLeft
- ]);
- }
- },
- child: Scaffold(
-
- body: Container(
-
- color: Colors.black,
- alignment: Alignment.center,
-
- child: Row(
- children: [
- Expanded(
- child: ibus_display(
- hasBorder: false,
- ),
+ child: Row(
+ children: [
+ Expanded(
+ child: ibus_display(
+ hasBorder: false,
),
- Container(
+ ),
+ Container(
- alignment: Alignment.bottomRight,
-
- child: ShadButton.ghost(
- icon: const Icon(Icons.arrow_back),
- padding: const EdgeInsets.all(8),
- onPressed: () {
- Navigator.pop(context);
- },
- ),
- )
- ],
- ),
+ alignment: Alignment.bottomRight,
+ child: ShadButton.ghost(
+ icon: const Icon(Icons.arrow_back),
+ padding: const EdgeInsets.all(8),
+ onPressed: () {
+ Navigator.pop(context);
+ },
+ ),
+ )
+ ],
),
),
+
);
}
}
@@ -1887,7 +2140,7 @@ List _getNearbyRoutes({bool multiMode = false}) {
if (kDebugMode) {
- // currentVector = OSGrid.toNorthingEasting(51.583781262560926, -0.020359583104595073);
+ currentVector = OSGrid.toNorthingEasting(51.583781262560926, -0.020359583104595073);
}
for (BusRoute route in busSequences.routes.values) {
diff --git a/lib/utils/audio wrapper.dart b/lib/utils/audio wrapper.dart
index 8aee000..daf7ee4 100644
--- a/lib/utils/audio wrapper.dart
+++ b/lib/utils/audio wrapper.dart
@@ -25,7 +25,7 @@ class AudioWrapper {
print("AudioWrapper mode: $mode");
- mode = AudioWrapper_Mode.Web;
+ // mode = AudioWrapper_Mode.Web;
}
justaudio.AudioSource _convertSource_JustAudio(AudioWrapperSource source){
diff --git a/lib/utils/web_socket_server.dart b/lib/utils/web_socket_server.dart
new file mode 100644
index 0000000..81a6b42
--- /dev/null
+++ b/lib/utils/web_socket_server.dart
@@ -0,0 +1,38 @@
+import 'dart:isolate';
+import 'dart:async';
+import 'dart:io';
+
+import 'package:shelf/shelf.dart' as shelf;
+import 'package:shelf/shelf_io.dart' as shelf_io;
+import 'package:shelf_web_socket/shelf_web_socket.dart';
+import 'package:web_socket_channel/io.dart'; // Import IOWebSocketChannel
+
+void webSocketServer(SendPort sendPort) async {
+ final clients = []; // Use IOWebSocketChannel
+
+ final handler = webSocketHandler((webSocket) {
+ clients.add(webSocket);
+ webSocket.stream.listen(
+ (message) {
+ print('Received: $message');
+ for (var client in clients) {
+ if (client != webSocket) {
+ client.sink.add(message); // Broadcast the message to other clients
+ }
+ }
+ },
+ onDone: () {
+ clients.remove(webSocket);
+ },
+ );
+ });
+
+ final server = await shelf_io.serve(
+ shelf.Pipeline().addMiddleware(shelf.logRequests()).addHandler(handler),
+ '0.0.0.0',
+ 8080,
+ );
+
+ print('WebSocket server running at ws://${server.address.host}:${server.port}');
+ sendPort.send('Server running at ws://${server.address.host}:${server.port}');
+}
diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift
index 22bd301..059bf89 100644
--- a/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -11,6 +11,7 @@ import device_info_plus
import flutter_web_auth_2
import geolocator_apple
import just_audio
+import network_info_plus
import package_info_plus
import path_provider_foundation
import rive_common
@@ -27,6 +28,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlutterWebAuth2Plugin.register(with: registry.registrar(forPlugin: "FlutterWebAuth2Plugin"))
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin"))
+ NetworkInfoPlusPlugin.register(with: registry.registrar(forPlugin: "NetworkInfoPlusPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
RivePlugin.register(with: registry.registrar(forPlugin: "RivePlugin"))
diff --git a/pubspec.lock b/pubspec.lock
index 48a77aa..4e228f9 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -137,6 +137,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.18.0"
+ convert:
+ dependency: transitive
+ description:
+ name: convert
+ sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.1"
cookie_jar:
dependency: transitive
description:
@@ -185,6 +193,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "9.0.1"
+ dbus:
+ dependency: transitive
+ description:
+ name: dbus
+ sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.7.10"
device_info_plus:
dependency: transitive
description:
@@ -429,6 +445,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.1"
+ http_methods:
+ dependency: transitive
+ description:
+ name: http_methods
+ sha256: "6bccce8f1ec7b5d701e7921dca35e202d425b57e317ba1a37f2638590e29e566"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.1"
http_parser:
dependency: transitive
description:
@@ -573,6 +597,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.0"
+ mime:
+ dependency: transitive
+ description:
+ name: mime
+ sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.5"
native_qr:
dependency: "direct main"
description:
@@ -581,6 +613,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.0.3"
+ network_info_plus:
+ dependency: "direct main"
+ description:
+ name: network_info_plus
+ sha256: "5bd4b86e28fed5ed4e6ac7764133c031dfb7d3f46aa2a81b46f55038aa78ecc0"
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.0.3"
+ network_info_plus_platform_interface:
+ dependency: transitive
+ description:
+ name: network_info_plus_platform_interface
+ sha256: "2e193d61d3072ac17824638793d3b89c6d581ce90c11604f4ca87311b42f2706"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.0"
+ nm:
+ dependency: transitive
+ description:
+ name: nm
+ sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.5.0"
ntp:
dependency: "direct main"
description:
@@ -869,6 +925,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.2"
+ shelf:
+ dependency: "direct main"
+ description:
+ name: shelf
+ sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.4.1"
+ shelf_router:
+ dependency: "direct main"
+ description:
+ name: shelf_router
+ sha256: f5e5d492440a7fb165fe1e2e1a623f31f734d3370900070b2b1e0d0428d59864
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.4"
+ shelf_static:
+ dependency: "direct main"
+ description:
+ name: shelf_static
+ sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.2"
+ shelf_web_socket:
+ dependency: "direct main"
+ description:
+ name: shelf_web_socket
+ sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.0"
sky_engine:
dependency: transitive
description: flutter
diff --git a/pubspec.yaml b/pubspec.yaml
index 22dc979..7f24537 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -40,7 +40,7 @@ dependencies:
intl: any
text_scroll: ^0.2.0
flutter_map: ^6.1.0
- appwrite: ^12.0.1
+ appwrite: ^12.0.3
shared_preferences: ^2.2.2
url_launcher: ^6.2.2
ntp: ^2.0.0
@@ -56,6 +56,12 @@ dependencies:
native_qr: ^0.0.3
qr_flutter: ^4.1.0
flutter_scroll_shadow: ^1.2.4
+ network_info_plus: ^5.0.3
+ shelf: ^1.4.1
+ shelf_router: ^1.1.4
+ shelf_static: ^1.1.2
+ shelf_web_socket: ^2.0.0
+# web_socket_channel: ^3.0.0
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.