Compare commits

...

6 Commits

16 changed files with 466 additions and 297 deletions

View File

@@ -14,8 +14,9 @@ PODS:
- geolocator_apple (1.2.0): - geolocator_apple (1.2.0):
- Flutter - Flutter
- OrderedSet (5.0.0) - OrderedSet (5.0.0)
- path_provider_ios (0.0.1): - path_provider_foundation (0.0.1):
- Flutter - Flutter
- FlutterMacOS
- url_launcher_ios (0.0.1): - url_launcher_ios (0.0.1):
- Flutter - Flutter
@@ -25,7 +26,7 @@ DEPENDENCIES:
- flutter_compass (from `.symlinks/plugins/flutter_compass/ios`) - flutter_compass (from `.symlinks/plugins/flutter_compass/ios`)
- flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/ios`) - flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/ios`)
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/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`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
SPEC REPOS: SPEC REPOS:
@@ -43,8 +44,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/flutter_inappwebview/ios" :path: ".symlinks/plugins/flutter_inappwebview/ios"
geolocator_apple: geolocator_apple:
:path: ".symlinks/plugins/geolocator_apple/ios" :path: ".symlinks/plugins/geolocator_apple/ios"
path_provider_ios: path_provider_foundation:
:path: ".symlinks/plugins/path_provider_ios/ios" :path: ".symlinks/plugins/path_provider_foundation/darwin"
url_launcher_ios: url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios" :path: ".symlinks/plugins/url_launcher_ios/ios"
@@ -55,9 +56,9 @@ SPEC CHECKSUMS:
flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721 flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721
geolocator_apple: cc556e6844d508c95df1e87e3ea6fa4e58c50401 geolocator_apple: cc556e6844d508c95df1e87e3ea6fa4e58c50401
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
COCOAPODS: 1.11.3 COCOAPODS: 1.12.1

View File

@@ -156,7 +156,7 @@
97C146E61CF9000F007C117D /* Project object */ = { 97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastUpgradeCheck = 1300; LastUpgradeCheck = 1430;
ORGANIZATIONNAME = ""; ORGANIZATIONNAME = "";
TargetAttributes = { TargetAttributes = {
97C146ED1CF9000F007C117D = { 97C146ED1CF9000F007C117D = {
@@ -205,6 +205,7 @@
files = ( files = (
); );
inputPaths = ( inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
); );
name = "Thin Binary"; name = "Thin Binary";
outputPaths = ( outputPaths = (

View File

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

View File

@@ -57,16 +57,7 @@ class _AppPageLayoutState extends State<AppPageLayout> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
body: AnnotatedRegion<SystemUiOverlayStyle>( body: AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle( value: constructOverlayStyle(dark: widget.darkStatusBar),
statusBarColor: Colors.transparent,
systemNavigationBarContrastEnforced: true,
statusBarIconBrightness: widget.darkStatusBar
? Brightness.dark
: Brightness.light,
statusBarBrightness: widget.darkStatusBar
? Brightness.light
: Brightness.dark,
),
child: Container( child: Container(
color: widget.backgroundColor ?? Colors.grey[100], color: widget.backgroundColor ?? Colors.grey[100],
child: SafeArea( child: SafeArea(

View File

@@ -35,15 +35,14 @@ class _MainLayoutState extends State<MainLayout> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AutoTabsScaffold( return AutoTabsScaffold(
animationDuration: const Duration(milliseconds: 150),
routes: const [ routes: const [
HomePageRouter(children: [ HomePageRouter(children: [
HomeRoute(), HomeRoute(),
]), ]),
MapRoute(), MapRoute(),
EventsRoute(), EventsRoute(),
InfoPageRouter(children: [ InfoPageRouter(children: [InfoRoute()]),
InfoRoute()
]),
], ],
bottomNavigationBuilder: (_, tabsRouter) { bottomNavigationBuilder: (_, tabsRouter) {
return WillPopScope( return WillPopScope(
@@ -69,7 +68,7 @@ class _MainLayoutState extends State<MainLayout> {
padding: const EdgeInsets.symmetric(vertical: 3, horizontal: 20), padding: const EdgeInsets.symmetric(vertical: 3, horizontal: 20),
child: SalomonBottomBar( child: SalomonBottomBar(
itemPadding: itemPadding:
const EdgeInsets.symmetric(vertical: 12, horizontal: 16), const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
items: <SalomonBottomBarItem>[ items: <SalomonBottomBarItem>[
SalomonBottomBarItem( SalomonBottomBarItem(
icon: const Icon(FlutterRemix.home_line), icon: const Icon(FlutterRemix.home_line),

View File

@@ -83,15 +83,13 @@ class _HomeContentState extends State<HomeContent> {
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
const HeaderWidget( const HeaderWidget(
title: "Today's Events", title: "Upcoming Events",
link: HeaderLink( link:
text: "View more", HeaderLink(text: "View more", href: EventsRoute()),
href: EventsRoute()
),
), ),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.symmetric(horizontal: 20),
child: EventsList(), child: EventsList(limit: 3),
), ),
const HeaderWidget(title: "Food & Dining"), const HeaderWidget(title: "Food & Dining"),
const RestaurantsList(), const RestaurantsList(),

View File

@@ -4,6 +4,7 @@ import 'package:flutter/services.dart';
import 'package:furman_now/src/routes/index.gr.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/content.dart';
import 'package:furman_now/src/screens/home/state.dart'; import 'package:furman_now/src/screens/home/state.dart';
import 'package:furman_now/src/utils/theme.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class HomeScreenProvider extends StatelessWidget { class HomeScreenProvider extends StatelessWidget {
@@ -101,7 +102,7 @@ class _HomeScreenState extends State<HomeScreen> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
body: AnnotatedRegion<SystemUiOverlayStyle>( body: AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.light, value: constructOverlayStyle(),
child: Container( child: Container(
color: const Color(0xffb7acc9), color: const Color(0xffb7acc9),
child: SafeArea( child: SafeArea(
@@ -121,7 +122,10 @@ class _HomeScreenState extends State<HomeScreen> {
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: Container( child: Container(
height: overscrollBottomAmount + 30, height: overscrollBottomAmount + 30,
color: Colors.grey.shade50, decoration: BoxDecoration(
color: Colors.grey.shade50,
border: Border.all(width: 0, color: Colors.white),
),
), ),
), ),
Consumer<HomePageState>( 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/material.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
ThemeData _baseTheme = ThemeData( ThemeData _baseTheme = ThemeData(
@@ -47,5 +48,14 @@ ThemeData myFurmanTheme = _baseTheme.copyWith(
textTheme: GoogleFonts.interTextTheme(_baseTheme.textTheme), textTheme: GoogleFonts.interTextTheme(_baseTheme.textTheme),
); );
var furmanTextStyle = (TextStyle baseStyle) => var furmanTextStyle =
GoogleFonts.inter(textStyle: baseStyle); (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 { class EventsList extends StatefulWidget {
final DateTimeRange dateRange; final DateTimeRange dateRange;
final int? limit;
const EventsList._({ const EventsList._({
required this.dateRange, required this.dateRange,
this.limit,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
factory EventsList({ factory EventsList({
DateTimeRange? dateRange, DateTimeRange? dateRange,
int? limit,
Key? key, Key? key,
}) { }) {
if (dateRange == null) { if (dateRange == null) {
@@ -27,6 +30,7 @@ class EventsList extends StatefulWidget {
} }
return EventsList._( return EventsList._(
dateRange: dateRange, dateRange: dateRange,
limit: limit,
key: key, key: key,
); );
} }
@@ -46,21 +50,24 @@ class _EventsListState extends State<EventsList> {
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasData) { if (snapshot.hasData) {
var events = snapshot.data!.where((event) { var events = snapshot.data!.where((event) {
return event.time.isAfter(widget.dateRange.start) && return event.time.isAfter(widget.dateRange.start) &&
event.time.isBefore(widget.dateRange.end); event.time.isBefore(widget.dateRange.end);
}); });
if (events.isNotEmpty) { if (widget.limit != null) {
return Column( events = events.take(widget.limit!);
children: events.map((event) { }
return Padding( if (events.isNotEmpty) {
padding: const EdgeInsets.only(bottom: 15), return Column(
child: EventCard(event), children: events.map((event) {
);
}).toList());
} else {
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 15), 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, width: double.infinity,
height: 50, height: 50,
child: Center( child: Center(

File diff suppressed because it is too large Load Diff

View File

@@ -35,7 +35,7 @@ dependencies:
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2
english_words: ^4.0.0 english_words: ^4.0.0
intl: ^0.17.0 intl: ^0.17.0
google_fonts: ^3.0.1 google_fonts: ^4.0.4
http: ^0.13.5 http: ^0.13.5
convert: ^3.0.2 convert: ^3.0.2
crypto: ^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);
});
}