diff --git a/.gitignore b/.gitignore index dac07a82..7fffa3d2 100644 --- a/.gitignore +++ b/.gitignore @@ -109,5 +109,7 @@ app.*.symbols # Secrets (shhhh) google-services.json tokens.txt -**/api-keys.xml -android/app/src/main/res/values/api-keys.xml \ No newline at end of file +api-keys.xml +android/app/src/main/res/values/api-keys.xml +strings.dart +android/app/src/main/res/api-keys.template diff --git a/README.md b/README.md index fe8db7cc..349f3b29 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,12 @@ Interact with our [mockups](https://xd.adobe.com/view/8a421d6f-ad6f-4196-7089-ff - For AMD CPUs: Enable the Windows Hypervisor Platform in Windows Features 4. Clone the smartrider repo with `git clone https://github.com/sirmammingtonham/smartrider.git`. 5. Download the `google-services.json` from the Firebase project and place it in the `android/app` folder. -6. Copy the API key for the Google Maps SDK from your Google Developers Console project and add it to `android/app/src/main/res/values/api-keys.xml`. -7. Download the required packages by following the steps in your editor of choice, or by running `flutter pub get` inside the cloned repository folder. -8. Open the emulator and run `lib/main.dart` in your editor, or run `flutter run` in the repo folder. +6. Copy and rename `android/app/src/main/res/api-keys.template` to `android/app/src/main/res/values/api-keys.xml` (Don't delete the template file!) + - Copy the API key for the Google Maps SDK from your Google Developers Console project and add it to `android/app/src/main/res/values/api-keys.xml`. +7. Create a file named `strings.dart` in the `lib/util` folder. + - Add the line `const google_api_key = "KEY_HERE";` and replace `KEY_HERE` with the same API key for Google Maps. +8. Download the required packages by following the steps in your editor of choice, or by running `flutter pub get` inside the cloned repository folder. +9. Open the emulator and run `lib/main.dart` in your editor, or run `flutter run` in the repo folder. ## Setting up (on Windows for iOS Development) 1. Follow the same steps to set up on windows for android development. diff --git a/assets/map_styles/aubergine.json b/assets/map_styles/aubergine.json new file mode 100644 index 00000000..d35a10c8 --- /dev/null +++ b/assets/map_styles/aubergine.json @@ -0,0 +1,233 @@ +[ + { + "elementType": "geometry", + "stylers": [ + { + "color": "#1d2c4d" + } + ] + }, + { + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#8ec3b9" + } + ] + }, + { + "elementType": "labels.text.stroke", + "stylers": [ + { + "color": "#1a3646" + } + ] + }, + { + "featureType": "administrative.country", + "elementType": "geometry.stroke", + "stylers": [ + { + "color": "#4b6878" + } + ] + }, + { + "featureType": "administrative.land_parcel", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#64779e" + } + ] + }, + { + "featureType": "administrative.province", + "elementType": "geometry.stroke", + "stylers": [ + { + "color": "#4b6878" + } + ] + }, + { + "featureType": "landscape.man_made", + "elementType": "geometry.stroke", + "stylers": [ + { + "color": "#334e87" + } + ] + }, + { + "featureType": "landscape.natural", + "elementType": "geometry", + "stylers": [ + { + "color": "#023e58" + } + ] + }, + { + "featureType": "poi", + "elementType": "geometry", + "stylers": [ + { + "color": "#283d6a" + } + ] + }, + { + "featureType": "poi", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#6f9ba5" + } + ] + }, + { + "featureType": "poi", + "elementType": "labels.text.stroke", + "stylers": [ + { + "color": "#1d2c4d" + } + ] + }, + { + "featureType": "poi.park", + "elementType": "geometry.fill", + "stylers": [ + { + "color": "#023e58" + } + ] + }, + { + "featureType": "poi.park", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#3C7680" + } + ] + }, + { + "featureType": "road", + "elementType": "geometry", + "stylers": [ + { + "color": "#304a7d" + } + ] + }, + { + "featureType": "road", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#98a5be" + } + ] + }, + { + "featureType": "road", + "elementType": "labels.text.stroke", + "stylers": [ + { + "color": "#1d2c4d" + } + ] + }, + { + "featureType": "road.highway", + "elementType": "geometry", + "stylers": [ + { + "color": "#2c6675" + } + ] + }, + { + "featureType": "road.highway", + "elementType": "geometry.stroke", + "stylers": [ + { + "color": "#255763" + } + ] + }, + { + "featureType": "road.highway", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#b0d5ce" + } + ] + }, + { + "featureType": "road.highway", + "elementType": "labels.text.stroke", + "stylers": [ + { + "color": "#023e58" + } + ] + }, + { + "featureType": "transit", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#98a5be" + } + ] + }, + { + "featureType": "transit", + "elementType": "labels.text.stroke", + "stylers": [ + { + "color": "#1d2c4d" + } + ] + }, + { + "featureType": "transit.line", + "elementType": "geometry.fill", + "stylers": [ + { + "color": "#283d6a" + } + ] + }, + { + "featureType": "transit.station", + "elementType": "geometry", + "stylers": [ + { + "color": "#3a4762" + } + ] + }, + { + "featureType": "water", + "elementType": "geometry", + "stylers": [ + { + "color": "#0e1626" + } + ] + }, + { + "featureType": "water", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#4e6d70" + } + ] + } + ] \ No newline at end of file diff --git a/assets/map_styles/dark.json b/assets/map_styles/dark.json new file mode 100644 index 00000000..11881da3 --- /dev/null +++ b/assets/map_styles/dark.json @@ -0,0 +1,161 @@ +[ + { + "elementType": "geometry", + "stylers": [ + { + "color": "#242f3e" + } + ] + }, + { + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#746855" + } + ] + }, + { + "elementType": "labels.text.stroke", + "stylers": [ + { + "color": "#242f3e" + } + ] + }, + { + "featureType": "administrative.locality", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#d59563" + } + ] + }, + { + "featureType": "poi", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#d59563" + } + ] + }, + { + "featureType": "poi.park", + "elementType": "geometry", + "stylers": [ + { + "color": "#263c3f" + } + ] + }, + { + "featureType": "poi.park", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#6b9a76" + } + ] + }, + { + "featureType": "road", + "elementType": "geometry", + "stylers": [ + { + "color": "#38414e" + } + ] + }, + { + "featureType": "road", + "elementType": "geometry.stroke", + "stylers": [ + { + "color": "#212a37" + } + ] + }, + { + "featureType": "road", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#9ca5b3" + } + ] + }, + { + "featureType": "road.highway", + "elementType": "geometry", + "stylers": [ + { + "color": "#746855" + } + ] + }, + { + "featureType": "road.highway", + "elementType": "geometry.stroke", + "stylers": [ + { + "color": "#1f2835" + } + ] + }, + { + "featureType": "road.highway", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#f3d19c" + } + ] + }, + { + "featureType": "transit", + "elementType": "geometry", + "stylers": [ + { + "color": "#2f3948" + } + ] + }, + { + "featureType": "transit.station", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#d59563" + } + ] + }, + { + "featureType": "water", + "elementType": "geometry", + "stylers": [ + { + "color": "#17263c" + } + ] + }, + { + "featureType": "water", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#515c6d" + } + ] + }, + { + "featureType": "water", + "elementType": "labels.text.stroke", + "stylers": [ + { + "color": "#17263c" + } + ] + } + ] \ No newline at end of file diff --git a/assets/marker_bus.png b/assets/marker_bus.png new file mode 100644 index 00000000..37ae9541 Binary files /dev/null and b/assets/marker_bus.png differ diff --git a/assets/marker_shuttle.png b/assets/marker_shuttle.png new file mode 100644 index 00000000..e88a8dc8 Binary files /dev/null and b/assets/marker_shuttle.png differ diff --git a/assets/shuttle_jsons/north.json b/assets/shuttle_jsons/north.json new file mode 100644 index 00000000..47d360b0 --- /dev/null +++ b/assets/shuttle_jsons/north.json @@ -0,0 +1,862 @@ +[ + { + "latitude": 42.73031, + "longitude": -73.67663 + }, + { + "latitude": 42.73033, + "longitude": -73.67666 + }, + { + "latitude": 42.73037, + "longitude": -73.67666 + }, + { + "latitude": 42.73042, + "longitude": -73.67665 + }, + { + "latitude": 42.7307, + "longitude": -73.67658 + }, + { + "latitude": 42.7307, + "longitude": -73.67658 + }, + { + "latitude": 42.73076, + "longitude": -73.67707 + }, + { + "latitude": 42.73078, + "longitude": -73.67723 + }, + { + "latitude": 42.73081, + "longitude": -73.67737 + }, + { + "latitude": 42.73098, + "longitude": -73.67851 + }, + { + "latitude": 42.73109, + "longitude": -73.67927 + }, + { + "latitude": 42.73125, + "longitude": -73.6802 + }, + { + "latitude": 42.73126, + "longitude": -73.6803 + }, + { + "latitude": 42.73134, + "longitude": -73.68069 + }, + { + "latitude": 42.73134, + "longitude": -73.68069 + }, + { + "latitude": 42.73134, + "longitude": -73.68069 + }, + { + "latitude": 42.73134, + "longitude": -73.68069 + }, + { + "latitude": 42.73135, + "longitude": -73.68072 + }, + { + "latitude": 42.73146, + "longitude": -73.68115 + }, + { + "latitude": 42.73155, + "longitude": -73.68135 + }, + { + "latitude": 42.73158, + "longitude": -73.68138 + }, + { + "latitude": 42.73168, + "longitude": -73.68145 + }, + { + "latitude": 42.7319, + "longitude": -73.68158 + }, + { + "latitude": 42.73218, + "longitude": -73.68181 + }, + { + "latitude": 42.73236, + "longitude": -73.68196 + }, + { + "latitude": 42.7327, + "longitude": -73.68226 + }, + { + "latitude": 42.73279, + "longitude": -73.68236 + }, + { + "latitude": 42.73286, + "longitude": -73.68248 + }, + { + "latitude": 42.73291, + "longitude": -73.68255 + }, + { + "latitude": 42.73291, + "longitude": -73.68255 + }, + { + "latitude": 42.73399, + "longitude": -73.68226 + }, + { + "latitude": 42.73399, + "longitude": -73.68226 + }, + { + "latitude": 42.73397, + "longitude": -73.68207 + }, + { + "latitude": 42.73395, + "longitude": -73.68189 + }, + { + "latitude": 42.73383, + "longitude": -73.68133 + }, + { + "latitude": 42.73377, + "longitude": -73.681 + }, + { + "latitude": 42.73374, + "longitude": -73.68085 + }, + { + "latitude": 42.73374, + "longitude": -73.68085 + }, + { + "latitude": 42.73374, + "longitude": -73.68085 + }, + { + "latitude": 42.73374, + "longitude": -73.68085 + }, + { + "latitude": 42.73365, + "longitude": -73.68036 + }, + { + "latitude": 42.73349, + "longitude": -73.67959 + }, + { + "latitude": 42.73346, + "longitude": -73.67943 + }, + { + "latitude": 42.7333, + "longitude": -73.67847 + }, + { + "latitude": 42.73317, + "longitude": -73.67753 + }, + { + "latitude": 42.73305, + "longitude": -73.67669 + }, + { + "latitude": 42.73303, + "longitude": -73.67659 + }, + { + "latitude": 42.73303, + "longitude": -73.67659 + }, + { + "latitude": 42.73313, + "longitude": -73.67656 + }, + { + "latitude": 42.73421, + "longitude": -73.67626 + }, + { + "latitude": 42.73425, + "longitude": -73.67624 + }, + { + "latitude": 42.73431, + "longitude": -73.67623 + }, + { + "latitude": 42.73468, + "longitude": -73.67612 + }, + { + "latitude": 42.73468, + "longitude": -73.67612 + }, + { + "latitude": 42.73468, + "longitude": -73.67612 + }, + { + "latitude": 42.73468, + "longitude": -73.67612 + }, + { + "latitude": 42.73527, + "longitude": -73.67595 + }, + { + "latitude": 42.73534, + "longitude": -73.67594 + }, + { + "latitude": 42.73534, + "longitude": -73.67594 + }, + { + "latitude": 42.73532, + "longitude": -73.67585 + }, + { + "latitude": 42.73519, + "longitude": -73.67498 + }, + { + "latitude": 42.73507, + "longitude": -73.67404 + }, + { + "latitude": 42.73495, + "longitude": -73.67328 + }, + { + "latitude": 42.73484, + "longitude": -73.67254 + }, + { + "latitude": 42.73484, + "longitude": -73.67254 + }, + { + "latitude": 42.73484, + "longitude": -73.67254 + }, + { + "latitude": 42.73507, + "longitude": -73.67404 + }, + { + "latitude": 42.73484, + "longitude": -73.67254 + }, + { + "latitude": 42.73478, + "longitude": -73.67217 + }, + { + "latitude": 42.73462, + "longitude": -73.6711 + }, + { + "latitude": 42.73462, + "longitude": -73.6711 + }, + { + "latitude": 42.7353, + "longitude": -73.6709 + }, + { + "latitude": 42.73569, + "longitude": -73.67079 + }, + { + "latitude": 42.73646, + "longitude": -73.67058 + }, + { + "latitude": 42.73693, + "longitude": -73.67045 + }, + { + "latitude": 42.73826, + "longitude": -73.67008 + }, + { + "latitude": 42.73826, + "longitude": -73.67008 + }, + { + "latitude": 42.73816, + "longitude": -73.66929 + }, + { + "latitude": 42.7381, + "longitude": -73.66888 + }, + { + "latitude": 42.7381, + "longitude": -73.66888 + }, + { + "latitude": 42.7381, + "longitude": -73.66888 + }, + { + "latitude": 42.7381, + "longitude": -73.66888 + }, + { + "latitude": 42.73801, + "longitude": -73.66822 + }, + { + "latitude": 42.73799, + "longitude": -73.66806 + }, + { + "latitude": 42.73795, + "longitude": -73.66778 + }, + { + "latitude": 42.73783, + "longitude": -73.66698 + }, + { + "latitude": 42.73777, + "longitude": -73.66656 + }, + { + "latitude": 42.73777, + "longitude": -73.66656 + }, + { + "latitude": 42.73596, + "longitude": -73.66711 + }, + { + "latitude": 42.73596, + "longitude": -73.66711 + }, + { + "latitude": 42.73579, + "longitude": -73.66587 + }, + { + "latitude": 42.73566, + "longitude": -73.66509 + }, + { + "latitude": 42.73559, + "longitude": -73.66447 + }, + { + "latitude": 42.73553, + "longitude": -73.66395 + }, + { + "latitude": 42.73553, + "longitude": -73.66395 + }, + { + "latitude": 42.73553, + "longitude": -73.66395 + }, + { + "latitude": 42.73553, + "longitude": -73.66395 + }, + { + "latitude": 42.73547, + "longitude": -73.66343 + }, + { + "latitude": 42.73545, + "longitude": -73.66335 + }, + { + "latitude": 42.73538, + "longitude": -73.66324 + }, + { + "latitude": 42.73538, + "longitude": -73.66324 + }, + { + "latitude": 42.7353, + "longitude": -73.66319 + }, + { + "latitude": 42.73522, + "longitude": -73.66319 + }, + { + "latitude": 42.73467, + "longitude": -73.66334 + }, + { + "latitude": 42.73445, + "longitude": -73.6634 + }, + { + "latitude": 42.73426, + "longitude": -73.66344 + }, + { + "latitude": 42.73414, + "longitude": -73.66347 + }, + { + "latitude": 42.73405, + "longitude": -73.66351 + }, + { + "latitude": 42.73398, + "longitude": -73.66356 + }, + { + "latitude": 42.73385, + "longitude": -73.66369 + }, + { + "latitude": 42.73365, + "longitude": -73.66394 + }, + { + "latitude": 42.73351, + "longitude": -73.66411 + }, + { + "latitude": 42.73283, + "longitude": -73.66498 + }, + { + "latitude": 42.73267, + "longitude": -73.66518 + }, + { + "latitude": 42.73226, + "longitude": -73.66568 + }, + { + "latitude": 42.73217, + "longitude": -73.66579 + }, + { + "latitude": 42.73205, + "longitude": -73.66592 + }, + { + "latitude": 42.73205, + "longitude": -73.66592 + }, + { + "latitude": 42.73205, + "longitude": -73.66592 + }, + { + "latitude": 42.73205, + "longitude": -73.66592 + }, + { + "latitude": 42.73201, + "longitude": -73.66596 + }, + { + "latitude": 42.73182, + "longitude": -73.66614 + }, + { + "latitude": 42.73141, + "longitude": -73.66646 + }, + { + "latitude": 42.73127, + "longitude": -73.66657 + }, + { + "latitude": 42.73104, + "longitude": -73.66675 + }, + { + "latitude": 42.73099, + "longitude": -73.6668 + }, + { + "latitude": 42.73096, + "longitude": -73.66685 + }, + { + "latitude": 42.73093, + "longitude": -73.66691 + }, + { + "latitude": 42.7309, + "longitude": -73.667 + }, + { + "latitude": 42.73088, + "longitude": -73.6671 + }, + { + "latitude": 42.73086, + "longitude": -73.66718 + }, + { + "latitude": 42.73086, + "longitude": -73.66724 + }, + { + "latitude": 42.73086, + "longitude": -73.66724 + }, + { + "latitude": 42.73087, + "longitude": -73.66731 + }, + { + "latitude": 42.73089, + "longitude": -73.66736 + }, + { + "latitude": 42.73093, + "longitude": -73.66749 + }, + { + "latitude": 42.73101, + "longitude": -73.66768 + }, + { + "latitude": 42.73119, + "longitude": -73.6681 + }, + { + "latitude": 42.73124, + "longitude": -73.66822 + }, + { + "latitude": 42.73132, + "longitude": -73.66842 + }, + { + "latitude": 42.73139, + "longitude": -73.66861 + }, + { + "latitude": 42.73149, + "longitude": -73.66884 + }, + { + "latitude": 42.73153, + "longitude": -73.66894 + }, + { + "latitude": 42.73157, + "longitude": -73.66902 + }, + { + "latitude": 42.73161, + "longitude": -73.66913 + }, + { + "latitude": 42.73163, + "longitude": -73.66922 + }, + { + "latitude": 42.73164, + "longitude": -73.66929 + }, + { + "latitude": 42.73179, + "longitude": -73.67022 + }, + { + "latitude": 42.73181, + "longitude": -73.67029 + }, + { + "latitude": 42.73184, + "longitude": -73.67043 + }, + { + "latitude": 42.73192, + "longitude": -73.67054 + }, + { + "latitude": 42.73192, + "longitude": -73.67055 + }, + { + "latitude": 42.73197, + "longitude": -73.6706 + }, + { + "latitude": 42.73201, + "longitude": -73.67064 + }, + { + "latitude": 42.73206, + "longitude": -73.67069 + }, + { + "latitude": 42.73212, + "longitude": -73.67071 + }, + { + "latitude": 42.7322, + "longitude": -73.67075 + }, + { + "latitude": 42.73223, + "longitude": -73.67077 + }, + { + "latitude": 42.73227, + "longitude": -73.67081 + }, + { + "latitude": 42.73229, + "longitude": -73.67089 + }, + { + "latitude": 42.73231, + "longitude": -73.67096 + }, + { + "latitude": 42.7324, + "longitude": -73.6717 + }, + { + "latitude": 42.7324, + "longitude": -73.6717 + }, + { + "latitude": 42.73168, + "longitude": -73.67193 + }, + { + "latitude": 42.73162, + "longitude": -73.67194 + }, + { + "latitude": 42.73162, + "longitude": -73.67194 + }, + { + "latitude": 42.73162, + "longitude": -73.67194 + }, + { + "latitude": 42.73162, + "longitude": -73.67194 + }, + { + "latitude": 42.73155, + "longitude": -73.67196 + }, + { + "latitude": 42.7314, + "longitude": -73.672 + }, + { + "latitude": 42.73132, + "longitude": -73.67202 + }, + { + "latitude": 42.73125, + "longitude": -73.67205 + }, + { + "latitude": 42.73107, + "longitude": -73.67209 + }, + { + "latitude": 42.73055, + "longitude": -73.67224 + }, + { + "latitude": 42.73033, + "longitude": -73.67229 + }, + { + "latitude": 42.7297, + "longitude": -73.67247 + }, + { + "latitude": 42.72933, + "longitude": -73.67257 + }, + { + "latitude": 42.72923, + "longitude": -73.67259 + }, + { + "latitude": 42.72905, + "longitude": -73.67264 + }, + { + "latitude": 42.72905, + "longitude": -73.67264 + }, + { + "latitude": 42.72922, + "longitude": -73.67306 + }, + { + "latitude": 42.72927, + "longitude": -73.67318 + }, + { + "latitude": 42.72934, + "longitude": -73.67342 + }, + { + "latitude": 42.72938, + "longitude": -73.67365 + }, + { + "latitude": 42.72943, + "longitude": -73.67402 + }, + { + "latitude": 42.72945, + "longitude": -73.67418 + }, + { + "latitude": 42.72949, + "longitude": -73.67434 + }, + { + "latitude": 42.72953, + "longitude": -73.67444 + }, + { + "latitude": 42.72959, + "longitude": -73.67453 + }, + { + "latitude": 42.72962, + "longitude": -73.67457 + }, + { + "latitude": 42.72962, + "longitude": -73.67457 + }, + { + "latitude": 42.72962, + "longitude": -73.67457 + }, + { + "latitude": 42.72962, + "longitude": -73.67457 + }, + { + "latitude": 42.72999, + "longitude": -73.67495 + }, + { + "latitude": 42.73004, + "longitude": -73.67505 + }, + { + "latitude": 42.73011, + "longitude": -73.67514 + }, + { + "latitude": 42.73014, + "longitude": -73.67519 + }, + { + "latitude": 42.7302, + "longitude": -73.67536 + }, + { + "latitude": 42.73034, + "longitude": -73.6758 + }, + { + "latitude": 42.73035, + "longitude": -73.67581 + }, + { + "latitude": 42.7304, + "longitude": -73.67593 + }, + { + "latitude": 42.73041, + "longitude": -73.67595 + }, + { + "latitude": 42.7305, + "longitude": -73.67612 + }, + { + "latitude": 42.73061, + "longitude": -73.67632 + }, + { + "latitude": 42.73061, + "longitude": -73.67632 + }, + { + "latitude": 42.73036, + "longitude": -73.67639 + }, + { + "latitude": 42.73032, + "longitude": -73.6764 + }, + { + "latitude": 42.73028, + "longitude": -73.67646 + }, + { + "latitude": 42.73028, + "longitude": -73.67646 + }, + { + "latitude": 42.73028, + "longitude": -73.67649 + }, + { + "latitude": 42.73029, + "longitude": -73.67656 + }, + { + "latitude": 42.7303, + "longitude": -73.67661 + }, + { + "latitude": 42.73031, + "longitude": -73.67663 + }, + { + "latitude": 42.73031, + "longitude": -73.67663 + }, + { + "latitude": 42.73031, + "longitude": -73.67663 + } +] \ No newline at end of file diff --git a/assets/shuttle_jsons/south.json b/assets/shuttle_jsons/south.json new file mode 100644 index 00000000..90b74787 --- /dev/null +++ b/assets/shuttle_jsons/south.json @@ -0,0 +1,770 @@ +[ + { + "latitude": 42.73033, + "longitude": -73.67666 + }, + { + "latitude": 42.73037, + "longitude": -73.67666 + }, + { + "latitude": 42.73042, + "longitude": -73.67665 + }, + { + "latitude": 42.7307, + "longitude": -73.67658 + }, + { + "latitude": 42.7307, + "longitude": -73.67658 + }, + { + "latitude": 42.73069, + "longitude": -73.67653 + }, + { + "latitude": 42.73061, + "longitude": -73.67632 + }, + { + "latitude": 42.7305, + "longitude": -73.67612 + }, + { + "latitude": 42.73041, + "longitude": -73.67595 + }, + { + "latitude": 42.7304, + "longitude": -73.67593 + }, + { + "latitude": 42.73035, + "longitude": -73.67581 + }, + { + "latitude": 42.73034, + "longitude": -73.6758 + }, + { + "latitude": 42.7302, + "longitude": -73.67536 + }, + { + "latitude": 42.73014, + "longitude": -73.67519 + }, + { + "latitude": 42.73011, + "longitude": -73.67514 + }, + { + "latitude": 42.73004, + "longitude": -73.67505 + }, + { + "latitude": 42.72999, + "longitude": -73.67495 + }, + { + "latitude": 42.72959, + "longitude": -73.67453 + }, + { + "latitude": 42.72953, + "longitude": -73.67444 + }, + { + "latitude": 42.72949, + "longitude": -73.67434 + }, + { + "latitude": 42.72945, + "longitude": -73.67418 + }, + { + "latitude": 42.72943, + "longitude": -73.67402 + }, + { + "latitude": 42.72938, + "longitude": -73.67365 + }, + { + "latitude": 42.72934, + "longitude": -73.67342 + }, + { + "latitude": 42.72927, + "longitude": -73.67318 + }, + { + "latitude": 42.72922, + "longitude": -73.67306 + }, + { + "latitude": 42.72905, + "longitude": -73.67264 + }, + { + "latitude": 42.72905, + "longitude": -73.67264 + }, + { + "latitude": 42.72923, + "longitude": -73.67259 + }, + { + "latitude": 42.72933, + "longitude": -73.67257 + }, + { + "latitude": 42.7297, + "longitude": -73.67247 + }, + { + "latitude": 42.73033, + "longitude": -73.67229 + }, + { + "latitude": 42.73055, + "longitude": -73.67224 + }, + { + "latitude": 42.73107, + "longitude": -73.67209 + }, + { + "latitude": 42.73117, + "longitude": -73.67207 + }, + { + "latitude": 42.73117, + "longitude": -73.67207 + }, + { + "latitude": 42.73117, + "longitude": -73.67207 + }, + { + "latitude": 42.73117, + "longitude": -73.67207 + }, + { + "latitude": 42.73125, + "longitude": -73.67205 + }, + { + "latitude": 42.73132, + "longitude": -73.67202 + }, + { + "latitude": 42.7314, + "longitude": -73.672 + }, + { + "latitude": 42.73155, + "longitude": -73.67196 + }, + { + "latitude": 42.73168, + "longitude": -73.67193 + }, + { + "latitude": 42.7324, + "longitude": -73.6717 + }, + { + "latitude": 42.7324, + "longitude": -73.6717 + }, + { + "latitude": 42.73231, + "longitude": -73.67096 + }, + { + "latitude": 42.73229, + "longitude": -73.67089 + }, + { + "latitude": 42.73227, + "longitude": -73.67081 + }, + { + "latitude": 42.73223, + "longitude": -73.67077 + }, + { + "latitude": 42.7322, + "longitude": -73.67075 + }, + { + "latitude": 42.73212, + "longitude": -73.67071 + }, + { + "latitude": 42.73206, + "longitude": -73.67069 + }, + { + "latitude": 42.73201, + "longitude": -73.67064 + }, + { + "latitude": 42.73197, + "longitude": -73.6706 + }, + { + "latitude": 42.73192, + "longitude": -73.67055 + }, + { + "latitude": 42.73192, + "longitude": -73.67054 + }, + { + "latitude": 42.73184, + "longitude": -73.67043 + }, + { + "latitude": 42.73181, + "longitude": -73.67029 + }, + { + "latitude": 42.73179, + "longitude": -73.67022 + }, + { + "latitude": 42.73171, + "longitude": -73.66969 + }, + { + "latitude": 42.73171, + "longitude": -73.66969 + }, + { + "latitude": 42.73171, + "longitude": -73.66969 + }, + { + "latitude": 42.73171, + "longitude": -73.66969 + }, + { + "latitude": 42.73164, + "longitude": -73.66929 + }, + { + "latitude": 42.73163, + "longitude": -73.66922 + }, + { + "latitude": 42.73161, + "longitude": -73.66913 + }, + { + "latitude": 42.73157, + "longitude": -73.66902 + }, + { + "latitude": 42.73153, + "longitude": -73.66894 + }, + { + "latitude": 42.73149, + "longitude": -73.66884 + }, + { + "latitude": 42.73139, + "longitude": -73.66861 + }, + { + "latitude": 42.73132, + "longitude": -73.66842 + }, + { + "latitude": 42.73124, + "longitude": -73.66822 + }, + { + "latitude": 42.73119, + "longitude": -73.6681 + }, + { + "latitude": 42.73101, + "longitude": -73.66768 + }, + { + "latitude": 42.73093, + "longitude": -73.66749 + }, + { + "latitude": 42.73089, + "longitude": -73.66736 + }, + { + "latitude": 42.73087, + "longitude": -73.66731 + }, + { + "latitude": 42.73086, + "longitude": -73.66724 + }, + { + "latitude": 42.73086, + "longitude": -73.66724 + }, + { + "latitude": 42.73075, + "longitude": -73.66726 + }, + { + "latitude": 42.73069, + "longitude": -73.66726 + }, + { + "latitude": 42.7303, + "longitude": -73.66742 + }, + { + "latitude": 42.72991, + "longitude": -73.66757 + }, + { + "latitude": 42.72954, + "longitude": -73.66778 + }, + { + "latitude": 42.72932, + "longitude": -73.66794 + }, + { + "latitude": 42.72897, + "longitude": -73.66818 + }, + { + "latitude": 42.72884, + "longitude": -73.66822 + }, + { + "latitude": 42.72862, + "longitude": -73.66823 + }, + { + "latitude": 42.7283, + "longitude": -73.66818 + }, + { + "latitude": 42.72789, + "longitude": -73.668 + }, + { + "latitude": 42.72757, + "longitude": -73.66786 + }, + { + "latitude": 42.72745, + "longitude": -73.66782 + }, + { + "latitude": 42.72735, + "longitude": -73.66779 + }, + { + "latitude": 42.72722, + "longitude": -73.66769 + }, + { + "latitude": 42.72708, + "longitude": -73.66755 + }, + { + "latitude": 42.72696, + "longitude": -73.66739 + }, + { + "latitude": 42.72689, + "longitude": -73.66726 + }, + { + "latitude": 42.72662, + "longitude": -73.66679 + }, + { + "latitude": 42.72662, + "longitude": -73.66679 + }, + { + "latitude": 42.72541, + "longitude": -73.66764 + }, + { + "latitude": 42.72534, + "longitude": -73.6677 + }, + { + "latitude": 42.72531, + "longitude": -73.66776 + }, + { + "latitude": 42.72529, + "longitude": -73.66786 + }, + { + "latitude": 42.72525, + "longitude": -73.66816 + }, + { + "latitude": 42.72525, + "longitude": -73.66816 + }, + { + "latitude": 42.72422, + "longitude": -73.668 + }, + { + "latitude": 42.72243, + "longitude": -73.66775 + }, + { + "latitude": 42.72243, + "longitude": -73.66775 + }, + { + "latitude": 42.72235, + "longitude": -73.66851 + }, + { + "latitude": 42.72234, + "longitude": -73.66876 + }, + { + "latitude": 42.72229, + "longitude": -73.67207 + }, + { + "latitude": 42.72227, + "longitude": -73.67359 + }, + { + "latitude": 42.72227, + "longitude": -73.67359 + }, + { + "latitude": 42.72227, + "longitude": -73.67359 + }, + { + "latitude": 42.72227, + "longitude": -73.67359 + }, + { + "latitude": 42.72226, + "longitude": -73.67437 + }, + { + "latitude": 42.72225, + "longitude": -73.67454 + }, + { + "latitude": 42.72223, + "longitude": -73.6755 + }, + { + "latitude": 42.72222, + "longitude": -73.67589 + }, + { + "latitude": 42.72221, + "longitude": -73.67603 + }, + { + "latitude": 42.7222, + "longitude": -73.67645 + }, + { + "latitude": 42.72218, + "longitude": -73.67659 + }, + { + "latitude": 42.72196, + "longitude": -73.67817 + }, + { + "latitude": 42.72196, + "longitude": -73.67833 + }, + { + "latitude": 42.72197, + "longitude": -73.67846 + }, + { + "latitude": 42.72203, + "longitude": -73.67869 + }, + { + "latitude": 42.72212, + "longitude": -73.67889 + }, + { + "latitude": 42.72216, + "longitude": -73.67894 + }, + { + "latitude": 42.72229, + "longitude": -73.67918 + }, + { + "latitude": 42.72253, + "longitude": -73.67952 + }, + { + "latitude": 42.72266, + "longitude": -73.67968 + }, + { + "latitude": 42.72272, + "longitude": -73.67976 + }, + { + "latitude": 42.72272, + "longitude": -73.67976 + }, + { + "latitude": 42.72297, + "longitude": -73.67949 + }, + { + "latitude": 42.72304, + "longitude": -73.67943 + }, + { + "latitude": 42.7231, + "longitude": -73.67937 + }, + { + "latitude": 42.7232, + "longitude": -73.6793 + }, + { + "latitude": 42.7234, + "longitude": -73.6792 + }, + { + "latitude": 42.72354, + "longitude": -73.67913 + }, + { + "latitude": 42.72385, + "longitude": -73.67902 + }, + { + "latitude": 42.72463, + "longitude": -73.67877 + }, + { + "latitude": 42.72476, + "longitude": -73.67875 + }, + { + "latitude": 42.72514, + "longitude": -73.67867 + }, + { + "latitude": 42.72573, + "longitude": -73.67847 + }, + { + "latitude": 42.72665, + "longitude": -73.67814 + }, + { + "latitude": 42.72684, + "longitude": -73.67809 + }, + { + "latitude": 42.72696, + "longitude": -73.67808 + }, + { + "latitude": 42.72704, + "longitude": -73.67806 + }, + { + "latitude": 42.72717, + "longitude": -73.67804 + }, + { + "latitude": 42.72731, + "longitude": -73.67805 + }, + { + "latitude": 42.72738, + "longitude": -73.67806 + }, + { + "latitude": 42.72743, + "longitude": -73.67807 + }, + { + "latitude": 42.72753, + "longitude": -73.67808 + }, + { + "latitude": 42.72766, + "longitude": -73.67808 + }, + { + "latitude": 42.72782, + "longitude": -73.67805 + }, + { + "latitude": 42.72787, + "longitude": -73.67804 + }, + { + "latitude": 42.72787, + "longitude": -73.67804 + }, + { + "latitude": 42.72787, + "longitude": -73.67804 + }, + { + "latitude": 42.72787, + "longitude": -73.67804 + }, + { + "latitude": 42.72819, + "longitude": -73.67795 + }, + { + "latitude": 42.72851, + "longitude": -73.67786 + }, + { + "latitude": 42.72858, + "longitude": -73.67784 + }, + { + "latitude": 42.72866, + "longitude": -73.67782 + }, + { + "latitude": 42.72899, + "longitude": -73.67774 + }, + { + "latitude": 42.72911, + "longitude": -73.67769 + }, + { + "latitude": 42.72948, + "longitude": -73.67759 + }, + { + "latitude": 42.73007, + "longitude": -73.67742 + }, + { + "latitude": 42.73027, + "longitude": -73.67737 + }, + { + "latitude": 42.73067, + "longitude": -73.67726 + }, + { + "latitude": 42.73078, + "longitude": -73.67723 + }, + { + "latitude": 42.73078, + "longitude": -73.67723 + }, + { + "latitude": 42.73076, + "longitude": -73.67707 + }, + { + "latitude": 42.7307, + "longitude": -73.67658 + }, + { + "latitude": 42.7307, + "longitude": -73.67658 + }, + { + "latitude": 42.73042, + "longitude": -73.67665 + }, + { + "latitude": 42.73037, + "longitude": -73.67666 + }, + { + "latitude": 42.73033, + "longitude": -73.67666 + }, + { + "latitude": 42.73033, + "longitude": -73.67666 + }, + { + "latitude": 42.7303, + "longitude": -73.67661 + }, + { + "latitude": 42.7303, + "longitude": -73.67658 + }, + { + "latitude": 42.7303, + "longitude": -73.67658 + }, + { + "latitude": 42.7303, + "longitude": -73.67658 + }, + { + "latitude": 42.7303, + "longitude": -73.67658 + }, + { + "latitude": 42.73029, + "longitude": -73.67656 + }, + { + "latitude": 42.73028, + "longitude": -73.67649 + }, + { + "latitude": 42.73028, + "longitude": -73.67646 + }, + { + "latitude": 42.73028, + "longitude": -73.67646 + }, + { + "latitude": 42.73032, + "longitude": -73.6764 + }, + { + "latitude": 42.73036, + "longitude": -73.67639 + }, + { + "latitude": 42.73051, + "longitude": -73.67635 + }, + { + "latitude": 42.73051, + "longitude": -73.67635 + }, + { + "latitude": 42.73051, + "longitude": -73.67635 + } +] \ No newline at end of file diff --git a/assets/shuttle_jsons/west.json b/assets/shuttle_jsons/west.json new file mode 100644 index 00000000..7a30748b --- /dev/null +++ b/assets/shuttle_jsons/west.json @@ -0,0 +1,826 @@ +[ + { + "latitude": 42.73028, + "longitude": -73.6765 + }, + { + "latitude": 42.73029, + "longitude": -73.67656 + }, + { + "latitude": 42.7303, + "longitude": -73.67661 + }, + { + "latitude": 42.73033, + "longitude": -73.67666 + }, + { + "latitude": 42.73033, + "longitude": -73.67666 + }, + { + "latitude": 42.73037, + "longitude": -73.67666 + }, + { + "latitude": 42.73042, + "longitude": -73.67665 + }, + { + "latitude": 42.7307, + "longitude": -73.67658 + }, + { + "latitude": 42.7307, + "longitude": -73.67658 + }, + { + "latitude": 42.73076, + "longitude": -73.67707 + }, + { + "latitude": 42.73078, + "longitude": -73.67723 + }, + { + "latitude": 42.73078, + "longitude": -73.67723 + }, + { + "latitude": 42.73067, + "longitude": -73.67726 + }, + { + "latitude": 42.73027, + "longitude": -73.67737 + }, + { + "latitude": 42.73007, + "longitude": -73.67742 + }, + { + "latitude": 42.72948, + "longitude": -73.67759 + }, + { + "latitude": 42.72911, + "longitude": -73.67769 + }, + { + "latitude": 42.72899, + "longitude": -73.67774 + }, + { + "latitude": 42.72866, + "longitude": -73.67782 + }, + { + "latitude": 42.72858, + "longitude": -73.67784 + }, + { + "latitude": 42.72851, + "longitude": -73.67786 + }, + { + "latitude": 42.72819, + "longitude": -73.67795 + }, + { + "latitude": 42.72782, + "longitude": -73.67805 + }, + { + "latitude": 42.72766, + "longitude": -73.67808 + }, + { + "latitude": 42.72753, + "longitude": -73.67808 + }, + { + "latitude": 42.72743, + "longitude": -73.67807 + }, + { + "latitude": 42.72738, + "longitude": -73.67806 + }, + { + "latitude": 42.72731, + "longitude": -73.67805 + }, + { + "latitude": 42.72717, + "longitude": -73.67804 + }, + { + "latitude": 42.72704, + "longitude": -73.67806 + }, + { + "latitude": 42.72696, + "longitude": -73.67808 + }, + { + "latitude": 42.72684, + "longitude": -73.67809 + }, + { + "latitude": 42.72665, + "longitude": -73.67814 + }, + { + "latitude": 42.72636, + "longitude": -73.67824 + }, + { + "latitude": 42.72636, + "longitude": -73.67824 + }, + { + "latitude": 42.72636, + "longitude": -73.67824 + }, + { + "latitude": 42.72636, + "longitude": -73.67824 + }, + { + "latitude": 42.72573, + "longitude": -73.67847 + }, + { + "latitude": 42.72514, + "longitude": -73.67867 + }, + { + "latitude": 42.72476, + "longitude": -73.67875 + }, + { + "latitude": 42.72463, + "longitude": -73.67877 + }, + { + "latitude": 42.72385, + "longitude": -73.67902 + }, + { + "latitude": 42.72354, + "longitude": -73.67913 + }, + { + "latitude": 42.7234, + "longitude": -73.6792 + }, + { + "latitude": 42.7232, + "longitude": -73.6793 + }, + { + "latitude": 42.7231, + "longitude": -73.67937 + }, + { + "latitude": 42.72304, + "longitude": -73.67943 + }, + { + "latitude": 42.72297, + "longitude": -73.67949 + }, + { + "latitude": 42.72272, + "longitude": -73.67976 + }, + { + "latitude": 42.72272, + "longitude": -73.67976 + }, + { + "latitude": 42.72326, + "longitude": -73.68053 + }, + { + "latitude": 42.72333, + "longitude": -73.68061 + }, + { + "latitude": 42.72356, + "longitude": -73.68077 + }, + { + "latitude": 42.72369, + "longitude": -73.68082 + }, + { + "latitude": 42.72369, + "longitude": -73.68082 + }, + { + "latitude": 42.72369, + "longitude": -73.68082 + }, + { + "latitude": 42.72369, + "longitude": -73.68082 + }, + { + "latitude": 42.72419, + "longitude": -73.68105 + }, + { + "latitude": 42.7243, + "longitude": -73.6811 + }, + { + "latitude": 42.72447, + "longitude": -73.68118 + }, + { + "latitude": 42.72473, + "longitude": -73.68129 + }, + { + "latitude": 42.7248, + "longitude": -73.68132 + }, + { + "latitude": 42.72512, + "longitude": -73.68141 + }, + { + "latitude": 42.72542, + "longitude": -73.68149 + }, + { + "latitude": 42.72573, + "longitude": -73.68155 + }, + { + "latitude": 42.72629, + "longitude": -73.68162 + }, + { + "latitude": 42.72639, + "longitude": -73.68164 + }, + { + "latitude": 42.72649, + "longitude": -73.68171 + }, + { + "latitude": 42.72658, + "longitude": -73.68178 + }, + { + "latitude": 42.72668, + "longitude": -73.68191 + }, + { + "latitude": 42.72681, + "longitude": -73.68221 + }, + { + "latitude": 42.7269, + "longitude": -73.68246 + }, + { + "latitude": 42.72708, + "longitude": -73.68303 + }, + { + "latitude": 42.72734, + "longitude": -73.68379 + }, + { + "latitude": 42.72741, + "longitude": -73.68403 + }, + { + "latitude": 42.72748, + "longitude": -73.68432 + }, + { + "latitude": 42.72752, + "longitude": -73.68453 + }, + { + "latitude": 42.7276, + "longitude": -73.68487 + }, + { + "latitude": 42.72762, + "longitude": -73.68503 + }, + { + "latitude": 42.72771, + "longitude": -73.68528 + }, + { + "latitude": 42.72776, + "longitude": -73.68547 + }, + { + "latitude": 42.7278, + "longitude": -73.68562 + }, + { + "latitude": 42.72787, + "longitude": -73.68586 + }, + { + "latitude": 42.72817, + "longitude": -73.68654 + }, + { + "latitude": 42.72821, + "longitude": -73.68662 + }, + { + "latitude": 42.72822, + "longitude": -73.68667 + }, + { + "latitude": 42.72827, + "longitude": -73.68683 + }, + { + "latitude": 42.72831, + "longitude": -73.68698 + }, + { + "latitude": 42.72833, + "longitude": -73.68709 + }, + { + "latitude": 42.72834, + "longitude": -73.68719 + }, + { + "latitude": 42.72837, + "longitude": -73.68744 + }, + { + "latitude": 42.72837, + "longitude": -73.68747 + }, + { + "latitude": 42.7284, + "longitude": -73.68769 + }, + { + "latitude": 42.7284, + "longitude": -73.68769 + }, + { + "latitude": 42.7284, + "longitude": -73.68769 + }, + { + "latitude": 42.7284, + "longitude": -73.68769 + }, + { + "latitude": 42.72848, + "longitude": -73.68834 + }, + { + "latitude": 42.72848, + "longitude": -73.68834 + }, + { + "latitude": 42.72738, + "longitude": -73.68857 + }, + { + "latitude": 42.72738, + "longitude": -73.68857 + }, + { + "latitude": 42.72732, + "longitude": -73.68815 + }, + { + "latitude": 42.72732, + "longitude": -73.68815 + }, + { + "latitude": 42.72732, + "longitude": -73.68815 + }, + { + "latitude": 42.72732, + "longitude": -73.68815 + }, + { + "latitude": 42.72729, + "longitude": -73.68792 + }, + { + "latitude": 42.72728, + "longitude": -73.68779 + }, + { + "latitude": 42.72726, + "longitude": -73.68766 + }, + { + "latitude": 42.72723, + "longitude": -73.68744 + }, + { + "latitude": 42.72722, + "longitude": -73.68734 + }, + { + "latitude": 42.72722, + "longitude": -73.68734 + }, + { + "latitude": 42.72731, + "longitude": -73.68732 + }, + { + "latitude": 42.72764, + "longitude": -73.68724 + }, + { + "latitude": 42.72825, + "longitude": -73.6871 + }, + { + "latitude": 42.72833, + "longitude": -73.68709 + }, + { + "latitude": 42.7284, + "longitude": -73.68707 + }, + { + "latitude": 42.72961, + "longitude": -73.68682 + }, + { + "latitude": 42.72999, + "longitude": -73.68673 + }, + { + "latitude": 42.72999, + "longitude": -73.68673 + }, + { + "latitude": 42.72999, + "longitude": -73.68673 + }, + { + "latitude": 42.72999, + "longitude": -73.68673 + }, + { + "latitude": 42.7306, + "longitude": -73.68657 + }, + { + "latitude": 42.7308, + "longitude": -73.68654 + }, + { + "latitude": 42.73085, + "longitude": -73.68652 + }, + { + "latitude": 42.7317, + "longitude": -73.68634 + }, + { + "latitude": 42.73203, + "longitude": -73.68626 + }, + { + "latitude": 42.7321, + "longitude": -73.68625 + }, + { + "latitude": 42.73219, + "longitude": -73.68624 + }, + { + "latitude": 42.7324, + "longitude": -73.68619 + }, + { + "latitude": 42.73285, + "longitude": -73.68608 + }, + { + "latitude": 42.73308, + "longitude": -73.68601 + }, + { + "latitude": 42.73316, + "longitude": -73.68597 + }, + { + "latitude": 42.73341, + "longitude": -73.68586 + }, + { + "latitude": 42.73358, + "longitude": -73.68575 + }, + { + "latitude": 42.7337, + "longitude": -73.68568 + }, + { + "latitude": 42.7337, + "longitude": -73.68568 + }, + { + "latitude": 42.73367, + "longitude": -73.68557 + }, + { + "latitude": 42.73355, + "longitude": -73.68517 + }, + { + "latitude": 42.73355, + "longitude": -73.68517 + }, + { + "latitude": 42.73355, + "longitude": -73.68517 + }, + { + "latitude": 42.73355, + "longitude": -73.68517 + }, + { + "latitude": 42.73353, + "longitude": -73.68509 + }, + { + "latitude": 42.7335, + "longitude": -73.68497 + }, + { + "latitude": 42.73341, + "longitude": -73.68474 + }, + { + "latitude": 42.73339, + "longitude": -73.68463 + }, + { + "latitude": 42.73335, + "longitude": -73.68447 + }, + { + "latitude": 42.73314, + "longitude": -73.68348 + }, + { + "latitude": 42.73314, + "longitude": -73.68348 + }, + { + "latitude": 42.7328, + "longitude": -73.68358 + }, + { + "latitude": 42.73239, + "longitude": -73.68368 + }, + { + "latitude": 42.73225, + "longitude": -73.68371 + }, + { + "latitude": 42.73185, + "longitude": -73.68383 + }, + { + "latitude": 42.73177, + "longitude": -73.68385 + }, + { + "latitude": 42.73177, + "longitude": -73.68385 + }, + { + "latitude": 42.73179, + "longitude": -73.68403 + }, + { + "latitude": 42.73178, + "longitude": -73.68407 + }, + { + "latitude": 42.73174, + "longitude": -73.68409 + }, + { + "latitude": 42.73137, + "longitude": -73.68419 + }, + { + "latitude": 42.73137, + "longitude": -73.68419 + }, + { + "latitude": 42.73137, + "longitude": -73.68419 + }, + { + "latitude": 42.73137, + "longitude": -73.68419 + }, + { + "latitude": 42.73137, + "longitude": -73.68419 + }, + { + "latitude": 42.73129, + "longitude": -73.68422 + }, + { + "latitude": 42.73125, + "longitude": -73.68419 + }, + { + "latitude": 42.73123, + "longitude": -73.68413 + }, + { + "latitude": 42.73121, + "longitude": -73.684 + }, + { + "latitude": 42.73121, + "longitude": -73.684 + }, + { + "latitude": 42.73177, + "longitude": -73.68385 + }, + { + "latitude": 42.73185, + "longitude": -73.68383 + }, + { + "latitude": 42.73225, + "longitude": -73.68371 + }, + { + "latitude": 42.73239, + "longitude": -73.68368 + }, + { + "latitude": 42.7328, + "longitude": -73.68358 + }, + { + "latitude": 42.73314, + "longitude": -73.68348 + }, + { + "latitude": 42.73314, + "longitude": -73.68348 + }, + { + "latitude": 42.73297, + "longitude": -73.68274 + }, + { + "latitude": 42.73291, + "longitude": -73.68255 + }, + { + "latitude": 42.73286, + "longitude": -73.68248 + }, + { + "latitude": 42.73279, + "longitude": -73.68236 + }, + { + "latitude": 42.7327, + "longitude": -73.68226 + }, + { + "latitude": 42.7324, + "longitude": -73.682 + }, + { + "latitude": 42.7324, + "longitude": -73.682 + }, + { + "latitude": 42.7324, + "longitude": -73.682 + }, + { + "latitude": 42.7324, + "longitude": -73.682 + }, + { + "latitude": 42.73236, + "longitude": -73.68196 + }, + { + "latitude": 42.73218, + "longitude": -73.68181 + }, + { + "latitude": 42.7319, + "longitude": -73.68158 + }, + { + "latitude": 42.73168, + "longitude": -73.68145 + }, + { + "latitude": 42.73158, + "longitude": -73.68138 + }, + { + "latitude": 42.73155, + "longitude": -73.68135 + }, + { + "latitude": 42.73146, + "longitude": -73.68115 + }, + { + "latitude": 42.73135, + "longitude": -73.68072 + }, + { + "latitude": 42.73126, + "longitude": -73.6803 + }, + { + "latitude": 42.73125, + "longitude": -73.6802 + }, + { + "latitude": 42.73109, + "longitude": -73.67927 + }, + { + "latitude": 42.73098, + "longitude": -73.67851 + }, + { + "latitude": 42.73081, + "longitude": -73.67737 + }, + { + "latitude": 42.73078, + "longitude": -73.67723 + }, + { + "latitude": 42.73076, + "longitude": -73.67707 + }, + { + "latitude": 42.7307, + "longitude": -73.67658 + }, + { + "latitude": 42.73069, + "longitude": -73.67653 + }, + { + "latitude": 42.73061, + "longitude": -73.67632 + }, + { + "latitude": 42.73061, + "longitude": -73.67632 + }, + { + "latitude": 42.73036, + "longitude": -73.67639 + }, + { + "latitude": 42.73032, + "longitude": -73.6764 + }, + { + "latitude": 42.73028, + "longitude": -73.67646 + }, + { + "latitude": 42.73028, + "longitude": -73.67646 + }, + { + "latitude": 42.73028, + "longitude": -73.67646 + } +] \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 356995c6..34afc7b0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,20 +1,26 @@ -// import 'package:floating_search_bar/floating_search_bar.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:smartrider/pages/home.dart'; +import 'package:smartrider/util/theme.dart'; +import 'package:smartrider/util/theme_notifier.dart'; -void main() => runApp(SmartRider()); +void main() => runApp( + ChangeNotifierProvider( + create: (_) => ThemeNotifier(lightTheme), + child: SmartRider(), + ), + ); class SmartRider extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { + final themeNotifier = Provider.of(context); return MaterialApp( debugShowCheckedModeBanner: false, - title: 'Smart Rider Prototype', - theme: ThemeData( - primarySwatch: Colors.blue, - ), + title: 'SmartRider Prototype', + theme: themeNotifier.getTheme(), home: HomePage(), ); } -} +} \ No newline at end of file diff --git a/lib/pages/authwrapper.dart b/lib/pages/authwrapper.dart new file mode 100644 index 00000000..8fe8e4ad --- /dev/null +++ b/lib/pages/authwrapper.dart @@ -0,0 +1,14 @@ +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/rendering.dart'; +import 'package:provider/provider.dart'; +import 'package:smartrider/pages/signup.dart'; +import 'package:smartrider/services/userauth.dart'; +class authwrapper extends StatelessWidget{ + @override + Widget build(BuildContext context){ + final fbuser = Provider.of(context); + print(fbuser); + return Signuppage(); + } +} \ No newline at end of file diff --git a/lib/pages/home.dart b/lib/pages/home.dart index a4349bd1..dba337fa 100644 --- a/lib/pages/home.dart +++ b/lib/pages/home.dart @@ -1,15 +1,13 @@ // ui imports import 'dart:ui'; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:sliding_up_panel/sliding_up_panel.dart'; -// map imports -import 'package:google_maps_flutter/google_maps_flutter.dart'; - // custom widget imports -// import '../widgets/map_ui.dart'; -import '../widgets/search_bar.dart'; -import 'schedule.dart'; +import 'package:smartrider/widgets/map_ui.dart'; +import 'package:smartrider/widgets/search_bar.dart'; +import 'package:smartrider/pages/schedule.dart'; class HomePage extends StatelessWidget { static const String route = '/'; @@ -24,109 +22,87 @@ class _HomePage extends StatefulWidget{ _HomePageState createState() => _HomePageState(); } class _HomePageState extends State<_HomePage> { + // We create a key so we can call schedule and map functions from this class (needed to scroll to current time) + static final GlobalKey scheduleState = GlobalKey(); + static final GlobalKey mapState = GlobalKey(); + PanelController _panelController; double _panelHeightOpen; double _panelHeightClosed = 95.0; + bool _isShuttle; // used to determine what text to display @override void initState(){ super.initState(); + _panelController = new PanelController(); + _isShuttle = true; } @override Widget build(BuildContext context) { _panelHeightOpen = MediaQuery.of(context).size.height * .95; - return Material( - child: Stack( - alignment: Alignment.topCenter, - children: [ - // sliding panel (body is the background, panelBuilder is the actual panel) - SlidingUpPanel( - maxHeight: _panelHeightOpen, - minHeight: _panelHeightClosed, - parallaxEnabled: true, - parallaxOffset: .5, - body: SearchBar(), - // body: ShuttleMap(), - panelBuilder: (sc) => ShuttleSchedule(), - borderRadius: BorderRadius.only(topLeft: Radius.circular(18.0), topRight: Radius.circular(18.0)), - // onPanelSlide: (double pos) => setState((){ - // }), + child: SlidingUpPanel( + // sliding panel (body is the background, panelBuilder is the actual panel) + controller: _panelController, + maxHeight: _panelHeightOpen, + minHeight: _panelHeightClosed, + parallaxEnabled: true, + renderPanelSheet: false, + backdropEnabled: true, + parallaxOffset: .5, + borderRadius: BorderRadius.vertical( + top: Radius.circular(20.0), + ), + collapsed: AppBar( + centerTitle: true, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(18.0), + ), ), - - // the fab icon - // Positioned( - // right: 20.0, - // bottom: _fabHeight, - // child: FloatingActionButton( - // child: Icon( - // Icons.gps_fixed, - // color: Theme.of(context).primaryColor, - // ), - // onPressed: () { - - // }, - // backgroundColor: Colors.white, - // ), - // ), - - // blur filter - Positioned( - top: 0, - child: ClipRRect( - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), - child: Container( - width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).padding.top, - color: Colors.transparent, - ) - ) + leading: Icon(Icons.arrow_upward), + title: Text(_isShuttle ? 'Shuttle Schedules' : 'Bus Schedules'), + actions: [ + Padding( + padding: const EdgeInsets.only(right: 12.0), + child: Icon(Icons.arrow_upward) ) + ], + ), + // stack the search bar widget over the map ui + body: Stack( + children: [ + ShuttleMap( + key: mapState, + ), + SearchBar(), + ] + ), + panel: NotificationListener( + child: + ShuttleSchedule( + key: scheduleState, + mapState: mapState, + panelController: _panelController, + scheduleChanged: () { + _isShuttle = !_isShuttle; + setState(() {}); + }, ), - ], - ), - ); - } - - Widget _panel(ScrollController sc){ - return MediaQuery.removePadding( - context: context, - removeTop: true, - child: ListView( - controller: sc, - children: [ - // gray pull bar (replace with the thing from mockup) - SizedBox(height: 12.0,), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - width: 30, - height: 5, - decoration: BoxDecoration( - color: Colors.grey[300], - borderRadius: BorderRadius.all(Radius.circular(12.0)) - ), - ), - ], - ), - // Title (replace with "Schedules" in the right font) - SizedBox(height: 15.0,), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "Shuttle Schedule Page Test", - style: TextStyle( - fontWeight: FontWeight.normal, - fontSize: 24.0, - ), - ), - ], - ) - ] - ) + onNotification: (t) { + if (t.overscroll < -10) { + _panelController.animatePanelToPosition(0); + return true; + } + return false; + }, + ), + // when the panel is closed, we can update the position of our list + // to the current time (sneaky like so they don't see our slow animations) + // onPanelClosed: () { + // scheduleState.currentState.scrollAllTabs(); + // }, + ), ); } } diff --git a/lib/pages/login.dart b/lib/pages/login.dart index 535326de..c8e44d8d 100644 --- a/lib/pages/login.dart +++ b/lib/pages/login.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:smartrider/services/userauth.dart'; import 'signup.dart'; class Loginpage extends StatelessWidget { @@ -25,36 +26,89 @@ import 'signup.dart'; class _MyHomePageState extends State { TextStyle style = TextStyle(fontFamily: 'Montserrat', fontSize: 20.0); - + String email = ''; + String password = ''; + String error = ''; + final Authsystem _auth = Authsystem(); + final formkey= GlobalKey(); @override Widget build(BuildContext context) { - final emailField = TextField( + final emailField = TextFormField( + validator:(val) { + if(val.isEmpty){ + return 'Enter an email'; + } + else if(!val.contains("@")){ + return "Please enter a valid email"; + } + else{ + return null; + } + }, + onChanged: (val){ + setState(() { + email = val; + }); + }, obscureText: false, style: style, decoration: InputDecoration( contentPadding: EdgeInsets.fromLTRB(20.0, 15.0, 20.0, 15.0), - hintText: "Username", + hintText: "Emails", + filled: true, + fillColor: Colors.white.withOpacity(1), border: OutlineInputBorder(borderRadius: BorderRadius.circular(10.0))), ); - final passwordField = TextField( + final passwordField = TextFormField( + validator: (val){ + if(val.length<6){ + return 'Password needs to be at least 6 characters'; + } + else if(val.isEmpty){ + return "Please enter a password."; + } + else{ + return null; + } + }, + onChanged: (val){ + setState(() { + password = val; + }); + + }, obscureText: true, style: style, decoration: InputDecoration( contentPadding: EdgeInsets.fromLTRB(20.0, 15.0, 20.0, 15.0), hintText: "Password", + filled: true, + fillColor: Colors.white.withOpacity(1), border: OutlineInputBorder(borderRadius: BorderRadius.circular(10.0))), ); final loginButon = Material( elevation: 4.0, borderRadius: BorderRadius.circular(10.0), - color: Colors.green, + color: Color.fromRGBO(93, 188, 210,1), child: MaterialButton( minWidth: MediaQuery.of(context).size.width, padding: EdgeInsets.fromLTRB(20.0, 15.0, 20.0, 15.0), - onPressed: () {}, + onPressed: () async{ + if(formkey.currentState.validate()){ + dynamic result = await _auth.signinwithEandP(email, password); + if(result == null){ + print("error"); + } + else{ + print("success"); + } + + } + + }, child: Text("Login", textAlign: TextAlign.center, style: style.copyWith( @@ -64,7 +118,7 @@ import 'signup.dart'; final signupButton = Material( elevation: 4.0, borderRadius: BorderRadius.circular(10.0), - color: Colors.lightBlue, + color: Color.fromRGBO(93, 188, 210,1), child: MaterialButton( minWidth: MediaQuery.of(context).size.width, padding: EdgeInsets.fromLTRB(20.0, 15.0, 20.0, 15.0), @@ -92,37 +146,40 @@ import 'signup.dart'; child: Padding( padding: const EdgeInsets.all(36.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox( - height: 50.0, - child:Text("SmartRider",style:GoogleFonts.prompt(fontSize: 25),), - ), - SizedBox( - height: 100.0, - child: Image.asset( - "assets/ridericon.png", - fit: BoxFit.contain, + child: Form( + key: formkey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + height: 50.0, + child:Text("SmartRider",style:GoogleFonts.prompt(fontSize: 35),), + ), + // SizedBox( + // height: 100.0, + // child: Image.asset( + // "assets/ridericon.png", + // fit: BoxFit.contain, + // ), + // ), + SizedBox(height: 45.0), + emailField, + SizedBox(height: 25.0), + passwordField, + SizedBox( + height: 35.0, + ), + loginButon, + SizedBox( + height: 15.0, + ), + signupButton, + SizedBox( + height: 10.0, ), - ), - SizedBox(height: 45.0), - emailField, - SizedBox(height: 25.0), - passwordField, - SizedBox( - height: 35.0, - ), - loginButon, - SizedBox( - height: 15.0, - ), - signupButton, - SizedBox( - height: 10.0, - ), - ], + ], + ), ), ), ), @@ -140,28 +197,11 @@ import 'signup.dart'; Path mainBackground = Path(); mainBackground.addRect(Rect.fromLTRB(0, 0, width, height)); - paint.color = Colors.blue.shade700; - canvas.drawPath(mainBackground, paint); - - Path ovalPath = Path(); - // Start paint from 20% height to the left - ovalPath.moveTo(0, height * 0.2); - - // paint a curve from current position to middle of the screen - ovalPath.quadraticBezierTo( - width * 0.45, height * 0.25, width * 0.51, height * 0.5); - - // Paint a curve from current position to bottom left of screen at width * 0.1 - ovalPath.quadraticBezierTo(width * 0.58, height * 0.8, width * 0.1, height); - - // draw remaining line to bottom left side - ovalPath.lineTo(0, height); - - // Close line to reset it back - ovalPath.close(); - - paint.color = Colors.blue.shade600; - canvas.drawPath(ovalPath, paint); + paint.color = Color.fromRGBO(102, 94, 255,1); + var xcoord = width; + var ycoord = height/2+40; + + canvas.drawCircle(Offset(xcoord,ycoord), 200, paint); } @override diff --git a/lib/pages/schedule.dart b/lib/pages/schedule.dart index 990b4237..c7bd109d 100644 --- a/lib/pages/schedule.dart +++ b/lib/pages/schedule.dart @@ -1,364 +1,218 @@ +// ui dependencies import 'package:flutter/material.dart'; +import 'package:sliding_up_panel/sliding_up_panel.dart'; +import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:intl/intl.dart'; -// TEMPORARY MOCKUP FILE TO HOUSE SHUTTLE SCHEDULE PAGE -// CURRENTLY home.dart has a bug concerning mapbox +// loading custom widgets and data +import 'package:smartrider/util/data.dart'; +import 'package:smartrider/widgets/filter_dialog.dart'; +import 'package:smartrider/widgets/shuttle_list.dart'; +import 'package:smartrider/widgets/bus_list.dart'; +import 'package:smartrider/widgets/map_ui.dart'; class ShuttleSchedule extends StatefulWidget { + final GlobalKey mapState; + final PanelController panelController; + final VoidCallback scheduleChanged; + ShuttleSchedule({Key key, this.mapState, this.panelController, this.scheduleChanged}) : super(key: key); @override - _ShuttleScheduleState createState() => _ShuttleScheduleState(); + ShuttleScheduleState createState() => ShuttleScheduleState(); } + +class ShuttleScheduleState extends State with TickerProviderStateMixin { + + final List _tabs = [ + Tab(icon: Icon(Icons.airport_shuttle)), + Tab(icon: Icon(Icons.directions_bus)), + ]; + + TabController _tabController; + TextEditingController _textController; + List _shuttleScrollControllers = List(); + List _busScrollControllers = List(); + double initial, distance; + String filter; + bool _isShuttle; // true if we have to build the shuttle schedule, false if bus + + @override + void initState() { + super.initState(); + _isShuttle = true; + _tabController = new TabController(vsync: this, length: _tabs.length); + _textController = new TextEditingController(); + _tabController.addListener(_handleTabSelection); + _textController.addListener(_handleSearchQuery); + + [0,1,2,3].forEach((idx) { + _shuttleScrollControllers.add(new ItemScrollController()); + }); + + [0,1,2].forEach((idx) { + _busScrollControllers.add(new ItemScrollController()); + }); + filter = null; + } + + @override + void dispose() { + _tabController.dispose(); + _textController.dispose(); + super.dispose(); + } + + _handleTabSelection() { + if (_tabController.indexIsChanging) { + _isShuttle = !_isShuttle; + this.widget.scheduleChanged(); + } + } + + _handleSearchQuery() { + setState(() { + filter = _textController.text; + }); + } + + _displayFilterDialog() async { + final builder = (BuildContext ctx) => FilterDialog( + stops: _isShuttle ? shuttleStopLists[_tabController.index] : busStopLists[_tabController.index], + controller: _textController, + ); + await showDialog(context: context, builder: builder); + } + + bool _containsFilter(var curStopList, var curTimeList, var index) { + if (this.filter == null) { + return true; + } + if (double.tryParse(this.filter) != null) { + return curTimeList[index].contains(this.filter); + } + if (this.filter.contains('am') || this.filter.contains('pm') || this.filter.contains(':')) { + return curTimeList[index].contains(this.filter); + } + if (this.filter.contains('@')) { + var filterSplit = this.filter.split('@'); + return (curStopList[index%curStopList.length][0].toLowerCase().contains(filterSplit[0].toLowerCase()) && + curTimeList[index].contains(filterSplit[1])); + } + return curStopList[index%curStopList.length][0].toLowerCase().contains(this.filter.toLowerCase().trim()); + } + + _jumpMap(double lat, double long) { + this.widget.panelController.animatePanelToPosition(0); + this.widget.mapState.currentState.scrollToLocation(LatLng(lat, long)); + } + + // These functions are broken with the current layout + // TODO: FIX THEM! + + // _scrollToCurrentTime(int idx, ItemScrollController _scrollController) { + // // TODO: update so it works with filter + // // List curTimeList = _isShuttle ? shuttleTimeLists[_tabController.index] : + // // busTimeLists[_tabController.index-1]; + // List curTimeList = shuttleTimeLists[idx]; + // var now = DateTime.now(); + // var f = DateFormat('H.m'); + // double min = double.maxFinite; + // double curTime = double.parse(f.format(now)); + // double compTime; + // String closest; + // curTimeList.forEach( + // (time) { + // var t = time.replaceAll(':', '.'); + // compTime = double.tryParse(t.substring(0,t.length-2)); + // if (compTime == null) + // return; + // if (t.endsWith('pm')) { + // compTime += 12.0; + // } + // if ((curTime - compTime).abs() < min) { + // min = (curTime - compTime).abs(); + // closest = time; + // } + // } + // ); + // var jTo = curTimeList.indexWhere((element) => element == closest); + // assert(_scrollController != null); + // if (_scrollController.isAttached) + // // _scrollController.jumpTo(index: 1); + // _scrollController.jumpTo(index: jTo); + // } + + // scrollAllTabs() { + // print(_shuttleScrollControllers); + // print(_busScrollControllers); + // _shuttleScrollControllers.asMap().forEach((idx, c) { + // print(c.toString()); + // _scrollToCurrentTime(idx, c); + // }); + // _busScrollControllers.asMap().forEach((idx, c) { + // print(c.toString()); + // _scrollToCurrentTime(idx, c); + // }); + // } -class _ShuttleScheduleState extends State { @override Widget build(BuildContext context) { - return MaterialApp( - home: DefaultTabController( - length: 3, - child: Scaffold( - appBar: AppBar( - bottom: TabBar( - tabs: [ - Text( - 'SOUTH', - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - style: TextStyle(fontWeight: FontWeight.bold), - ), - Text( - 'NORTH', - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - style: TextStyle(fontWeight: FontWeight.bold), - ), - Text( - 'WEST', - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - style: TextStyle(fontWeight: FontWeight.bold), - ) - ], + return ClipRRect( + borderRadius: BorderRadius.vertical( + top: Radius.circular(20.0), + ), + child: Scaffold( + appBar: AppBar( + centerTitle: true, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(20), ), - title: Text('Shuttle Schedules'), ), - body: TabBarView( - children: [ - GridView.count( - primary: false, - padding: const EdgeInsets.all(40), - crossAxisSpacing: 0, - mainAxisSpacing: 0, - crossAxisCount: 3, - children: [ - Container( - padding: const EdgeInsets.all(8), - child: const Text('Union to B-Lot', textScaleFactor: 1.15, textAlign: TextAlign.right), - ), - Container( - child: VerticalDivider(thickness: 15, color: Colors.purple[400]) - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('[time]', textScaleFactor: 1.15), - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('B-Lot to LXA', textScaleFactor: 1.15, textAlign: TextAlign.right), - ), - Container( - child: VerticalDivider(thickness: 15, color: Colors.purple[400]) - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('[time]', textScaleFactor: 1.15), - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('LXA to Tibitts/Orchard', textScaleFactor: 1.15, textAlign: TextAlign.right), - ), - Container( - child: VerticalDivider(thickness: 15, color: Colors.purple[400]) - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('[time]', textScaleFactor: 1.15), - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('Tibitts/Orchard to Polytech', textScaleFactor: 1.15, textAlign: TextAlign.right), - ), - Container( - child: VerticalDivider(thickness: 15, color: Colors.purple[400]) - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('[time]', textScaleFactor: 1.15), - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('Polytech to 15th/College', textScaleFactor: 1.15, textAlign: TextAlign.right), - ), - Container( - child: VerticalDivider(thickness: 15, color: Colors.purple[400]) - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('[time]', textScaleFactor: 1.15), - ), - ], - ), - GridView.count( - primary: false, - padding: const EdgeInsets.all(40), - crossAxisSpacing: 0, - mainAxisSpacing: 0, - crossAxisCount: 3, - children: [ - Container( - padding: const EdgeInsets.all(8), - child: const Text('Union to Troy Crosswalk', textScaleFactor: 1.15, textAlign: TextAlign.right), - ), - Container( - child: VerticalDivider(thickness: 15, color: Colors.blue[400]) - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('[time]', textScaleFactor: 1.15), - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('Troy Crosswalk to 9th St', textScaleFactor: 1.15, textAlign: TextAlign.right), - ), - Container( - child: VerticalDivider(thickness: 15, color: Colors.blue[400]) - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('[time]', textScaleFactor: 1.15), - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('9th St to Alumni House', textScaleFactor: 1.15, textAlign: TextAlign.right), - ), - Container( - child: VerticalDivider(thickness: 15, color: Colors.blue[400]) - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('[time]', textScaleFactor: 1.15), - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('Alumni House to Jacob', textScaleFactor: 1.15, textAlign: TextAlign.right), - ), - Container( - child: VerticalDivider(thickness: 15, color: Colors.blue[400]) - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('[time]', textScaleFactor: 1.15), - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('Jacob to Colonie', textScaleFactor: 1.15, textAlign: TextAlign.right), - ), - Container( - child: VerticalDivider(thickness: 15, color: Colors.blue[400]) - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('[time]', textScaleFactor: 1.15), - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('Colonie to Georgian', textScaleFactor: 1.15, textAlign: TextAlign.right), - ), - Container( - child: VerticalDivider(thickness: 15, color: Colors.blue[400]) - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('[time]', textScaleFactor: 1.15), - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('Colonie to Brinsmade', textScaleFactor: 1.15, textAlign: TextAlign.right), - ), - Container( - child: VerticalDivider(thickness: 15, color: Colors.blue[400]) - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('[time]', textScaleFactor: 1.15), - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('Brinsmade to Sunset 1', textScaleFactor: 1.15, textAlign: TextAlign.right), - ), - Container( - child: VerticalDivider(thickness: 15, color: Colors.blue[400]) - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('[time]', textScaleFactor: 1.15), - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('Sunset 1 to Sunset 2', textScaleFactor: 1.15, textAlign: TextAlign.right), - ), - Container( - child: VerticalDivider(thickness: 15, color: Colors.blue[400]) - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('[time]', textScaleFactor: 1.15), - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('Sunset 2 to E-Lot', textScaleFactor: 1.15, textAlign: TextAlign.right), - ), - Container( - child: VerticalDivider(thickness: 15, color: Colors.blue[400]) - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('[time]', textScaleFactor: 1.15), - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('E-Lot to B-Lot', textScaleFactor: 1.15, textAlign: TextAlign.right), - ), - Container( - child: VerticalDivider(thickness: 15, color: Colors.blue[400]) - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('[time]', textScaleFactor: 1.15), - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('B-Lot to Union', textScaleFactor: 1.15, textAlign: TextAlign.right), - ), - Container( - child: VerticalDivider(thickness: 15, color: Colors.blue[400]) - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('[time]', textScaleFactor: 1.15), - ), - ], - ), - GridView.count( - primary: false, - padding: const EdgeInsets.all(40), - crossAxisSpacing: 0, - mainAxisSpacing: 0, - crossAxisCount: 3, - children: [ - Container( - padding: const EdgeInsets.all(8), - child: const Text('Union to CBIS/AH', textScaleFactor: 1.15, textAlign: TextAlign.right), - ), - Container( - child: VerticalDivider(thickness: 15, color: Colors.orange[400]) - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('[time]', textScaleFactor: 1.15), - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('CBIS/AH to 15th/Off Commons', textScaleFactor: 1.15, textAlign: TextAlign.right), - ), - Container( - child: VerticalDivider(thickness: 15, color: Colors.orange[400]) - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('[time]', textScaleFactor: 1.15), - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('15th/Off Commons to 15th/Poly', textScaleFactor: 1.15, textAlign: TextAlign.right), - ), - Container( - child: VerticalDivider(thickness: 15, color: Colors.orange[400]) - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('[time]', textScaleFactor: 1.15), - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('15th/Poly to City Station', textScaleFactor: 1.15, textAlign: TextAlign.right), - ), - Container( - child: VerticalDivider(thickness: 15, color: Colors.orange[400]) - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('[time]', textScaleFactor: 1.15), - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('City Station to Blitman', textScaleFactor: 1.15, textAlign: TextAlign.right), - ), - Container( - child: VerticalDivider(thickness: 15, color: Colors.orange[400]) - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('[time]', textScaleFactor: 1.15), - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('Blitman to Winslow', textScaleFactor: 1.15, textAlign: TextAlign.right), - ), - Container( - child: VerticalDivider(thickness: 15, color: Colors.orange[400]) - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('[time]', textScaleFactor: 1.15), - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('Winslow to West', textScaleFactor: 1.15, textAlign: TextAlign.right), - ), - Container( - child: VerticalDivider(thickness: 15, color: Colors.orange[400]) - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('[time]', textScaleFactor: 1.15), - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('West to 87 Gym', textScaleFactor: 1.15, textAlign: TextAlign.right), - ), - Container( - child: VerticalDivider(thickness: 15, color: Colors.orange[400]) - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('[time]', textScaleFactor: 1.15), - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('87 Gym to Union', textScaleFactor: 1.15, textAlign: TextAlign.right), - ), - Container( - child: VerticalDivider(thickness: 15, color: Colors.orange[400]) - ), - Container( - padding: const EdgeInsets.all(8), - child: const Text('[time]', textScaleFactor: 1.15), - ), - ], - ), - ], + title: Text(_isShuttle ? 'Shuttle Schedules' : 'Bus Schedules'), + leading: IconButton( + icon: Icon(Icons.arrow_downward), + onPressed: () { + this.widget.panelController.animatePanelToPosition(0); + }, + ), + actions: [ + IconButton( + icon: Icon(Icons.arrow_downward), + onPressed: () { + this.widget.panelController.animatePanelToPosition(0); + }, + ) + ], + bottom: TabBar( + unselectedLabelColor: Colors.white.withOpacity(0.3), + indicatorColor: Colors.white, + controller: _tabController, + tabs: _tabs, ), ), - ), + body: TabBarView( + controller: _tabController, + children: [ + ShuttleList( + scrollControllers: _shuttleScrollControllers, + containsFilter: _containsFilter, + jumpMap: _jumpMap, + ), + BusList( + scrollControllers: _busScrollControllers, + containsFilter: _containsFilter, + jumpMap: _jumpMap, + ), + ], + ), + floatingActionButton: FloatingActionButton( + heroTag: "Filter", + child: Icon(Icons.search), + elevation: 5.0, + onPressed: _displayFilterDialog, + ), + ) ); } -} +} \ No newline at end of file diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart new file mode 100644 index 00000000..3d0bc5e8 --- /dev/null +++ b/lib/pages/settings.dart @@ -0,0 +1,285 @@ +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +// theme stuff +import 'package:smartrider/util/theme_notifier.dart'; +import 'package:smartrider/util/theme.dart'; +import 'package:provider/provider.dart'; + +class SettingsPage extends StatefulWidget { + @override + _SettingsPageState createState() => _SettingsPageState(); +} + +class _SettingsPageState extends State { + bool configDarkMode = false, configPush = true, configTest2 = true, configTest3 = true; + var _darkTheme = true; + @override + void initState() { + super.initState(); + } + @override + Widget build(BuildContext context) { + final themeNotifier = Provider.of(context); + _darkTheme = (themeNotifier.getTheme() == darkTheme); + final height = MediaQuery.of(context).size.height; + final width = MediaQuery.of(context).size.width; + return Scaffold( + appBar: AppBar( + centerTitle: true, + // first down arrow + leading: IconButton( + icon: Icon(Icons.arrow_downward), + tooltip: 'Go back', + onPressed: () { + Navigator.pop(context); + }, + ), + // title + title: Text('Settings', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 32 + ), + ), + // second down arrow + actions: [ + IconButton( + icon: Icon(Icons.arrow_downward), + tooltip: 'Go back', + onPressed: () { + Navigator.pop(context); + }, + ), + ], + ), + + body: Stack( + alignment: AlignmentDirectional.center, + children: [ + ListView( + children: [ + // GENERAL SETTINGS + Container( + margin: EdgeInsets.fromLTRB(8, 15, 8, 0), + child: Center( + child: Text('General', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 24 + ), + ), + ), + ), + Container( + margin: EdgeInsets.fromLTRB(8, 15, 8, 0), + child: Material( + elevation: 5, + borderRadius: BorderRadius.circular(20.0), + child: Container( + padding: EdgeInsets.only(right: 10,left: 10), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(20.0))), + child: Center( + child: Column( + children: [ + SwitchListTile( + title: Text('Push Notifications', + ), + value: configPush, + onChanged: (bool value) { + setState(() { + configPush = value; + }); + }, + secondary: const Icon(Icons.notifications), + ), + SwitchListTile( + title: Text('Lights Out', + ), + value: _darkTheme, + onChanged: (val) { + setState(() { + _darkTheme = val; + }); + onThemeChanged(val, themeNotifier); + }, + secondary: const Icon(Icons.lightbulb_outline), + ), + ], + ), + ), + ), + ), + ), + // SHUTTLE SETTINGS + Container( + margin: EdgeInsets.fromLTRB(8, 15, 8, 0), + child: Center( + child: Text('Shuttle Settings', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 24 + ), + ), + ), + ), + Container( + margin: EdgeInsets.fromLTRB(8, 15, 8, 0), + child: Material( + elevation: 5, + borderRadius: BorderRadius.circular(20.0), + child: Container( + padding: EdgeInsets.only(right: 10,left: 10), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(20.0))), + child: Center( + child: Column( + children: [ + SwitchListTile( + title: Text('Placeholder', + ), + value: configTest2, + onChanged: (bool value) { + setState(() { + configTest2 = value; + }); + }, + secondary: const Icon(Icons.lightbulb_outline), + ), + SwitchListTile( + title: Text('Placeholder', + ), + value: configTest3, + onChanged: (bool value) { + setState(() { + configTest3 = value; + }); + }, + secondary: const Icon(Icons.lightbulb_outline), + ), + SwitchListTile( + title: Text('Placeholder', + ), + value: configTest2, + onChanged: (bool value) { + setState(() { + configTest2 = value; + }); + }, + secondary: const Icon(Icons.lightbulb_outline), + ), + SwitchListTile( + title: Text('Placeholder', + ), + value: configTest3, + onChanged: (bool value) { + setState(() { + configTest3 = value; + }); + }, + secondary: const Icon(Icons.lightbulb_outline), + ), + ], + ), + ), + ), + ), + ), + + // SAFE RIDE SETTINGS + Container( + margin: EdgeInsets.fromLTRB(8, 15, 8, 0), + child: Center( + child: Text('Safe Ride Settings', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 24 + ), + ), + ), + ), + Container( + margin: EdgeInsets.fromLTRB(8, 15, 8, 0), + child: Material( + elevation: 5, + borderRadius: BorderRadius.circular(20.0), + child: Container( + padding: EdgeInsets.only(right: 10,left: 10), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(20.0))), + child: Center( + child: Column( + children: [ + SwitchListTile( + title: Text('Dark Mode', + ), + value: configDarkMode, + onChanged: (bool value) { + setState(() { + configDarkMode = value; + }); + }, + secondary: const Icon(Icons.lightbulb_outline), + ), SwitchListTile( + title: Text('Dark Mode', + ), + value: configDarkMode, + onChanged: (bool value) { + setState(() { + configDarkMode = value; + }); + }, + secondary: const Icon(Icons.lightbulb_outline), + ), + SwitchListTile( + title: Text('Dark Mode', + ), + value: configDarkMode, + onChanged: (bool value) { + setState(() { + configDarkMode = value; + }); + }, + secondary: const Icon(Icons.lightbulb_outline), + ), + ], + ), + ), + ), + ), + ), + SizedBox( + child: Stack( + alignment: AlignmentDirectional.bottomCenter, + children: [ + Padding( + padding: const EdgeInsets.all(25.0), + child: RaisedButton( + child: Text( + 'SIGN OUT', + style: Theme.of(context).textTheme.button,), + onPressed: () { + print('pressed'); + }, + shape: RoundedRectangleBorder(borderRadius: new BorderRadius.circular(20.0)) + ), + ), + ], + ), + ) + ], + ), + ], + ), + ); + } +} + +void onThemeChanged(bool value, ThemeNotifier themeNotifier) async { + (value) + ? themeNotifier.setTheme(darkTheme) + : themeNotifier.setTheme(lightTheme); + var prefs = await SharedPreferences.getInstance(); + prefs.setBool('darkMode', value); +} \ No newline at end of file diff --git a/lib/pages/signup.dart b/lib/pages/signup.dart index bf61e97a..84c0c111 100644 --- a/lib/pages/signup.dart +++ b/lib/pages/signup.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:smartrider/services/userauth.dart'; class Signuppage extends StatelessWidget { @@ -22,9 +23,15 @@ import 'package:google_fonts/google_fonts.dart'; @override _SignupHomeState createState() => _SignupHomeState(); } - + class _SignupHomeState extends State { TextStyle style = TextStyle(fontFamily: 'Montserrat', fontSize: 20.0); + String email = ''; + String password = ''; + String confirmpassword = ''; + String error = ''; + final Authsystem _auth = Authsystem(); + final formkey= GlobalKey(); @override Widget build(BuildContext context) { @@ -33,38 +40,52 @@ import 'package:google_fonts/google_fonts.dart'; textAlign: TextAlign.center, style: GoogleFonts.alef(fontSize: 30), ); - final emailField = TextField( + final emailField = TextFormField( + validator: (val) { + if(val.isEmpty){ + return 'Enter an email'; + } + else{ + return null; + } + }, obscureText: false, style: style, + onChanged: (val){ + email = val; + }, decoration: InputDecoration( contentPadding: EdgeInsets.fromLTRB(20.0, 15.0, 20.0, 15.0), - hintText: "Username", + hintText: "Enter email", border: OutlineInputBorder(borderRadius: BorderRadius.circular(32.0))), ); - final passwordField = TextField( + final passwordField = TextFormField( + validator: (val){ + if(val.length<6){ + return 'Password too short (needs to be at least 6 character long)'; + } + else if(val.isEmpty){ + return "Please enter a password."; + } + else{ + return null; + } + }, obscureText: true, style: style, + onChanged: (val){ + setState(() { + password = val; + }); + }, decoration: InputDecoration( contentPadding: EdgeInsets.fromLTRB(20.0, 15.0, 20.0, 15.0), - hintText: "Password", + hintText: "Enter Password", border: OutlineInputBorder(borderRadius: BorderRadius.circular(32.0))), ); - final loginButon = Material( - elevation: 5.0, - borderRadius: BorderRadius.circular(30.0), - color: Colors.green, - child: MaterialButton( - minWidth: MediaQuery.of(context).size.width, - padding: EdgeInsets.fromLTRB(20.0, 15.0, 20.0, 15.0), - onPressed: () {}, - child: Text("Login", - textAlign: TextAlign.center, - style: style.copyWith( - color: Colors.white, fontWeight: FontWeight.bold)), - ), - ); + final registerButton = Material( elevation: 5.0, borderRadius: BorderRadius.circular(50.0), @@ -72,7 +93,16 @@ import 'package:google_fonts/google_fonts.dart'; child: MaterialButton( minWidth: MediaQuery.of(context).size.width, padding: EdgeInsets.fromLTRB(20.0, 15.0, 20.0, 15.0), - onPressed: () {}, + onPressed: () async { + if(formkey.currentState.validate()){//make sure user input are acceptable + dynamic result = await _auth.registerwithEandP(email, password); + if(result == null){ + setState((){ + error= 'bruh'; + }); + } + } + }, child: Text("Register", textAlign: TextAlign.center, style: style.copyWith( @@ -88,38 +118,40 @@ import 'package:google_fonts/google_fonts.dart'; color: Colors.white, child: Padding( padding: const EdgeInsets.all(36.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox( - height: 50.0, - child: titletext, - ), - // SizedBox( - // height: 100.0, - // child: Image.asset( - // "assets/ridericon.png", - // fit: BoxFit.contain, - // ), - // ), //change this to a signup title - - SizedBox(height: 45.0), - emailField, - SizedBox(height: 25.0), - passwordField, - SizedBox( - height: 35.0, - ), - loginButon, - SizedBox( - height: 15.0, - ), - registerButton, - SizedBox( - height: 10.0, - ), - ], + child: Form( + key: formkey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + height: 50.0, + child: titletext, + ), + // SizedBox( + // height: 100.0, + // child: Image.asset( + // "assets/ridericon.png", + // fit: BoxFit.contain, + // ), + // ), //change this to a signup title + + SizedBox(height: 45.0), + emailField, + SizedBox(height: 25.0), + passwordField, + SizedBox( + height: 35.0, + ), + SizedBox( + height: 15.0, + ), + registerButton, + SizedBox( + height: 10.0, + ), + ], + ), ), ), ), diff --git a/lib/services/userauth.dart b/lib/services/userauth.dart new file mode 100644 index 00000000..be62f3dc --- /dev/null +++ b/lib/services/userauth.dart @@ -0,0 +1,65 @@ +import 'package:firebase_auth/firebase_auth.dart'; + +class Authsystem { + + final FirebaseAuth fbauth = FirebaseAuth.instance; + // User fireuser(FirebaseUser user) { + // if(user != null){ + // return User(userid: user.uid); + // } + // else{ + // return null; + // } + // } + + Stream get getuser{ //get keyword used for get method + return fbauth.onAuthStateChanged; //listen to firebase if a user's auth changed return the user + } + + Future signInAnon() async { //for debugging purpose only (signin anonomysly) + try { //try signing user in + AuthResult result = await fbauth.signInAnonymously(); + FirebaseUser user = result.user; + return user; + } catch (e) { + print(e.toString()); //print error if it fails + return null; + } + } + + Future signout() async{ + try{ + return await fbauth.signOut(); + } + catch(e){ + print(e); //catch the error if signout fails. + } + } + + Future registerwithEandP(String email, String pass) async{ + try{ + AuthResult result = await fbauth.createUserWithEmailAndPassword(email: email, password: pass); + FirebaseUser user= result.user; + return user.uid; + } + catch(e){ + print(e.toString()); + return null; + } + } + Future signinwithEandP(String email, String pass) async{ + try{ + AuthResult result = await fbauth.signInWithEmailAndPassword(email: email, password: pass); + FirebaseUser user= result.user; + return user.uid; + } + catch(e){ + print(e.toString()); + return null; + } + } + + + + +} \ No newline at end of file diff --git a/lib/util/data.dart b/lib/util/data.dart new file mode 100644 index 00000000..d7fa946f --- /dev/null +++ b/lib/util/data.dart @@ -0,0 +1,1215 @@ +// ENCODED POLYLINES +// ROUTE 87: "y|{cGtum`MdIVHfHwCLsDmIa@pB`K`SbAoCvFR~@eBjAn@mAhBeAu@sFQ_AjC|EhKfNpy@rNlb@lFbXuCfc@hsAnVwD|[gLkAq_@qH~Bu]}c@yHyEto@u@hq@nXdIlG~D@vE|g@jH^wFwh@aGgIaMeHhB" +// ROUTE 286: "s|pcGvyh`Me@i@a@r@b@h@`@q@x@IKlf@aAzFoEjKw@fDaDxWuAvSeBpMgDxFyB`EcA`FkFnYeE~\_CdQqBrE_FtFqIbPoBjJsAnBcQ`PyDpEiDrCkNlJcFnc@sOaPtE_D|JpKbD}YoFfFqOdKyF~DcKtLeFdG_FpLyAnFgB`AkLvBcAbBt@vM[vBaClD_CmBuMyCgIqAcBAa_@aHnCmb@tA}Ah@sFhBgGt@cCWwAoDmCoImK{AkBk@_@j@`@|AjBnIlKpDlCRzA}CxJq@hGoAhAmCvb@zLdC{AfQo@rEe@n@cB~@oCjCo@hEcCi@z@bFOhDqAxFaCfHnAbBDbFtg@|G`@wF}h@yGkAyA|DiMbBwL" +// ROUTE 289: "ap{cGtit`MtZlFdy@hOkCfX_HtFs@tE{Bi@v@xHfAmGcCk@z@nI_FtQhAnBBjEbh@jHxA{ZoAgHhBiLrA{FrLkBvFkCrFmJs@wRtQsDhIqUrPoRnH{FrOjPWxEbDbNbGlH}H~BcCtFx@pL{@dBeBH{MgJpNcf@g@wBxCwAvA_L_KgK}EtCmHvFcP`SwIhToQzEp@dRwF|IyF`DwLpBeEtS{BfJkAzOwb@{F" + +// I think that eventually we should create a custom object for each stop, a list of lists is pretty clunky +final busPolylines = [ + polyline_87, polyline_286, polyline_289 +]; + + +// File to hold a bunch of lists for our listbuilders +final shuttleStopLists = [south_stops, north_stops, west_stops, weekend_express_stops]; +final shuttleTimeLists = [ + weekday_south.expand((i) => i).toList(), + weekday_north.expand((i) => i).toList(), + weekday_west.expand((i) => i).toList(), + weekend_express.expand((i) => i).toList() +]; +final busStopLists = [ + stops_87, + stops_286, + stops_289, + +]; +final busTimeLists = [ + times_87.expand((i) => i).toList(), + times_286.expand((i) => i).toList(), + times_289.expand((i) => i).toList(), +]; + +// Shuttle Schedules // +const List> north_stops = [ + ["Union to Troy Crosswalk","42.73029109316892","-73.67655873298646","1"], + ["Troy Crosswalk to 9th St","42.7312289168093","-73.6801850795746","26"], + ["9th St to Alumni House","42.7328759845107","-73.6825019716889","28"], + ["Alumni House to Jacob","42.73328577446417","-73.67845773696901","13"], + ["Jacob to Colonie","42.73433031016419","-73.6761257175685","31"], + ["Colonie to Georgian","42.73637487312414","-73.67058759924475","14"], + ["Georgian to Brinsmade","42.736894431805204","-73.6668883604528","39"], + ["Brinsmade to Sunset 1","42.73565383452506","-73.66511642932893","15"], + ["Sunset 1 to Sunset 2","42.73445074085895","-73.66340217450477","24"], + ["Sunset 2 to E-Lot","42.73265532726045","-73.66520762443544","17"], + ["E-Lot to B-Lot","42.731418","-73.666462","21"], + ["B-Lot to Union","42.731635","-73.669706","18"], +]; + +const List> south_stops = [ + ["Union to B-Lot","42.73029109316892","-73.67655873298646","1"], + ["B-Lot to LXA","42.731635","-73.669706","18"], + ["LXA to Tibitts/Orchard","42.730603231713005","-73.66728060672497","23"], + ["Tibitts/Orchard to Polytech","42.72538585964356","-73.66795652333111","33"], + ["Polytech to 15th/College","42.72264515147823","-73.67949028031151","34"], +]; + +const List> west_stops = [ + ["Union to CBIS/AH","42.73029109316892","-73.67655873298646","1"], + ["CBIS/AH to 15th/Off Commons","42.727836972407424","-73.6781703753778","35"], + ["15th/Off Commons to 15th/Poly","42.726823386814196","-73.67809295654298","12"], + ["15th/Poly to City Station","42.723073","-73.680344","11"], + ["City Station to Blitman","42.72739","-73.687328","10"], + ["Blitman to Winslow","42.73080144429565","-73.68637887775277","38"], + ["Winslow to West","42.731534","-73.68416","20"], + ["West to 87 Gym","42.7315441484621","-73.6813545227051","27"], + ["87 Gym to Union","42.73122584540517","-73.68019812550736","29"], +]; + +const List> weekend_express_stops = [ + ["15th/Col. to Union","lat","long","id"], + ["Union to Colonie","lat","long","id"], + ["Colonie to Brinsmade","lat","long","id"], + ["Brinsmade to Sunset 1&2","lat","long","id"], + ["Sunset 1&2 to E-Lot","lat","long","id"], + ["E-lot to B-Lot","lat","long","id"], + ["B-lot to 13th/Peoples","lat","long","id"], + ["13th/Peoples to Blitman","lat","long","id"], + ["Blitman to City Stat.","lat","long","id"], + ["City Stat. to Poly","lat","long","id"], + ["Poly to 15th/Col.", "lat","long","id"], +]; + +const List> weekend_express = [ + ["4:30p","4:33p","4:36p","4:37p","4:38p","4:39p","4:41p","4:43p","4:45p","4:48p","4:49p","- - - -"], + ["4:50p","4:53p","4:56p","4:57p","4:58p","4:59p","5:01p","5:03p","5:05p","5:08p","5:09p","- - - -"], + ["5:10p","5:13p","5:16p","5:17p","5:18p","5:19p","5:21p","5:23p","5:25p","5:28p","5:29p","- - - -"], + ["5:30p","5:33p","5:36p","5:37p","5:38p","5:39p","5:41p","5:43p","5:45p","5:48p","5:49p","- - - -"], + ["5:50p","5:53p","5:56p","5:57p","5:58p","5:59p","6:01p","6:03p","6:05p","6:08p","6:09p","- - - -"], + ["6:10p","6:13p","6:16p","6:17p","6:18p","6:19p","6:21p","6:23p","6:25p","6:28p","6:39p","6:40p"], + ["6:30p","6:33p","6:36p","6:37p","6:38p","6:39p","6:41p","6:43P","6:45p","6:48p","6:49p","- - - -"], + ["6:50p","6:53p","6:56p","6:57p","6:58p","6:59p","7:01p","7:03p","7:05p","7:08p","7:09p","- - - -"], + ["7:10p","7:13p","7:16p","7:17p","7:18p","7:19p","7:21p","7:23p","7:25p","7:28p","7:29p","- - - -"], + ["7:30p","7:33p","7:36p","7:37p","7:38p","7:39p","7:41p","7:43p","7:45p","7:48p","7:49p","7:50p"] +]; + +const List> weekday_south = [ + ["7:15am","7:17am","7:18am","7:20am","7:23am","7:25am"], + ["7:27am","7:29am","7:30am","7:32am","7:35am","7:37am"], + ["7:39am","7:41am","7:42am","7:44am","7:47am","7:49am"], + ["7:51am","7:53am","7:54am","7:56am","7:59am","8:01am"], + ["8:03am","8:05am","8:06am","8:08am","8:11am","8:13am"], + ["8:15am","8:17am","8:18am","8:20am","8:23am","8:25am"], + ["8:27am","8:29am","8:30am","8:32am","8:35am","8:37am"], + ["8:39am","8:41am","8:42am","8:44am","8:47am","8:49am"], + ["8:51am","8:53am","8:54am","8:56am","8:59am","9:01am"], + ["9:03am","9:05am","9:06am","9:08am","9:11am","9:13am"], + ["9:15am","9:17am","9:18am","9:20am","9:23am","9:25am"], + ["9:20am","9:22am","9:23am","9:25am","9:28am","9:30am"], + ["9:32am","9:34am","9:35am","9:37am","9:40am","9:42am"], + ["9:44am","9:46am","9:47am","9:49am","9:52am","9:54am"], + ["9:56am","9:58am","9:59am","10:01am","10:04am","10:06am"], + ["10:08am","10:10am","10:11am","10:13am","10:16am","10:18am"], + ["10:20am","10:22am","10:23am","10:25am","10:28am","10:30am"], + ["10:32am","10:34am","10:35am","10:37am","10:40am","10:42am"], + ["10:44am","10:46am","10:47am","10:49am","10:52am","10:54am"], + ["10:56am","10:58am","10:59am","11:01am","11:04am","11:06am"], + ["11:08am","11:10am","11:11am","11:13am","11:16am","11:18am"], + ["11:20am","11:22am","11:23am","11:25am","11:28am","11:30am"], + ["11:32am","11:34am","11:35am","11:37am","11:40am","11:42am"], + ["11:44am","11:46am","11:47am","11:49am","11:52am","11:54am"], + ["11:56am","11:58am","11:59am","12:01pm","12:04pm","12:06pm"], + ["12:08pm","12:10pm","12:11pm","12:13pm","12:16pm","12:18pm"], + ["12:20pm","12:22pm","12:23pm","12:25pm","12:28pm","12:30pm"], + ["12:44pm","12:46pm","12:47pm","12:49pm","12:52pm","12:54pm"], + ["12:56pm","12:58pm","12:59pm","1:01pm","1:04pm","1:06pm"], + ["1:08pm","1:10pm","1:11pm","1:13pm","1:16pm","1:18pm"], + ["1:20pm","1:22pm","1:23pm","1:25pm","1:28pm","1:30pm"], + ["1:44pm","1:46pm","1:47pm","1:49pm","1:52pm","1:54pm"], + ["1:56pm","1:58pm","1:59pm","2:01pm","2:04pm","2:06pm"], + ["2:08pm","2:10pm","2:11pm","2:13pm","2:16pm","2:18pm"], + ["2:20pm","2:22pm","2:23pm","2:25pm","2:28pm","2:30pm"], + ["2:44pm","2:46pm","2:47pm","2:49pm","2:52pm","2:54pm"], + ["2:56pm","2:58pm","2:59pm","3:01pm","3:04pm","3:06pm"], + ["3:08pm","3:10pm","3:11pm","3:13pm","3:16pm","3:18pm"], + ["3:20pm","3:22pm","3:23pm","3:25pm","3:28pm","3:30pm"], + ["3:44pm","3:46pm","3:47pm","3:49pm","3:52pm","3:54pm"], + ["3:56pm","3:58pm","3:59pm","4:01pm","4:04pm","4:06pm"], + ["4:08pm","4:10pm","4:11pm","4:13pm","4:16pm","4:18pm"], + ["4:20pm","4:22pm","4:23pm","4:25pm","4:28pm","4:30pm"], + ["4:44pm","4:46pm","4:47pm","4:49pm","4:52pm","4:54pm"], + ["4:50pm","4:52pm","4:53pm","4:55pm","4:58pm","5:00pm"], + ["4:56pm","4:58pm","4:59pm","5:01pm","5:04pm","5:06pm"], + ["5:02pm","5:04pm","5:05pm","5:07pm","5:10pm","5:12pm"], + ["5:14pm","5:16pm","5:17pm","5:19pm","5:22pm","5:24pm"], + ["5:26pm","5:28pm","5:29pm","5:31pm","5:34pm","5:36pm"], + ["5:38pm","5:40pm","5:41pm","5:43pm","5:46pm","5:48pm"], + ["5:50pm","5:52pm","5:53pm","5:55pm","5:58pm","6:00pm"], + ["6:02pm","6:04pm","6:05pm","6:07pm","6:10pm","6:12pm"], + ["6:14pm","6:16pm","6:17pm","6:19pm","6:22pm","6:24pm"], + ["6:26pm","6:28pm","6:29pm","6:31pm","6:34pm","6:36pm"], + ["6:38pm","6:40pm","6:41pm","6:43pm","6:46pm","6:48pm"], + ["6:50pm","6:52pm","6:53pm","6:55pm","6:58pm","7:00pm"], + ["7:02pm","7:04pm","7:05pm","7:07pm","7:10pm","7:12pm"], + ["7:14pm","7:16pm","7:17pm","7:19pm","7:22pm","7:24pm"], + ["7:20pm","7:22pm","7:23pm","7:25pm","7:28pm","7:30pm"], + ["7:26pm","7:28pm","7:29pm","7:31pm","7:34pm","7:36pm"], + ["7:32pm","7:34pm","7:35pm","7:37pm","7:40pm","7:42pm"], + ["7:38pm","7:40pm","7:41pm","7:43pm","7:46pm","7:48pm"], + ["7:44pm","7:46pm","7:47pm","7:49pm","7:52pm","7:54pm"], + ["7:50pm","7:52pm","7:53pm","7:55pm","7:58pm","8:00pm"], + ["7:56pm","7:58pm","7:59pm","8:01pm","8:04pm","8:06pm"], + ["8:02pm","8:04pm","8:05pm","8:07pm","8:10pm","8:12pm"], + ["8:08pm","8:10pm","8:11pm","8:13pm","8:16pm","8:18pm"], + ["8:20pm","8:22pm","8:23pm","8:25pm","8:28pm","8:30pm"], + ["8:32pm","8:34pm","8:35pm","8:37pm","8:40pm","8:42pm"], + ["8:44pm","8:46pm","8:47pm","8:49pm","8:52pm","8:54pm"], + ["8:56pm","8:58pm","8:59pm","9:01pm","9:04pm","9:06pm"], + ["9:08pm","9:10pm","9:11pm","9:13pm","9:16pm","9:18pm"], + ["9:20pm","9:22pm","9:23pm","9:25pm","9:28pm","9:30pm"], + ["9:32pm","9:34pm","9:35pm","9:37pm","9:40pm","9:42pm"], + ["9:44pm","9:46pm","9:47pm","9:49pm","9:52pm","9:54pm"], + ["9:56pm","9:58pm","9:59pm","10:01pm","10:04pm","10:06pm"], + ["10:08pm","10:10pm","10:11pm","10:13pm","10:16pm","10:18pm"], + ["10:20pm","10:22pm","10:23pm","10:25pm","10:28pm","10:30pm"], + ["10:32pm","10:34pm","10:35pm","10:37pm","10:40pm","10:42pm"] +]; + +const List> weekday_north = [ + ["7:00am","7:02am","7:03am","7:04am","7:06am","7:08am","7:10am","7:11am","7:12am","7:13am","7:14am","7:15am"], + ["7:18am","7:20am","7:21am","7:22am","7:24am","7:26am","7:28am","7:29am","7:30am","7:31am","7:32am","7:33am"], + ["7:36am","7:38am","7:39am","7:40am","7:42am","7:44am","7:46am","7:47am","7:48am","7:49am","7:50am","7:51am"], + ["7:54am","7:56am","7:57am","7:58am","8:00am","8:02am","8:03am","8:04am","8:05am","8:06am","8:07am","8:08am"], + ["8:11am","8:13am","8:14am","8:15am","8:17am","8:19am","8:20am","8:21am","8:22am","8:23am","8:24am","8:25am"], + ["8:20am","8:22am","8:23am","8:24am","8:26am","8:28am","8:30am","8:31am","8:32am","8:33am","8:34am","8:35am"], + ["8:28am","8:30am","8:31am","8:32am","8:34am","8:36am","8:37am","8:38am","8:39am","8:40am","8:41am","8:42am"], + ["8:38am","8:40am","8:41am","8:42am","8:44am","8:46am","8:48am","8:49am","8:50am","8:51am","8:52am","8:53am"], + ["8:45am","8:47am","8:48am","8:49am","8:51am","8:53am","8:55am","8:56am","8:57am","8:58am","8:59am","9:00am"], + ["8:56am","8:58am","8:59am","9:00am","9:02am","9:04am","9:06am","9:07am","9:08am","9:09am","9:10am","9:11am"], + ["9:03am","9:05am","9:06am","9:07am","9:09am","9:11am","9:13am","9:14am","9:15am","9:16am","9:17am","9:18am"], + ["9:14am","9:16am","9:17am","9:18am","9:20am","9:22am","9:24am","9:25am","9:26am","9:27am","9:28am","9:29am"], + ["9:21am","9:23am","9:24am","9:25am","9:27am","9:29am","9:31am","9:32am","9:33am","9:34am","9:35am","9:36am"], + ["9:32am","9:34am","9:35am","9:36am","9:38am","9:40am","9:42am","9:43am","9:44am","9:45am","9:46am","9:47am"], + ["9:39am","9:41am","9:42am","9:43am","9:45am","9:47am","9:49am","9:50am","9:51am","9:52am","9:53am","9:54am"], + ["9:50am","9:52am","9:53am","9:54am","9:56am","9:58am","10:00am","10:01am","10:02am","10:03am","10:04am","10:05am"], + ["9:57am","9:59am","10:00am","10:01am","10:03am","10:05am","10:07am","10:08am","10:09am","10:10am","10:11am","10:12am"], + ["10:08am","10:10am","10:11am","10:12am","10:14am","10:16am","10:18am","10:19am","10:20am","10:21am","10:22am","10:23am"], + ["10:15am","10:17am","10:18am","10:19am","10:21am","10:23am","10:25am","10:26am","10:27am","10:28am","10:29am","10:30am"], + ["10:26am","10:28am","10:29am","10:30am","10:32am","10:34am","10:36am","10:37am","10:38am","10:39am","10:40am","10:41am"], + ["10:33am","10:35am","10:36am","10:37am","10:39am","10:41am","10:43am","10:44am","10:45am","10:46am","10:47am","10:48am"], + ["10:44am","10:46am","10:47am","10:48am","10:50am","10:52am","10:54am","10:55am","10:56am","10:57am","10:58am","10:59am"], + ["10:51am","10:53am","10:54am","10:55am","10:57am","10:59am","11:01am","11:02am","11:03am","11:04am","11:05am","11:06am"], + ["11:02am","11:04am","11:05am","11:06am","11:08am","11:10am","11:12am","11:13am","11:14am","11:15am","11:16am","11:17am"], + ["11:09am","11:11am","11:12am","11:13am","11:15am","11:17am","11:19am","11:20am","11:21am","11:22am","11:23am","11:24am"], + ["11:20am","11:22am","11:23am","11:24am","11:26am","11:28am","11:30am","11:31am","11:32am","11:33am","11:34am","11:35am"], + ["11:27am","11:29am","11:30am","11:31am","11:33am","11:35am","11:37am","11:38am","11:39am","11:40am","11:41am","11:42am"], + ["11:38am","11:40am","11:41am","11:42am","11:44am","11:46am","11:48am","11:49am","11:50am","11:51am","11:52am","11:53am"], + ["11:45am","11:47am","11:48am","11:49am","11:51am","11:53am","11:55am","11:56am","11:57am","11:58am","11:59am","12:00pm"], + ["11:56am","11:58am","11:59am","12:00pm","12:02pm","12:04pm","12:06pm","12:07pm","12:08pm","12:09pm","12:10pm","12:11pm"], + ["12:03pm","12:05pm","12:06pm","12:07pm","12:09pm","12:11pm","12:13pm","12:14pm","12:15pm","12:16pm","12:17pm","12:18pm"], + ["12:14pm","12:16pm","12:17pm","12:18pm","12:20pm","12:22pm","12:24pm","12:25pm","12:26pm","12:27pm","12:28pm","12:29pm"], + ["12:20pm","12:22pm","12:23pm","12:24pm","12:26pm","12:28pm","12:30pm","12:31pm","12:32pm","12:33pm","12:34pm","12:35pm"], + ["12:21pm","12:23pm","12:24pm","12:25pm","12:27pm","12:29pm","12:31pm","12:32pm","12:33pm","12:34pm","12:35pm","12:36pm"], + ["12:32pm","12:34pm","12:35pm","12:36pm","12:38pm","12:40pm","12:42pm","12:43pm","12:44pm","12:45pm","12:46pm","12:47pm"], + ["12:39pm","12:41pm","12:42pm","12:43pm","12:45pm","12:47pm","12:49pm","12:50pm","12:51pm","12:52pm","12:53pm","12:54pm"], + ["12:56pm","12:58pm","12:59pm","1:00pm","1:02pm","1:04pm","1:06pm","1:07pm","1:08pm","1:09pm","1:10pm","1:11pm"], + ["12:57pm","12:59pm","1:00pm","1:02pm","1:04pm","1:06pm","1:08pm","1:09pm","1:10pm","1:11pm","1:12pm","1:13pm"], + ["1:14pm","1:16pm","1:17pm","1:18pm","1:20pm","1:22pm","1:24pm","1:26pm","1:27pm","1:28pm","1:29pm","1:30pm"], + ["1:16pm","1:18pm","1:19pm","1:21pm","1:23pm","1:25pm","1:27pm","1:28pm","1:29pm","1:30pm","1:31pm","1:32pm"], + ["1:33pm","1:35pm","1:36pm","1:37pm","1:39pm","1:41pm","1:43pm","1:44pm","1:45pm","1:46pm","1:47pm","1:43pm"], + ["1:35pm","1:37pm","1:38pm","1:39pm","1:41pm","1:43pm","1:45pm","1:46pm","1:47pm","1:48pm","1:49pm","1:50pm"], + ["1:46pm","1:48pm","1:49pm","1:50pm","1:52pm","1:54pm","1:56pm","1:57pm","1:58pm","1:59pm","2:00pm","2:01pm"], + ["1:53pm","1:55pm","1:56pm","1:57pm","1:59pm","2:01pm","2:03pm","2:04pm","2:05pm","2:06pm","2:07pm","2:08pm"], + ["2:04pm","2:06pm","2:07pm","2:08pm","2:10pm","2:12pm","2:14pm","2:15pm","2:16pm","2:17pm","2:18pm","2:19pm"], + ["2:11pm","2:13pm","2:14pm","2:15pm","2:17pm","2:19pm","2:21pm","2:22pm","2:23pm","2:24pm","2:25pm","2:26pm"], + ["2:22pm","2:24pm","2:25pm","2:26pm","2:28pm","2:30pm","2:32pm","2:31pm","2:32pm","2:33pm","2:34pm","2:35pm"], + ["2:29pm","2:31pm","2:32pm","2:33pm","2:35pm","2:37pm","2:39pm","2:40pm","2:41pm","2:42pm","2:43pm","2:44pm"], + ["2:38pm","2:40pm","2:41pm","2:42pm","2:44pm","2:46pm","2:48pm","2:49pm","2:50pm","2:51pm","2:52pm","2:53pm"], + ["2:50pm","2:52pm","2:53pm","2:54pm","2:56pm","2:58pm","3:00pm","3:01pm","3:02pm","3:03pm","3:04pm","3:05pm"], + ["2:56pm","2:58pm","2:59pm","3:00pm","3:02pm","3:04pm","3:06pm","3:08pm","3:10pm","3:11pm","3:12pm","3:13pm"], + ["3:00pm","3:02pm","3:03pm","3:04pm","3:06pm","3:08pm","3:10pm","3:11pm","3:12pm","3:13pm","3:14pm","3:15pm"], + ["3:08pm","3:10pm","3:11pm","3:12pm","3:14pm","3:16pm","3:18pm","3:19pm","3:20pm","3:21pm","3:22pm","3:23pm"], + ["3:16pm","3:18pm","3:19pm","3:20pm","3:22pm","3:24pm","3:26pm","3:27pm","3:28pm","3:29pm","3:30pm","3:31pm"], + ["3:18pm","3:20pm","3:21pm","3:22pm","3:24pm","3:26pm","3:28pm","3:29pm","3:30pm","3:31pm","3:32pm","3:33pm"], + ["3:26pm","3:28pm","3:29pm","3:30pm","3:32pm","3:34pm","3:36pm","3:37pm","3:38pm","3:39pm","3:40pm","3:41pm"], + ["3:34pm","3:36pm","3:37pm","3:38pm","3:40pm","3:42pm","3:44pm","3:45pm","3:46pm","3:47pm","3:48pm","3:49pm"], + ["3:36pm","3:38pm","3:39pm","3:40pm","3:42pm","3:44pm","3:46pm","3:47pm","3:48pm","3:49pm","3:50pm","3:51pm"], + ["3:44pm","3:46pm","3:47pm","3:48pm","3:50pm","3:52pm","3:54pm","3:55pm","3:56pm","3:57pm","3:58pm","3:59pm"], + ["3:52pm","3:54pm","3:55pm","3:56pm","3:58pm","4:00pm","4:02pm","4:03pm","4:04pm","4:05pm","4:06pm","4:07pm"], + ["3:54pm","3:56pm","3:57pm","3:58pm","4:00pm","4:02pm","4:04pm","4:05pm","4:06pm","4:07pm","4:08pm","4:09pm"], + ["4:02pm","4:04pm","4:05pm","4:06pm","4:08pm","4:10pm","4:12pm","4:13pm","4:14pm","4:15pm","4:16pm","4:17pm"], + ["4:12pm","4:14pm","4:15pm","4:16pm","4:18pm","4:20pm","4:22pm","4:23pm","4:24pm","4:25pm","4:26pm","4:27pm"], + ["4:20pm","4:22pm","4:23pm","4:24pm","4:26pm","4:28pm","4:30pm","4:31pm","4:32pm","4:33pm","4:34pm","4:35pm"], + ["4:30pm","4:32pm","4:33pm","4:34pm","4:36pm","4:38pm","4:40pm","4:41pm","4:42pm","4:43pm","4:44pm","4:45pm"], + ["4:38pm","4:40pm","4:41pm","4:42pm","4:44pm","4:46pm","4:48pm","4:49pm","4:50pm","4:51pm","4:52pm","4:53pm"], + ["4:48pm","4:50pm","4:51pm","4:52pm","4:54pm","4:56pm","4:58pm","4:59pm","5:00pm","5:01pm","5:02pm","5:03pm"], + ["4:56pm","4:58pm","4:59pm","5:00pm","5:02pm","5:04pm","5:06pm","5:07pm","5:08pm","5:09pm","5:10pm","5:11pm"], + ["5:06pm","5:08pm","5:09pm","5:10pm","5:12pm","5:14pm","5:16pm","5:17pm","5:18pm","5:19pm","5:20pm","5:21pm"], + ["5:14pm","5:16pm","5:17pm","5:18pm","5:20pm","5:22pm","5:24pm","5:25pm","5:26pm","5:27pm","5:28pm","5:29pm"], + ["5:24pm","5:26pm","5:27pm","5:28pm","5:30pm","5:32pm","5:34pm","5:35pm","5:36pm","5:37pm","5:38pm","5:39pm"], + ["5:32pm","5:34pm","5:35pm","5:36pm","5:38pm","5:40pm","5:42pm","5:43pm","5:44pm","5:45pm","5:46pm","5:47pm"], + ["5:42pm","5:44pm","5:45pm","5:46pm","5:48pm","5:50pm","5:52pm","5:53pm","5:54pm","5:55pm","5:56pm","5:57pm"], + ["5:50pm","5:52pm","5:53pm","5:54pm","5:56pm","5:58pm","6:00pm","6:01pm","6:02pm","6:03pm","6:04pm","6:05pm"], + ["6:00pm","6:02pm","6:03pm","6:04pm","6:06pm","6:08pm","6:10pm","6:11pm","6:12pm","6:13pm","6:14pm","6:15pm"], + ["6:18pm","6:20pm","6:21pm","6:22pm","6:24pm","6:26pm","6:28pm","6:29pm","6:30pm","6:31pm","6:32pm","6:33pm"], + ["6:36pm","6:38pm","6:39pm","6:40pm","6:42pm","6:44pm","6:46pm","6:47pm","6:48pm","6:49pm","6:50pm","6:51pm"], + ["6:54pm","6:56pm","6:57pm","6:58pm","7:00pm","7:02pm","7:04pm","7:05pm","7:06pm","7:07pm","7:08pm","7:09pm"], + ["7:12pm","7:14pm","7:15pm","7:16pm","7:18pm","7:20pm","7:22pm","7:23pm","7:24pm","7:25pm","7:26pm","7:27pm"], + ["7:30pm","7:32pm","7:33pm","7:34pm","7:36pm","7:38pm","7:40pm","7:41pm","7:42pm","7:43pm","7:44pm","7:45pm"], + ["7:48pm","7:50pm","7:51pm","7:52pm","7:54pm","7:56pm","7:58pm","7:59pm","8:00pm","8:01pm","8:02pm","8:03pm"], + ["8:06pm","8:08pm","8:09pm","8:10pm","8:12pm","8:14pm","8:16pm","8:17pm","8:18pm","8:19pm","8:20pm","8:21pm"], + ["8:24pm","8:26pm","8:27pm","8:28pm","8:30pm","8:32pm","8:34pm","8:35pm","8:36pm","8:37pm","8:38pm","8:39pm"], + ["8:42pm","8:44pm","8:45pm","8:46pm","8:48pm","8:50pm","8:52pm","8:53pm","8:54pm","8:55pm","8:56pm","8:57pm"], + ["9:00pm","9:02pm","9:03pm","9:04pm","9:06pm","9:08pm","9:10pm","9:11pm","9:12pm","9:13pm","9:14pm","9:15pm"], + ["9:18pm","9:20pm","9:21pm","9:22pm","9:24pm","9:26pm","9:28pm","9:29pm","9:30pm","9:31pm","9:32pm","9:33pm"], + ["9:36pm","9:38pm","9:39pm","9:40pm","9:42pm","9:44pm","9:46pm","9:47pm","9:48pm","9:49pm","9:50pm","9:51pm"], + ["9:54pm","9:56pm","9:57pm","9:58pm","10:00pm","10:02pm","10:04pm","10:05pm","10:06pm","10:07pm","10:08pm","10:09pm"], + ["10:12pm","10:14pm","10:15pm","10:16pm","10:18pm","10:20pm","10:22pm","10:23pm","10:24pm","10:25pm","10:26pm","10:27pm"], + ["10:30pm","10:32pm","10:33pm","10:34pm","10:36pm","10:38pm","10:40pm","10:41pm","10:42pm","10:43pm","10:44pm","10:45pm"], + ["10:48pm","10:50pm","10:51pm","10:52pm","10:54pm","10:56pm","10:58pm","10:59pm","11:00pm","11:01pm","11:02pm","11:03pm"] +]; + +const List> weekday_west =[ + ["7:00am","7:01am","7:01am","7:03am","7:06am","7:08am","7:10am","7:12am","7:13am"], + ["7:15am","7:16am","7:16am","7:18am","7:21am","7:23am","7:25am","7:27am","7:28am"], + ["7:30am","7:31am","7:31am","7:33am","7:36am","7:38am","7:40am","7:42am","7:43am"], + ["7:45am","7:46am","7:46am","7:48am","7:51am","7:53am","7:55am","7:57am","7:58am"], + ["7:52am","7:53am","7:53am","7:55am","7:58am","8:01am","8:03am","8:05am","8:06am"], + ["8:00am","8:01am","8:01am","8:03am","8:06am","8:08am","8:10am","8:12am","8:13am"], + ["8:08am","8:09am","8:09am","8:10am","8:13am","8:15am","8:17am","8:19am","8:20am"], + ["8:15am","8:16am","8:16am","8:18am","8:21am","8:23am","8:25am","8:27am","8:28am"], + ["8:22am","8:23am","8:23am","8:25am","8:28am","8:30am","8:32am","8:34am","8:35am"], + ["8:30am","8:31am","8:31am","8:33am","8:36am","8:38am","8:40am","8:42am","8:43am"], + ["8:37am","8:38am","8:38am","8:40am","8:43am","8:45am","8:47am","8:49am","8:50am"], + ["8:45am","8:46am","8:46am","8:48am","8:51am","8:53am","8:55am","8:57am","8:58am"], + ["8:52am","8:53am","8:53am","8:55am","8:58am","9:00am","9:02am","9:04am","9:05am"], + ["9:00am","9:01am","9:01am","9:03am","9:06am","9:08am","9:10am","9:12am","9:13am"], + ["9:07am","9:08am","9:08am","9:10am","9:13am","9:15am","9:17am","9:19am","9:20am"], + ["9:15am","9:16am","9:16am","9:18am","9:21am","9:23am","9:25am","9:27am","9:28am"], + ["9:22am","9:23am","9:23am","9:25am","9:28am","9:30am","9:32am","9:34am","9:35am"], + ["9:30am","9:31am","9:31am","9:33am","9:36am","9:38am","9:40am","9:42am","9:43am"], + ["9:37am","9:38am","9:38am","9:40am","9:43am","9:45am","9:47am","9:49am","9:50am"], + ["9:45am","9:46am","9:46am","9:48am","9:51am","9:53am","9:55am","9:57am","9:58am"], + ["9:52am","9:53am","9:53am","9:55am","9:58am","10:00am","10:02am","10:04am","10:05am"], + ["10:00am","10:01am","10:01am","10:03am","10:06am","10:08am","10:10am","10:12am","10:13am"], + ["10:07pm","10:08pm","10:08pm","10:10pm","10:13pm","10:15pm","10:17pm","10:19pm","10207pm"], + ["10:15am","10:16am","10:16am","10:18am","10:21am","10:23am","10:25am","10:27am","10:28am"], + ["10:22pm","10:23pm","10:23pm","10:25pm","10:28pm","10:30pm","10:32pm","10:24pm","10:35pm"], + ["10:30am","10:31am","10:31am","10:33am","10:36am","10:38am","10:40am","10:42am","10:43am"], + ["10:37am","10:38am","10:38am","10:40am","10:43am","10:45am","10:47am","10:49am","10:50am"], + ["10:45am","10:46am","10:46am","10:48am","10:51am","10:53am","10:55am","10:57am","10:58am"], + ["10:52am","10:53am","10:53am","10:55am","10:58am","11:00am","11:02am","11:04am","11:05am"], + ["11:00am","11:01am","11:01am","11:03am","11:06am","11:08am","11:10am","11:12am","11:13am"], + ["11:07am","11:08am","11:08am","11:10am","11:13am","11:15am","11:17am","11:19am","11:20am"], + ["11:15am","11:16am","11:16am","11:18am","11:21am","11:23am","11:25am","11:27am","11:28am"], + ["11:22am","11:23am","11:23am","11:25am","11:28am","11:30am","11:32am","11:34am","11:35am"], + ["11:30am","11:31am","11:31am","11:33am","11:36am","11:38am","11:40am","11:42am","11:43am"], + ["11:37am","11:38am","11:38am","11:40am","11:43am","11:45am","11:47am","11:49am","11:50am"], + ["11:45am","11:46am","11:46am","11:48am","11:51am","11:53am","11:55am","11:57am","11:58am"], + ["11:52am","11:53am","11:53am","11:55am","11:58am","12:00pm","12:02pm","12:04pm","12:05pm"], + ["12:00pm","12:01pm","12:01pm","12:03pm","12:06pm","12:08pm","12:10pm","12:12pm","12:13pm"], + ["12:07pm","12:08pm","12:08pm","12:10pm","12:13pm","12:15pm","12:17pm","12:19pm","12:20pm"], + ["12:15pm","12:16pm","12:16pm","12:18pm","12:21pm","12:23pm","12:25pm","12:27pm","12:28pm"], + ["12:22pm","12:23pm","12:23pm","12:25pm","12:28pm","12:30pm","12:32pm","12:34pm","12:35pm"], + ["12:30pm","12:31pm","12:31pm","12:33pm","12:36pm","12:38pm","12:40pm","12:42pm","12:43pm"], + ["12:37pm","12:38pm","12:38pm","12:40pm","12:43pm","12:45pm","12:47pm","12:49pm","12:50pm"], + ["12:45pm","12:46pm","12:46pm","12:48pm","12:51pm","12:53pm","12:55pm","12:57pm","12:58pm"], + ["12:52pm","12:53pm","12:53pm","12:55pm","12:58pm","1:00pm","1:02pm","1:04pm","1:05pm"], + ["1:00pm","1:01pm","1:01pm","1:03pm","1:06pm","1:08pm","1:10pm","1:12pm","1:13pm"], + ["1:07pm","1:08pm","1:08pm","1:10pm","1:13pm","1:15pm","1:17pm","1:19pm","1:20pm"], + ["1:15pm","1:16pm","1:16pm","1:18pm","1:21pm","1:23pm","1:25pm","1:27pm","1:28pm"], + ["1:22pm","1:23pm","1:23pm","1:25pm","1:28pm","1:30pm","1:32pm","1:34pm","1:35pm"], + ["1:30pm","1:31pm","1:31pm","1:33pm","1:36pm","1:38pm","1:40pm","1:42pm","1:43pm"], + ["1:37pm","1:38pm","1:38pm","1:40pm","1:43pm","1:45pm","1:47pm","1:49pm","1:50pm"], + ["1:45pm","1:46pm","1:46pm","1:48pm","1:50pm","1:52pm","1:54pm","1:56pm","1:57pm"], + ["1:52pm","1:53pm","1:53pm","1:55pm","1:58pm","2:00pm","2:02pm","2:04pm","2:05pm"], + ["2:00pm","2:01pm","2:01pm","2:03pm","2:06pm","2:08pm","2:10pm","2:12pm","2:13pm"], + ["2:07pm","2:08pm","2:08pm","2:10pm","2:13pm","2:15pm","2:17pm","2:19pm","2:20pm"], + ["2:15pm","2:16pm","2:16pm","2:18pm","2:21pm","2:23pm","2:25pm","2:27pm","2:28pm"], + ["2:22pm","2:23pm","2:23pm","2:25pm","2:28pm","2:30pm","2:32pm","2:34pm","2:35pm"], + ["2:30pm","2:31pm","2:31pm","2:33pm","2:36pm","2:38pm","2:40pm","2:42pm","2:43pm"], + ["2:37pm","2:38pm","2:38pm","2:40pm","2:43pm","2:45pm","2:47pm","2:49pm","2:50pm"], + ["2:52pm","2:53pm","2:53pm","2:55pm","2:58pm","3:00pm","3:02pm","3:04pm","3:05pm"], + ["3:00pm","3:01pm","3:01pm","3:03pm","3:06pm","3:08pm","3:10pm","3:12pm","3:13pm"], + ["3:07pm","3:08pm","3:08pm","3:10pm","3:13pm","3:15pm","3:17pm","3:19pm","3:20pm"], + ["3:15pm","3:16pm","3:16pm","3:18pm","3:21pm","3:23pm","3:25pm","3:27pm","3:28pm"], + ["3:22pm","3:23pm","3:23pm","3:25pm","3:28pm","3:30pm","3:32pm","3:34pm","3:35pm"], + ["3:30pm","3:31pm","3:31pm","3:33pm","3:36pm","3:38pm","3:40pm","3:42pm","3:43pm"], + ["3:45pm","3:46pm","3:46pm","3:48pm","3:51pm","3:53pm","3:55pm","3:57pm","3:58pm"], + ["4:00pm","4:01pm","4:01pm","4:03pm","4:06pm","4:08pm","4:10pm","4:12pm","4:13pm"], + ["4:15pm","4:16pm","4:16pm","4:18pm","4:21pm","4:23pm","4:25pm","4:27pm","4:28pm"], + ["4:30pm","4:31pm","4:31pm","4:33pm","4:36pm","4:38pm","4:40pm","4:42pm","4:43pm"], + ["4:45pm","4:46pm","4:46pm","4:48pm","4:51pm","4:53pm","4:55pm","4:57pm","4:58pm"], + ["5:00pm","5:01pm","5:01pm","5:03pm","5:06pm","5:08pm","5:10pm","5:12pm","5:13pm"], + ["5:15pm","5:16pm","5:16pm","5:18pm","5:21pm","5:23pm","5:25pm","5:27pm","5:28pm"], + ["5:30pm","5:31pm","5:31pm","5:33pm","5:36pm","5:38pm","5:40pm","5:42pm","5:43pm"], + ["5:45pm","5:46pm","5:46pm","5:48pm","5:51pm","5:53pm","5:55pm","5:57pm","5:58pm"], + ["6:00pm","6:01pm","6:01pm","6:03pm","6:06pm","6:08pm","6:10pm","6:12pm","6:13pm"], + ["6:15pm","6:16pm","6:16pm","6:18pm","6:21pm","6:23pm","6:25pm","6:27pm","6:28pm"], + ["6:30pm","6:31pm","6:31pm","6:33pm","6:36pm","6:38pm","6:40pm","6:42pm","6:43pm"], + ["6:45pm","6:46pm","6:46pm","6:48pm","6:51pm","6:53pm","6:55pm","6:57pm","6:58pm"], + ["6:50pm","6:51pm","6:51pm","6:53pm","6:56pm","6:58pm","7:00pm","7:02pm","7:03pm"], + ["7:00pm","7:01pm","7:01pm","7:03pm","7:06pm","7:08pm","7:10pm","7:12pm","7:13pm"], + ["7:05pm","7:06pm","7:06pm","7:08pm","7:11pm","7:13pm","7:15pm","7:17pm","7:18pm"], + ["7:15pm","7:16pm","7:16pm","7:18pm","7:21pm","7:23pm","7:25pm","7:27pm","7:28pm"], + ["7:20pm","7:21pm","7:21pm","7:23pm","7:26pm","7:28pm","7:30pm","7:32pm","7:33pm"], + ["7:30pm","7:31pm","7:31pm","7:33pm","7:36pm","7:38pm","7:40pm","7:42pm","7:43pm"], + ["7:35pm","7:36pm","7:36pm","7:38pm","7:41pm","7:43pm","7:45pm","7:47pm","7:48pm"], + ["7:45pm","7:46pm","7:46pm","7:48pm","7:51pm","7:53pm","7:55pm","7:57pm","7:58pm"], + ["7:50pm","7:51pm","7:51pm","7:53pm","7:56pm","7:58pm","8:00pm","8:02pm","8:03pm"], + ["8:00pm","8:01pm","8:01pm","8:03pm","8:06pm","8:08pm","8:10pm","8:12pm","8:13pm"], + ["8:05pm","8:06pm","8:06pm","8:08pm","8:11pm","8:13pm","8:15pm","8:17pm","8:18pm"], + ["8:15pm","8:16pm","8:16pm","8:18pm","8:21pm","8:23pm","8:25pm","8:27pm","8:28pm"], + ["8:20pm","8:21pm","8:21pm","8:23pm","8:26pm","8:28pm","8:30pm","8:32pm","8:33pm"], + ["8:30pm","8:31pm","8:31pm","8:33pm","8:36pm","8:38pm","8:40pm","8:42pm","8:43pm"], + ["8:35pm","8:36pm","8:36pm","8:38pm","8:41pm","8:43pm","8:45pm","8:47pm","8:48pm"], + ["8:45pm","8:46pm","8:46pm","8:48pm","8:51pm","8:53pm","8:55pm","8:57pm","8:58pm"], + ["8:50pm","8:51pm","8:51pm","8:53pm","8:56pm","8:58pm","9:00pm","9:02pm","9:03pm"], + ["9:00pm","9:01pm","9:01pm","9:03pm","9:06pm","9:08pm","9:10pm","9:12pm","9:13pm"], + ["9:05pm","9:06pm","9:06pm","9:08pm","9:11pm","9:13pm","9:15pm","9:17pm","9:18pm"], + ["9:15pm","9:16pm","9:16pm","9:18pm","9:21pm","9:23pm","9:25pm","9:27pm","9:28pm"], + ["9:20pm","9:21pm","9:21pm","9:23pm","9:26pm","9:28pm","9:30pm","9:32pm","9:33pm"], + ["9:30pm","9:31pm","9:31pm","9:33pm","9:36pm","9:38pm","9:40pm","9:42pm","9:43pm"], + ["9:35pm","9:36pm","9:36pm","9:38pm","9:41pm","9:43pm","9:45pm","9:47pm","9:48pm"], + ["9:45pm","9:46pm","9:46pm","9:48pm","9:51pm","9:53pm","9:55pm","9:57pm","9:58pm"], + ["9:50pm","9:51pm","9:51pm","9:53pm","9:56pm","9:58pm","10:00pm","10:02pm","10:03pm"], + ["10:00pm","10:01pm","10:01pm","10:03pm","10:06pm","10:08pm","10:10pm","10:12pm","10:13pm"], + ["10:15pm","10:16pm","10:16pm","10:18pm","10:21pm","10:23pm","10:25pm","10:27pm","10:28pm"], + ["10:30pm","10:31pm","10:31pm","10:33pm","10:36pm","10:38pm","10:40pm","10:42pm","10:43pm"], + ["10:45pm","10:46pm","10:46pm","10:48pm","10:51pm","10:53pm","10:55pm","10:57pm","10:58pm"] +]; + +// BUS SCHEDULES // +const List> stops_87 = [ + ["River St & Front St to 4th St & Fulton St","42.732941","-73.690010","07118"], + ["4th St & Fulton St to Hoosick St & 6th Ave","42.732837","-73.688384","07130"], + ["Hoosick St & 6th Ave to Burdett Ave & Samaritan Hospital","42.739727","-73.682889","03103"], + ["Burdett Ave & Samaritan Hospital to 15th St & RPI Walk Over Bridge","42.733552"," -73.671388","0970"], + ["15th St & RPI Walk Over Bridge to 2212 Burdett Ave","42.729469","-73.677643","02886"], + ["2212 Burdett Ave to 716 Hoosick Rd (Price Chopper)","42.733488","-73.671350","07151"], + ["716 Hoosick Rd (Price Chopper) to Walmart - Brunswick Plaza","42.74271","-73.642987","00033"], +]; + +const List> stops_286 = [ + ["Vanderheyden Hall to Main Ave & Atlantic Ave","42.690565","73.614608","10978"], + ["Main Ave & Atlantic Ave to Pawling Ave & Spring Ave","42.696309","-73.641741","04339"], + ["Pawling Ave & Spring Ave to Myrtle Ave","42.708893","-73.663011","04368"], + ["Myrtle Ave to Congress St & 15th St","42.711505","-73.666573","12357"], + ["Congress St & 15th St to Sage Ave & Anderson Field","42.722699","-73.679570","10037"], + ["Sage Ave & Anderson Field to River St & Front St","42.731096","-73.678897","11943"], + ["River St & Front St to 4th St & Fulton St","42.732941","-73.690010","07118"], + ["4th St & Fulton St to Sage Ave @ 87 Gymnasium PE Building","42.732837","-73.688384","07130"], + ["Sage Ave @ 87 Gymnasium PE Building to Sunset Terr & Forsyth Dr","42.730971","-73.678716","11637"], +]; +const List> stops_289 = [ + ["Project St & Madison Ave to Myrtle Ave","42.713669","-73.677208","12359"], + ["Myrtle Ave to Congress St & 15th St","42.711505","-73.666573","12357"], + ["Congress St & 15th St to 4th St & Fulton St","42.722699","-73.679570","10037"], + ["4th St & Fulton St to Sage Ave @ 87 Gymnasium PE Building","42.732837","-73.688384","07130"], + ["Sage Ave @ 87 Gymnasium PE Building to 15th St & Massachusetts Ave","42.730971","-73.678716","11637"], +]; + +const List> times_87 = [ + ["6:25 am","6:30 am","6:32 am","6:37 am","6:40 am","6:44 am","- - - -","6:49 am"], + ["6:55 am","7:00 am","7:02 am","7:07 am","7:10 am","7:14 am","7:19 am","7:22 am"], + ["7:25 am","7:30 am","7:32 am","7:37 am","7:40 am","7:44 am","- - - -","7:49 am"], + ["7:55 am","8:00 am","8:02 am","8:07 am","8:10 am","8:14 am","8:19 am","8:22 am"], + ["8:25 am","8:30 am","8:32 am","8:37 am","8:40 am","8:44 am","- - - -","8:49 am"], + ["8:55 am","9:00 am","9:02 am","9:07 am","9:10 am","9:14 am","9:19 am","9:22 am"], + ["9:25 am","9:30 am","9:34 am","9:40 am","9:43 am","9:48 am","- - - -","9:57 am"], + ["9:55 am","10:00 am","10:04 am","10:10 am","10:13 am","10:18 am","10:26 am","10:30 am"], + ["10:20 am","10:25 am","10:29 am","10:35 am","10:38 am","10:43 am","- - - -","10:52 am"], + ["10:45 am","10:50 am","10:54 am","11:00 am","11:03 am","11:08 am","11:16 am","11:20 am"], + ["11:10 am","11:15 am","11:19 am","11:25 am","11:28 am","11:33 am","- - - -","11:42 am"], + ["11:35 am","11:40 am","11:44 am","11:50 am","11:53 am","11:58 am","12:06 pm","12:10 pm"], + ["12:00 pm","12:05 pm","12:09 pm","12:15 pm","12:18 pm","12:23 pm","- - - -","12:32 pm"], + ["12:25 pm","12:30 pm","12:34 pm","12:40 pm","12:43 pm","12:48 pm","12:56 pm","1:00 pm"], + ["12:50 pm","12:55 pm","12:59 pm","1:05 pm","1:08 pm","1:13 pm","- - - -","1:22 pm"], + ["1:15 pm","1:20 pm","1:24 pm","1:30 pm","1:33 pm","1:38 pm","1:46 pm","1:50 pm"], + ["1:40 pm","1:45 pm","1:49 pm","1:55 pm","1:58 pm","2:03 pm","- - - -","2:12 pm"], + ["2:05 pm","2:10 pm","2:14 pm","2:20 pm","2:23 pm","2:28 pm","2:36 pm","2:40 pm"], + ["2:30 pm","2:35 pm","2:39 pm","2:45 pm","2:48 pm","2:53 pm","- - - -","3:02 pm"], + ["2:55 pm","3:00 pm","3:04 pm","3:10 pm","3:13 pm","3:18 pm","3:26 pm","3:30 pm"], + ["3:20 pm","3:25 pm","3:29 pm","3:35 pm","3:38 pm","3:43 pm","- - - -","3:52 pm"], + ["3:45 pm","3:50 pm","3:54 pm","4:00 pm","4:03 pm","4:08 pm","4:16 pm","4:20 pm"], + ["4:10 pm","4:15 pm","4:19 pm","4:25 pm","4:28 pm","4:33 pm","- - - -","4:42 pm"], + ["4:35 pm","4:40 pm","4:44 pm","4:50 pm","4:53 pm","4:58 pm","5:06 pm","5:10 pm"], + ["5:00 pm","5:05 pm","5:09 pm","5:15 pm","5:18 pm","5:23 pm","- - - -","5:32 pm"], + ["5:25 pm","5:30 pm","5:34 pm","5:40 pm","5:43 pm","5:48 pm","5:56 pm","6:00 pm"], + ["5:50 pm","5:55 pm","5:59 pm","6:05 pm","6:08 pm","6:13 pm","- - - -","6:22 pm"], + ["6:30 pm","6:34 pm","6:37 pm","6:43 pm","6:46 pm","6:51 pm","6:58 pm","7:01 pm"], + ["7:10 pm","7:14 pm","7:17 pm","7:23 pm","7:26 pm","7:31 pm","7:38 pm","7:41 pm"], + ["7:50 pm","7:54 pm","7:57 pm","8:03 pm","8:06 pm","8:11 pm","8:18 pm","8:21 pm"], + ["8:30 pm","8:34 pm","8:37 pm","8:43 pm","8:46 pm","8:51 pm","8:58 pm","9:01 pm"], + ["9:35 pm","9:39 pm","9:42 pm","9:48 pm","9:51 pm","9:56 pm","10:03 pm","10:06 pm"], + ["10:35 pm","10:39 pm","10:41 pm","10:46 pm","10:49 pm","10:53 pm","10:58 pm","11:02 pm"], + +]; + +const List> times_286 = [ + ["10:56 am","11:00 am","11:04 am","- - - -","11:08 am","11:10 am","11:15 am","- - - -","- - - -","- - - -"], + ["11:56 am","12:00 pm","12:04 pm","- - - -","12:08 pm","12:10 pm","12:15 pm","- - - -","- - - -","- - - -"], + ["12:56 pm","1:00 pm","1:04 pm","- - - -","1:08 pm","1:10 pm","1:15 pm","- - - -","- - - -","- - - -"], + ["1:56 pm","2:00 pm","2:04 pm","- - - -","2:08 pm","2:10 pm","2:15 pm","- - - -","- - - -","- - - -"], + ["2:56 pm","3:00 pm","3:04 pm","- - - -","3:08 pm","3:10 pm","3:15 pm","- - - -","- - - -","- - - -"], + ["3:56 pm","4:00 pm","4:04 pm","- - - -","4:08 pm","4:10 pm","4:15 pm","- - - -","- - - -","- - - -"], + ["4:56 pm","5:00 pm","5:04 pm","- - - -","5:08 pm","5:10 pm","5:15 pm","- - - -","- - - -","- - - -"], + ["5:56 pm","6:00 pm","6:04 pm","- - - -","6:08 pm","6:10 pm","6:15 pm","- - - -","- - - -","- - - -"], + ["6:56 pm","7:00 pm","7:04 pm","- - - -","7:08 pm","7:10 pm","7:15 pm","- - - -","- - - -","- - - -"], + ["7:56 pm","8:00 pm","8:04 pm","- - - -","8:08 pm","8:10 pm","8:15 pm","- - - -","- - - -","- - - -"], + ["- - - -","- - - -","- - - -","9:00 pm","9:03 pm","9:05 pm","9:10 pm","9:15 pm","9:19 pm","9:24 pm"], + ["- - - -","- - - -","- - - -","10:00 pm","10:03 pm","10:05 pm","10:10 pm","10:15 pm","10:19 pm","10:24 pm"], + ["- - - -","- - - -","- - - -","11:00 pm","11:03 pm","11:05 pm","11:10 pm","11:15 pm","11:19 pm","11:24 pm"], + ["- - - -","- - - -","- - - -","12:00 am","12:03 am","12:05 am","12:10 am","12:15 am","12:19 am","12:24 am"], +]; +const List> times_289 = [ + ["7:30 am","7:33 am","7:39 am","7:45 am","7:50 am","7:55 am"], + ["8:30 am","8:33 am","8:39 am","8:45 am","8:50 am","8:55 am"], + ["9:30 am","9:33 am","9:39 am","9:45 am","9:50 am","9:55 am"], + ["10:30 am","10:33 am","10:39 am","10:45 am","10:50 am","10:55 am"], + ["11:30 am","11:33 am","11:39 am","11:45 am","11:50 am","11:55 am"], + ["12:30 pm","12:33 pm","12:39 pm","12:45 pm","12:50 pm","12:55 pm"], + ["1:30 pm","1:33 pm","1:39 pm","1:45 pm","1:50 pm","1:55 pm"], + ["2:30 pm","2:33 pm","2:39 pm","2:45 pm","2:50 pm","2:55 pm"], + ["3:30 pm","3:33 pm","3:39 pm","3:45 pm","3:50 pm","3:55 pm"], + ["4:30 pm","4:33 pm","4:39 pm","4:45 pm","4:50 pm","4:55 pm"], + ["5:30 pm","5:33 pm","5:39 pm","5:45 pm","5:50 pm","5:55 pm"], + ["6:30 pm","6:33 pm","6:39 pm","6:45 pm","6:50 pm","6:55 pm"], + ["7:30 pm","7:33 pm","7:39 pm","7:45 pm","7:50 pm","7:55 pm"], + ["8:30 pm","8:33 pm","8:39 pm","8:45 pm","8:50 pm","8:55 pm"], + ["9:30 pm","9:33 pm","9:39 pm","9:45 pm","9:50 pm","9:55 pm"], + ["10:30 pm","10:33 pm","10:39 pm","10:45 pm","10:50 pm","10:55 pm"], + ["11:30 pm","11:33 pm","11:39 pm","11:45 pm","- - - -","- - - -"], +]; + +const List> polyline_87 = [ + [ + 42.74653, + -73.63947 + ], + [ + 42.7449, + -73.63959 + ], + [ + 42.74485, + -73.64107 + ], + [ + 42.74561, + -73.64114 + ], + [ + 42.74651, + -73.63947 + ], + [ + 42.74668, + -73.64004 + ], + [ + 42.74475, + -73.64325 + ], + [ + 42.74441, + -73.64253 + ], + [ + 42.74317, + -73.64263 + ], + [ + 42.74285, + -73.64212 + ], + [ + 42.74247, + -73.64236 + ], + [ + 42.74286, + -73.64289 + ], + [ + 42.74321, + -73.64262 + ], + [ + 42.74443, + -73.64253 + ], + [ + 42.74475, + -73.64323 + ], + [ + 42.74364, + -73.6452 + ], + [ + 42.7412, + -73.65457 + ], + [ + 42.7387, + -73.66024 + ], + [ + 42.73751, + -73.66426 + ], + [ + 42.73826, + -73.67006 + ], + [ + 42.72477, + -73.67382 + ], + [ + 42.72569, + -73.67845 + ], + [ + 42.72781, + -73.67807 + ], + [ + 42.73302, + -73.67654 + ], + [ + 42.73238, + -73.67163 + ], + [ + 42.73829, + -73.67006 + ], + [ + 42.73938, + -73.67785 + ], + [ + 42.73965, + -73.6859 + ], + [ + 42.73557, + -73.68753 + ], + [ + 42.73422, + -73.68849 + ], + [ + 42.73421, + -73.68957 + ], + [ + 42.72766, + -73.69107 + ], + [ + 42.7275, + -73.68983 + ], + [ + 42.73418, + -73.68854 + ], + [ + 42.73582, + -73.68629 + ], + [ + 42.73729, + -73.68682 + ] +]; + +const List> polyline_286 = [ + [ + 42.69018, + -73.61452 + ], + [ + 42.69037, + -73.61431 + ], + [ + 42.69054, + -73.61457 + ], + [ + 42.69036, + -73.61478 + ], + [ + 42.69019, + -73.61453 + ], + [ + 42.6899, + -73.61448 + ], + [ + 42.68996, + -73.62079 + ], + [ + 42.69029, + -73.62205 + ], + [ + 42.69133, + -73.62403 + ], + [ + 42.69161, + -73.62487 + ], + [ + 42.69242, + -73.62884 + ], + [ + 42.69285, + -73.63216 + ], + [ + 42.69336, + -73.63449 + ], + [ + 42.6942, + -73.63574 + ], + [ + 42.69481, + -73.63671 + ], + [ + 42.69515, + -73.63784 + ], + [ + 42.69633, + -73.64208 + ], + [ + 42.69732, + -73.64688 + ], + [ + 42.69796, + -73.64979 + ], + [ + 42.69853, + -73.65085 + ], + [ + 42.69965, + -73.65208 + ], + [ + 42.70134, + -73.65482 + ], + [ + 42.7019, + -73.65664 + ], + [ + 42.70232, + -73.6572 + ], + [ + 42.70522, + -73.65993 + ], + [ + 42.70615, + -73.66098 + ], + [ + 42.707, + -73.66172 + ], + [ + 42.70946, + -73.66355 + ], + [ + 42.7106, + -73.66939 + ], + [ + 42.71326, + -73.66666 + ], + [ + 42.71219, + -73.66586 + ], + [ + 42.71028, + -73.66787 + ], + [ + 42.70946, + -73.66356 + ], + [ + 42.71066, + -73.66472 + ], + [ + 42.71331, + -73.66667 + ], + [ + 42.71456, + -73.66763 + ], + [ + 42.7165, + -73.66982 + ], + [ + 42.71765, + -73.67113 + ], + [ + 42.71877, + -73.6733 + ], + [ + 42.71922, + -73.6745 + ], + [ + 42.71974, + -73.67483 + ], + [ + 42.72188, + -73.67543 + ], + [ + 42.72222, + -73.67593 + ], + [ + 42.72195, + -73.67829 + ], + [ + 42.72209, + -73.67889 + ], + [ + 42.72274, + -73.67976 + ], + [ + 42.72338, + -73.67921 + ], + [ + 42.72573, + -73.67844 + ], + [ + 42.72737, + -73.67803 + ], + [ + 42.72787, + -73.67802 + ], + [ + 42.733, + -73.67657 + ], + [ + 42.73228, + -73.6709 + ], + [ + 42.73185, + -73.67043 + ], + [ + 42.73164, + -73.66921 + ], + [ + 42.73111, + -73.66789 + ], + [ + 42.73084, + -73.66723 + ], + [ + 42.73096, + -73.66679 + ], + [ + 42.73184, + -73.66608 + ], + [ + 42.73352, + -73.66409 + ], + [ + 42.73398, + -73.66355 + ], + [ + 42.7342, + -73.66339 + ], + [ + 42.73398, + -73.66356 + ], + [ + 42.73351, + -73.6641 + ], + [ + 42.73183, + -73.66609 + ], + [ + 42.73094, + -73.6668 + ], + [ + 42.73084, + -73.66726 + ], + [ + 42.73163, + -73.66915 + ], + [ + 42.73188, + -73.67048 + ], + [ + 42.73228, + -73.67085 + ], + [ + 42.73299, + -73.67657 + ], + [ + 42.73077, + -73.67724 + ], + [ + 42.73123, + -73.68016 + ], + [ + 42.73147, + -73.68122 + ], + [ + 42.73166, + -73.68146 + ], + [ + 42.73216, + -73.68178 + ], + [ + 42.73288, + -73.68248 + ], + [ + 42.73312, + -73.68349 + ], + [ + 42.73378, + -73.68328 + ], + [ + 42.73348, + -73.68442 + ], + [ + 42.73356, + -73.68527 + ], + [ + 42.73397, + -73.68652 + ], + [ + 42.73462, + -73.688 + ], + [ + 42.73422, + -73.6885 + ], + [ + 42.73419, + -73.68964 + ], + [ + 42.72768, + -73.69107 + ], + [ + 42.72751, + -73.68983 + ], + [ + 42.73422, + -73.68842 + ], + [ + 42.7346, + -73.68797 + ], + [ + 42.73365, + -73.68568 + ], + [ + 42.73315, + -73.68348 + ] +]; + +const List> polyline_289 = [ + [ + 42.74449, + -73.67339 + ], + [ + 42.74006, + -73.67458 + ], + [ + 42.73075, + -73.67719 + ], + [ + 42.73145, + -73.68123 + ], + [ + 42.73289, + -73.68246 + ], + [ + 42.73315, + -73.68353 + ], + [ + 42.73377, + -73.68332 + ], + [ + 42.73349, + -73.68489 + ], + [ + 42.73313, + -73.68354 + ], + [ + 42.73379, + -73.68332 + ], + [ + 42.73349, + -73.685 + ], + [ + 42.73461, + -73.68799 + ], + [ + 42.73424, + -73.68855 + ], + [ + 42.73422, + -73.68957 + ], + [ + 42.72764, + -73.69107 + ], + [ + 42.72719, + -73.68661 + ], + [ + 42.72759, + -73.68513 + ], + [ + 42.72706, + -73.683 + ], + [ + 42.72664, + -73.68174 + ], + [ + 42.72446, + -73.6812 + ], + [ + 42.72322, + -73.6805 + ], + [ + 42.722, + -73.67867 + ], + [ + 42.72226, + -73.67551 + ], + [ + 42.71927, + -73.67461 + ], + [ + 42.71762, + -73.671 + ], + [ + 42.7148, + -73.66788 + ], + [ + 42.71328, + -73.66662 + ], + [ + 42.71062, + -73.6694 + ], + [ + 42.71074, + -73.67049 + ], + [ + 42.70992, + -73.67291 + ], + [ + 42.70862, + -73.67442 + ], + [ + 42.71021, + -73.67506 + ], + [ + 42.71087, + -73.67629 + ], + [ + 42.71058, + -73.67846 + ], + [ + 42.71088, + -73.67897 + ], + [ + 42.71139, + -73.67902 + ], + [ + 42.71377, + -73.67722 + ], + [ + 42.71128, + -73.67096 + ], + [ + 42.71148, + -73.67036 + ], + [ + 42.71071, + -73.66992 + ], + [ + 42.71027, + -73.66784 + ], + [ + 42.71219, + -73.66588 + ], + [ + 42.7133, + -73.66663 + ], + [ + 42.71481, + -73.66787 + ], + [ + 42.71755, + -73.67108 + ], + [ + 42.71927, + -73.67449 + ], + [ + 42.72223, + -73.67559 + ], + [ + 42.72198, + -73.67866 + ], + [ + 42.72322, + -73.68041 + ], + [ + 42.72447, + -73.68122 + ], + [ + 42.72667, + -73.68179 + ], + [ + 42.72766, + -73.6851 + ], + [ + 42.72828, + -73.6869 + ], + [ + 42.72866, + -73.6896 + ], + [ + 42.73438, + -73.68834 + ] +]; \ No newline at end of file diff --git a/lib/util/theme.dart b/lib/util/theme.dart new file mode 100644 index 00000000..23899535 --- /dev/null +++ b/lib/util/theme.dart @@ -0,0 +1,907 @@ + import 'package:flutter/material.dart'; + + final ThemeData darkTheme = ThemeData( + primarySwatch: Colors.purple, + brightness: Brightness.dark, + primaryColor: Color( 0xff2a2e43 ), + primaryColorBrightness: Brightness.dark, + primaryColorLight: Color( 0xff9e9e9e ), + primaryColorDark: Color( 0xff2a2e43 ), + accentColor: Color( 0xff665eff ), + accentColorBrightness: Brightness.light, + canvasColor: Color( 0xff2a2e43 ), + scaffoldBackgroundColor: Color( 0xff2a2e43 ), + bottomAppBarColor: Color( 0xff2a2e43 ), + cardColor: Color( 0xff2a2e43 ), + dividerColor: Color( 0x1fffffff ), + highlightColor: Color( 0x40cccccc ), + splashColor: Color( 0x40cccccc ), + selectedRowColor: Color( 0xfff5f5f5 ), + unselectedWidgetColor: Color( 0xb3ffffff ), + disabledColor: Color( 0x62ffffff ), + buttonColor: Color( 0xff665eff ), + toggleableActiveColor: Color( 0xff665eff ), + secondaryHeaderColor: Color( 0xff2a2e43 ), + textSelectionColor: Color( 0xff665eff ), + cursorColor: Color( 0xff4285f4 ), + textSelectionHandleColor: Color( 0xff1de9b6 ), + backgroundColor: Color( 0xff2a2e43 ), + dialogBackgroundColor: Color( 0xff2a2e43 ), + indicatorColor: Color( 0xff665eff ), + hintColor: Color( 0x80ffffff ), + errorColor: Color( 0xffd32f2f ), + buttonTheme: ButtonThemeData( + textTheme: ButtonTextTheme.normal, + minWidth: 88, + height: 36, + padding: EdgeInsets.only(top:0,bottom:0,left:16, right:16), + shape: RoundedRectangleBorder( + side: BorderSide(color: Color( 0xff000000 ), width: 0, style: BorderStyle.none, ), + borderRadius: BorderRadius.all(Radius.circular(2.0)), + ) + , + alignedDropdown: false , + buttonColor: Color( 0xff665eff ), + disabledColor: Color( 0x61ffffff ), + highlightColor: Color( 0x29ffffff ), + splashColor: Color( 0x1fffffff ), + focusColor: Color( 0x1fffffff ), + hoverColor: Color( 0x0affffff ), + colorScheme: ColorScheme( + primary: Color( 0xff2196f3 ), + primaryVariant: Color( 0xff000000 ), + secondary: Color( 0xff665eff ), + secondaryVariant: Color( 0xff00bfa5 ), + surface: Color( 0xff2a2e43 ), + background: Color( 0xff2a2e43 ), + error: Color( 0xffd32f2f ), + onPrimary: Color( 0xffffffff ), + onSecondary: Color( 0xff000000 ), + onSurface: Color( 0xffffffff ), + onBackground: Color( 0xffffffff ), + onError: Color( 0xff000000 ), + brightness: Brightness.dark, + ), + ), + textTheme: TextTheme( + display4: TextStyle( + color: Color( 0xb3ffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + display3: TextStyle( + color: Color( 0xb3ffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + display2: TextStyle( + color: Color( 0xb3ffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + display1: TextStyle( + color: Color( 0xb3ffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + headline: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + title: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + subhead: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + body2: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + body1: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + caption: TextStyle( + color: Color( 0xb3ffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + button: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + subtitle: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + overline: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + ), + primaryTextTheme: TextTheme( + display4: TextStyle( + color: Color( 0xb3ffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + display3: TextStyle( + color: Color( 0xb3ffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + display2: TextStyle( + color: Color( 0xb3ffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + display1: TextStyle( + color: Color( 0xb3ffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + headline: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + title: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + subhead: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + body2: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + body1: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + caption: TextStyle( + color: Color( 0xb3ffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + button: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + subtitle: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + overline: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + ), + accentTextTheme: TextTheme( + display4: TextStyle( + color: Color( 0x8a000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + display3: TextStyle( + color: Color( 0x8a000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + display2: TextStyle( + color: Color( 0x8a000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + display1: TextStyle( + color: Color( 0x8a000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + headline: TextStyle( + color: Color( 0xdd000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + title: TextStyle( + color: Color( 0xdd000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + subhead: TextStyle( + color: Color( 0xdd000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + body2: TextStyle( + color: Color( 0xdd000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + body1: TextStyle( + color: Color( 0xdd000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + caption: TextStyle( + color: Color( 0x8a000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + button: TextStyle( + color: Color( 0xdd000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + subtitle: TextStyle( + color: Color( 0xff000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + overline: TextStyle( + color: Color( 0xff000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + ), + inputDecorationTheme: InputDecorationTheme( + labelStyle: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + helperStyle: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + hintStyle: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + errorStyle: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + errorMaxLines: null, + hasFloatingPlaceholder: true, + isDense: false, + contentPadding: EdgeInsets.only(top:12,bottom:12,left:0, right:0), + isCollapsed : false, + prefixStyle: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + suffixStyle: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + counterStyle: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + filled: false, + fillColor: Color( 0x00000000 ), + errorBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Color( 0xff000000 ), width: 1, style: BorderStyle.solid, ), + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Color( 0xff000000 ), width: 1, style: BorderStyle.solid, ), + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + focusedErrorBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Color( 0xff000000 ), width: 1, style: BorderStyle.solid, ), + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + disabledBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Color( 0xff000000 ), width: 1, style: BorderStyle.solid, ), + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Color( 0xff000000 ), width: 1, style: BorderStyle.solid, ), + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + border: UnderlineInputBorder( + borderSide: BorderSide(color: Color( 0xff000000 ), width: 1, style: BorderStyle.solid, ), + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + ), + iconTheme: IconThemeData( + color: Color( 0xffffffff ), + opacity: 1, + size: 24, + ), + primaryIconTheme: IconThemeData( + color: Color( 0xffffffff ), + opacity: 1, + size: 24, + ), + accentIconTheme: IconThemeData( + color: Color( 0xff000000 ), + opacity: 1, + size: 24, + ), + sliderTheme: SliderThemeData( + activeTrackColor: null, + inactiveTrackColor: null, + disabledActiveTrackColor: null, + disabledInactiveTrackColor: null, + activeTickMarkColor: null, + inactiveTickMarkColor: null, + disabledActiveTickMarkColor: null, + disabledInactiveTickMarkColor: null, + thumbColor: null, + disabledThumbColor: null, + thumbShape: null, + overlayColor: null, + valueIndicatorColor: null, + valueIndicatorShape: null, + showValueIndicator: null, + valueIndicatorTextStyle: TextStyle( + color: Color( 0xdd000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + ), + tabBarTheme: TabBarTheme( + indicatorSize: TabBarIndicatorSize.tab, + labelColor: Color( 0xffffffff ), + unselectedLabelColor: Color( 0xb2ffffff ), + ), + chipTheme: ChipThemeData( + backgroundColor: Color( 0x1fffffff ), + brightness: Brightness.dark, + deleteIconColor: Color( 0xdeffffff ), + disabledColor: Color( 0x0cffffff ), + labelPadding: EdgeInsets.only(top:0,bottom:0,left:8, right:8), + labelStyle: TextStyle( + color: Color( 0xdeffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + padding: EdgeInsets.only(top:4,bottom:4,left:4, right:4), + secondaryLabelStyle: TextStyle( + color: Color( 0x3dffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + secondarySelectedColor: Color( 0x3d212121 ), + selectedColor: Color( 0x3dffffff ), + shape: StadiumBorder( side: BorderSide(color: Color( 0xff000000 ), width: 0, style: BorderStyle.none, ) ), + ), + dialogTheme: DialogTheme( + shape: RoundedRectangleBorder( + side: BorderSide(color: Color( 0xff000000 ), width: 0, style: BorderStyle.none, ), + borderRadius: BorderRadius.all(Radius.circular(0.0)), + ) + + ), + ); + + final ThemeData lightTheme = ThemeData( + primarySwatch: Colors.purple, + brightness: Brightness.light, + primaryColor: Color( 0xff2a2e43 ), + primaryColorBrightness: Brightness.dark, + primaryColorLight: Color( 0xff2a2e43 ), + primaryColorDark: Color( 0xff1976d2 ), + accentColor: Color( 0xff2a2e43 ), + accentColorBrightness: Brightness.dark, + canvasColor: Color( 0xfffafafa ), + scaffoldBackgroundColor: Color( 0xfffafafa ), + bottomAppBarColor: Color( 0xffffffff ), + cardColor: Color( 0xffffffff ), + dividerColor: Color( 0x1f000000 ), + highlightColor: Color( 0x66bcbcbc ), + splashColor: Color( 0x66c8c8c8 ), + selectedRowColor: Color( 0xfff5f5f5 ), + unselectedWidgetColor: Color( 0x8a000000 ), + disabledColor: Color( 0x61000000 ), + buttonColor: Color( 0xff665eff ), + toggleableActiveColor: Color( 0xff665eff ), + secondaryHeaderColor: Color( 0xffe3f2fd ), + textSelectionColor: Color( 0xff90caf9 ), + cursorColor: Color( 0xff4285f4 ), + textSelectionHandleColor: Color( 0xff64b5f6 ), + backgroundColor: Color( 0xff90caf9 ), + dialogBackgroundColor: Color( 0xffffffff ), + indicatorColor: Color( 0xff2196f3 ), + hintColor: Color( 0x8a000000 ), + errorColor: Color( 0xffd32f2f ), + buttonTheme: ButtonThemeData( + textTheme: ButtonTextTheme.normal, + minWidth: 88, + height: 36, + padding: EdgeInsets.only(top:0,bottom:0,left:16, right:16), + shape: RoundedRectangleBorder( + side: BorderSide(color: Color( 0xff000000 ), width: 0, style: BorderStyle.none, ), + borderRadius: BorderRadius.all(Radius.circular(2.0)), + ) + , + alignedDropdown: false , + buttonColor: Color( 0xffe0e0e0 ), + disabledColor: Color( 0x61000000 ), + highlightColor: Color( 0x29000000 ), + splashColor: Color( 0x1f000000 ), + focusColor: Color( 0x1f000000 ), + hoverColor: Color( 0x0a000000 ), + colorScheme: ColorScheme( + primary: Color( 0xff2196f3 ), + primaryVariant: Color( 0xff1976d2 ), + secondary: Color( 0xff2196f3 ), + secondaryVariant: Color( 0xff1976d2 ), + surface: Color( 0xffffffff ), + background: Color( 0xff90caf9 ), + error: Color( 0xffd32f2f ), + onPrimary: Color( 0xffffffff ), + onSecondary: Color( 0xffffffff ), + onSurface: Color( 0xff000000 ), + onBackground: Color( 0xffffffff ), + onError: Color( 0xffffffff ), + brightness: Brightness.light, + ), + ), + textTheme: TextTheme( + display4: TextStyle( + color: Color( 0x8a000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + display3: TextStyle( + color: Color( 0x8a000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + display2: TextStyle( + color: Color( 0x8a000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + display1: TextStyle( + color: Color( 0x8a000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + headline: TextStyle( + color: Color( 0xdd000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + title: TextStyle( + color: Color( 0xdd000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + subhead: TextStyle( + color: Color( 0xdd000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + body2: TextStyle( + color: Color( 0xdd000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + body1: TextStyle( + color: Color( 0xdd000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + caption: TextStyle( + color: Color( 0x8a000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + button: TextStyle( + color: Color( 0xdd000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + subtitle: TextStyle( + color: Color( 0xff000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + overline: TextStyle( + color: Color( 0xff000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + ), + primaryTextTheme: TextTheme( + display4: TextStyle( + color: Color( 0xb3ffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + display3: TextStyle( + color: Color( 0xb3ffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + display2: TextStyle( + color: Color( 0xb3ffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + display1: TextStyle( + color: Color( 0xb3ffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + headline: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + title: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + subhead: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + body2: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + body1: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + caption: TextStyle( + color: Color( 0xb3ffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + button: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + subtitle: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + overline: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + ), + accentTextTheme: TextTheme( + display4: TextStyle( + color: Color( 0xb3ffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + display3: TextStyle( + color: Color( 0xb3ffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + display2: TextStyle( + color: Color( 0xb3ffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + display1: TextStyle( + color: Color( 0xb3ffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + headline: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + title: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + subhead: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + body2: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + body1: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + caption: TextStyle( + color: Color( 0xb3ffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + button: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + subtitle: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + overline: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + ), + inputDecorationTheme: InputDecorationTheme( + labelStyle: TextStyle( + color: Color( 0xdd000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + helperStyle: TextStyle( + color: Color( 0xdd000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + hintStyle: TextStyle( + color: Color( 0xdd000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + errorStyle: TextStyle( + color: Color( 0xdd000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + errorMaxLines: null, + hasFloatingPlaceholder: true, + isDense: false, + contentPadding: EdgeInsets.only(top:12,bottom:12,left:0, right:0), + isCollapsed : false, + prefixStyle: TextStyle( + color: Color( 0xdd000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + suffixStyle: TextStyle( + color: Color( 0xdd000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + counterStyle: TextStyle( + color: Color( 0xdd000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + filled: false, + fillColor: Color( 0x00000000 ), + errorBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Color( 0xff000000 ), width: 1, style: BorderStyle.solid, ), + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Color( 0xff000000 ), width: 1, style: BorderStyle.solid, ), + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + focusedErrorBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Color( 0xff000000 ), width: 1, style: BorderStyle.solid, ), + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + disabledBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Color( 0xff000000 ), width: 1, style: BorderStyle.solid, ), + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Color( 0xff000000 ), width: 1, style: BorderStyle.solid, ), + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + border: UnderlineInputBorder( + borderSide: BorderSide(color: Color( 0xff000000 ), width: 1, style: BorderStyle.solid, ), + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + ), + iconTheme: IconThemeData( + color: Color( 0xdd000000 ), + opacity: 1, + size: 24, + ), + primaryIconTheme: IconThemeData( + color: Color( 0xffffffff ), + opacity: 1, + size: 24, + ), + accentIconTheme: IconThemeData( + color: Color( 0xffffffff ), + opacity: 1, + size: 24, + ), + sliderTheme: SliderThemeData( + activeTrackColor: null, + inactiveTrackColor: null, + disabledActiveTrackColor: null, + disabledInactiveTrackColor: null, + activeTickMarkColor: null, + inactiveTickMarkColor: null, + disabledActiveTickMarkColor: null, + disabledInactiveTickMarkColor: null, + thumbColor: null, + disabledThumbColor: null, + thumbShape: null, + overlayColor: null, + valueIndicatorColor: null, + valueIndicatorShape: null, + showValueIndicator: null, + valueIndicatorTextStyle: TextStyle( + color: Color( 0xffffffff ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + ), + tabBarTheme: TabBarTheme( + indicatorSize: TabBarIndicatorSize.tab, + labelColor: Color( 0xffffffff ), + unselectedLabelColor: Color( 0xb2ffffff ), + ), + chipTheme: ChipThemeData( + backgroundColor: Color( 0x1f000000 ), + brightness: Brightness.light, + deleteIconColor: Color( 0xde000000 ), + disabledColor: Color( 0x0c000000 ), + labelPadding: EdgeInsets.only(top:0,bottom:0,left:8, right:8), + labelStyle: TextStyle( + color: Color( 0xde000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + padding: EdgeInsets.only(top:4,bottom:4,left:4, right:4), + secondaryLabelStyle: TextStyle( + color: Color( 0x3d000000 ), + fontSize: null, + fontWeight: FontWeight.w400, + fontStyle: FontStyle.normal, + ), + secondarySelectedColor: Color( 0x3d2196f3 ), + selectedColor: Color( 0x3d000000 ), + shape: StadiumBorder( side: BorderSide(color: Color( 0xff000000 ), width: 0, style: BorderStyle.none, ) ), + ), + dialogTheme: DialogTheme( + shape: RoundedRectangleBorder( + side: BorderSide(color: Color( 0xff000000 ), width: 0, style: BorderStyle.none, ), + borderRadius: BorderRadius.all(Radius.circular(0.0)), + ) + + ), + ); diff --git a/lib/util/theme_notifier.dart b/lib/util/theme_notifier.dart new file mode 100644 index 00000000..2a91f462 --- /dev/null +++ b/lib/util/theme_notifier.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; + +class ThemeNotifier with ChangeNotifier { + ThemeData _themeData; + + ThemeNotifier(this._themeData); + + getTheme() => _themeData; + + setTheme(ThemeData themeData) async { + _themeData = themeData; + notifyListeners(); + } +} diff --git a/lib/widgets/autocomplete.dart b/lib/widgets/autocomplete.dart new file mode 100644 index 00000000..6ac3c7e3 --- /dev/null +++ b/lib/widgets/autocomplete.dart @@ -0,0 +1,272 @@ +// ui imports +import 'package:flutter/material.dart'; +import 'package:smartrider/widgets/icons.dart'; + +// import places api +import 'package:google_maps_webservice/places.dart'; +import 'package:smartrider/widgets/flutter_google_places.dart'; + +// session token generation +import 'dart:math'; + +/// Below from https://github.com/fluttercommunity/flutter_google_places + +/// A text field like widget to input places with autocomplete. +/// +/// The autocomplete field calls [onChanged] with the new address line +/// whenever the user input a new location. +/// +/// To control the text that is displayed in the text field, use the +/// [controller]. For example, to set the initial value of the text field, use +/// a [controller] that already contains some text. +/// +/// By default, an autocomplete field has a [decoration] that draws a divider +/// below the field. You can use the [decoration] property to control the +/// decoration, for example by adding a label or an icon. If you set the [decoration] +/// property to null, the decoration will be removed entirely, including the +/// extra padding introduced by the decoration to save space for the labels. +/// If you want the icon to be outside the input field use [decoration.icon]. +/// If it should be inside the field use [leading]. +/// +/// To integrate the [PlacesAutocompleteField] into a [Form] with other [FormField] +/// widgets, consider using [PlacesAutocompleteFormField]. +/// +/// See also: +/// +/// * [PlacesAutocompleteFormField], which integrates with the [Form] widget. +/// * [InputDecorator], which shows the labels and other visual elements that +/// surround the actual text editing widget. +class PlacesAutocompleteField extends StatefulWidget { + /// Creates a text field like widget. + /// + /// To remove the decoration entirely (including the extra padding introduced + /// by the decoration to save space for the labels), set the [decoration] to + /// null. + const PlacesAutocompleteField({ + Key key, + @required this.apiKey, + this.controller, + this.leading, + this.hint = "Search", + this.trailing, + this.trailingOnTap, + this.offset, + this.location, + this.radius, + this.language, + this.sessionToken, + this.types, + this.components, + this.strictbounds, + this.onChanged, + this.onSelected, + this.onError, + this.inputDecoration = const InputDecoration(), + }) : super(key: key); + + /// Controls the text being edited. + /// + /// If null, this widget will create its own [TextEditingController]. + final TextEditingController controller; + + /// Icon shown inside the field left to the text. + final Icon leading; + + /// Icon shown inside the field right to the text. + final Icon trailing; + + /// Callback when [trailing] is tapped on. + final VoidCallback trailingOnTap; + + /// Text that is shown, when no input was done, yet. + final String hint; + + /// Your Google Maps Places API Key. + /// + /// For this key the Places Web API needs to be activated. For further + /// information on how to do this, see their official documentation below. + /// + /// See also: + /// + /// * + final String apiKey; + + /// The decoration to show around the text field. + /// + /// By default, draws a horizontal line under the autocomplete field but can be + /// configured to show an icon, label, hint text, and error text. + /// + /// Specify null to remove the decoration entirely (including the + /// extra padding introduced by the decoration to save space for the labels). + final InputDecoration inputDecoration; + + /// The position, in the input term, of the last character that the service + /// uses to match predictions. + /// + /// For example, if the input is 'Google' and the + /// offset is 3, the service will match on 'Goo'. The string determined by the + /// offset is matched against the first word in the input term only. For + /// example, if the input term is 'Google abc' and the offset is 3, the service + /// will attempt to match against 'Goo abc'. If no offset is supplied, the + /// service will use the whole term. The offset should generally be set to the + /// position of the text caret. + /// + /// Source: https://developers.google.com/places/web-service/autocomplete + final num offset; + + final String language; + + final String sessionToken; + + final List types; + + final List components; + + final Location location; + + final num radius; + + final bool strictbounds; + + /// Called when the text being edited changes. + final ValueChanged onChanged; + + /// Called when an autocomplete entry is selected. + final ValueChanged onSelected; + + /// Callback when autocomplete has error. + final ValueChanged onError; + + @override + _LocationAutocompleteFieldState createState() => + _LocationAutocompleteFieldState(); +} + +class _LocationAutocompleteFieldState extends State { + TextEditingController _controller; + TextEditingController get _effectiveController => + widget.controller ?? _controller; + + @override + void initState() { + super.initState(); + if (widget.controller == null) _controller = TextEditingController(); + } + + @override + void didUpdateWidget(PlacesAutocompleteField oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.controller == null && oldWidget.controller != null) + _controller = TextEditingController.fromValue(oldWidget.controller.value); + else if (widget.controller != null && oldWidget.controller == null) + _controller = null; + } + + Future _showAutocomplete() async => PlacesAutocomplete.show( + context: context, + apiKey: widget.apiKey, + offset: widget.offset, + onError: widget.onError, + hint: widget.hint, + language: widget.language, + sessionToken: widget.sessionToken, + components: widget.components, + location: widget.location, + radius: widget.radius, + types: widget.types, + strictbounds: widget.strictbounds, + ); + + void _handleTap() async { + Prediction p = await _showAutocomplete(); + + if (p == null) return; + + setState(() { + _effectiveController.text = p.description; + if (widget.onChanged != null) { + widget.onChanged(p.description); + } + if (widget.onSelected != null) { + widget.onSelected(p); + } + }); + } + + @override + Widget build(BuildContext context) { + final TextEditingController controller = _effectiveController; + + var text = controller.text.isNotEmpty + ? Text( + controller.text, + softWrap: true, + ) + : Text( + widget.hint ?? '', + style: TextStyle(fontSize: 16), + ); + + Widget child = Container( + height: 50, + child: Row( + children: [ + widget.leading ?? SizedBox(), + SizedBox( + width: 16.0, + ), + Expanded( + child: text, + ), + widget.trailing != null + ? GestureDetector( + onTap: widget.trailingOnTap, + child: widget.trailingOnTap != null + ? widget.trailing + : Icon( + widget.trailing.icon, + color: Colors.grey, + ), + ) + : SizedBox() + ], + ) + ); + + if (widget.inputDecoration != null) { + child = InputDecorator( + decoration: widget.inputDecoration, + child: child, + ); + } + + return GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: _handleTap, + child: child, + ); + } +} + +class Uuid { + final Random _random = Random(); + + String generateV4() { + // Generate xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx / 8-4-4-4-12. + final int special = 8 + _random.nextInt(4); + + return '${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}-' + '${_bitsDigits(16, 4)}-' + '4${_bitsDigits(12, 3)}-' + '${_printDigits(special, 1)}${_bitsDigits(12, 3)}-' + '${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}'; + } + + String _bitsDigits(int bitCount, int digitCount) => + _printDigits(_generateBits(bitCount), digitCount); + + int _generateBits(int bitCount) => _random.nextInt(1 << bitCount); + + String _printDigits(int value, int count) => + value.toRadixString(16).padLeft(count, '0'); +} \ No newline at end of file diff --git a/lib/widgets/bus_list.dart b/lib/widgets/bus_list.dart new file mode 100644 index 00000000..1187cf9c --- /dev/null +++ b/lib/widgets/bus_list.dart @@ -0,0 +1,91 @@ +// ui dependencies +import 'package:flutter/material.dart'; +import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; + +// loading custom widgets and data +import 'package:smartrider/util/data.dart'; + +class BusList extends StatefulWidget { + final List scrollControllers; + final Function containsFilter; + final Function jumpMap; + BusList({Key key, this.scrollControllers, this.containsFilter, this.jumpMap}) : super(key: key); + @override + BusListState createState() => BusListState(); +} + +class BusListState extends State with SingleTickerProviderStateMixin, +AutomaticKeepAliveClientMixin +{ + final List busTabs = [ + Tab(text: 'Route 87'), + Tab(text: 'Route 286'), + Tab(text: 'Route 289'), + ]; + TabController _tabController; + + @override + void initState() { + super.initState(); + _tabController = new TabController(vsync: this, length: busTabs.length); + } + + @override + void dispose() { + _tabController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + TabBar( + tabs: busTabs, + // unselectedLabelColor: Colors.white.withOpacity(0.3), + labelColor: Theme.of(context).brightness == Brightness.light ? Colors.black : null, + unselectedLabelColor: Theme.of(context).brightness == Brightness.light ? Colors.black : null, + controller: _tabController, + ), + Container( + height: MediaQuery.of(context).size.height * 0.7, + child: TabBarView( + controller: _tabController, + children: [ + busList(0, this.widget.scrollControllers[0], this.widget.containsFilter, this.widget.jumpMap), + busList(1, this.widget.scrollControllers[1], this.widget.containsFilter, this.widget.jumpMap), + busList(2, this.widget.scrollControllers[2], this.widget.containsFilter, this.widget.jumpMap), + ], + ), + ) + ] + ); + } + @override + bool get wantKeepAlive => true; +} + +Widget busList(int idx, ItemScrollController _scrollController, Function _containsFilter, Function _jumpMap) { + return ScrollablePositionedList.builder( + itemCount: busTimeLists[idx].length, + itemScrollController: _scrollController, + itemBuilder: (context, index) { + var curStopList = busStopLists[idx]; + var curTimeList = busTimeLists[idx]; + if (!_containsFilter(curStopList, curTimeList, index) || curTimeList[index] == "- - - -") { + return null; + } + return Card( + child: ListTile( + leading: Icon(Icons.directions_bus), + title: Text(curStopList[index%curStopList.length][0]), + subtitle: Text(curTimeList[index]), + trailing: Icon(Icons.arrow_forward), + onTap: () { + _jumpMap(double.parse(curStopList[index%curStopList.length][1]), double.parse(curStopList[index%curStopList.length][2])); + }, + ), + ); + }, + ); +} \ No newline at end of file diff --git a/lib/widgets/filter_dialog.dart b/lib/widgets/filter_dialog.dart new file mode 100644 index 00000000..60364f54 --- /dev/null +++ b/lib/widgets/filter_dialog.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; + +class FilterDialog extends StatefulWidget { + List> stops; + TextEditingController controller; + // ValueChanged updateTime, updateStop; + FilterDialog({Key key, this.stops, this.controller}) : super(key: key); + @override + _FilterDialogState createState() => _FilterDialogState(); +} + +class _FilterDialogState extends State { + String searchQuery; + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + + @override + Widget build(BuildContext context) { + return Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(18.0))), + child: + FractionallySizedBox( + heightFactor: 0.7, + child:Stack( + children: [ ListView( + physics: const NeverScrollableScrollPhysics(), + children: [ SizedBox(height: 10), + Container( + padding: EdgeInsets.symmetric(horizontal: 10.0), + height: 64, + child: Material( + borderRadius: BorderRadius.circular(10.0), + elevation: 5.0, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + IconButton( + icon: Icon(Icons.search), + onPressed: null, + ), + Expanded( + child: Padding( + child: TextField( + controller: widget.controller, + autofocus: false, + onSubmitted: (query) { + Navigator.pop(context); + }, + decoration: InputDecoration( + hintText: "Filter Results" + ), + ), + padding: const EdgeInsets.only(right: 8.0), + ) + ), + IconButton( + icon: Icon(Icons.cancel), + onPressed: () { + widget.controller.text = ''; + Navigator.pop(context); + }, + ), + ], + ), + ), + ), + Container( + height: 400, + child:ListView.builder( + scrollDirection: Axis.vertical, + shrinkWrap: true, + itemCount: widget.stops.length+1, + itemBuilder: (context, index) { + // hack that allows us to show the last list item without clipping + if (index == widget.stops.length){ + return SizedBox(height: 20); + } + String stop_name = widget.stops[index][0]; + return ListTile( + leading: Icon(Icons.departure_board), + title: Text(stop_name), + trailing: Icon(Icons.search), + onTap: () { + widget.controller.text = stop_name; + Navigator.pop(context); + }, + ); + } + ), + ), + ]), + Positioned( + bottom: 20, + right: 20, + child: FloatingActionButton( + onPressed: () { + Navigator.pop(context); + }, + child: Icon(Icons.arrow_back), + ) + ), + ] + ) + )); + } +} \ No newline at end of file diff --git a/lib/widgets/flutter_google_places.dart b/lib/widgets/flutter_google_places.dart new file mode 100644 index 00000000..406a3770 --- /dev/null +++ b/lib/widgets/flutter_google_places.dart @@ -0,0 +1,396 @@ +/// Below from https://github.com/fluttercommunity/flutter_google_places +/// Modified to better fit the ui of smartrider + +import 'dart:async'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:google_maps_webservice/places.dart'; +import 'package:http/http.dart'; +import 'package:rxdart/rxdart.dart'; + +class PlacesAutocompleteWidget extends StatefulWidget { + final String apiKey; + final String startText; + final String hint; + final BorderRadius overlayBorderRadius; + final Location location; + final num offset; + final num radius; + final String language; + final String sessionToken; + final List types; + final List components; + final bool strictbounds; + final String region; + final Widget logo; + final ValueChanged onError; + final int debounce; + + /// optional - sets 'proxy' value in google_maps_webservice + /// + /// In case of using a proxy the baseUrl can be set. + /// The apiKey is not required in case the proxy sets it. + /// (Not storing the apiKey in the app is good practice) + final String proxyBaseUrl; + + /// optional - set 'client' value in google_maps_webservice + /// + /// In case of using a proxy url that requires authentication + /// or custom configuration + final BaseClient httpClient; + + PlacesAutocompleteWidget( + {@required this.apiKey, + this.hint = "Search", + this.overlayBorderRadius, + this.offset, + this.location, + this.radius, + this.language, + this.sessionToken, + this.types, + this.components, + this.strictbounds, + this.region, + this.logo, + this.onError, + Key key, + this.proxyBaseUrl, + this.httpClient, + this.startText, + this.debounce = 300}) + : super(key: key); + + @override + State createState() => _PlacesAutocompleteState(); + + static PlacesAutocompleteState of(BuildContext context) => + context.findAncestorStateOfType(); +} + +class _PlacesAutocompleteState extends PlacesAutocompleteState { + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final header = + Container( + padding: EdgeInsets.symmetric(horizontal: 10.0), + height: 64, + child: Material( + borderRadius: BorderRadius.circular(10.0), + elevation: 5.0, + child: Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + IconButton( + color: theme.brightness == Brightness.light + ? Colors.black45 + : null, + icon: _iconBack, + onPressed: () { + Navigator.pop(context); + }, + ), + Expanded( + child: Padding( + child: _textField(context), + padding: const EdgeInsets.only(right: 8.0), + ) + ), + ], + ), + Divider( + //height: 1.0, + ) + ]))); + + Widget body; + + final bodyBottomLeftBorderRadius = Radius.circular(10); + + final bodyBottomRightBorderRadius = Radius.circular(10); + + if (_searching) { + body = Stack( + children: [_Loader()], + alignment: FractionalOffset.bottomCenter, + ); + } else if (_queryTextController.text.isEmpty || + _response == null || + _response.predictions.isEmpty) { + body = Container( + height: 30, + width: 7777777, + child: Material( + borderRadius: BorderRadius.only( + bottomLeft: bodyBottomLeftBorderRadius, + bottomRight: bodyBottomRightBorderRadius, + ), + child: + Center( + child: Text("Note: Safe Ride won't go past 1 mile of campus", + style: TextStyle( + fontStyle: FontStyle.italic, + fontSize: 12, + ), + textAlign: TextAlign.center, + ), + ), + ), + ); + } else { + body = SingleChildScrollView( + child: Material( + borderRadius: BorderRadius.only( + bottomLeft: bodyBottomLeftBorderRadius, + bottomRight: bodyBottomRightBorderRadius, + ), + color: theme.dialogBackgroundColor, + child: ListBody( + children: _response.predictions + .map( + (p) => PredictionTile( + prediction: p, + onTap: Navigator.of(context).pop, + ), + ) + .toList(), + ), + ), + ); + } + + final container = Container( + child: Stack(children: [ + header, + Padding(padding: EdgeInsets.only(top: 48.0), child: + Container( + padding: EdgeInsets.symmetric(horizontal: 10.0), + child: body + ) + ), + ])); + + if (Theme.of(context).platform == TargetPlatform.iOS) { + return Padding(padding: EdgeInsets.only(top: 8.0), child: container); + } + return container; + } + + Icon get _iconBack => Theme.of(context).platform == TargetPlatform.iOS + ? Icon(Icons.arrow_back_ios): Icon(Icons.arrow_back); + + + + Widget _textField(BuildContext context) => TextField( + controller: _queryTextController, + autofocus: true, + style: TextStyle( + color: Theme.of(context).brightness == Brightness.light + ? Colors.black87 + : null, + fontSize: 16.0), + decoration: InputDecoration( + hintText: widget.hint, + hintStyle: TextStyle( + color: Theme.of(context).brightness == Brightness.light + ? Colors.black45 + : null, + fontSize: 16.0, + ), + border: InputBorder.none, + ), + ); +} + +class _Loader extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + constraints: BoxConstraints(maxHeight: 2.0), + child: LinearProgressIndicator()); + } +} + +class PoweredByGoogleImage extends StatelessWidget { + final _poweredByGoogleWhite = + "packages/flutter_google_places/assets/google_white.png"; + final _poweredByGoogleBlack = + "packages/flutter_google_places/assets/google_black.png"; + + @override + Widget build(BuildContext context) { + return Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + Padding( + padding: EdgeInsets.all(16.0), + child: Image.asset( + Theme.of(context).brightness == Brightness.light + ? _poweredByGoogleWhite + : _poweredByGoogleBlack, + scale: 2.5, + )) + ]); + } +} + +class PredictionTile extends StatelessWidget { + final Prediction prediction; + final ValueChanged onTap; + + PredictionTile({@required this.prediction, this.onTap}); + + @override + Widget build(BuildContext context) { + return ListTile( + leading: Icon(Icons.location_on), + title: Text(prediction.description), + onTap: () { + if (onTap != null) { + onTap(prediction); + } + }, + trailing: Icon(Icons.arrow_forward), + ); + } +} + + +abstract class PlacesAutocompleteState extends State { + TextEditingController _queryTextController; + PlacesAutocompleteResponse _response; + GoogleMapsPlaces _places; + bool _searching; + Timer _debounce; + + final _queryBehavior = BehaviorSubject.seeded(''); + + @override + void initState() { + super.initState(); + _queryTextController = TextEditingController(text: widget.startText); + + _places = GoogleMapsPlaces( + apiKey: widget.apiKey, + baseUrl: widget.proxyBaseUrl, + httpClient: widget.httpClient); + _searching = false; + + _queryTextController.addListener(_onQueryChange); + + _queryBehavior.stream.listen(doSearch); + } + + Future doSearch(String value) async { + if (mounted && value.isNotEmpty) { + setState(() { + _searching = true; + }); + + final res = await _places.autocomplete( + value, + offset: widget.offset, + location: widget.location, + radius: widget.radius, + language: widget.language, + sessionToken: widget.sessionToken, + types: widget.types, + components: widget.components, + strictbounds: widget.strictbounds, + region: widget.region, + ); + + if (res.errorMessage?.isNotEmpty == true || + res.status == "REQUEST_DENIED") { + onResponseError(res); + } else { + onResponse(res); + } + } else { + onResponse(null); + } + } + + void _onQueryChange() { + if (_debounce?.isActive ?? false) _debounce.cancel(); + _debounce = Timer(Duration(milliseconds: widget.debounce), () { + _queryBehavior.add(_queryTextController.text); + }); + } + + @override + void dispose() { + super.dispose(); + + _places.dispose(); + _queryBehavior.close(); + _queryTextController.removeListener(_onQueryChange); + } + + @mustCallSuper + void onResponseError(PlacesAutocompleteResponse res) { + if (!mounted) return; + + if (widget.onError != null) { + widget.onError(res); + } + setState(() { + _response = null; + _searching = false; + }); + } + + @mustCallSuper + void onResponse(PlacesAutocompleteResponse res) { + if (!mounted) return; + + setState(() { + _response = res; + _searching = false; + }); + } +} + +class PlacesAutocomplete { + static Future show( + {@required BuildContext context, + @required String apiKey, + String hint = "Search", + BorderRadius overlayBorderRadius, + num offset, + Location location, + num radius, + String language, + String sessionToken, + List types, + List components, + bool strictbounds, + String region, + Widget logo, + ValueChanged onError, + String proxyBaseUrl, + Client httpClient, + String startText=""}) { + final builder = (BuildContext ctx) => PlacesAutocompleteWidget( + apiKey: apiKey, + overlayBorderRadius: overlayBorderRadius, + language: language, + sessionToken: sessionToken, + components: components, + types: types, + location: location, + radius: radius, + strictbounds: strictbounds, + region: region, + offset: offset, + hint: hint, + logo: logo, + onError: onError, + proxyBaseUrl: proxyBaseUrl, + httpClient: httpClient, + startText: startText,); + + return showDialog(context: context, builder: builder); + } +} \ No newline at end of file diff --git a/lib/widgets/map_ui.dart b/lib/widgets/map_ui.dart index 3eebde8b..46687d58 100644 --- a/lib/widgets/map_ui.dart +++ b/lib/widgets/map_ui.dart @@ -6,21 +6,26 @@ // ui imports import 'package:flutter/material.dart'; +import 'dart:convert'; // map imports import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:geolocator/geolocator.dart'; import 'package:flutter/services.dart' show rootBundle; +// data imports +import 'package:smartrider/util/data.dart'; final LatLngBounds rpiBounds = LatLngBounds( - southwest: const LatLng(42.720779, -73.698129), - northeast: const LatLng(42.739179, -73.659123), + // southwest: const LatLng(42.720779, -73.698129), + southwest: const LatLng(42.691255, -73.698129), + northeast: const LatLng(42.751583, -73.616713), + // northeast: const LatLng(42.739179, -73.659123), ); -class ShuttleMap extends StatefulWidget { - const ShuttleMap(); +class ShuttleMap extends StatefulWidget { + ShuttleMap({Key key}) : super(key: key); @override State createState() => ShuttleMapState(); } @@ -48,16 +53,103 @@ class ShuttleMapState extends State { bool _indoorViewEnabled = true; bool _myLocationEnabled = true; bool _myTrafficEnabled = false; - bool _myLocationButtonEnabled = true; - GoogleMapController _controller; - bool _nightMode = false; - String _mapStyle; + bool _myLocationButtonEnabled = false; + GoogleMapController _controller; + String _lightMapStyle; + String _darkMapStyle; + + Set markers = {}; + List westPoints = []; + List southPoints = []; + List northPoints = []; + Map polylines = {}; + int _polylineIdCounter = 1; + BitmapDescriptor shuttleIcon, busIcon; + PolylineId selectedPolyline; @override void initState() { super.initState(); + + BitmapDescriptor.fromAssetImage( + ImageConfiguration(), + 'assets/marker_shuttle.png').then((onValue) { + shuttleIcon = onValue; + rootBundle.loadString('assets/shuttle_jsons/stops.json').then((string) { + var data = json.decode(string); + data.forEach( (stop) { + var position = LatLng(stop['latitude'], stop['longitude']); + markers.add(Marker( + icon: shuttleIcon, + markerId: MarkerId(stop['id'].toString()), + position: position, + onTap: () { + _controller.animateCamera( + CameraUpdate.newCameraPosition( + CameraPosition( + target: position, + zoom: 18, + tilt: 50) + ), + ); + } + )); + }); + }); + }); + + BitmapDescriptor.fromAssetImage( + ImageConfiguration(), + 'assets/marker_bus.png').then((onValue) { + busIcon = onValue; + busStopLists.forEach((List> stopList) { + stopList.forEach((stopData) { + var position = LatLng(double.parse(stopData[1]), double.parse(stopData[2])); + markers.add(Marker( + icon: busIcon, + markerId: MarkerId(stopData[3]), + position: position, + onTap: () { + _controller.animateCamera( + CameraUpdate.newCameraPosition( + CameraPosition( + target: position, + zoom: 18, + tilt: 50) + ), + ); + } + )); + }); + }); + }); + + rootBundle.loadString('assets/map_styles/aubergine.json').then((string) { + _darkMapStyle = string; + }); rootBundle.loadString('assets/map_styles/light.json').then((string) { - _mapStyle = string; + _lightMapStyle = string; + }); + + rootBundle.loadString('assets/shuttle_jsons/west.json').then((string) { + var data = json.decode(string); + data.forEach( (point) { + westPoints.add(LatLng(point['latitude'], point['longitude'])); + }); + }); + + rootBundle.loadString('assets/shuttle_jsons/south.json').then((string) { + var data = json.decode(string); + data.forEach( (point) { + southPoints.add(LatLng(point['latitude'], point['longitude'])); + }); + }); + + rootBundle.loadString('assets/shuttle_jsons/north.json').then((string) { + var data = json.decode(string); + data.forEach( (point) { + northPoints.add(LatLng(point['latitude'], point['longitude'])); + }); }); } @@ -66,38 +158,19 @@ class ShuttleMapState extends State { super.dispose(); } - // Future _getFileData(String path) async { - // return await rootBundle.loadString(path); - // } - - // void _setMapStyle(String mapStyle) { - // setState(() { - // _nightMode = true; - // _controller.setMapStyle(mapStyle); - // }); - // } - - // Widget _nightModeToggler() { - // if (!_isMapCreated) { - // return null; - // } - // return FlatButton( - // child: Text('${_nightMode ? 'disable' : 'enable'} night mode'), - // onPressed: () { - // if (_nightMode) { - // setState(() { - // _nightMode = false; - // _controller.setMapStyle(null); - // }); - // } else { - // _getFileData('assets/night_mode.json').then(_setMapStyle); - // } - // }, - // ); - // } - @override Widget build(BuildContext context) { + bool isDark = Theme.of(context).brightness == Brightness.dark; + if (_controller != null ) { + if (isDark) { + _controller.setMapStyle(_darkMapStyle); + } + else { + _controller.setMapStyle(_lightMapStyle); + } + } + + final GoogleMap googleMap = GoogleMap( onMapCreated: onMapCreated, initialCameraPosition: _kInitialPosition, @@ -115,6 +188,9 @@ class ShuttleMapState extends State { myLocationButtonEnabled: _myLocationButtonEnabled, trafficEnabled: _myTrafficEnabled, onCameraMove: _updateCameraPosition, + + polylines: Set.of(polylines.values), + markers: markers, ); return Stack( @@ -122,7 +198,6 @@ class ShuttleMapState extends State { children: [ // Actual map googleMap, - // Location Button Positioned( right: 20.0, @@ -130,17 +205,17 @@ class ShuttleMapState extends State { child: FloatingActionButton( child: Icon( Icons.gps_fixed, - color: Theme.of(context).primaryColor, + color: Theme.of(context).brightness == Brightness.light ? Colors.black87 : null, ), - onPressed: _scrollToLocation, - backgroundColor: Colors.white, + backgroundColor: Theme.of(context).brightness == Brightness.light ? Colors.white : null, + onPressed: _scrollToCurrentLocation, ), ), ] ); } - void _scrollToLocation() async { + void _scrollToCurrentLocation() async { var currentLocation = await Geolocator() .getCurrentPosition(desiredAccuracy: LocationAccuracy.best); @@ -152,7 +227,6 @@ class ShuttleMapState extends State { CameraPosition( bearing: 0.0, target: loc, - // tilt: 30.0, zoom: 17.0, ), ), @@ -164,13 +238,23 @@ class ShuttleMapState extends State { CameraPosition( bearing: 0.0, target: LatLng(42.729280, -73.679056), - // tilt: 30.0, zoom: 15.0, ), ), ); } } + void scrollToLocation(LatLng loc) { + _controller.animateCamera( + CameraUpdate.newCameraPosition( + CameraPosition( + target: loc, + zoom: 18, + tilt: 50) + ), + ); + } + void _updateCameraPosition(CameraPosition position) { setState(() { @@ -182,8 +266,81 @@ class ShuttleMapState extends State { setState(() { _controller = controller; _isMapCreated = true; - print(_mapStyle); - _controller.setMapStyle(_mapStyle); + + setPolylines(); + }); + } + + void setPolylines() { + final int polylineCount = polylines.length; + + if (polylineCount == 12) { + return; + } + + final busLineColors = [ + Colors.cyan, + Colors.pink, + Colors.lightGreen, + ]; + + busPolylines.asMap().forEach((int idx, List> rawLine) { + PolylineId buslineId = PolylineId('polyline_id_$_polylineIdCounter'); + _polylineIdCounter++; + List linePoints = List(); + rawLine.forEach((pair) { + linePoints.add(LatLng(pair[0],pair[1])); + }); + Polyline busLine = Polyline( + polylineId: buslineId, + patterns: [PatternItem.dash(50), PatternItem.gap(50)], + color: busLineColors[idx], + width: 5, + points: linePoints, + ); + polylines[buslineId] = busLine; + }); + + final String polylineIdVal = 'polyline_id_$_polylineIdCounter'; + _polylineIdCounter++; + final PolylineId polylineId = PolylineId(polylineIdVal); + + final Polyline polylineWest = Polyline( + polylineId: polylineId, + color: Colors.orange, + width: 5, + points: westPoints, + ); + polylines[polylineId] = polylineWest; + + final String polylineIdVal1 = 'polyline_id_$_polylineIdCounter'; + _polylineIdCounter++; + final PolylineId polylineId1 = PolylineId(polylineIdVal1); + + final Polyline polylineSouth = Polyline( + polylineId: polylineId1, + color: Colors.blue, + width: 5, + points: southPoints, + ); + polylines[polylineId1] = polylineSouth; + + final String polylineIdVal2 = 'polyline_id_$_polylineIdCounter'; + _polylineIdCounter++; + final PolylineId polylineId2 = PolylineId(polylineIdVal2); + + final Polyline polylineNorth = Polyline( + polylineId: polylineId2, + color: Colors.purple, + width: 5, + points: northPoints, + ); + + setState(() { + polylines[polylineId] = polylineWest; + polylines[polylineId1] = polylineSouth; + polylines[polylineId2] = polylineNorth; + }); } } \ No newline at end of file diff --git a/lib/widgets/search_bar.dart b/lib/widgets/search_bar.dart index aac000a9..c50e796c 100644 --- a/lib/widgets/search_bar.dart +++ b/lib/widgets/search_bar.dart @@ -3,7 +3,13 @@ import 'package:flutter/material.dart'; import 'package:smartrider/widgets/icons.dart'; // import map background -import 'map_ui.dart'; +import 'package:smartrider/pages/settings.dart'; + +// import places api +import 'package:google_maps_webservice/places.dart'; +import 'package:smartrider/widgets/autocomplete.dart'; +import 'package:smartrider/util/strings.dart'; + class SearchBar extends StatefulWidget { const SearchBar(); @@ -16,57 +22,49 @@ class SearchBarState extends State { @override Widget build(BuildContext context) { - return Stack( - children: [ - // Replace this container with your Map widget - ShuttleMap(), - Positioned( - top: 30, - right: 15, - left: 15, - child: Container( - padding: EdgeInsets.symmetric(horizontal: 10.0), - height: 50, - child: Material( - color: Colors.white, - borderRadius: BorderRadius.circular(10.0), - elevation: 5.0, - child: Row( - children: [ - IconButton( - splashColor: Colors.grey, - icon: Icon(SR_Icons.Settings), - onPressed: () {}, - ), - Expanded( - child: TextField( - style: TextStyle( - fontSize: 18.0, - // height: 2.0, - ), - cursorColor: Colors.black, - keyboardType: TextInputType.text, - textInputAction: TextInputAction.go, - decoration: InputDecoration( - border: InputBorder.none, - contentPadding: - EdgeInsets.symmetric(horizontal: 15), - hintText: "Need a Safe Ride?"), - ), - ), - Padding( - padding: const EdgeInsets.only(right: 8.0), - child: CircleAvatar( - backgroundColor: Colors.deepPurple, - child: Text('JS'), - ), - ), - ], + return Positioned( + top: 30, + right: 15, + left: 15, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 10.0), + height: 50, + child: Material( + borderRadius: BorderRadius.circular(10.0), + elevation: 5.0, + child: Row( + children: [ + IconButton( + icon: Icon(SR_Icons.Settings), + onPressed: () { + Navigator.push(context, MaterialPageRoute(builder: (context) => SettingsPage())); + }, + ), + Expanded( + // creates the autocomplete field (requires strings.dart in the utils folder to contain the api key) + child: PlacesAutocompleteField( + apiKey: google_api_key, + hint: "Need a Safe Ride?", + location: Location(42.729980, -73.676682), // location of union as center + radius: 1000, // 1km from union seems to be a good estimate of the bounds on safe ride's website + language: "en", + components: [Component(Component.country, "us")], + strictbounds: true, + sessionToken: Uuid().generateV4(), + inputDecoration: null, + ) ), - ), + Padding( + padding: const EdgeInsets.only(right: 8.0), + child: CircleAvatar( + backgroundColor: Theme.of(context).buttonColor, + child: Text('JS', style: TextStyle(color: Colors.white70)), + ), + ), + ], ), ), - ], + ), ); } -} +} \ No newline at end of file diff --git a/lib/widgets/shuttle_list.dart b/lib/widgets/shuttle_list.dart new file mode 100644 index 00000000..58dc08d8 --- /dev/null +++ b/lib/widgets/shuttle_list.dart @@ -0,0 +1,98 @@ +// ui dependencies +import 'package:flutter/material.dart'; +import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; +import 'package:intl/intl.dart'; + +// loading custom widgets and data +import 'package:smartrider/util/data.dart'; + +class ShuttleList extends StatefulWidget { + final List scrollControllers; + final Function containsFilter; + final Function jumpMap; + ShuttleList({Key key, this.scrollControllers, this.containsFilter, this.jumpMap}) : super(key: key); + @override + ShuttleListState createState() => ShuttleListState(); +} + +class ShuttleListState extends State with SingleTickerProviderStateMixin, +AutomaticKeepAliveClientMixin +{ + final List shuttleTabs = [ + Tab(text: 'SOUTH'), + Tab(text: 'NORTH'), + Tab(text: 'WEST'), + Tab(text: 'WEEKEND'), + ]; + TabController _tabController; + + @override + void initState() { + super.initState(); + _tabController = new TabController(vsync: this, length: shuttleTabs.length); + _tabController.addListener(() { + print(_tabController.indexIsChanging); + }); + } + + @override + void dispose() { + _tabController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + TabBar( + isScrollable: true, + tabs: shuttleTabs, + // unselectedLabelColor: Colors.white.withOpacity(0.3), + labelColor: Theme.of(context).brightness == Brightness.light ? Colors.black : null, + unselectedLabelColor: Theme.of(context).brightness == Brightness.light ? Colors.black : null, + controller: _tabController, + ), + Container( + height: MediaQuery.of(context).size.height * 0.7, + child: TabBarView( + controller: _tabController, + children: [ + shuttleList(0, this.widget.scrollControllers[0], this.widget.containsFilter, this.widget.jumpMap), + shuttleList(1, this.widget.scrollControllers[1], this.widget.containsFilter, this.widget.jumpMap), + shuttleList(2, this.widget.scrollControllers[2], this.widget.containsFilter, this.widget.jumpMap), + shuttleList(3, this.widget.scrollControllers[3], this.widget.containsFilter, this.widget.jumpMap), + ], + ), + ) + ] + ); + } + @override + bool get wantKeepAlive => true; +} + +Widget shuttleList(int idx, ItemScrollController _scrollController, Function _containsFilter, Function _jumpMap) { + return ScrollablePositionedList.builder( + itemCount: shuttleTimeLists[idx].length, + itemScrollController: _scrollController, + itemBuilder: (context, index) { + var curStopList = shuttleStopLists[idx]; + var curTimeList = shuttleTimeLists[idx]; + if (!_containsFilter(curStopList, curTimeList, index)) { + return null; + } + return Card( + child: ListTile( + leading: Icon(Icons.airport_shuttle), + title: Text(curStopList[index%curStopList.length][0]), + subtitle: Text(curTimeList[index]), + trailing: Icon(Icons.arrow_forward), + onTap: () { + _jumpMap(double.parse(curStopList[index%curStopList.length][1]), double.parse(curStopList[index%curStopList.length][2])); + }, + ), + ); + }, + ); +} \ No newline at end of file diff --git a/lib/widgets/stops.dart b/lib/widgets/stops.dart new file mode 100644 index 00000000..887a66f3 --- /dev/null +++ b/lib/widgets/stops.dart @@ -0,0 +1,20 @@ +class Stop { + final int id; + final int lat; + final int long; + final String name; + final String description; + + Stop({this.id, this.name, this.description, this.lat, this.long}); + + factory Stop.fromJson(Map json) { + return new Stop( + id: json['id'] as int, + name: json['name'] as String, + description: json['description'] as String, + + lat: json['latitude'] as int, + long: json['longitude'] as int, + ); + } +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 8d5a043e..00784d3a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,12 +29,23 @@ dependencies: #map dependencies google_maps_flutter: ^0.5.24+1 + google_map_polyline: ^0.2.0+1 + flutter_polyline_points: ^0.1.0 geolocator: ^5.3.0 + google_maps_webservice: ^0.0.16 + rxdart: ^0.23.1 #ui dependencies - sliding_up_panel: ^1.0.0 + sliding_up_panel: ^1.0.1 + scrollable_positioned_list: ^0.1.2 google_fonts: ^0.3.9 cupertino_icons: ^0.1.2 + day_night_switch: ^0.0.2+1 + intl: ^0.16.1 + + #state management + provider: ^3.1.0 + shared_preferences: ^0.5.3+4 dev_dependencies: flutter_test: @@ -56,6 +67,14 @@ flutter: assets: - assets/ridericon.png - assets/map_styles/light.json + - assets/map_styles/dark.json + - assets/map_styles/aubergine.json + - assets/shuttle_jsons/stops.json + - assets/shuttle_jsons/west.json + - assets/shuttle_jsons/south.json + - assets/shuttle_jsons/north.json + - assets/marker_shuttle.png + - assets/marker_bus.png # - images/a_dot_ham.jpeg # An image asset can refer to one or more resolution-specific "variants", see