Compare commits

...

8 Commits

18 changed files with 487 additions and 313 deletions

View File

@@ -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

View File

@@ -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 = (

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
LastUpgradeVersion = "1430"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -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(

View File

@@ -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),

View File

@@ -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: "",

View File

@@ -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>(

View File

@@ -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(),

View File

@@ -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>(

View 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),
);
}
}

View File

@@ -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.');
}
}
}

View 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 }

View 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;
}

View File

@@ -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);
}

View File

@@ -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(

File diff suppressed because it is too large Load Diff

View File

@@ -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

View 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);
});
}