2220 lines
68 KiB
Dart
2220 lines
68 KiB
Dart
|
|
import 'package:bus_infotainment/auth/auth_api.dart';
|
|
import 'package:bus_infotainment/backend/live_information.dart';
|
|
import 'package:bus_infotainment/backend/modules/tracker.dart';
|
|
import 'package:bus_infotainment/pages/components/ibus_display.dart';
|
|
import 'package:bus_infotainment/pages/home.dart';
|
|
import 'package:bus_infotainment/tfl_datasets.dart';
|
|
import 'package:bus_infotainment/utils/OrdinanceSurveyUtils.dart';
|
|
import 'package:bus_infotainment/utils/delegates.dart';
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:dart_ping/dart_ping.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter/widgets.dart';
|
|
|
|
import 'package:qr_flutter/qr_flutter.dart';
|
|
import 'package:native_qr/native_qr.dart';
|
|
|
|
|
|
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;
|
|
|
|
import '../backend/modules/tube_info.dart';
|
|
|
|
Color rgb(int r, int g, int b) {
|
|
return Color.fromRGBO(r, g, b, 1);
|
|
}
|
|
|
|
|
|
|
|
class HomePage_Re extends StatefulWidget {
|
|
|
|
@override
|
|
State<HomePage_Re> createState() => _HomePage_ReState();
|
|
}
|
|
|
|
class _HomePage_ReState extends State<HomePage_Re> {
|
|
Future<bool> _shouldRedirectToSetup() async {
|
|
|
|
List<bool> perms = [];
|
|
|
|
perms.addAll([
|
|
await Permission.manageExternalStorage.isGranted,
|
|
await Permission.location.isGranted
|
|
]);
|
|
|
|
bool shouldRedirectA = !perms.contains(false);
|
|
bool shouldRedirectB = true;
|
|
|
|
try {
|
|
Uint8List bytes = await LiveInformation().announcementModule.getBundleBytes();
|
|
shouldRedirectB = true;
|
|
} catch (e) {
|
|
print("Failed to load bundle");
|
|
shouldRedirectB = false;
|
|
}
|
|
|
|
print("Should redirect to setup: ${shouldRedirectA || shouldRedirectB}");
|
|
print("Permissions: $shouldRedirectA");
|
|
print("Bundle: $shouldRedirectB");
|
|
print("Permissions_indv: $perms");
|
|
|
|
return (!shouldRedirectA || !shouldRedirectB);
|
|
}
|
|
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
|
|
_shouldRedirectToSetup().then((value) {
|
|
if (value) {
|
|
Navigator.pushNamed(context, "/setup");
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
|
|
|
|
return Scaffold(
|
|
body: Container(
|
|
|
|
padding: const EdgeInsets.all(16),
|
|
alignment: Alignment.center,
|
|
|
|
child: Column(
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
|
|
children: [
|
|
|
|
Text(
|
|
"Choose mode:",
|
|
style: ShadTheme.of(context).textTheme.h1.copyWith(),
|
|
),
|
|
|
|
SizedBox(
|
|
height: 16,
|
|
),
|
|
|
|
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)"
|
|
),
|
|
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(
|
|
height: 16,
|
|
),
|
|
|
|
ShadButton(
|
|
text: Text("Re-setup"),
|
|
onPressed: () {
|
|
Navigator.popAndPushNamed(context, "/setup");
|
|
},
|
|
)
|
|
|
|
],
|
|
|
|
),
|
|
),
|
|
);
|
|
|
|
// 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"),
|
|
// )
|
|
//
|
|
// ],
|
|
// ),
|
|
// )
|
|
//
|
|
//
|
|
//
|
|
// ],
|
|
//
|
|
// ),
|
|
// ],
|
|
// ),
|
|
// )
|
|
//
|
|
// ),
|
|
// );
|
|
}
|
|
|
|
}
|
|
|
|
class RoutePage extends StatelessWidget {
|
|
|
|
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
|
|
return Scaffold(
|
|
body: Row(
|
|
|
|
children: [
|
|
|
|
Container(
|
|
height: double.infinity,
|
|
|
|
child: Row(
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
|
|
children: [
|
|
|
|
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
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
Container(
|
|
width: 230,
|
|
child: Scrollbar(
|
|
thumbVisibility: true,
|
|
child: GridView.count(
|
|
crossAxisCount: 2,
|
|
padding: const EdgeInsets.fromLTRB(
|
|
4,
|
|
4,
|
|
4,
|
|
4
|
|
),
|
|
shrinkWrap: true,
|
|
children: [
|
|
..._getNearbyRoutes(
|
|
multiMode: ModalRoute.of(context)!.settings.name!.contains("multi")
|
|
)
|
|
],
|
|
),
|
|
),
|
|
)
|
|
],
|
|
),
|
|
),
|
|
|
|
Container(
|
|
width: 2,
|
|
height: double.infinity,
|
|
color: Colors.grey.shade400,
|
|
),
|
|
|
|
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()
|
|
// ],
|
|
// ),
|
|
// );
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
class RouteSearch extends StatefulWidget {
|
|
|
|
final bool multiMode;
|
|
|
|
RouteSearch({required this.multiMode});
|
|
|
|
@override
|
|
State<RouteSearch> createState() => _RouteSearchState();
|
|
}
|
|
|
|
class _RouteSearchState extends State<RouteSearch> {
|
|
TextEditingController controller = TextEditingController();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
|
|
List<Widget> routes = [];
|
|
|
|
for (BusRoute route in LiveInformation().busSequences.routes.values.toList()) {
|
|
|
|
if (controller.text.isNotEmpty && !route.routeNumber.toLowerCase().contains(controller.text.toLowerCase())) {
|
|
continue;
|
|
}
|
|
|
|
routes.add(RouteCard(route: route, multiMode: widget.multiMode));
|
|
}
|
|
|
|
return Expanded(
|
|
child: Column(
|
|
|
|
children: [
|
|
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(
|
|
4,
|
|
4,
|
|
4,
|
|
0
|
|
),
|
|
child: ShadInput(
|
|
placeholder: const Text("Search for a route..."),
|
|
controller: controller,
|
|
onChanged: (value) {
|
|
setState(() {
|
|
|
|
});
|
|
},
|
|
),
|
|
),
|
|
|
|
Expanded(
|
|
child: Scrollbar(
|
|
thumbVisibility: true,
|
|
trackVisibility: true,
|
|
scrollbarOrientation: ScrollbarOrientation.bottom,
|
|
child: GridView.extent(
|
|
// padding: const EdgeInsets.all(4),
|
|
scrollDirection: Axis.vertical,
|
|
maxCrossAxisExtent: 120,
|
|
children: [
|
|
...routes
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
SizedBox(
|
|
height: 4,
|
|
)
|
|
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class RouteCard extends StatelessWidget {
|
|
|
|
BusRoute route;
|
|
final bool multiMode;
|
|
|
|
RouteCard({required this.route, required this.multiMode});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
// TODO: implement build
|
|
|
|
Map<String, Widget> variants = {};
|
|
|
|
for (BusRouteVariant variant in route.routeVariants.values) {
|
|
String variantLabel = "${variant.busStops.first.formattedStopName} -> ${variant.busStops.last.formattedStopName}";
|
|
|
|
variants[variantLabel] = ShadOption(
|
|
value: variant.routeVariant.toString(),
|
|
child: Text(variantLabel),
|
|
);
|
|
}
|
|
|
|
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 ShadButton.secondary(
|
|
text: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
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))
|
|
],
|
|
),
|
|
padding: const EdgeInsets.all(8),
|
|
width: 100,
|
|
height: 100,
|
|
size: ShadButtonSize.icon,
|
|
|
|
onPressed: () {
|
|
showShadSheet(
|
|
side: ShadSheetSide.right,
|
|
context: context,
|
|
builder: (context) {
|
|
|
|
List<Widget> 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)
|
|
],
|
|
),
|
|
),
|
|
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),
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
},
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
class EnRoutePage extends StatelessWidget {
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
// TODO: implement build
|
|
return Scaffold(
|
|
body: _dash(),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _dash extends StatelessWidget {
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Column(
|
|
|
|
children: [
|
|
|
|
Container(
|
|
padding: const EdgeInsets.all(8),
|
|
child: ibus_display()
|
|
),
|
|
|
|
const Divider(
|
|
height: 1,
|
|
),
|
|
|
|
|
|
ExpandableCarousel(
|
|
items: [
|
|
EasyAnnouncementPicker(
|
|
announcements: LiveInformation().announcementModule.manualAnnouncements,
|
|
title: "Manual"
|
|
),
|
|
|
|
Container(
|
|
padding: const EdgeInsets.all(8),
|
|
child: StopAnnouncementPicker(
|
|
routeVariant: LiveInformation().getRouteVariant()!,
|
|
backgroundColor: Colors.transparent,
|
|
outlineColor: Colors.white,
|
|
label: "Bus Stops",
|
|
)
|
|
)
|
|
|
|
|
|
],
|
|
options: CarouselOptions(
|
|
showIndicator: false
|
|
|
|
),
|
|
),
|
|
|
|
const Divider(
|
|
height: 1,
|
|
),
|
|
|
|
Expanded(
|
|
child: Container(
|
|
padding: const EdgeInsets.all(8),
|
|
child: Column(
|
|
|
|
children: [
|
|
|
|
Text(
|
|
"Quick actions",
|
|
style: ShadTheme.of(context).textTheme.h3,
|
|
),
|
|
|
|
const SizedBox(
|
|
height: 8,
|
|
),
|
|
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.transparent,
|
|
borderRadius: BorderRadius.circular(8),
|
|
border: Border.all(
|
|
color: ShadTheme.of(context).colorScheme.primary,
|
|
width: 1
|
|
)
|
|
),
|
|
padding: const EdgeInsets.all(4),
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.transparent,
|
|
borderRadius: BorderRadius.circular(4),
|
|
border: Border.all(
|
|
color: ShadTheme.of(context).colorScheme.primary,
|
|
width: 1
|
|
)
|
|
),
|
|
child: Column(
|
|
children: [
|
|
|
|
if (!kIsWeb)
|
|
AnnouncementEntry(
|
|
label: "Display next stop",
|
|
index: 0,
|
|
outlineColor: ShadTheme.of(context).colorScheme.primary,
|
|
onPressed: () {
|
|
LiveInformation liveInformation = LiveInformation();
|
|
TrackerModule trackerModule = liveInformation.trackerModule;
|
|
|
|
BusRouteStop? stop = trackerModule.nearestStop;
|
|
|
|
if (stop != null) {
|
|
liveInformation.announcementModule.queueAnnounceByAudioName(displayText: stop.formattedStopName);
|
|
} else {
|
|
ShadToaster.of(context).show(
|
|
const ShadToast(
|
|
title: Text("No bus stop found"),
|
|
description: Text("No bus stop found nearby"),
|
|
duration: Duration(seconds: 5),
|
|
)
|
|
);
|
|
}
|
|
},
|
|
),
|
|
|
|
Container(
|
|
height: 1,
|
|
color: ShadTheme.of(context).colorScheme.primary,
|
|
),
|
|
|
|
AnnouncementEntry(
|
|
label: "Announce destination",
|
|
index: 1,
|
|
outlineColor: ShadTheme.of(context).colorScheme.primary,
|
|
onPressed: () {
|
|
LiveInformation liveInformation = LiveInformation();
|
|
liveInformation.announcementModule.queueAnnouncementByRouteVariant(
|
|
routeVariant:
|
|
liveInformation.getRouteVariant()!
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
SizedBox(
|
|
height: 8,
|
|
),
|
|
|
|
Container(
|
|
padding: const EdgeInsets.all(8),
|
|
child: ShadButton(
|
|
text: const Text("Fullscreen display"),
|
|
onPressed: () {
|
|
Navigator.pushNamed(context, "/display");
|
|
},
|
|
icon: const Icon(Icons.fullscreen),
|
|
width: double.infinity,
|
|
),
|
|
),
|
|
//
|
|
// ShadCard(
|
|
// title: Text("Stop announcements"),
|
|
// width: double.infinity,
|
|
// ),
|
|
//
|
|
// ShadButton(
|
|
// text: Text("Route scanner"),
|
|
// onPressed: () {
|
|
//
|
|
// LiveInformation liveInformation = LiveInformation();
|
|
//
|
|
// TubeLine? line = liveInformation.tubeStations.getClosestLine(liveInformation.getRouteVariant()!);
|
|
//
|
|
// ShadToaster.of(context).show(
|
|
// ShadToast(
|
|
// title: Text("Closest line"),
|
|
// description: Text(line == null ? "No line found" : line.name),
|
|
// duration: Duration(seconds: 5),
|
|
// )
|
|
// );
|
|
//
|
|
// },
|
|
// ),
|
|
//
|
|
// ShadButton(
|
|
// text: Text("dest"),
|
|
// onPressed: () {
|
|
// LiveInformation liveInformation = LiveInformation();
|
|
// liveInformation.announcementModule.queueAnnouncementByRouteVariant(routeVariant: liveInformation.getRouteVariant()!);
|
|
// },
|
|
// ),
|
|
//
|
|
// ShadButton(
|
|
// text: Text("Open Legacy dashboard"),
|
|
// onPressed: () {
|
|
// Navigator.pushNamed(context, "/legacy");
|
|
// },
|
|
// )
|
|
|
|
],
|
|
|
|
)
|
|
),
|
|
),
|
|
|
|
const Divider(
|
|
height: 1,
|
|
),
|
|
|
|
NavigationBar()
|
|
|
|
],
|
|
|
|
);
|
|
}
|
|
}
|
|
|
|
class EasyAnnouncementPicker extends StatelessWidget {
|
|
|
|
late final List<AnnouncementQueueEntry> announcements;
|
|
|
|
late final String title;
|
|
Color outlineColor = Colors.white;
|
|
|
|
EasyAnnouncementPicker({this.announcements = const [], this.title = "Announcements", this.outlineColor = Colors.white});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
|
|
List<Widget> announcementWidgets = [];
|
|
|
|
for (AnnouncementQueueEntry entry in announcements) {
|
|
if (entry is NamedAnnouncementQueueEntry) {
|
|
announcementWidgets.add(
|
|
AnnouncementEntry(
|
|
label: entry.shortName,
|
|
onPressed: () {
|
|
LiveInformation liveInformation = LiveInformation();
|
|
liveInformation.announcementModule.queueAnnouncementByInfoIndex(
|
|
infoIndex: liveInformation.announcementModule.manualAnnouncements.indexOf(entry),
|
|
sendToServer: true
|
|
);
|
|
|
|
},
|
|
index: announcements.indexOf(entry),
|
|
outlineColor: outlineColor,
|
|
|
|
)
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
return AnnouncementPicker(
|
|
announcements: announcementWidgets,
|
|
backgroundColor: const Color(/*Transparent*/0x00000000),
|
|
outlineColor: outlineColor,
|
|
label: title,
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
ShadSelectFormField<String>(
|
|
options: [
|
|
Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Text("Choose a variant"),
|
|
),
|
|
...variants.values
|
|
],
|
|
selectedOptionBuilder: (context, value) => value ==
|
|
'none'
|
|
? const Text('Select a verified email to display')
|
|
: Text(variants.keys.toList()[int.parse(value)-1]!),
|
|
placeholder: Text("Choose a variant"),
|
|
|
|
),
|
|
*/
|
|
|
|
class MultiModeSetup extends StatefulWidget {
|
|
|
|
@override
|
|
State<MultiModeSetup> createState() => _MultiModeSetupState();
|
|
}
|
|
|
|
class _MultiModeSetupState extends State<MultiModeSetup> {
|
|
|
|
@override
|
|
void initState() {
|
|
// Check if the user is logged in
|
|
|
|
super.initState();
|
|
|
|
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
|
|
body: Column(
|
|
|
|
children: [
|
|
|
|
Expanded(
|
|
child: Container(
|
|
|
|
alignment: Alignment.center,
|
|
padding: const EdgeInsets.all(16),
|
|
|
|
child: Column(
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
children: [
|
|
|
|
Text(
|
|
"Multi mode options:",
|
|
style: ShadTheme.of(context).textTheme.h1.copyWith(),
|
|
),
|
|
|
|
const SizedBox(
|
|
height: 16,
|
|
),
|
|
|
|
Row(
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
children: [
|
|
|
|
ShadCard(
|
|
title: const Text("Host a group"),
|
|
width: 300,
|
|
description: const Text(
|
|
""
|
|
),
|
|
content: Column(
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
children: [
|
|
|
|
const SizedBox(
|
|
height: 4,
|
|
),
|
|
|
|
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"),
|
|
)
|
|
|
|
],
|
|
),
|
|
),
|
|
|
|
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"),
|
|
)
|
|
|
|
],
|
|
),
|
|
)
|
|
|
|
],
|
|
|
|
)
|
|
|
|
],
|
|
|
|
),
|
|
),
|
|
),
|
|
|
|
NavigationBar()
|
|
|
|
|
|
|
|
|
|
],
|
|
|
|
)
|
|
|
|
);
|
|
}
|
|
}
|
|
|
|
class MultiModeEnroute extends StatefulWidget {
|
|
|
|
@override
|
|
State<MultiModeEnroute> createState() => _MultiModeEnrouteState();
|
|
}
|
|
|
|
class _MultiModeEnrouteState extends State<MultiModeEnroute> {
|
|
|
|
late final Future<void> roomCodeFuture;
|
|
|
|
late ListenerReceipt<BusRouteVariant?> listenerReceipt;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
LiveInformation liveInformation = LiveInformation();
|
|
listenerReceipt = liveInformation.routeVariantDelegate.addListener((value) {
|
|
setState(() {
|
|
|
|
});
|
|
});
|
|
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
// TODO: implement dispose
|
|
super.dispose();
|
|
LiveInformation liveInformation = LiveInformation();
|
|
liveInformation.routeVariantDelegate.removeListener(listenerReceipt);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
|
|
// Set the screen to portrait
|
|
|
|
|
|
// Generate random uuid
|
|
LiveInformation liveInformation = LiveInformation();
|
|
|
|
|
|
return PopScope(
|
|
canPop: false,
|
|
onPopInvoked: (didPop) {
|
|
|
|
if (didPop){
|
|
print("Compensating for pop");
|
|
liveInformation.leaveRoom();
|
|
return;
|
|
}
|
|
|
|
// return;
|
|
// Ask the user to confirm if they want to leave the group
|
|
showShadDialog(
|
|
context: context,
|
|
builder: (context) {
|
|
return ShadDialog(
|
|
title: const Text("Leave group?"),
|
|
content: const Text("Are you sure you want to leave the group?"),
|
|
actions: [
|
|
ShadButton(
|
|
text: const Text("Leave"),
|
|
onPressed: () {
|
|
liveInformation.leaveRoom();
|
|
Navigator.pop(context);
|
|
Navigator.pop(context);
|
|
},
|
|
),
|
|
ShadButton(
|
|
text: const Text("Cancel"),
|
|
onPressed: () {
|
|
Navigator.pop(context);
|
|
},
|
|
)
|
|
],
|
|
);
|
|
}
|
|
);
|
|
|
|
},
|
|
|
|
child: Scaffold(
|
|
|
|
body: Column(
|
|
children: [
|
|
const Divider(
|
|
height: 1,
|
|
),
|
|
|
|
Container(
|
|
padding: EdgeInsets.all(8),
|
|
child: ibus_display()
|
|
),
|
|
|
|
const Divider(
|
|
height: 1,
|
|
),
|
|
|
|
Container(
|
|
padding: EdgeInsets.all(8),
|
|
child: Text(
|
|
"* Swipe left and right below for more options!",
|
|
textAlign: TextAlign.center,
|
|
),
|
|
),
|
|
|
|
const Divider(
|
|
height: 1,
|
|
),
|
|
|
|
SizedBox(
|
|
height: 16,
|
|
),
|
|
|
|
Expanded(
|
|
child: FlutterCarousel(
|
|
items: [
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
border: Border.all(
|
|
color: Colors.white,
|
|
width: 1
|
|
),
|
|
borderRadius: BorderRadius.circular(8)
|
|
),
|
|
padding: const EdgeInsets.all(4),
|
|
margin: const EdgeInsets.only(
|
|
left: 16,
|
|
right: 16,
|
|
),
|
|
width: double.infinity,
|
|
|
|
child: Column(
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
children: [
|
|
|
|
if (!kIsWeb)
|
|
Container(
|
|
padding: const EdgeInsets.only(
|
|
top: 4,
|
|
left: 8,
|
|
right: 8,
|
|
bottom: 4
|
|
),
|
|
child: Text(
|
|
"Nearby routes",
|
|
style: ShadTheme.of(context).textTheme.h4,
|
|
),
|
|
),
|
|
if (!kIsWeb)
|
|
Expanded(
|
|
child: Scrollbar(
|
|
interactive: true,
|
|
radius: const Radius.circular(8),
|
|
thickness: 8,
|
|
thumbVisibility: true,
|
|
child: GridView.count(
|
|
crossAxisCount: 3,
|
|
children: [
|
|
..._getNearbyRoutes(multiMode: true)
|
|
],
|
|
shrinkWrap: true,
|
|
),
|
|
),
|
|
)
|
|
|
|
],
|
|
)
|
|
),
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
border: Border.all(
|
|
color: Colors.white,
|
|
width: 1
|
|
),
|
|
borderRadius: BorderRadius.circular(8)
|
|
),
|
|
padding: const EdgeInsets.all(4),
|
|
margin: const EdgeInsets.only(
|
|
left: 16,
|
|
right: 16,
|
|
),
|
|
|
|
child: Expanded(child: RouteSearch(multiMode: true,))
|
|
),
|
|
Container(
|
|
|
|
decoration: BoxDecoration(
|
|
border: Border.all(
|
|
width: 1,
|
|
color: Colors.white
|
|
),
|
|
borderRadius: BorderRadius.all(Radius.circular(8))
|
|
),
|
|
|
|
margin: const EdgeInsets.only(
|
|
left: 16,
|
|
right: 16,
|
|
),
|
|
|
|
padding: EdgeInsets.all(8),
|
|
|
|
child: SingleChildScrollView(
|
|
child: Column(
|
|
children: [
|
|
EasyAnnouncementPicker(
|
|
announcements: LiveInformation().announcementModule.manualAnnouncements,
|
|
title: "Manual",
|
|
outlineColor: ShadTheme.of(context).colorScheme.secondary
|
|
),
|
|
|
|
if (liveInformation.getRouteVariant() != null)
|
|
SizedBox(
|
|
height: 16,
|
|
),
|
|
|
|
if (liveInformation.getRouteVariant() != null)
|
|
Container(
|
|
|
|
child: StopAnnouncementPicker(
|
|
routeVariant: LiveInformation().getRouteVariant()!,
|
|
backgroundColor: Colors.transparent,
|
|
outlineColor: ShadTheme.of(context).colorScheme.secondary,
|
|
label: "Bus Stops",
|
|
)
|
|
)
|
|
],
|
|
),
|
|
),
|
|
),
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.transparent,
|
|
borderRadius: BorderRadius.circular(8),
|
|
border: Border.all(
|
|
color: ShadTheme.of(context).colorScheme.primary,
|
|
width: 1
|
|
)
|
|
),
|
|
margin: const EdgeInsets.only(
|
|
left: 16,
|
|
right: 16,
|
|
),
|
|
padding: const EdgeInsets.all(4),
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.transparent,
|
|
borderRadius: BorderRadius.circular(4),
|
|
border: Border.all(
|
|
color: ShadTheme.of(context).colorScheme.primary,
|
|
width: 1
|
|
)
|
|
),
|
|
child: Column(
|
|
children: [
|
|
|
|
if (!kIsWeb)
|
|
AnnouncementEntry(
|
|
label: "Display next stop",
|
|
index: 0,
|
|
outlineColor: ShadTheme.of(context).colorScheme.primary,
|
|
onPressed: () {
|
|
LiveInformation liveInformation = LiveInformation();
|
|
TrackerModule trackerModule = liveInformation.trackerModule;
|
|
|
|
BusRouteStop? stop = trackerModule.nearestStop;
|
|
|
|
if (stop != null) {
|
|
liveInformation.announcementModule.queueAnnounceByAudioName(displayText: stop.formattedStopName);
|
|
} else {
|
|
ShadToaster.of(context).show(
|
|
const ShadToast(
|
|
title: Text("No bus stop found"),
|
|
description: Text("No bus stop found nearby"),
|
|
duration: Duration(seconds: 5),
|
|
)
|
|
);
|
|
}
|
|
},
|
|
),
|
|
|
|
if (!kIsWeb)
|
|
Container(
|
|
height: 1,
|
|
color: ShadTheme.of(context).colorScheme.primary,
|
|
),
|
|
|
|
AnnouncementEntry(
|
|
label: "Announce destination",
|
|
index: 1,
|
|
outlineColor: ShadTheme.of(context).colorScheme.primary,
|
|
onPressed: () {
|
|
LiveInformation liveInformation = LiveInformation();
|
|
liveInformation.announcementModule.queueAnnouncementByRouteVariant(
|
|
routeVariant:
|
|
liveInformation.getRouteVariant()!
|
|
);
|
|
},
|
|
),
|
|
|
|
Container(
|
|
height: 1,
|
|
color: ShadTheme.of(context).colorScheme.primary,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
options: CarouselOptions(
|
|
showIndicator: false,
|
|
viewportFraction: 1,
|
|
height: double.infinity,
|
|
enableInfiniteScroll: true
|
|
),
|
|
),
|
|
),
|
|
|
|
Container(
|
|
padding: const EdgeInsets.all(8),
|
|
child: ShadButton(
|
|
text: const Text("Fullscreen display"),
|
|
onPressed: () {
|
|
Navigator.pushNamed(context, "/display");
|
|
},
|
|
icon: const Icon(Icons.fullscreen),
|
|
width: double.infinity,
|
|
),
|
|
),
|
|
const Divider(
|
|
height: 1,
|
|
),
|
|
|
|
Container(
|
|
padding: const EdgeInsets.all(16),
|
|
|
|
// height: 200,
|
|
|
|
child: ShadCard(
|
|
title: liveInformation.isHost ? const Text("Currently hosting group") : const Text("Successfully joined group"),
|
|
border: Border.all(
|
|
color: Colors.amber,
|
|
width: 1
|
|
),
|
|
padding: const EdgeInsets.all(16),
|
|
width: double.infinity,
|
|
description: liveInformation.isHost ? const Text(
|
|
"You are hosting a group. \nShare the room code with others to join"
|
|
) : const Text(
|
|
"You have joined a group."
|
|
),
|
|
content: Column(
|
|
children: [
|
|
const SizedBox(
|
|
height: 4,
|
|
),
|
|
|
|
FutureBuilder(
|
|
future: Future.delayed(const Duration(seconds: 1)),
|
|
builder: (context, snapshot) {
|
|
return Row(
|
|
children: [
|
|
Expanded(
|
|
child: ShadButton(
|
|
text: Text(
|
|
liveInformation.roomCode!,
|
|
),
|
|
|
|
icon: const Icon(Icons.copy),
|
|
padding: const EdgeInsets.all(8),
|
|
onPressed: () {
|
|
Clipboard.setData(ClipboardData(text: liveInformation.roomCode!));
|
|
ShadToaster.of(context).show(
|
|
const ShadToast(
|
|
title: Text("Copied to clipboard"),
|
|
description: Text("Room code copied to clipboard"),
|
|
duration: Duration(seconds: 5),
|
|
)
|
|
);
|
|
},
|
|
),
|
|
),
|
|
ShadButton(
|
|
icon: const Icon(Icons.qr_code),
|
|
onPressed: () {
|
|
showShadDialog(
|
|
context: context,
|
|
builder: (context) {
|
|
return ShadDialog(
|
|
title: const Text("QR Code"),
|
|
content: Container(
|
|
|
|
width: 200,
|
|
height: 225,
|
|
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
QrImageView(
|
|
data: liveInformation.roomCode!,
|
|
size: 200,
|
|
backgroundColor: Colors.white,
|
|
),
|
|
|
|
const SizedBox(
|
|
height: 8,
|
|
),
|
|
|
|
Text("Scan QR code to join the group")
|
|
|
|
],
|
|
),
|
|
),
|
|
actions: [
|
|
ShadButton(
|
|
text: const Text("Close"),
|
|
onPressed: () {
|
|
Navigator.pop(context);
|
|
},
|
|
)
|
|
],
|
|
);
|
|
}
|
|
);
|
|
},
|
|
)
|
|
],
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
const Divider(
|
|
height: 1,
|
|
),
|
|
NavigationBar()
|
|
],
|
|
),
|
|
|
|
),
|
|
);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
class MultiModeJoin extends StatefulWidget {
|
|
|
|
@override
|
|
State<MultiModeJoin> createState() => _MultiModeJoinState();
|
|
}
|
|
|
|
class _MultiModeJoinState extends State<MultiModeJoin> {
|
|
TextEditingController controller = TextEditingController();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
body: Column(
|
|
children: [
|
|
Expanded(
|
|
child: Container(
|
|
|
|
alignment: Alignment.center,
|
|
padding: EdgeInsets.all(16),
|
|
width: double.infinity,
|
|
|
|
child: Column(
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
|
|
children: [
|
|
ShadCard(
|
|
title: const Text("Join a group"),
|
|
width: double.infinity,
|
|
description: const Text("Enter the room code to join a group"),
|
|
content: Column(
|
|
children: [
|
|
ShadInputFormField(
|
|
controller: controller,
|
|
id: "roomCode",
|
|
label: const Text("Room code"),
|
|
placeholder: const Text("Enter the room code"),
|
|
validator: (value) {
|
|
if (value.isEmpty) {
|
|
return "Please enter the room code";
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
ShadButton(
|
|
text: const Text("Join"),
|
|
width: double.infinity,
|
|
onPressed: () async {
|
|
LiveInformation liveInformation = LiveInformation();
|
|
|
|
|
|
|
|
if (controller.text.isNotEmpty ? await liveInformation.joinRoom(controller.text) : false) {
|
|
liveInformation.setRouteVariant(null);
|
|
Navigator.popAndPushNamed(context, "/multi/enroute");
|
|
} else {
|
|
showShadDialog(
|
|
context: context,
|
|
builder: (context) {
|
|
return ShadDialog(
|
|
title: const Text("Failed to join group"),
|
|
content: const Text("Failed to join group. Please check the room code and try again"),
|
|
actions: [
|
|
ShadButton(
|
|
text: const Text("Close"),
|
|
onPressed: () {
|
|
Navigator.pop(context);
|
|
},
|
|
)
|
|
],
|
|
);
|
|
}
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
},
|
|
)
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(
|
|
height: 16,
|
|
),
|
|
ShadButton.secondary(
|
|
text: Text("Scan QR code"),
|
|
icon: const Icon(Icons.qr_code),
|
|
onPressed: () async {
|
|
try {
|
|
NativeQr nativeQr = NativeQr();
|
|
String? result = await nativeQr.get();
|
|
controller.text = result!;
|
|
} catch (e) {
|
|
print("Failed to scan QR code");
|
|
}
|
|
}
|
|
)
|
|
|
|
|
|
// todo: This is the new code, couldnt finish it on time so still using the old code
|
|
/*Text(
|
|
"Join a group",
|
|
style: ShadTheme.of(context).textTheme.h2,
|
|
),
|
|
|
|
SizedBox(
|
|
height: 16,
|
|
),
|
|
|
|
Row(
|
|
children: [
|
|
|
|
Container(
|
|
width: 150,
|
|
height: 150,
|
|
child: ElevatedButton(
|
|
onPressed: () {
|
|
|
|
},
|
|
// border radius
|
|
style: ElevatedButton.styleFrom(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(30)
|
|
)
|
|
),
|
|
|
|
child: Text(
|
|
"Join from QR Code",
|
|
style: ShadTheme.of(context).textTheme.h4,
|
|
textAlign: TextAlign.center,
|
|
),
|
|
|
|
),
|
|
),
|
|
|
|
Container(
|
|
width: 150,
|
|
height: 150,
|
|
child: ElevatedButton(
|
|
onPressed: () {
|
|
|
|
},
|
|
// border radius
|
|
style: ElevatedButton.styleFrom(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(30)
|
|
)
|
|
),
|
|
|
|
child: Text(
|
|
"Join from Clipboard",
|
|
style: ShadTheme.of(context).textTheme.h4,
|
|
textAlign: TextAlign.center,
|
|
),
|
|
|
|
),
|
|
)
|
|
|
|
],
|
|
)*/
|
|
|
|
]
|
|
),
|
|
),
|
|
),
|
|
Divider(
|
|
height: 1
|
|
),
|
|
NavigationBar()
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class FullscreenDisplay extends StatefulWidget {
|
|
|
|
@override
|
|
State<FullscreenDisplay> createState() => _FullscreenDisplayState();
|
|
}
|
|
|
|
class _FullscreenDisplayState extends State<FullscreenDisplay> {
|
|
|
|
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
|
|
// Get the current screen orientation
|
|
final Orientation orientation = MediaQuery.of(context).orientation;
|
|
|
|
return Scaffold(
|
|
|
|
body: Container(
|
|
|
|
color: Colors.black,
|
|
alignment: Alignment.center,
|
|
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: ibus_display(
|
|
hasBorder: false,
|
|
),
|
|
),
|
|
Container(
|
|
|
|
alignment: Alignment.bottomRight,
|
|
|
|
child: ShadButton.ghost(
|
|
icon: const Icon(Icons.arrow_back),
|
|
padding: const EdgeInsets.all(8),
|
|
onPressed: () {
|
|
Navigator.pop(context);
|
|
},
|
|
),
|
|
)
|
|
],
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
}
|
|
}
|
|
|
|
class MultiModeLogin extends StatelessWidget {
|
|
|
|
final formKey = GlobalKey<ShadFormState>();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
// TODO: implement build
|
|
return Scaffold(
|
|
|
|
body: Column(
|
|
children: [
|
|
Expanded(
|
|
child: Container(
|
|
|
|
padding: const EdgeInsets.all(16),
|
|
alignment: Alignment.centerLeft,
|
|
|
|
child: Column(
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
children: [
|
|
|
|
Text(
|
|
"Login",
|
|
style: ShadTheme.of(context).textTheme.h2,
|
|
),
|
|
|
|
ShadForm(
|
|
key: formKey,
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
ShadInputFormField(
|
|
id: "email",
|
|
label: const Text("Email"),
|
|
autofillHints: const [AutofillHints.email],
|
|
placeholder: const Text("Enter your email"),
|
|
validator: (value) {
|
|
if (value.isEmpty) {
|
|
return "Please enter your email";
|
|
}
|
|
if (!value.contains("@")) {
|
|
return "Please enter a valid email";
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
ShadInputFormField(
|
|
id: "password",
|
|
label: const Text("Password"),
|
|
placeholder: const Text("Enter your password"),
|
|
autofillHints: const [AutofillHints.password],
|
|
obscureText: true,
|
|
validator: (value) {
|
|
if (value.isEmpty) {
|
|
return "Please enter your password";
|
|
}
|
|
if (value.length < 8) {
|
|
return "Password must be at least 8 characters long";
|
|
}
|
|
return null;
|
|
},
|
|
)
|
|
],
|
|
),
|
|
),
|
|
|
|
ShadButton(
|
|
text: const Text("Login"),
|
|
width: double.infinity,
|
|
onPressed: () async {
|
|
if (formKey.currentState!.validate()) {
|
|
formKey.currentState!.save();
|
|
print("Logging in...");
|
|
|
|
LiveInformation liveInformation = LiveInformation();
|
|
await liveInformation.auth.createEmailSession(
|
|
email: formKey.currentState!.value["email"],
|
|
password: formKey.currentState!.value["password"]
|
|
);
|
|
print("Done something");
|
|
print(liveInformation.auth.status);
|
|
}
|
|
},
|
|
),
|
|
|
|
Row(
|
|
mainAxisSize: MainAxisSize.max,
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const Text("Don't have an account?"),
|
|
ShadButton.link(
|
|
onPressed: () {
|
|
Navigator.pushNamed(context, "/multi/register");
|
|
},
|
|
text: const Text("Register"),
|
|
)
|
|
],
|
|
)
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
),
|
|
const Divider(
|
|
height: 1,
|
|
),
|
|
NavigationBar()
|
|
],
|
|
),
|
|
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
class MultiModeRegister extends StatelessWidget {
|
|
|
|
final formKey = GlobalKey<ShadFormState>();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
// TODO: implement build
|
|
return Scaffold(
|
|
|
|
body: Column(
|
|
children: [
|
|
Expanded(
|
|
child: Container(
|
|
|
|
padding: const EdgeInsets.all(16),
|
|
alignment: Alignment.centerLeft,
|
|
|
|
child: SingleChildScrollView(
|
|
child: Column(
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
children: [
|
|
|
|
Text(
|
|
"Register",
|
|
style: ShadTheme.of(context).textTheme.h2,
|
|
),
|
|
|
|
ShadForm(
|
|
key: formKey,
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
ShadInputFormField(
|
|
id: "email",
|
|
label: const Text("Email"),
|
|
autofillHints: const [AutofillHints.email],
|
|
placeholder: const Text("Enter your email"),
|
|
validator: (value) {
|
|
if (value.isEmpty) {
|
|
return "Please enter your email";
|
|
}
|
|
if (!value.contains("@")) {
|
|
return "Please enter a valid email";
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
ShadInputFormField(
|
|
id: "password",
|
|
label: const Text("Password"),
|
|
placeholder: const Text("Enter your password"),
|
|
autofillHints: const [AutofillHints.password],
|
|
obscureText: true,
|
|
validator: (value) {
|
|
if (value.isEmpty) {
|
|
return "Please enter your password";
|
|
}
|
|
if (value.length < 8) {
|
|
return "Password must be at least 8 characters long";
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
ShadInputFormField(
|
|
id: "confirmPassword",
|
|
label: const Text("Confirm password"),
|
|
placeholder: const Text("Re-enter your password"),
|
|
autofillHints: const [AutofillHints.password],
|
|
obscureText: true,
|
|
validator: (value) {
|
|
if (value.isEmpty) {
|
|
return "Please enter your password";
|
|
}
|
|
if (value.length < 8) {
|
|
return "Password must be at least 8 characters long";
|
|
}
|
|
if (value != formKey.currentState!.value["password"]) {
|
|
return "Passwords do not match";
|
|
}
|
|
return null;
|
|
},
|
|
)
|
|
],
|
|
),
|
|
),
|
|
|
|
ShadButton(
|
|
text: const Text("Register"),
|
|
width: double.infinity,
|
|
onPressed: () async {
|
|
if (formKey.currentState!.validate()) {
|
|
formKey.currentState!.save();
|
|
print("Logging in...");
|
|
|
|
LiveInformation liveInformation = LiveInformation();
|
|
await liveInformation.auth.createUser(
|
|
displayName: formKey.currentState!.value["email"],
|
|
username: formKey.currentState!.value["email"],
|
|
email: formKey.currentState!.value["email"],
|
|
password: formKey.currentState!.value["password"],
|
|
);
|
|
await liveInformation.auth.createEmailSession(
|
|
email: formKey.currentState!.value["email"],
|
|
password: formKey.currentState!.value["password"]
|
|
);
|
|
if (liveInformation.auth.status == AuthStatus.AUTHENTICATED) {
|
|
Navigator.pop(context);
|
|
Navigator.popAndPushNamed(context, "/multi");
|
|
} else {
|
|
ShadToaster.of(context).show(
|
|
const ShadToast(
|
|
title: Text("Failed to register"),
|
|
description: Text("Failed to register with the provided details"),
|
|
duration: Duration(seconds: 5),
|
|
)
|
|
);
|
|
}
|
|
}
|
|
},
|
|
),
|
|
|
|
Row(
|
|
mainAxisSize: MainAxisSize.max,
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const Text("Have an account?"),
|
|
ShadButton.link(
|
|
onPressed: () {
|
|
Navigator.pushNamed(context, "/multi/login");
|
|
},
|
|
text: const Text("Login"),
|
|
)
|
|
],
|
|
)
|
|
|
|
],
|
|
|
|
),
|
|
),
|
|
|
|
),
|
|
),
|
|
const Divider(
|
|
height: 1,
|
|
),
|
|
NavigationBar()
|
|
],
|
|
),
|
|
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
class NavigationBar extends StatefulWidget {
|
|
|
|
final Widget? content;
|
|
|
|
NavigationBar({this.content = null});
|
|
|
|
@override
|
|
State<NavigationBar> createState() => _NavigationBarState();
|
|
}
|
|
|
|
class _NavigationBarState extends State<NavigationBar> {
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
|
|
// Is the on screen keyboard visible?
|
|
bool isKeyboardVisible = MediaQuery.of(context).viewInsets.bottom > 0;
|
|
if (isKeyboardVisible) {
|
|
return Container();
|
|
}
|
|
|
|
return Row(
|
|
children: [
|
|
ShadButton.ghost(
|
|
onPressed: () {
|
|
Navigator.pop(context);
|
|
},
|
|
icon: const Icon(Icons.arrow_back),
|
|
text: const Text("Back"),
|
|
padding: const EdgeInsets.all(8),
|
|
),
|
|
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
List<Widget> _getNearbyRoutes({bool multiMode = false}) {
|
|
|
|
print("Getting nearby routes");
|
|
|
|
LiveInformation liveInformation = LiveInformation();
|
|
BusSequences busSequences = liveInformation.busSequences;
|
|
|
|
List<BusRoute> 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<Widget> routeCards = [];
|
|
|
|
for (BusRoute route in nearbyRoutes) {
|
|
routeCards.add(RouteCard(route: route, multiMode: multiMode));
|
|
}
|
|
|
|
return routeCards;
|
|
|
|
} |