Compare commits
8 Commits
279532ba7c
...
main
Author | SHA1 | Date | |
---|---|---|---|
54cfe936c3 | |||
2a780f13c7 | |||
7b6a276c21 | |||
3f0342b6cb | |||
513b2d2f14 | |||
662a1ae157 | |||
cb9ccc1865 | |||
be50f8b293 |
@@ -14,8 +14,9 @@ PODS:
|
||||
- geolocator_apple (1.2.0):
|
||||
- Flutter
|
||||
- OrderedSet (5.0.0)
|
||||
- path_provider_ios (0.0.1):
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
|
||||
@@ -25,7 +26,7 @@ DEPENDENCIES:
|
||||
- flutter_compass (from `.symlinks/plugins/flutter_compass/ios`)
|
||||
- flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/ios`)
|
||||
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/ios`)
|
||||
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
@@ -43,8 +44,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/flutter_inappwebview/ios"
|
||||
geolocator_apple:
|
||||
:path: ".symlinks/plugins/geolocator_apple/ios"
|
||||
path_provider_ios:
|
||||
:path: ".symlinks/plugins/path_provider_ios/ios"
|
||||
path_provider_foundation:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
|
||||
@@ -55,9 +56,9 @@ SPEC CHECKSUMS:
|
||||
flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721
|
||||
geolocator_apple: cc556e6844d508c95df1e87e3ea6fa4e58c50401
|
||||
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
|
||||
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
|
||||
url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de
|
||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
|
||||
|
||||
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
|
||||
|
||||
COCOAPODS: 1.11.3
|
||||
COCOAPODS: 1.12.1
|
||||
|
@@ -156,7 +156,7 @@
|
||||
97C146E61CF9000F007C117D /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1300;
|
||||
LastUpgradeCheck = 1430;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
97C146ED1CF9000F007C117D = {
|
||||
@@ -205,6 +205,7 @@
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
|
||||
);
|
||||
name = "Thin Binary";
|
||||
outputPaths = (
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1300"
|
||||
LastUpgradeVersion = "1430"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@@ -57,16 +57,7 @@ class _AppPageLayoutState extends State<AppPageLayout> {
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
systemNavigationBarContrastEnforced: true,
|
||||
statusBarIconBrightness: widget.darkStatusBar
|
||||
? Brightness.dark
|
||||
: Brightness.light,
|
||||
statusBarBrightness: widget.darkStatusBar
|
||||
? Brightness.light
|
||||
: Brightness.dark,
|
||||
),
|
||||
value: constructOverlayStyle(dark: widget.darkStatusBar),
|
||||
child: Container(
|
||||
color: widget.backgroundColor ?? Colors.grey[100],
|
||||
child: SafeArea(
|
||||
|
@@ -35,15 +35,14 @@ class _MainLayoutState extends State<MainLayout> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AutoTabsScaffold(
|
||||
animationDuration: const Duration(milliseconds: 150),
|
||||
routes: const [
|
||||
HomePageRouter(children: [
|
||||
HomeRoute(),
|
||||
]),
|
||||
MapRoute(),
|
||||
EventsRoute(),
|
||||
InfoPageRouter(children: [
|
||||
InfoRoute()
|
||||
]),
|
||||
InfoPageRouter(children: [InfoRoute()]),
|
||||
],
|
||||
bottomNavigationBuilder: (_, tabsRouter) {
|
||||
return WillPopScope(
|
||||
@@ -69,7 +68,7 @@ class _MainLayoutState extends State<MainLayout> {
|
||||
padding: const EdgeInsets.symmetric(vertical: 3, horizontal: 20),
|
||||
child: SalomonBottomBar(
|
||||
itemPadding:
|
||||
const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
||||
const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
||||
items: <SalomonBottomBarItem>[
|
||||
SalomonBottomBarItem(
|
||||
icon: const Icon(FlutterRemix.home_line),
|
||||
|
@@ -37,19 +37,23 @@ Route<T> mapRouteBuilder<T>(BuildContext context, Widget child, CustomPage<T> pa
|
||||
replaceInRouteName: 'Screen,Route',
|
||||
routes: <AutoRoute>[
|
||||
AutoRoute(path: "/", page: MainLayout, children: [
|
||||
AutoRoute(path: "home", name: "HomePageRouter", page: HomeScreen, children: [
|
||||
CustomRoute(
|
||||
path: "",
|
||||
page: HomePageHeader,
|
||||
name: "HomeRoute",
|
||||
transitionsBuilder: TransitionsBuilders.fadeIn,
|
||||
),
|
||||
CustomRoute(
|
||||
path: "student-id",
|
||||
page: StudentIdScreen,
|
||||
transitionsBuilder: TransitionsBuilders.fadeIn,
|
||||
),
|
||||
]),
|
||||
AutoRoute(
|
||||
path: "home",
|
||||
name: "HomePageRouter",
|
||||
page: HomeScreenProvider,
|
||||
children: [
|
||||
CustomRoute(
|
||||
path: "",
|
||||
page: HomePageHeader,
|
||||
name: "HomeRoute",
|
||||
transitionsBuilder: TransitionsBuilders.fadeIn,
|
||||
),
|
||||
CustomRoute(
|
||||
path: "student-id",
|
||||
page: StudentIdScreen,
|
||||
transitionsBuilder: TransitionsBuilders.fadeIn,
|
||||
),
|
||||
]),
|
||||
AutoRoute(path: "map", page: MapScreen, children: [
|
||||
CustomRoute(
|
||||
path: "",
|
||||
|
@@ -43,7 +43,7 @@ class AppRouter extends _i15.RootStackRouter {
|
||||
},
|
||||
HomePageRouter.name: (routeData) {
|
||||
return _i15.MaterialPageX<dynamic>(
|
||||
routeData: routeData, child: const _i2.HomeScreen());
|
||||
routeData: routeData, child: _i2.HomeScreenProvider());
|
||||
},
|
||||
MapRoute.name: (routeData) {
|
||||
return _i15.MaterialPageX<dynamic>(
|
||||
|
@@ -83,15 +83,13 @@ class _HomeContentState extends State<HomeContent> {
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const HeaderWidget(
|
||||
title: "Today's Events",
|
||||
link: HeaderLink(
|
||||
text: "View more",
|
||||
href: EventsRoute()
|
||||
),
|
||||
title: "Upcoming Events",
|
||||
link:
|
||||
HeaderLink(text: "View more", href: EventsRoute()),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: EventsList(),
|
||||
child: EventsList(limit: 3),
|
||||
),
|
||||
const HeaderWidget(title: "Food & Dining"),
|
||||
const RestaurantsList(),
|
||||
|
@@ -4,9 +4,12 @@ import 'package:flutter/services.dart';
|
||||
import 'package:furman_now/src/routes/index.gr.dart';
|
||||
import 'package:furman_now/src/screens/home/content.dart';
|
||||
import 'package:furman_now/src/screens/home/state.dart';
|
||||
import 'package:furman_now/src/utils/theme.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class HomeScreenProvider extends StatelessWidget {
|
||||
const HomeScreenProvider({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider(
|
||||
@@ -14,7 +17,6 @@ class HomeScreenProvider extends StatelessWidget {
|
||||
child: const HomeScreen(),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class HomeScreen extends StatefulWidget {
|
||||
@@ -100,7 +102,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle.light,
|
||||
value: constructOverlayStyle(),
|
||||
child: Container(
|
||||
color: const Color(0xffb7acc9),
|
||||
child: SafeArea(
|
||||
@@ -120,7 +122,10 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Container(
|
||||
height: overscrollBottomAmount + 30,
|
||||
color: Colors.grey.shade50,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade50,
|
||||
border: Border.all(width: 0, color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
Consumer<HomePageState>(
|
||||
|
61
lib/src/services/transportation/route.dart
Normal file
61
lib/src/services/transportation/route.dart
Normal file
@@ -0,0 +1,61 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:furman_now/src/widgets/map/route_marker.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
class TransportationRoute {
|
||||
final Polyline route;
|
||||
final List<Stop> stops;
|
||||
|
||||
TransportationRoute({
|
||||
required this.route,
|
||||
required this.stops,
|
||||
});
|
||||
|
||||
factory TransportationRoute.fromPoints({
|
||||
required List<LatLng> points,
|
||||
required Color color,
|
||||
required List<Stop> stops,
|
||||
}) {
|
||||
var route = Polyline(
|
||||
points: points,
|
||||
color: color,
|
||||
strokeWidth: 4,
|
||||
);
|
||||
return TransportationRoute(
|
||||
route: route,
|
||||
stops: stops,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Stop {
|
||||
int id;
|
||||
String name;
|
||||
LatLng location;
|
||||
|
||||
Stop({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.location,
|
||||
});
|
||||
|
||||
factory Stop.fromLiveSafeJson(Map<String, dynamic> json) {
|
||||
return Stop(
|
||||
id: json["AddressID"],
|
||||
name: json["Description"],
|
||||
location: LatLng(json["Latitude"], json["Longitude"]),
|
||||
);
|
||||
}
|
||||
|
||||
Marker toMarker({required Color color}) {
|
||||
return Marker(
|
||||
point: location,
|
||||
height: 100,
|
||||
width: 100,
|
||||
builder: (context) => RouteMarker(color: color),
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,3 +1,48 @@
|
||||
class TransportationSafeRideShuttleService {
|
||||
import 'dart:convert';
|
||||
|
||||
}
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:furman_now/src/utils/decode_polyline.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import 'route.dart';
|
||||
|
||||
class TransportationSafeRideShuttleService {
|
||||
static const service =
|
||||
"https://furmansaferide.ridesystems.net/Services/JSONPRelay.svc";
|
||||
|
||||
static const apiKey = "8882812681";
|
||||
|
||||
static Future<http.Response> _serviceRequest(
|
||||
String endpoint, Map<String, dynamic>? queryParameters) async {
|
||||
Uri serviceUri = Uri.https(
|
||||
'furmansaferide.ridesystems.net',
|
||||
"/Services/JSONPRelay.svc/$endpoint",
|
||||
{...?queryParameters, 'apiKey': apiKey});
|
||||
return http.get(serviceUri);
|
||||
}
|
||||
|
||||
static Future<TransportationRoute> fetchShuttleRoute() async {
|
||||
final response = await _serviceRequest(
|
||||
'GetRoutesForMapWithScheduleWithEncodedLine', {'isDispatch': 'false'});
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
// If the server did return a 200 OK response,
|
||||
// then parse the JSON.
|
||||
final json = jsonDecode(response.body);
|
||||
String encodedPolyline = json[0]["EncodedPolyline"];
|
||||
final points = decodeEncodedPolyline(encodedPolyline);
|
||||
var stops = (json[0]["Stops"] as List<dynamic>)
|
||||
.map((e) => Stop.fromLiveSafeJson(e))
|
||||
.toList();
|
||||
return TransportationRoute.fromPoints(
|
||||
points: points,
|
||||
color: Colors.red.shade300,
|
||||
stops: stops,
|
||||
);
|
||||
} else {
|
||||
// If the server did not return a 200 OK response,
|
||||
// then throw an exception.
|
||||
throw Exception('Failed to load SafeRide shuttle route.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
20
lib/src/services/transportation/vehicle.dart
Normal file
20
lib/src/services/transportation/vehicle.dart
Normal file
@@ -0,0 +1,20 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract class TransportationVehicle {
|
||||
// name of the vehicle
|
||||
String get name;
|
||||
|
||||
// is the vehicle currently running?
|
||||
VehicleStatus get status;
|
||||
|
||||
// name of vehicle's next stop
|
||||
String get nextStop;
|
||||
|
||||
// vehicle icon
|
||||
IconData get icon;
|
||||
|
||||
// vehicle route
|
||||
TransitionRoute get route;
|
||||
}
|
||||
|
||||
enum VehicleStatus { running, stopped }
|
34
lib/src/utils/decode_polyline.dart
Normal file
34
lib/src/utils/decode_polyline.dart
Normal file
@@ -0,0 +1,34 @@
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
/// Decodes the an encoded polyline using the Encoded Polyline Algorithm Format
|
||||
/// for more info about the algorithm check
|
||||
/// https://developers.google.com/maps/documentation/utilities/polylinealgorithm
|
||||
List<LatLng> decodeEncodedPolyline(String encoded) {
|
||||
List<LatLng> poly = [];
|
||||
int index = 0, len = encoded.length;
|
||||
int lat = 0, lng = 0;
|
||||
|
||||
while (index < len) {
|
||||
int b, shift = 0, result = 0;
|
||||
do {
|
||||
b = encoded.codeUnitAt(index++) - 63;
|
||||
result |= (b & 0x1f) << shift;
|
||||
shift += 5;
|
||||
} while (b >= 0x20);
|
||||
int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
|
||||
lat += dlat;
|
||||
|
||||
shift = 0;
|
||||
result = 0;
|
||||
do {
|
||||
b = encoded.codeUnitAt(index++) - 63;
|
||||
result |= (b & 0x1f) << shift;
|
||||
shift += 5;
|
||||
} while (b >= 0x20);
|
||||
int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
|
||||
lng += dlng;
|
||||
LatLng p = LatLng((lat / 1E5).toDouble(), (lng / 1E5).toDouble());
|
||||
poly.add(p);
|
||||
}
|
||||
return poly;
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
ThemeData _baseTheme = ThemeData(
|
||||
@@ -47,5 +48,14 @@ ThemeData myFurmanTheme = _baseTheme.copyWith(
|
||||
textTheme: GoogleFonts.interTextTheme(_baseTheme.textTheme),
|
||||
);
|
||||
|
||||
var furmanTextStyle = (TextStyle baseStyle) =>
|
||||
GoogleFonts.inter(textStyle: baseStyle);
|
||||
var furmanTextStyle =
|
||||
(TextStyle baseStyle) => GoogleFonts.inter(textStyle: baseStyle);
|
||||
|
||||
SystemUiOverlayStyle constructOverlayStyle({bool dark = false}) {
|
||||
return SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
systemNavigationBarContrastEnforced: true,
|
||||
statusBarIconBrightness: dark ? Brightness.dark : Brightness.light,
|
||||
statusBarBrightness: dark ? Brightness.light : Brightness.dark,
|
||||
systemNavigationBarColor: Colors.grey.shade50);
|
||||
}
|
||||
|
@@ -8,14 +8,17 @@ import 'event_card.dart';
|
||||
|
||||
class EventsList extends StatefulWidget {
|
||||
final DateTimeRange dateRange;
|
||||
final int? limit;
|
||||
|
||||
const EventsList._({
|
||||
required this.dateRange,
|
||||
this.limit,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
factory EventsList({
|
||||
DateTimeRange? dateRange,
|
||||
int? limit,
|
||||
Key? key,
|
||||
}) {
|
||||
if (dateRange == null) {
|
||||
@@ -27,6 +30,7 @@ class EventsList extends StatefulWidget {
|
||||
}
|
||||
return EventsList._(
|
||||
dateRange: dateRange,
|
||||
limit: limit,
|
||||
key: key,
|
||||
);
|
||||
}
|
||||
@@ -46,21 +50,24 @@ class _EventsListState extends State<EventsList> {
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
var events = snapshot.data!.where((event) {
|
||||
return event.time.isAfter(widget.dateRange.start) &&
|
||||
event.time.isBefore(widget.dateRange.end);
|
||||
});
|
||||
if (events.isNotEmpty) {
|
||||
return Column(
|
||||
children: events.map((event) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 15),
|
||||
child: EventCard(event),
|
||||
);
|
||||
}).toList());
|
||||
} else {
|
||||
return event.time.isAfter(widget.dateRange.start) &&
|
||||
event.time.isBefore(widget.dateRange.end);
|
||||
});
|
||||
if (widget.limit != null) {
|
||||
events = events.take(widget.limit!);
|
||||
}
|
||||
if (events.isNotEmpty) {
|
||||
return Column(
|
||||
children: events.map((event) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 15),
|
||||
child: SizedBox(
|
||||
child: EventCard(event),
|
||||
);
|
||||
}).toList());
|
||||
} else {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 15),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
height: 50,
|
||||
child: Center(
|
||||
|
472
pubspec.lock
472
pubspec.lock
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
name: furman_now
|
||||
description: A new Flutter project.
|
||||
description: The place for all things Furman.
|
||||
|
||||
# The following line prevents the package from being accidentally published to
|
||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
||||
@@ -35,7 +35,7 @@ dependencies:
|
||||
cupertino_icons: ^1.0.2
|
||||
english_words: ^4.0.0
|
||||
intl: ^0.17.0
|
||||
google_fonts: ^3.0.1
|
||||
google_fonts: ^4.0.4
|
||||
http: ^0.13.5
|
||||
convert: ^3.0.2
|
||||
crypto: ^3.0.2
|
||||
|
22
test/service_tests/transportation/saferide_shuttle_test.dart
Normal file
22
test/service_tests/transportation/saferide_shuttle_test.dart
Normal file
@@ -0,0 +1,22 @@
|
||||
import "package:furman_now/src/services/transportation/saferide_shuttle.dart";
|
||||
import "package:test/test.dart";
|
||||
|
||||
void main() {
|
||||
test("shuttle status is fetched successfully", () async {
|
||||
var route = await TransportationSafeRideShuttleService.fetchShuttleRoute();
|
||||
|
||||
// ensure polyline has points
|
||||
expect(route.route.points.length, isNonZero);
|
||||
// ensure stops are listed
|
||||
expect(route.stops.length, isNonZero);
|
||||
});
|
||||
|
||||
test("shuttle route is fetched successfully", () async {
|
||||
var route = await TransportationSafeRideShuttleService.fetchShuttleRoute();
|
||||
|
||||
// ensure polyline has points
|
||||
expect(route.route.points.length, isNonZero);
|
||||
// ensure stops are listed
|
||||
expect(route.stops.length, isNonZero);
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user