Compare commits
No commits in common. "main" and "feedback" have entirely different histories.
1944 changed files with 25 additions and 485999 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
.vscode/settings.json
|
14
.vscode/cspell.json
vendored
14
.vscode/cspell.json
vendored
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"version": "0.2",
|
||||
"ignorePaths": [
|
||||
"src/client/data/**"
|
||||
],
|
||||
"dictionaryDefinitions": [],
|
||||
"dictionaries": [],
|
||||
"words": [
|
||||
"dotw",
|
||||
"Occt"
|
||||
],
|
||||
"ignoreWords": [],
|
||||
"import": []
|
||||
}
|
23
.vscode/launch.json
vendored
23
.vscode/launch.json
vendored
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "client (windows)",
|
||||
"cwd": "src\\client",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"args": ["-d", "windows"]
|
||||
},
|
||||
{
|
||||
"name": "client (web)",
|
||||
"cwd": "src\\client",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"args": ["-d", "chrome", "--dart-define-from-file=.env"],
|
||||
"program": "lib/main.dart",
|
||||
},
|
||||
]
|
||||
}
|
41
LICENSE
41
LICENSE
|
@ -1,41 +0,0 @@
|
|||
Copyright (c) 2025 Spencer Powell & Levi Lesches
|
||||
|
||||
At time of writing this software is not licensed under a permissive license.
|
||||
If you are Professor Steven Moore of Binghamton University you may access the code as it exists on May 5th 2025 under an MIT license for the purposes of grading.
|
||||
A copy of the MIT license can be found at https://choosealicense.com/licenses/mit/ or at the bottom of this file.
|
||||
|
||||
If you are anyone else you ARE NOT granted permission to read, use, modify, copy, merge, publich, distribute, sublicense or sell the software or any other rights to this software.
|
||||
|
||||
Versions of this software written/published after May 5th 2025 may fall under a different license.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
-----------------------------------------------------
|
||||
|
||||
Example of MIT License THIS IS NOT THE LICENSE THIS SOFTWARE FALLS UNDER barring relicensing.
|
||||
|
||||
Copyright (c) [year] [fullname]
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
66
README.md
66
README.md
|
@ -1,60 +1,44 @@
|
|||
[](https://classroom.github.com/a/vfKrPwQS)
|
||||
|
||||
# Binghamton Better Bus (BBB) v2
|
||||
# << Project Title >>
|
||||
## CS 445 Final Project
|
||||
### Spring, 2025
|
||||
### << Semester, Year >>
|
||||
|
||||
### Team: Team 4
|
||||
|
||||
- Spencer Powell
|
||||
- Levi Lesches
|
||||
### Team: << team name >>
|
||||
<< List Team Members >>
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a website which allows for picking a source and destination and getting the ideal bus route between the 2 given static information.
|
||||
<<One paragraph of project description goes here>>
|
||||
|
||||
### Roadmap
|
||||
|
||||
<<
|
||||
A list of features, function or non-functional, you would like to add in the future if you had time, i.e. Phase 2 stuff
|
||||
|
||||
- [ ] Make use of time/direction information available in static scheduling information
|
||||
- [ ] Place our hardware on OCCT buses to remove ETA Spot dependence/legal question mark
|
||||
- [ ] Design/build hardware
|
||||
- Keyword reminder for mobile data: IoT data plan and/or M2M
|
||||
- [ ] Negotiate placing hardware on OCCT buses
|
||||
- [ ] Chat with BCT about getting their live bus info
|
||||
- [ ] Use machine learning to predict bus locations based on current position, current time of day and current route,
|
||||
|
||||
- [ ] Add Changelog
|
||||
- [ ] Add back to top links
|
||||
- [ ] Add Additional Templates w/ Examples
|
||||
- [ ] Add "components" document to easily copy & paste sections of the readme
|
||||
>>
|
||||
|
||||
## SRS
|
||||
|
||||
[doc](https://docs.google.com/document/d/1kSWMxsK0NakhvHRQnNx0wRlL6wDgYGO-tA7JB_4qldE/edit?usp=sharing)
|
||||
[document](url to google doc)
|
||||
|
||||
### Prerequisites
|
||||
|
||||
* [Docker](https://www.docker.com/)
|
||||
* Docker Compose
|
||||
* [Just](https://github.com/casey/just)
|
||||
* <<any additional software. Be specific about versions.>>
|
||||
|
||||
### Installing
|
||||
|
||||
cd into the `src` directory and run `just setup` then run `docker-compose build`.
|
||||
|
||||
To run just run `docker-compose up` in the `src` directory, the site will be up on `localhost:8080`
|
||||
<<
|
||||
A step by step series of examples that tell you how to get a development env running
|
||||
Clearly outline each step and repeat until the environment is set up.
|
||||
End with an example of getting some output from the system, such as a menu or prompt
|
||||
>>
|
||||
|
||||
## Built With
|
||||
|
||||
* [Deno](https://deno.com/)
|
||||
- [Acorn](https://oakserver.org/acorn)
|
||||
* [Dart](https://dart.dev/)
|
||||
* [Flutter](https://flutter.dev/)
|
||||
- Google maps
|
||||
* [Caddy](https://caddyserver.com/)
|
||||
<< list all frameworks and modules used here >>
|
||||
* [requests](https://docs.python-requests.org/en/latest/user/quickstart/#make-a-request) - request for humans
|
||||
|
||||
## License
|
||||
|
||||
DIY license written out which grants MIT rights to professor moore for the version of this submitted for grading and no rights to anyone else.
|
||||
<< Add a [license](https://choosealicense.com/) >>
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
* Claude and chatGPT were used for the creation of scripts for certain rote data conversion tasks
|
||||
* This project would not exist with the data it has if not for prior instances of attempts to make this idea by US, of particular note is the usage of GTFS data which was only learned of in a prior attempt with Lucy Loerker
|
||||
* Hat tip to anyone whose code was used
|
||||
* Inspiration
|
||||
* etc
|
||||
|
|
BIN
demo.mp4
BIN
demo.mp4
Binary file not shown.
|
@ -1,130 +0,0 @@
|
|||
## Client Meeting Notes
|
||||
|
||||
**Project Name:** [Your Project Name]
|
||||
**Date:** [MM/DD/YYYY]
|
||||
**Client Name:** [Client’s Name]
|
||||
**Interviewed By:** [Your Name(s)]
|
||||
|
||||
---
|
||||
|
||||
### **Client Overview**
|
||||
- **Brief description of the client and their background:**
|
||||
(Example: Kevin is a Dungeon Master who wants a tool to help manage his D&D campaigns.)
|
||||
|
||||
- **Client’s main goal for the software:**
|
||||
(Example: The tool should help track player turns, roll dice automatically, and send notifications.)
|
||||
|
||||
---
|
||||
|
||||
### **Interview Questions & Responses**
|
||||
|
||||
#### **1. What are the costs vs. budget?**
|
||||
**Client’s Response:**
|
||||
(Example: "I don’t want to spend much, ideally free, but I’d consider paying a one-time fee.")
|
||||
|
||||
**Clarification & Notes:**
|
||||
(Example: The client is looking for a free tool but may accept optional paid features.)
|
||||
|
||||
---
|
||||
|
||||
#### **2. What is the timeframe for this project?**
|
||||
**Client’s Response:**
|
||||
(Example: "I need this ready before my next campaign starts in three months.")
|
||||
|
||||
**Clarification & Notes:**
|
||||
(Example: A strict deadline, must focus on core features first.)
|
||||
|
||||
---
|
||||
|
||||
#### **3. What problem are you trying to solve?**
|
||||
**Client’s Response:**
|
||||
(Example: "Managing initiative order and tracking actions is too slow.")
|
||||
|
||||
**Clarification & Notes:**
|
||||
- The real problem is **speed and efficiency** during gameplay, not just "initiative tracking."
|
||||
- Consider features like **automation** and **UI improvements** to streamline this.
|
||||
|
||||
---
|
||||
|
||||
#### **4. What is the most important feature?**
|
||||
**Client’s Response:**
|
||||
(Example: "Initiative tracking needs to be easy and automatic.")
|
||||
|
||||
**Clarification & Notes:**
|
||||
- This feature should be the **core focus** of the software.
|
||||
- Other features (e.g., dice rolling) are secondary.
|
||||
|
||||
---
|
||||
|
||||
#### **5. Is there a similar product on the market?**
|
||||
**Client’s Response:**
|
||||
(Example: "Yes, but they are either too complex or too expensive.")
|
||||
|
||||
**Clarification & Notes:**
|
||||
- Competitor analysis needed.
|
||||
- Find a way to differentiate: **Simpler UI? Free version? Customizable?**
|
||||
|
||||
---
|
||||
|
||||
#### **6. Are there any existing products or technologies that will need to be integrated?**
|
||||
**Client’s Response:**
|
||||
(Example: "It would be nice if it worked with Roll20 or D&D Beyond.")
|
||||
|
||||
**Clarification & Notes:**
|
||||
- Check for **APIs** for possible integrations.
|
||||
- Consider a **manual import/export** feature if full integration isn’t feasible.
|
||||
|
||||
---
|
||||
|
||||
#### **7. What does success look like for this project?**
|
||||
**Client’s Response:**
|
||||
(Example: "If my players can see whose turn it is without me saying anything, that’s a win.")
|
||||
|
||||
**Clarification & Notes:**
|
||||
- Focus on **visual clarity and notifications**.
|
||||
|
||||
---
|
||||
|
||||
#### **8. What risks are associated with a malicious user?**
|
||||
**Client’s Response:**
|
||||
(Example: "Not much, unless players try to cheat dice rolls.")
|
||||
|
||||
**Clarification & Notes:**
|
||||
- Consider **server-side dice validation** to prevent cheating.
|
||||
|
||||
---
|
||||
|
||||
#### **9. What is the expected lifetime of this software?**
|
||||
**Client’s Response:**
|
||||
(Example: "I want to use it for many campaigns, so it should last for years.")
|
||||
|
||||
**Clarification & Notes:**
|
||||
- Future **maintenance and updates** should be considered.
|
||||
|
||||
---
|
||||
|
||||
#### **10. What additional features would you like?**
|
||||
**Client’s Response:**
|
||||
(Example: "A way to send private messages to players would be cool.")
|
||||
|
||||
**Clarification & Notes:**
|
||||
- Nice-to-have, but **not essential** for version 1.
|
||||
|
||||
---
|
||||
|
||||
## **Key Takeaways & Next Steps**
|
||||
- **Most important feature:** Initiative tracking
|
||||
- **Secondary features:** Dice rolling, notifications, private messaging
|
||||
- **Deadline:** 3 months
|
||||
- **Risks:** Potential cheating in dice rolls
|
||||
|
||||
### **Next Steps:**
|
||||
✅ Prioritize initiative tracking
|
||||
✅ Research existing products for inspiration
|
||||
✅ Explore integration options with Roll20/D&D Beyond
|
||||
|
||||
---
|
||||
|
||||
### **Signatures**
|
||||
(Client) **[Client’s Name]** – *Reviewed & Approved*
|
||||
(Developer) **[Your Name]** – *Re
|
|
@ -1,181 +0,0 @@
|
|||
## Client Meeting Notes
|
||||
|
||||
**Project Name:** WIP Map Project
|
||||
**Date:** 02/05/2025
|
||||
**Client Name:** Spencer Powell
|
||||
**Interviewed By:** Spencer Powell
|
||||
|
||||
---
|
||||
|
||||
### **Client Overview**
|
||||
- **Brief description of the client and their background:**
|
||||
The client is an undergraduate student at Binghamton University who regularly uses the bus system as a method of transit.
|
||||
|
||||
- **Client’s main goal for the software:**
|
||||
The tool should assist in transiting around Binghamton using the 2 bus systems via providing arrival timing information and ideal routes.
|
||||
|
||||
---
|
||||
|
||||
### **Interview Questions & Responses**
|
||||
|
||||
#### **1. What are the costs vs. budget?**
|
||||
**Client’s Response:**
|
||||
I'm a college student, so ideally free.
|
||||
|
||||
> Comparing to whatever subscriptions you currently pay for how much would you pay for this?
|
||||
|
||||
A dollar maybe a few bucks.
|
||||
|
||||
**Clarification & Notes:**
|
||||
Client is looking for something free and will be relatively price sensitive if they're paying.
|
||||
|
||||
---
|
||||
|
||||
#### **2. What is the timeframe for this project?**
|
||||
**Client’s Response:**
|
||||
I am graduating in 3 months or so, so if you deliver after that it'll be useless to me.
|
||||
|
||||
> Do you have any friends that aren't graduating that may be interested?
|
||||
|
||||
Most of my friends are also graduating in free months but a couple of them aren't I guess.
|
||||
|
||||
**Clarification & Notes:**
|
||||
|
||||
While this particular client provides a strict deadline there are other clients in similar circumstances who have a looser deadline.
|
||||
|
||||
As such if we want to meet their needs we'll need to work quickly on core features but there's reason to work on extra features if we go over the deadline.
|
||||
|
||||
---
|
||||
|
||||
#### **3. What problem are you trying to solve?**
|
||||
**Client’s Response:**
|
||||
I would like to turn my brain off when I want to go somewhere outside walking distance.
|
||||
|
||||
> What do you need to think about when going somewhere?
|
||||
|
||||
How far I need to walk to take a bus
|
||||
|
||||
When the bus arrives/departs
|
||||
|
||||
Often I need to take multiple buses.
|
||||
|
||||
> Could you elaborate on how multiple buses complicates things?
|
||||
|
||||
Well lets say I want to go to/from Walmart from my home for example.
|
||||
I have two main routes, one that goes to BU then takes a 47/91 or OCCT bus to walmart and one which goes to the downtown bus station and then takes the 57 to Walmart.
|
||||
Vice versa to go back home from Walmart.
|
||||
I need to think about which choice will get me to/from Walmart with the least amount of time spent on buses/waiting and I don't always get it right.
|
||||
Combine that with the other things I mentioned and it's a pain.
|
||||
|
||||
**Clarification & Notes:**
|
||||
|
||||
- forgot line of thought due to interview being interrupted
|
||||
|
||||
---
|
||||
|
||||
#### **4. What is the most important feature?**
|
||||
|
||||
**Client’s Response:**
|
||||
|
||||
Getting a reliable preferably optimal route of buses to get from point to point
|
||||
|
||||
**Clarification & Notes:**
|
||||
|
||||
- Focus on getting routes first and be reasonably certain they'll work
|
||||
|
||||
---
|
||||
|
||||
#### **5. Is there a similar product on the market?**
|
||||
**Client’s Response:**
|
||||
|
||||
Google maps can be used to get routes which only use broome county buses but doesn't take OCCT buses into account.
|
||||
Furthermore it isn't clear if google maps can/would take into account live info for it's routes.
|
||||
|
||||
There's also etaSpot which is only a live map of OCCT buses
|
||||
|
||||
**Clarification & Notes:**
|
||||
- Worth investigating how google maps determines routes algorithmically
|
||||
- Need to integrate OCCT buses to be useful
|
||||
|
||||
---
|
||||
|
||||
#### **6. Are there any existing products or technologies that will need to be integrated?**
|
||||
|
||||
**Client’s Response:**
|
||||
|
||||
No
|
||||
|
||||
**Clarification & Notes:**
|
||||
|
||||
- The client isn't aware of Broome county providing GTFS
|
||||
- Furthermore from prior question etaSpot can be used for live OCCT bus info
|
||||
|
||||
---
|
||||
|
||||
#### **7. What does success look like for this project?**
|
||||
|
||||
**Client’s Response:**
|
||||
|
||||
If I can put in a place I want to go on my phone and get instructions to go there via bus that are good then I'd say it's a success.
|
||||
|
||||
**Clarification & Notes:**
|
||||
|
||||
- Again focus on reliability and speed
|
||||
|
||||
---
|
||||
|
||||
#### **8. What risks are associated with a malicious user?**
|
||||
|
||||
**Client’s Response:**
|
||||
|
||||
nothing?
|
||||
|
||||
**Clarification & Notes:**
|
||||
|
||||
- There's some risk of DDOS on our endpoints and possible free riders but that isn't really a risk to other users
|
||||
|
||||
---
|
||||
|
||||
#### **9. What is the expected lifetime of this software?**
|
||||
|
||||
**Client’s Response:**
|
||||
|
||||
For my remaining time in university.
|
||||
|
||||
**Clarification & Notes:**
|
||||
|
||||
- While this client will be leaving university soon there will be a continuous stream of new users so long term sustainability should be somewhat accounted for
|
||||
|
||||
---
|
||||
|
||||
#### **10. What additional features would you like?**
|
||||
|
||||
**Client’s Response:**
|
||||
|
||||
Live map
|
||||
|
||||
**Clarification & Notes:**
|
||||
|
||||
- nice to have and reasonably easy to implement for OCCT, harder for broome county
|
||||
|
||||
---
|
||||
|
||||
## **Key Takeaways & Next Steps**
|
||||
|
||||
- **Most important feature:** Route calculation
|
||||
- **Secondary features:** Live map and viewing schedules
|
||||
- **Deadline:** 3 months
|
||||
- **Risks:** DDOS and freeloading users
|
||||
|
||||
### **Next Steps:**
|
||||
|
||||
- [ ] Collect data from GTFS and etaSpot api
|
||||
- [ ] Use data to calculate crude route from point to point
|
||||
- [ ] Improve route making
|
||||
- [ ] Add live map
|
||||
|
||||
---
|
||||
|
||||
### **Signatures**
|
||||
(Client) **Spencer Powell** – *Reviewed & Approved*
|
||||
(Developer) **Spencer Powell** – *Re
|
|
@ -1,130 +0,0 @@
|
|||
## Client Meeting Notes
|
||||
|
||||
**Project Name:** [Your Project Name]
|
||||
**Date:** [MM/DD/YYYY]
|
||||
**Client Name:** [Client’s Name]
|
||||
**Interviewed By:** [Your Name(s)]
|
||||
|
||||
---
|
||||
|
||||
### **Client Overview**
|
||||
- **Brief description of the client and their background:**
|
||||
(Example: Kevin is a Dungeon Master who wants a tool to help manage his D&D campaigns.)
|
||||
|
||||
- **Client’s main goal for the software:**
|
||||
(Example: The tool should help track player turns, roll dice automatically, and send notifications.)
|
||||
|
||||
---
|
||||
|
||||
### **Interview Questions & Responses**
|
||||
|
||||
#### **1. What are the costs vs. budget?**
|
||||
**Client’s Response:**
|
||||
(Example: "I don’t want to spend much, ideally free, but I’d consider paying a one-time fee.")
|
||||
|
||||
**Clarification & Notes:**
|
||||
(Example: The client is looking for a free tool but may accept optional paid features.)
|
||||
|
||||
---
|
||||
|
||||
#### **2. What is the timeframe for this project?**
|
||||
**Client’s Response:**
|
||||
(Example: "I need this ready before my next campaign starts in three months.")
|
||||
|
||||
**Clarification & Notes:**
|
||||
(Example: A strict deadline, must focus on core features first.)
|
||||
|
||||
---
|
||||
|
||||
#### **3. What problem are you trying to solve?**
|
||||
**Client’s Response:**
|
||||
(Example: "Managing initiative order and tracking actions is too slow.")
|
||||
|
||||
**Clarification & Notes:**
|
||||
- The real problem is **speed and efficiency** during gameplay, not just "initiative tracking."
|
||||
- Consider features like **automation** and **UI improvements** to streamline this.
|
||||
|
||||
---
|
||||
|
||||
#### **4. What is the most important feature?**
|
||||
**Client’s Response:**
|
||||
(Example: "Initiative tracking needs to be easy and automatic.")
|
||||
|
||||
**Clarification & Notes:**
|
||||
- This feature should be the **core focus** of the software.
|
||||
- Other features (e.g., dice rolling) are secondary.
|
||||
|
||||
---
|
||||
|
||||
#### **5. Is there a similar product on the market?**
|
||||
**Client’s Response:**
|
||||
(Example: "Yes, but they are either too complex or too expensive.")
|
||||
|
||||
**Clarification & Notes:**
|
||||
- Competitor analysis needed.
|
||||
- Find a way to differentiate: **Simpler UI? Free version? Customizable?**
|
||||
|
||||
---
|
||||
|
||||
#### **6. Are there any existing products or technologies that will need to be integrated?**
|
||||
**Client’s Response:**
|
||||
(Example: "It would be nice if it worked with Roll20 or D&D Beyond.")
|
||||
|
||||
**Clarification & Notes:**
|
||||
- Check for **APIs** for possible integrations.
|
||||
- Consider a **manual import/export** feature if full integration isn’t feasible.
|
||||
|
||||
---
|
||||
|
||||
#### **7. What does success look like for this project?**
|
||||
**Client’s Response:**
|
||||
(Example: "If my players can see whose turn it is without me saying anything, that’s a win.")
|
||||
|
||||
**Clarification & Notes:**
|
||||
- Focus on **visual clarity and notifications**.
|
||||
|
||||
---
|
||||
|
||||
#### **8. What risks are associated with a malicious user?**
|
||||
**Client’s Response:**
|
||||
(Example: "Not much, unless players try to cheat dice rolls.")
|
||||
|
||||
**Clarification & Notes:**
|
||||
- Consider **server-side dice validation** to prevent cheating.
|
||||
|
||||
---
|
||||
|
||||
#### **9. What is the expected lifetime of this software?**
|
||||
**Client’s Response:**
|
||||
(Example: "I want to use it for many campaigns, so it should last for years.")
|
||||
|
||||
**Clarification & Notes:**
|
||||
- Future **maintenance and updates** should be considered.
|
||||
|
||||
---
|
||||
|
||||
#### **10. What additional features would you like?**
|
||||
**Client’s Response:**
|
||||
(Example: "A way to send private messages to players would be cool.")
|
||||
|
||||
**Clarification & Notes:**
|
||||
- Nice-to-have, but **not essential** for version 1.
|
||||
|
||||
---
|
||||
|
||||
## **Key Takeaways & Next Steps**
|
||||
- **Most important feature:** Initiative tracking
|
||||
- **Secondary features:** Dice rolling, notifications, private messaging
|
||||
- **Deadline:** 3 months
|
||||
- **Risks:** Potential cheating in dice rolls
|
||||
|
||||
### **Next Steps:**
|
||||
✅ Prioritize initiative tracking
|
||||
✅ Research existing products for inspiration
|
||||
✅ Explore integration options with Roll20/D&D Beyond
|
||||
|
||||
---
|
||||
|
||||
### **Signatures**
|
||||
(Client) **[Client’s Name]** – *Reviewed & Approved*
|
||||
(Developer) **[Your Name]** – *Re
|
|
@ -1,17 +0,0 @@
|
|||
# Topic: Flutter
|
||||
|
||||
## Outline
|
||||
|
||||
- What Flutter is
|
||||
- What Flutter is not
|
||||
- Who is Dart?
|
||||
- Asynchronous code
|
||||
- Building app with flutters
|
||||
|
||||
## Questions
|
||||
|
||||
- Compared to other frameworks
|
||||
|
||||
## Review
|
||||
|
||||
Good Job!
|
|
@ -1,16 +0,0 @@
|
|||
# Topic: Deno
|
||||
|
||||
## Outline
|
||||
|
||||
- WHat is deno
|
||||
- Deno imporvements over node
|
||||
- typescript
|
||||
- security
|
||||
|
||||
## Questions
|
||||
|
||||
- migration to deno
|
||||
|
||||
## Review
|
||||
|
||||
Good Job
|
|
@ -1,57 +0,0 @@
|
|||
## How will you use this application?
|
||||
|
||||
Open app/web app on phone and use it to plan out trip.
|
||||
|
||||
## Where would the user be located physically when using this application?
|
||||
|
||||
Somewhere in or around Binghamton.
|
||||
|
||||
## Where would the results be visible?
|
||||
|
||||
On a website.
|
||||
|
||||
## When will this application be used?
|
||||
|
||||
When transiting via bus.
|
||||
|
||||
## When can the application fail?
|
||||
|
||||
Multiple failure cases.
|
||||
|
||||
## Who is my user?
|
||||
|
||||
Binghamton student who use the bus systems to get around.
|
||||
|
||||
## Who will deliver the inputs for the application?
|
||||
|
||||
User/ OCCT eta spot api
|
||||
|
||||
## Who will receive the outputs of the application?
|
||||
|
||||
User
|
||||
|
||||
## What do I know about this application?
|
||||
|
||||
_
|
||||
|
||||
## What does this application need to do?
|
||||
|
||||
Gather data on available buses and plan a route based on that information
|
||||
|
||||
## What are the components of this application?
|
||||
|
||||
Backend to gather bus info and calculate routes
|
||||
|
||||
Frontend to receive route requests and display the calculated route
|
||||
|
||||
## What needs to happen next?
|
||||
|
||||
API between frontend and backend needs to be decided and tasks divided up/planned out
|
||||
|
||||
## What must happen before the next step?
|
||||
|
||||
_
|
||||
|
||||
## What needs to be tracked?
|
||||
|
||||
_
|
19
etc/stack.md
19
etc/stack.md
|
@ -1,19 +0,0 @@
|
|||
## **Backend**
|
||||
|
||||
- **NodeJS** → [node](https://hub.docker.com/_/node)
|
||||
|
||||
## **Frontend**
|
||||
|
||||
- **Flutter** → [the thing](https://flutter.dev/)
|
||||
|
||||
## **Web Server**
|
||||
|
||||
- **Caddy** → [caddy:latest](https://hub.docker.com/_/caddy)
|
||||
|
||||
## **Database**
|
||||
|
||||
- **Sqlite** → [file format lmao](https://www.sqlite.org/index.html)
|
||||
|
||||
## **Additional Dependencies**
|
||||
|
||||
-
|
|
@ -1,22 +0,0 @@
|
|||
|Technology | Spencer | Levi |
|
||||
|---------------------|-------------|-------------|
|
||||
|Flutter | None | Profiecient |
|
||||
|Unity | Novice | Novice |
|
||||
|Docker | Profiecient | Novice |
|
||||
|Git | Profiecient | Profiecient |
|
||||
|Rust | Profiecient | Competent |
|
||||
|Javascript/Typescript| Profiecient | Profiecient |
|
||||
|Python | Competent | Profiecient |
|
||||
|Haskell | Competent | Novice |
|
||||
|Node | Competent | Competent |
|
||||
|Nginx | Competent | None |
|
||||
|Dart | None | Profiecient |
|
||||
|Caddy | Novice | None |
|
||||
|Shell | Competent | Competent |
|
||||
|React | Novice | Novice |
|
||||
|Svelte | Novice | Novice |
|
||||
|HTML | Competent | Competent |
|
||||
|CSS | Profiecient | Compentent |
|
||||
|Linux | Profiecient | Profiecient |
|
||||
|Kubernetes | None | None |
|
||||
|Firebase | None | Profiecient |
|
|
@ -1,31 +0,0 @@
|
|||
# [Feature Name]
|
||||
|
||||
## **Persona**
|
||||
|
||||
- **Name:** Sam
|
||||
- **Relevant Characteristics:** Freshman binghamton university student who just downloaded the app
|
||||
- **Behaviors & Attitudes:** Reasonably comfortable with a phone/google maps and have little to no experience/understanding of the bus systems in Binghamton
|
||||
- **Goal:** Go from home to somewhere else in binghamton and back again
|
||||
|
||||
---
|
||||
|
||||
## **User Story**
|
||||
|
||||
As a **university student**
|
||||
I want **a way to figure out good routes with buses without memorizing every bus route/stop**
|
||||
so that **I can buy groceries, go to downtown and easily get to/from the university**
|
||||
|
||||
---
|
||||
|
||||
## **Acceptance Criteria**
|
||||
- A route to get from point A to point B which isn't obnoxiously slow is generated
|
||||
- said route is reliable
|
||||
- route is generated within seconds (10 seconds max if possible)
|
||||
|
||||
---
|
||||
|
||||
## **Notes**
|
||||
- route should be reasonably fast
|
||||
- route should have reasonably low amounts of walking
|
||||
- OCCT can sometimes drop the ball on live map info leading to suboptimal routing
|
||||
- furthermore it's possible OCCT or broome county service is temporarily canceled which can cause a route to be wrong
|
|
@ -1,25 +0,0 @@
|
|||
# [Feature Name]
|
||||
|
||||
## **Persona**
|
||||
- **Name:** [Fictional User Name]
|
||||
- **Relevant Characteristics:** [Background, role, experience level]
|
||||
- **Behaviors & Attitudes:** [How they use technology, frustrations, goals]
|
||||
- **Goal:** [What they want to achieve]
|
||||
|
||||
---
|
||||
|
||||
## **User Story**
|
||||
[Write the user story using one of the approved formats]
|
||||
|
||||
---
|
||||
|
||||
## **Acceptance Criteria**
|
||||
- [Define the success conditions for this feature]
|
||||
- [How will we know when this is done?]
|
||||
- [What must be functional before release?]
|
||||
|
||||
---
|
||||
|
||||
## **Notes**
|
||||
- [Any additional considerations or insights]
|
||||
- [Potential risks or challenges]
|
|
@ -1,34 +0,0 @@
|
|||
|
||||
# Client Meeting Notes
|
||||
|
||||
## Sprint Notes
|
||||
|
||||
## SRS Updated
|
||||
|
||||
N/A
|
||||
|
||||
## Requirements Attempted in this Sprint
|
||||
|
||||
N/A
|
||||
|
||||
## Requirements complete
|
||||
|
||||
N/A
|
||||
|
||||
## Requirements incomplete
|
||||
|
||||
N/A
|
||||
|
||||
## Requirement Flex Remaining
|
||||
|
||||
<< 5 / 5 >>
|
||||
|
||||
## Requirements Attempted in next Sprint
|
||||
|
||||
Access transit to information
|
||||
|
||||
# Milestone Status
|
||||
|
||||
Pass
|
||||
|
||||
## Requested Changes
|
|
@ -1,71 +0,0 @@
|
|||
# **Project Overview**
|
||||
|
||||
## **Application Vision/Goal:**
|
||||
|
||||
The two main public transit providers for Binghamton University students are OCCT and BC buses. Each provider has their own routes, schedules, and quirks, making the simple question “how do I get there” surprisingly difficult to answer, especially in a timely manner. This project will pull data from both services to get people to their destinations.
|
||||
|
||||
Users will be able to input a start and end location and the app will respond with a route using either, both or neither bus providers to get there.
|
||||
|
||||
## **Scope:**
|
||||
|
||||
Allow viewing bus routes and pick start and end point and get bus route between them.
|
||||
|
||||
## **Deliverables:**
|
||||
|
||||
Google maps which you can click on 2 points to get route between them and get a visual of the bus routes for current time.
|
||||
|
||||
## **Success Criteria:**
|
||||
|
||||
Making something which is more functional than [Spencer's HackBU 2024 project](https://git.pagwin.xyz/Pagwin/BinghamonBetterBus), ([Video demo](https://youtu.be/5WcKPhMqveY)) hackBU project doesn't handle Broome county buses due to a bug and suggests comically bad bus routes.
|
||||
|
||||
## **Assumptions:**
|
||||
|
||||
- etaSPOT data is reasonably accurate
|
||||
- Broome county GTFS data is reasonably accurate
|
||||
- Google maps will continue to exist and allow for drawing of path on map
|
||||
|
||||
## **Risks:**
|
||||
|
||||
- if we want/need static OCCT data that will involve manual work
|
||||
- potential combinatorial explosion causing calculation to take too long
|
||||
|
||||
## **Design / Architectural Review:**
|
||||
|
||||
Client server split maybe database
|
||||
|
||||
## **Test Environment:**
|
||||
|
||||
Unit tests for the backend.
|
||||
|
||||
---
|
||||
|
||||
# **Team Setup**
|
||||
|
||||
## **Team Members:**
|
||||
|
||||
- Spencer Powell
|
||||
- Levi Lesches
|
||||
|
||||
## **Team Roles:**
|
||||
|
||||
- Spencer: server
|
||||
- Levi: client
|
||||
|
||||
## **Team Norms:**
|
||||
|
||||
- handle CI for their side of the app
|
||||
|
||||
## **Application Stack:**
|
||||
|
||||
- Caddy for reverse proxy
|
||||
- Flutter for client
|
||||
- Google Maps API for visual
|
||||
- Nodejs + Typescript for backend
|
||||
- sqlite for db
|
||||
|
||||
### **Libraries/Frameworks:**
|
||||
|
||||
- JSON for api
|
||||
- etaSpot for dynamic OCCT info
|
||||
- maybe scraping for static OCCT info
|
||||
- GTFS data for static BC info
|
|
@ -1,36 +0,0 @@
|
|||
|
||||
# Client Meeting Notes
|
||||
|
||||
## Sprint Notes
|
||||
|
||||
## SRS Updated
|
||||
|
||||
Requirements section - complete
|
||||
|
||||
## Requirements Attempted in this Sprint
|
||||
|
||||
Access transit to information
|
||||
|
||||
## Requirements complete
|
||||
|
||||
Access transit to information
|
||||
|
||||
## Requirements incomplete
|
||||
|
||||
N/A
|
||||
|
||||
## Requirement Flex Remaining
|
||||
|
||||
<< 5 / 5 >>
|
||||
|
||||
## Requirements Attempted in next Sprint
|
||||
|
||||
Communication between front and backend
|
||||
|
||||
# Milestone Status
|
||||
|
||||
Pass
|
||||
|
||||
## Requested Changes
|
||||
|
||||
Break requirements section of the SRS into function and non-function, and connect each requirement to one or more user story
|
|
@ -1,74 +0,0 @@
|
|||
# Sprint Meeting Notes
|
||||
|
||||
**Attended**: Spencer Powell, Levi Lesches
|
||||
|
||||
**DATE**: 03-03-2025
|
||||
|
||||
***
|
||||
|
||||
## Sprint 1 Review
|
||||
|
||||
### SRS Sections Updated
|
||||
|
||||
<< List any SRS sections that were updated in the last sprint >>
|
||||
|
||||
Functional and non functional requirements
|
||||
|
||||
### User Story
|
||||
|
||||
<< Corresponding User Stories completed in this sprint >>
|
||||
No User stories
|
||||
|
||||
|
||||
### Sprint Requirements Attempted
|
||||
|
||||
|
||||
Convert data into desired format from data available via APIs
|
||||
|
||||
### Completed Requirements
|
||||
|
||||
TBD
|
||||
|
||||
### Incomplete Requirements
|
||||
|
||||
TBD
|
||||
|
||||
### The summary of the entire project
|
||||
|
||||
<< A general overview of the entire project >>
|
||||
|
||||
***
|
||||
|
||||
## Sprint << num >> Planning
|
||||
|
||||
## Requirements Flex
|
||||
|
||||
<< # >>/5 requirement flexes remaining
|
||||
|
||||
## Technical Debt
|
||||
|
||||
<< Any requirements from the previous sprint that are using a technical flex >>
|
||||
|
||||
### Requirement Target
|
||||
|
||||
<< The corresponding SRS requirement that our team will be complete in next sprint >>
|
||||
|
||||
### User Stories
|
||||
|
||||
<< Corresponding User Stories >>
|
||||
|
||||
### Planning
|
||||
|
||||
<< Our team's detailed plan to complete the sprint >>
|
||||
|
||||
### Action Items
|
||||
|
||||
<< A list of things that need to happen in our for our team to complete the sprint >>
|
||||
|
||||
### Issues and Risks
|
||||
|
||||
<< A list of potential obstacles that could keep us from completing the sprint and what's being done about them >>
|
||||
|
||||
### Team Work Assignments
|
||||
|
||||
<< A list of each team member and their works assignments for this sprint >>
|
|
@ -1,39 +0,0 @@
|
|||
|
||||
# Client Meeting Notes
|
||||
|
||||
## Sprint Notes
|
||||
|
||||
## SRS Updated
|
||||
|
||||
Archetecture Diagram
|
||||
|
||||
## Requirements Attempted in this Sprint
|
||||
|
||||
Communication between front and backend
|
||||
- CLient/Server integration
|
||||
|
||||
## Requirements complete\
|
||||
|
||||
Communication between front and backend
|
||||
- CLient/Server integration
|
||||
|
||||
## Requirements incomplete
|
||||
|
||||
|
||||
## Requirement Flex Remaining
|
||||
|
||||
<< 3 / 5 >>
|
||||
|
||||
- Missed sprint goal
|
||||
|
||||
## Requirements Attempted in next Sprint
|
||||
|
||||
- Convert data into desired format from data available via APIs
|
||||
- Client and server should integrate without issue
|
||||
|
||||
# Milestone Status
|
||||
|
||||
Pass
|
||||
|
||||
## Requested Changes
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
# Sprint Meeting Notes
|
||||
|
||||
**Attended**: Spencer Powell
|
||||
|
||||
**DATE**: 3/21/2025
|
||||
|
||||
***
|
||||
|
||||
## Sprint 2 Review
|
||||
|
||||
### SRS Sections Updated
|
||||
|
||||
None
|
||||
|
||||
### User Story
|
||||
|
||||
None
|
||||
|
||||
### Sprint Requirements Attempted
|
||||
|
||||
None
|
||||
|
||||
### Completed Requirements
|
||||
|
||||
None
|
||||
|
||||
### Incomplete Requirements
|
||||
|
||||
- Timing information for OCCT is a work in progress
|
||||
- work on the client hasn't started
|
||||
- api endpoint that makes a route isn't mocked or working
|
||||
- while the data for the trips/stops is gathered the setup for the server to provide that data to the client isn't made yet
|
||||
|
||||
### The summary of the entire project
|
||||
|
||||
Still very much in an early messy state.
|
||||
|
||||
***
|
||||
|
||||
## Sprint 3 Planning
|
||||
|
||||
## Requirements Flex
|
||||
|
||||
4/5 requirement flexes remaining
|
||||
|
||||
## Technical Debt
|
||||
|
||||
Connecting up the client and the server/putting data in desired format
|
||||
|
||||
### Requirement Target
|
||||
|
||||
- Convert data into desired format from data available via APIs
|
||||
- Client and server should integrate without issue
|
||||
|
||||
### User Stories
|
||||
|
||||
Initial user story
|
||||
|
||||
### Planning
|
||||
|
||||
Levi will finish up handling getting static timing for OCCT and build the client to display some data and Spencer will setup the server to show the generated files and for the api endpoint which gives a route have it emit a static hand-written route
|
||||
|
||||
### Action Items
|
||||
|
||||
Static timing info needs to be brought into the OCCT data from the OCCT site, client needs to be built and server needs to be built
|
||||
|
||||
### Issues and Risks
|
||||
|
||||
Other school work could take too much time and prevent progress from being made.
|
||||
|
||||
### Team Work Assignments
|
||||
|
||||
- Levi
|
||||
- OCCT static timing info
|
||||
- Client
|
||||
- Spencer
|
||||
- Server
|
|
@ -1,37 +0,0 @@
|
|||
|
||||
# Client Meeting Notes
|
||||
|
||||
## Sprint Notes
|
||||
|
||||
## SRS Updated
|
||||
|
||||
UI
|
||||
|
||||
## Requirements Attempted in this Sprint
|
||||
|
||||
- Convert data into desired format from data available via APIs
|
||||
- Client and server should integrate without issue
|
||||
|
||||
## Requirements complete\
|
||||
|
||||
- Convert data into desired format from data available via APIs
|
||||
- Client and server should integrate without issue
|
||||
|
||||
## Requirements incomplete
|
||||
|
||||
|
||||
## Requirement Flex Remaining
|
||||
|
||||
<< 3 / 5 >>
|
||||
|
||||
## Requirements Attempted in next Sprint
|
||||
|
||||
- Merge client sevrer to one branch, and integration
|
||||
- Docker Services working
|
||||
|
||||
# Milestone Status
|
||||
|
||||
Pass
|
||||
|
||||
## Requested Changes
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
# Sprint Meeting Notes
|
||||
|
||||
*note: replace anything surrounded by << >> and **remove** the << >>*
|
||||
|
||||
**Attended**: Spencer Powell
|
||||
|
||||
**DATE**: << meeting date >>
|
||||
|
||||
***
|
||||
|
||||
## Sprint 3 Review
|
||||
|
||||
### SRS Sections Updated
|
||||
|
||||
requirements
|
||||
|
||||
### User Story
|
||||
|
||||
arguably the initial story
|
||||
|
||||
### Sprint Requirements Attempted
|
||||
|
||||
- Archetecture Diagram completion
|
||||
|
||||
### Completed Requirements
|
||||
|
||||
- Client and server now share a language although the plumbing between them isn't setup yet
|
||||
- server now determine's stops for a "route" although those stops aren't related to the response given by `/path` due to them not considering bus directionality
|
||||
|
||||
### Incomplete Requirements
|
||||
|
||||
Depends on how you interpret the requirements and completing them, ultimately it was determined that picking a somewhat useful route was a bigger requirement than expected however the naive algorithm which ignores bus directionality was implemented but not connected to the actual API due to being fundamentally unfit for purpose
|
||||
|
||||
### The summary of the entire project
|
||||
|
||||
Things are almost coming toegether into a state where we can show something but getting useful functionality will probably be pushed back a few weeks
|
||||
|
||||
***
|
||||
|
||||
## Sprint 4 Planning
|
||||
|
||||
## Requirements Flex
|
||||
|
||||
3/5 requirement flexes remaining
|
||||
|
||||
## Technical Debt
|
||||
|
||||
### Requirement Target
|
||||
|
||||
- Display routes or display routes (changed from text based simply giving back info retrieved from api to actually using google maps)
|
||||
- decide on representation of graph and graph representation of the bus systems and create nodes in the representation of the graph for the graph representation of the system
|
||||
|
||||
### User Stories
|
||||
|
||||
Initial user story
|
||||
|
||||
### Issues and Risks
|
||||
|
||||
- Graph representation will probably need to be complicated
|
||||
|
||||
### Team Work Assignments
|
||||
|
||||
- Levi will setup the client to use google maps instead of just displaying text
|
||||
- Spencer will figure out the representation of the graph and graph representation of the system
|
|
@ -1,32 +0,0 @@
|
|||
|
||||
# Client Meeting Notes
|
||||
|
||||
## Sprint Notes
|
||||
|
||||
## Requirements Attempted in this Sprint
|
||||
|
||||
- Merge client sevrer to one branch, and integration
|
||||
- Docker Services working
|
||||
|
||||
## Requirements complete
|
||||
|
||||
- Merge client sevrer to one branch, and integration
|
||||
- Docker Services working
|
||||
|
||||
## Requirements incomplete
|
||||
|
||||
|
||||
## Requirement Flex Remaining
|
||||
|
||||
<< 3 / 5 >>
|
||||
|
||||
## Requirements Attempted in next Sprint
|
||||
|
||||
- Routing is based on start and stop location
|
||||
|
||||
# Milestone Status
|
||||
|
||||
Pass
|
||||
|
||||
## Requested Changes
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
# Sprint Meeting Notes
|
||||
|
||||
*note: replace anything surrounded by << >> and **remove** the << >>*
|
||||
|
||||
**Attended**: Spencer Powell
|
||||
|
||||
**DATE**: 2025-04-02
|
||||
|
||||
***
|
||||
|
||||
## Sprint 4 Review
|
||||
|
||||
### SRS Sections Updated
|
||||
|
||||
None
|
||||
|
||||
### User Story
|
||||
|
||||
None
|
||||
|
||||
### Sprint Requirements Attempted
|
||||
|
||||
Frontend visualization and graph node creation
|
||||
|
||||
### Completed Requirements
|
||||
|
||||
Frontend visualization and graph node creation
|
||||
|
||||
### The summary of the entire project
|
||||
|
||||
Very close to being usable and giving answers.
|
||||
|
||||
***
|
||||
|
||||
## Sprint 5 Planning
|
||||
|
||||
## Requirements Flex
|
||||
|
||||
3/5 requirement flexes remaining
|
||||
|
||||
### Requirement Target
|
||||
|
||||
Client and server will be hooked up to make client use information from server and things being brought up via docker-compose
|
||||
|
||||
### Planning
|
||||
|
||||
We are going to meet out of class in person to get this done
|
||||
|
||||
### Issues and Risks
|
||||
|
||||
Unintended/undocumented desync from the api specification
|
|
@ -1,25 +0,0 @@
|
|||
|
||||
# Client Meeting Notes
|
||||
|
||||
## Sprint Notes
|
||||
|
||||
## Requirements Attempted in this Sprint
|
||||
|
||||
- Routing is based on start and stop location
|
||||
|
||||
## Requirements complete
|
||||
|
||||
- Routing is based on start and stop location
|
||||
|
||||
## Requirements incomplete
|
||||
|
||||
## Requirement Flex Remaining
|
||||
|
||||
<< 3 / 5 >>
|
||||
|
||||
# Milestone Status
|
||||
|
||||
Pass
|
||||
|
||||
## Requested Changes
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
# Sprint Meeting Notes
|
||||
|
||||
*note: replace anything surrounded by << >> and **remove** the << >>*
|
||||
|
||||
**Attended**: Spencer Powell
|
||||
|
||||
**DATE**: 04-16-2025
|
||||
|
||||
***
|
||||
|
||||
## Sprint 5 Review
|
||||
|
||||
### SRS Sections Updated
|
||||
|
||||
### Sprint Requirements Attempted
|
||||
|
||||
Integrate client and server
|
||||
|
||||
### Completed Requirements
|
||||
|
||||
Integrate client and server
|
||||
|
||||
### The summary of the entire project
|
||||
|
||||
The project is nearing completion, all that needs to be done is the addition of graph edges to the graph database
|
||||
|
||||
***
|
||||
|
||||
## Sprint 6 Planning
|
||||
|
||||
## Requirements Flex
|
||||
|
||||
3/5 requirement flexes remaining
|
||||
|
||||
## Technical Debt
|
||||
|
||||
Need to fix a bug in the client that causes it to not display info fetched from server correctly
|
||||
|
||||
### Requirement Target
|
||||
|
||||
Setup edges in graph
|
||||
|
||||
### User Stories
|
||||
|
||||
Baseline user story
|
||||
|
||||
### Planning
|
||||
|
||||
We'll probably use a technical flex but otherwise the plan would be for us to both work on setups which add data into the graph database
|
||||
|
||||
### Action Items
|
||||
|
||||
Edges need to be added to graph and graph db needs to be used to find a valid graph path for a given query
|
||||
|
||||
### Issues and Risks
|
||||
|
||||
This will probably be the most obnoxious part of the project for various reasons
|
|
@ -1,71 +0,0 @@
|
|||
# Sprint Meeting Notes
|
||||
|
||||
*note: replace anything surrounded by << >> and **remove** the << >>*
|
||||
|
||||
**Attended**: << record the team members in attendance (virtual counts as long as they are participating) >>
|
||||
|
||||
**DATE**: << meeting date >>
|
||||
|
||||
***
|
||||
|
||||
## Sprint << num >> Review
|
||||
|
||||
### SRS Sections Updated
|
||||
|
||||
<< List any SRS sections that were updated in the last sprint >>
|
||||
|
||||
### User Story
|
||||
|
||||
<< Corresponding User Stories completed in this sprint >>
|
||||
|
||||
### Sprint Requirements Attempted
|
||||
|
||||
<< The corresponding SRS requirement that the team completed in the last sprint >>
|
||||
|
||||
### Completed Requirements
|
||||
|
||||
<< The work that's been completed in this sprint >>
|
||||
|
||||
### Incomplete Requirements
|
||||
|
||||
<< The work that has not been completed in this sprint. Be VERY detailed and specific regarding what isn't working and what needs to be completed >>
|
||||
|
||||
### The summary of the entire project
|
||||
|
||||
<< A general overview of the entire project >>
|
||||
|
||||
***
|
||||
|
||||
## Sprint << num >> Planning
|
||||
|
||||
## Requirements Flex
|
||||
|
||||
<< # >>/5 requirement flexes remaining
|
||||
|
||||
## Technical Debt
|
||||
|
||||
<< Any requirements from the previous sprint that are using a technical flex >>
|
||||
|
||||
### Requirement Target
|
||||
|
||||
<< The corresponding SRS requirement that our team will be complete in next sprint >>
|
||||
|
||||
### User Stories
|
||||
|
||||
<< Corresponding User Stories >>
|
||||
|
||||
### Planning
|
||||
|
||||
<< Our team's detailed plan to complete the sprint >>
|
||||
|
||||
### Action Items
|
||||
|
||||
<< A list of things that need to happen in our for our team to complete the sprint >>
|
||||
|
||||
### Issues and Risks
|
||||
|
||||
<< A list of potential obstacles that could keep us from completing the sprint and what's being done about them >>
|
||||
|
||||
### Team Work Assignments
|
||||
|
||||
<< A list of each team member and their works assignments for this sprint >>
|
3
src/.gitignore
vendored
3
src/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
|||
*.env
|
||||
pubspec.lock
|
||||
.dart_tool
|
13
src/Justfile
13
src/Justfile
|
@ -1,13 +0,0 @@
|
|||
setup:
|
||||
# this is a hack that is done due to a lack of communication
|
||||
# towards the end of this project
|
||||
# a slightly better hack could be done which avoids this but I
|
||||
# don't wanna write things to work that way
|
||||
cp -r client shared/client-dir
|
||||
cp -r shared client/shared-dir
|
||||
cp -r server/data shared/server-data
|
||||
cp pubspec.yaml client/root-pubspec.yaml
|
||||
cp pubspec.yaml shared/root-pubspec.yaml
|
||||
|
||||
clean:
|
||||
rm -rf client/root-pubspec.yaml shared/root-pubspec.yaml shared/client-dir client/shared-dir shared/server-data
|
|
@ -1,52 +0,0 @@
|
|||
# This file configures the analyzer, which statically analyzes Dart code to
|
||||
# check for errors, warnings, and lints. See the following for docs:
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
#
|
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||
# invoked from the command line by running `flutter analyze`.
|
||||
include: package:very_good_analysis/analysis_options.yaml # has more lints
|
||||
|
||||
analyzer:
|
||||
language:
|
||||
# Strict casts isn't helpful with null safety. It only notifies you on `dynamic`,
|
||||
# which happens all the time in JSON.
|
||||
#
|
||||
# See https://github.com/dart-lang/language/blob/main/resources/type-system/strict-casts.md
|
||||
strict-casts: false
|
||||
|
||||
# Don't let any types be inferred as `dynamic`.
|
||||
#
|
||||
# See https://github.com/dart-lang/language/blob/main/resources/type-system/strict-inference.md
|
||||
strict-inference: true
|
||||
|
||||
# Don't let Dart infer the wrong type on the left side of an assignment.
|
||||
#
|
||||
# See https://github.com/dart-lang/language/blob/main/resources/type-system/strict-raw-types.md
|
||||
strict-raw-types: true
|
||||
|
||||
exclude:
|
||||
- lib/generated/**.dart
|
||||
- test/**.dart
|
||||
- example/**.dart
|
||||
|
||||
linter:
|
||||
rules:
|
||||
# Rules NOT in package:very_good_analysis
|
||||
prefer_double_quotes: true
|
||||
prefer_expression_function_bodies: true
|
||||
|
||||
# Rules to be disabled from package:very_good_analysis
|
||||
prefer_single_quotes: false # prefer_double_quotes
|
||||
lines_longer_than_80_chars: false # lines should be at most 100 chars
|
||||
sort_pub_dependencies: false # Sort dependencies by function
|
||||
use_key_in_widget_constructors: false # not in Flutter apps
|
||||
directives_ordering: false # sort dart, then flutter, then package imports
|
||||
always_use_package_imports: false # not when importing sibling files
|
||||
sort_constructors_first: false # final properties, then constructor
|
||||
avoid_dynamic_calls: false # this lint takes over errors in the IDE
|
||||
one_member_abstracts: false # abstract classes are good for interfaces
|
||||
cascade_invocations: false # cascades are often harder to read
|
||||
|
||||
# Temporarily disabled until we are ready to document
|
||||
public_member_api_docs: false
|
101
src/api_spec.md
101
src/api_spec.md
|
@ -1,101 +0,0 @@
|
|||
# API spec for communicating between the frontend and the backend
|
||||
|
||||
## List of routes
|
||||
- `/trips/`
|
||||
- `/trips/{id}`
|
||||
- `/stops/{id}`
|
||||
- `/path?start_lat={latitude}&start_lon={longitude}&end_lat={latitude}&end_lon={longitude}&time={unix_epoch}`
|
||||
|
||||
|
||||
## `/trips`
|
||||
being a json list of urls to each of all the trips over the course of the week, probably won't be used for core functionality but it'll be trivial to implement so might as well
|
||||
|
||||
## `/trips/{id}`
|
||||
json object describing a route, I'm planning on this having an infinite cache time, if a route changes a new id will be made for the route after the change
|
||||
```yaml
|
||||
{
|
||||
"_links":{
|
||||
"self":{
|
||||
"href":"https://$base_domain/trips/{id}"
|
||||
},
|
||||
# stops in order of when the bus reaches them
|
||||
"stops": [{
|
||||
"href":"https://$base_domain/stops/{id}",
|
||||
"latitude":{latitude},
|
||||
"longitude":{longitude},
|
||||
"times":[
|
||||
{"dotw": "Mo, Tu, We, Th, Fr, Sa or Su", "time": "HH:MM"},
|
||||
]
|
||||
},
|
||||
...
|
||||
]
|
||||
},
|
||||
"name": "name of the route",
|
||||
"abbr": "shortened name of the route or null",
|
||||
"polyline": "whatever the route's polyline is",
|
||||
}
|
||||
```
|
||||
|
||||
## `GET /stops`
|
||||
|
||||
```yaml
|
||||
[
|
||||
{
|
||||
"name": {name},
|
||||
"longitude": {longitude},
|
||||
"latitude": {latitude},
|
||||
# "provider": {occt|bc},
|
||||
"routeID":
|
||||
}
|
||||
]
|
||||
|
||||
## `/stops/{id}`
|
||||
|
||||
```yaml
|
||||
{
|
||||
"_links":{
|
||||
"self":{
|
||||
"href":"https://$base_domain/stops/{id}"
|
||||
},
|
||||
# trips that make use of the stop in arbitrary order
|
||||
"trips": [
|
||||
{
|
||||
"href":"https://$base_domain/trips/{id}",
|
||||
}
|
||||
]
|
||||
}
|
||||
"latitude": {latitude},
|
||||
"longitude": {longitude}
|
||||
}
|
||||
```
|
||||
|
||||
`/path?start_lat={latitude}&start_lon={longitude}&end_lat={latitude}&end_lon={longitude}&time={unix_epoch}`
|
||||
```yaml
|
||||
{
|
||||
"_links":{
|
||||
"self":{
|
||||
"href": "https://$base_domain/path?start_lat={latitude}&start_lon={longitude}&end_lat={latitude}&end_lon={longitude}&time={unix_epoch}"
|
||||
}
|
||||
"path":[
|
||||
{
|
||||
"trip":{
|
||||
"href":"https://$base_domain/trips/{id}",
|
||||
"polyline": "{polyline}"
|
||||
},
|
||||
"enter_bus":{
|
||||
"href":"https://$base_domain/stops/{id}",
|
||||
"latitude": # {latitude},
|
||||
"longitude": # {longitude},
|
||||
"time": "same format as above",
|
||||
},
|
||||
"exit_bus":{
|
||||
"href":"https://$base_domain/stops/{id}",
|
||||
"latitude": # {latitude},
|
||||
"longitude": # {longitude},
|
||||
"time": "same format as above",
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
```
|
|
@ -1 +0,0 @@
|
|||
GOOGLE_MAPS_API_KEY=
|
|
@ -1,2 +0,0 @@
|
|||
Dockerfile
|
||||
.dockerignore
|
|
@ -1 +0,0 @@
|
|||
GOOGLE_MAPS_API_KEY=
|
50
src/client/.gitignore
vendored
50
src/client/.gitignore
vendored
|
@ -1,50 +0,0 @@
|
|||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.build/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
.swiftpm/
|
||||
migrate_working_dir/
|
||||
pubspec.lock
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
.env
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
|
||||
shared-dir
|
||||
root-pubspec.yaml
|
|
@ -1,33 +0,0 @@
|
|||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "3e493a3e4d0a5c99fa7da51faae354e95a9a1abe"
|
||||
channel: "beta"
|
||||
|
||||
project_type: app
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: 3e493a3e4d0a5c99fa7da51faae354e95a9a1abe
|
||||
base_revision: 3e493a3e4d0a5c99fa7da51faae354e95a9a1abe
|
||||
- platform: web
|
||||
create_revision: 3e493a3e4d0a5c99fa7da51faae354e95a9a1abe
|
||||
base_revision: 3e493a3e4d0a5c99fa7da51faae354e95a9a1abe
|
||||
- platform: windows
|
||||
create_revision: 3e493a3e4d0a5c99fa7da51faae354e95a9a1abe
|
||||
base_revision: 3e493a3e4d0a5c99fa7da51faae354e95a9a1abe
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
|
@ -1,35 +0,0 @@
|
|||
FROM alpine:3.21
|
||||
|
||||
RUN mkdir /client
|
||||
|
||||
RUN apk add bash curl file git unzip which zip gcompat wget tar xz
|
||||
|
||||
#ENTRYPOINT bash
|
||||
|
||||
WORKDIR /client
|
||||
|
||||
RUN wget -O flutter.tar.xz https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.29.0-stable.tar.xz
|
||||
|
||||
RUN tar xf flutter.tar.xz
|
||||
|
||||
ENV PATH="/client/flutter/bin/:${PATH}"
|
||||
|
||||
RUN git config --global --add safe.directory /client/flutter
|
||||
|
||||
RUN flutter upgrade
|
||||
|
||||
RUN flutter doctor
|
||||
|
||||
COPY root-pubspec.yaml /client/pubspec.yaml
|
||||
COPY ./shared-dir /client/shared
|
||||
COPY ./ /client/client
|
||||
|
||||
WORKDIR /client/client
|
||||
|
||||
RUN flutter build web --dart-define-from-file=.env
|
||||
|
||||
WORKDIR /client/client/build/web
|
||||
|
||||
EXPOSE 80/tcp
|
||||
|
||||
CMD ["dart", "run", "dhttpd", "--host", "0.0.0.0", "--port", "80"]
|
|
@ -1,16 +0,0 @@
|
|||
# client
|
||||
|
||||
A new Flutter project.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a starting point for a Flutter application.
|
||||
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
|
||||
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
||||
|
||||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
|
@ -1,132 +0,0 @@
|
|||
// Entrypoints can print
|
||||
// ignore_for_file: avoid_print
|
||||
|
||||
import "dart:convert";
|
||||
import "dart:io";
|
||||
import "package:http/http.dart";
|
||||
import "package:html/parser.dart";
|
||||
|
||||
typedef Json = Map<String, dynamic>;
|
||||
|
||||
extension <E> on List<E> {
|
||||
Iterable<(int, E)> enumerate() sync* {
|
||||
for (var i = 0; i < length; i++) {
|
||||
yield (i, this[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final spotClient = Client();
|
||||
|
||||
class OcctStop {
|
||||
static final Map<int, OcctStop> stopsById = {};
|
||||
static final Map<String, OcctStop> stopsByName = {};
|
||||
|
||||
static Future<void> readStops() async {
|
||||
final file = File("data/stops.json");
|
||||
final json = jsonDecode(await file.readAsString()) as List;
|
||||
for (final stopJson in json.cast<Json>()) {
|
||||
final name = stopJson["name"] as String;
|
||||
final id = stopJson["id"] as int;
|
||||
final routeId = stopJson["rid"] as int;
|
||||
var stop = stopsById[id];
|
||||
if (stop == null) {
|
||||
stop = OcctStop(id, name);
|
||||
stopsById[id] = stop;
|
||||
stopsByName[name] = stop;
|
||||
}
|
||||
stop.routes.add(routeId);
|
||||
}
|
||||
}
|
||||
|
||||
final String name;
|
||||
final int id;
|
||||
final List<int> routes = [];
|
||||
OcctStop(this.id, this.name);
|
||||
}
|
||||
|
||||
class OcctRoute {
|
||||
final int id;
|
||||
final String name;
|
||||
final String abbr;
|
||||
final List<OcctRouteStop> stops = [];
|
||||
final String encodedLine;
|
||||
final String hexColor;
|
||||
|
||||
OcctRoute.fromJson(Json json) :
|
||||
id = json["id"],
|
||||
name = json["name"],
|
||||
abbr = json["abbr"],
|
||||
encodedLine = json["encLine"],
|
||||
hexColor = json["color"];
|
||||
}
|
||||
|
||||
class OcctRouteStop {
|
||||
final int id;
|
||||
final String name;
|
||||
final List<String> times = [];
|
||||
|
||||
OcctRouteStop(this.id, this.name);
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
"route_id": "int",
|
||||
"stops": [
|
||||
{
|
||||
"stop_id": "int",
|
||||
"stop_name": "string",
|
||||
"times": [ "HH:MM PM" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
*/
|
||||
|
||||
String getStopName(String text) {
|
||||
final regex = RegExp("(?:Returns to|Arrives at|Leaves)? ?(?:the )?(.+)");
|
||||
const stopNameSubstitutions = {
|
||||
"Binghamton University": "University Union",
|
||||
"Innovative Tech Complex": "Innovative Technology Center",
|
||||
"UClub / University Plaza": "UCLUB",
|
||||
};
|
||||
print("Trying to find stop name in [$text]");
|
||||
final match = regex.firstMatch(text)!;
|
||||
final name = match.group(1)!;
|
||||
return stopNameSubstitutions[name] ?? name;
|
||||
}
|
||||
|
||||
void main() async {
|
||||
await OcctStop.readStops();
|
||||
final uri = Uri.parse("https://occtransport.org/pages/routes/iu.html");
|
||||
final response = await get(uri);
|
||||
if (response.statusCode != 200) {
|
||||
print("Got non-200 status: ");
|
||||
print(response.body);
|
||||
return;
|
||||
}
|
||||
final html = parse(response.body);
|
||||
final table = html.getElementsByTagName("tbody").first;
|
||||
print(table.children.first.innerHtml);
|
||||
final numStops = table.children.first.children.length;
|
||||
print("There are $numStops stops");
|
||||
|
||||
final stopsHtml = html.getElementsByClassName("sch-notes").first;
|
||||
final routeStops = <OcctRouteStop>[];
|
||||
for (final stopHtml in stopsHtml.children) {
|
||||
final sourceName = stopHtml.text.substring(2); // eg, 1 Leaves Mohawk
|
||||
final name = getStopName(sourceName);
|
||||
final id = OcctStop.stopsByName[name]?.id;
|
||||
if (id == null) {
|
||||
print("Could not find a stop with the name: $name");
|
||||
return;
|
||||
}
|
||||
routeStops.add(OcctRouteStop(id, name));
|
||||
}
|
||||
for (final row in table.children) {
|
||||
for (final (index, timeHtml) in row.children.enumerate()) {
|
||||
final time = timeHtml.innerHtml;
|
||||
routeStops[index].times.add(time);
|
||||
}
|
||||
}
|
||||
print(routeStops);
|
||||
}
|
|
@ -1,442 +0,0 @@
|
|||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Westside Outbound",
|
||||
"abbr": "WS O",
|
||||
"stops": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
132,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21
|
||||
],
|
||||
"vType": "Bus",
|
||||
"encLine": "wb{_GhitnMBAFh@LZ@nCO~CIfA??IpAUjBM|@AVSbA_@`B??e@|Ao@rA}@lAm@f@??o@PSCy@?aAAy@O??eAYi@W??WWkA|Be@v@??eBjBy@l@s@b@??YNuBv@gB`@??aCd@SRUb@iAO??s@?}DNBwA??D{CTcAPk@V_@??`CeDRi@Fc@?YA_@EUM]W_@OKe@M??cIS??sHU??mCOsC]??mD}@iA_@??yCkAyB_A??iH{C??mGoCUY??oAm@g@[g@k@u@iA??e@q@Q]GYCWAe@B]NWBg@CUWMMaA??i@iGI]E{@??m@wK??s@kL??u@kL??u@mL??QoCo@qG??]gD[_E??s@qJ??YkDUqA??Qw@cB{F??w@eB]a@kAmA??_CiB~@iC??rA_EdAgC??`E}I??`EcJ??`DgH^u@??fAwBdCuF??nD_I??rCmG??nBkE??nBiE??pBmE??pBkE??rBkE??xByE??nAuC`@sA??tAqG??^aBTuAXyB??dAeJ??`A_J??n@cGDwB??FwK??@aAlCf@PFTA??XGPIV[Re@F[L{A~B^????|Dv@",
|
||||
"color": "#191970",
|
||||
"type": "INBOUND",
|
||||
"order": 1,
|
||||
"showDirection": false,
|
||||
"showPlatform": false,
|
||||
"showScheduleNumber": 1,
|
||||
"useCustomShape": 0,
|
||||
"showVehicleCapacity": true
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Westside Inbound",
|
||||
"abbr": "WS I",
|
||||
"stops": [
|
||||
42,
|
||||
43,
|
||||
44,
|
||||
45,
|
||||
46,
|
||||
47,
|
||||
48,
|
||||
49,
|
||||
50,
|
||||
51,
|
||||
52,
|
||||
53,
|
||||
54,
|
||||
55,
|
||||
56,
|
||||
57,
|
||||
58,
|
||||
59,
|
||||
60,
|
||||
32,
|
||||
118,
|
||||
1,
|
||||
61
|
||||
],
|
||||
"vType": "Bus",
|
||||
"encLine": "qv|_G`_jnMA?iDq@_@S}B_@??y@OSFM`BSn@Y^OJ[J??]LmCg@AxA??IrMCb@]xC??UdCiBhP??a@nDg@~C_BjH??k@~Bs@fB{@jB??wE`K??uE~J??uEbK??uEdK??uEbK??w@fBgBlD{@nB??wEfK??wEhK??gB|D_C|G????i@zA??A@zBdBh@h@bAhAVf@??f@lAzAjF\\zA??RlBfAxN@?TbDnA`M??pA|S??pA~S??~@bP?r@??DxAR|C@|@OHCHCLA\\D\\PJFR??Fp@HzAD`@Lp@Zn@X`@XRl@T~Ab@??j@RRBhGhC??pInD??vFfC|Aj@??d@PfBVfBRrBN??~CLzA?`BH@?|IV??d@BTJPNLRL`@Db@?RGf@Sd@ST??QHa@FYCWOSWOc@CU?a@???]EUNcJ??J{K??BcAPo@DIRMXGjAH??dAH~ATpBd@??p@R|@f@jBxA??NTxAjEFh@??^|Cf@|Bb@`B??BXHTp@xAv@|@~@l@??r@Xh@JfAF|@A??XANERKn@Q^WRU??v@gAXg@^_A^uA??h@_CDY@WTaB??LgARyCJaCB]ZOHIBa@?{@???qAGeB????g@DC@Ij@Cd@",
|
||||
"color": "#191970",
|
||||
"type": "INBOUND",
|
||||
"order": 2,
|
||||
"showDirection": false,
|
||||
"showPlatform": false,
|
||||
"showScheduleNumber": 1,
|
||||
"useCustomShape": 0,
|
||||
"showVehicleCapacity": true
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Downtown Center Leroy Outbound",
|
||||
"abbr": "DCL O",
|
||||
"stops": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
33,
|
||||
34,
|
||||
35,
|
||||
36,
|
||||
37,
|
||||
38,
|
||||
39,
|
||||
40,
|
||||
41,
|
||||
42
|
||||
],
|
||||
"vType": "Bus",
|
||||
"encLine": "sb{_GlitnM??Fb@LZ@nCO~CMhB??[zCM|@AVq@|C??]pA_@~@Yf@_ApA??k@b@o@PSCy@?aAAo@K??oA]i@Y??WUkA|Bg@z@??cBfBy@l@mAr@a@P??sAd@mAXuCj@??YTUb@o@Ka@Ek@@{BH??aADHsFTcAJ[??DOT]bCgDL[FUDk@??Ai@Kc@O[OSOKSIQC{BE??gJY??eFQ{@G}AS??q@ImAYcBc@{Ag@??gCcA{DaB??{IwD??yCqASWqAo@q@e@??i@q@oAkBWk@EYA[Bq@NWXKr@Mb@OTM??ROd@e@nBsCL]h@q@??lFqG??vCsDlAeB??Zo@hCsDr@kA??vEwH??l@eARe@H[L}@ZiD??z@sL??~@eM??LmAVcBtAgG??d@mBVw@pCmG??fFmL??r@gBTo@tAkG?AlAoFfAkE??dD}K@?pBuIVkA??fAqFd@oD??^kDsA?kBL??{EX??iDRKcE??SuJ??SuJ??SwJ??SmK???cA^{I??`@eL????L{C~A`@??lJ|B??nAZc@uI??e@qJ??k@qJ??e@yH??E]@YM@u@IiFcA??GAIIi@K",
|
||||
"color": "#D2042D",
|
||||
"type": "INBOUND",
|
||||
"order": 3,
|
||||
"showDirection": false,
|
||||
"showPlatform": false,
|
||||
"showScheduleNumber": 1,
|
||||
"useCustomShape": 0,
|
||||
"showVehicleCapacity": true
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Downtown Center Leroy Inbound",
|
||||
"abbr": "DCL I",
|
||||
"stops": [
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30,
|
||||
31,
|
||||
32,
|
||||
118,
|
||||
61,
|
||||
1
|
||||
],
|
||||
"vType": "Bus",
|
||||
"encLine": "iy|_Gz~inM?A??xB`@H@JClCf@??bB\\t@HJVTpC??`AxP??RrDOFIBS?{EkA??gCo@QxA??_AtIG|@??i@pN??a@bK?z@??TxL??VhM??TnK??PfI??D`B|CQ??~DU??rCSj@???f@?c@`E]hC??Y|AsAlG??qArFk@zB??qCdJ??gAjEcApE??_BjHUn@a@bA??{D~I??_E|I??g@xAoBnI??YxAS|Am@hI??eAzN??g@|FGd@Kh@KXq@pA??uErH??}@xAqBtC??W\\a@x@_CzC??oEtF??gD`Ec@`@??yBfC]Z]R??c@RQ@UCYI??[QY?WLGV?j@BNPJDNJ`A??FnAL`AXt@`@j@??XTXJbD`A??NBjEhB??rElB??~EtB??dF|B??pBr@|B\\@?nALjCRX@??nFJ??tGT??`BBhAFJDZTLRL`@D^??Cj@Kf@U^[Pa@FYCa@YIMKY??G_@?_AEUNaJ??L{M??Ru@DIRMXG|CT??rAR|@RdBd@b@T??p@`@rAhANTf@tA??`@rAN`@V`CL|@??h@dCb@`BBXb@hA??Vd@dAhAj@^n@V`@J??xAJl@?\\A??ZGRKn@Q^Wn@u@??Zg@n@sATq@f@oB??ZaB@WL}@TkBNwB??RaETK??NMBa@?eD??IoA??@Bk@DIj@CZ",
|
||||
"color": "#D2042D",
|
||||
"type": "INBOUND",
|
||||
"order": 4,
|
||||
"showDirection": false,
|
||||
"showPlatform": false,
|
||||
"showScheduleNumber": 1,
|
||||
"useCustomShape": 0,
|
||||
"showVehicleCapacity": true
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"name": "Campus Shuttle",
|
||||
"abbr": "CS",
|
||||
"stops": [
|
||||
61,
|
||||
117,
|
||||
62,
|
||||
63,
|
||||
64,
|
||||
65,
|
||||
110,
|
||||
111,
|
||||
112,
|
||||
113,
|
||||
114,
|
||||
115,
|
||||
116,
|
||||
1
|
||||
],
|
||||
"vType": "Bus",
|
||||
"encLine": "{a{_GdptnM?u@??A_A??AgA??CiA?AAe@Ee@??GkA??KgA??MaAQ{@??g@kBy@uB??aAqBm@y@??MKg@Ye@IS?A?UBe@Pg@ZGL??[f@Sj@M`A??IhB?jB??LtD??@LgAl@WNY\\??_@j@s@vA??e@v@{@x@??GFI@g@Tg@L??a@JWLWDMLEJEZ??B^LP`AtC??Vt@????V`CMj@??I`C??IdCEn@??WxB?jA??J`Av@tB??FRbBgB??f@{@t@{A?ADEx@j@n@V??^JtAJ??p@???RpHA?nAE??nCO??dCU??pAGr@M??bC]??bC]??bC]H?AhASjB??WfAYv@o@pA??s@~@cAdA??cBdB??q@r@]j@??q@CG@]zB?h@`@?@I??TqAV{@??b@w@`AcA??|B_C??|@eAj@cA??Rc@Xw@XoA?AJaAFw@AqAEg@??Gm@Mq@Qk@g@oAKShB{C??|BgE??v@eBp@mB??HW}@o@?A?@YWCAe@zAABG^BfB??Dl@DL??HN}@`B??wAhC??sAxBg@n@??}@|@{@n@??gCnA??u@\\[Jo@L??m@D_AE??a@Gk@SQOO[??e@cBl@g@j@s@??j@aATk@f@aB??j@gCDY@WNeA??RcBTeD??LiC",
|
||||
"color": "#228B22",
|
||||
"type": "Linear",
|
||||
"order": 5,
|
||||
"showDirection": false,
|
||||
"showPlatform": false,
|
||||
"showScheduleNumber": 0,
|
||||
"useCustomShape": true,
|
||||
"showVehicleCapacity": true
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"name": "ITC - UCLUB",
|
||||
"abbr": "IU",
|
||||
"stops": [
|
||||
61,
|
||||
63,
|
||||
64,
|
||||
65,
|
||||
109,
|
||||
66,
|
||||
67,
|
||||
68,
|
||||
134,
|
||||
69
|
||||
],
|
||||
"vType": "Bus",
|
||||
"encLine": "ub{_GfbtnMYkBQaAe@gBe@qA??a@aAs@sAs@_Ag@[??]KOAU?UBe@Pg@Zc@t@Qd@??OdAIjB?lBJrC??Bl@gAl@c@\\m@z@e@|@??m@jAWXq@l@I@g@Te@L??c@JWLWDy@OkA]??uAe@uBi@A?{B_@wBK??OAe@_@M[Kq@J}F??P{L??FqE|CM??hBKh@Gb@Q^U??`A{@??e@qAUa@o@m@??QWEQ?a@n@yARoA??\\cCJBD_@??f@uD??BMa@D??[Be@C?B???Cc@ESBMH??ITSpA??CV@VFLLH??bAZ\\HRAGb@In@?AWfBK^??k@pA?N???PDPHNRR??b@`@Zl@??^dAu@n@??k@`@c@PYD??wBL??sAF??kADBqB??HqF??F}ANkC??PiBTaB??t@qE??v@mE??x@oE??x@mE??t@cE??t@eE??`@aCn@R??jA\\T@??`AZ??O~@??e@nC??g@xC??e@lC??QhATJ?AbC|@??lBr@??fA`@d@H??lC`@??lC\\`AX??zCjAXXBN??RbAFbAEx@o@bI??CVxE[??tDWdAOj@O??d@KzCcAn@O??h@Gv@@zBH??lBDr@AF|A??NfDmCR??[JKJUd@G\\?nADjCAt@??IjB]zBa@tAi@nA??e@x@]b@wAtAs@h@??y@f@y@\\o@R_@H??WBy@BaAQIB[T??UPPxA@H?DHpAH|@??????DdB??@bB??@rA@FXMHIBa@?}@???oAGcB??c@B??G@I}@Cw@??AW",
|
||||
"color": "#CF9FFF",
|
||||
"type": "INBOUND",
|
||||
"order": 6,
|
||||
"showDirection": false,
|
||||
"showPlatform": false,
|
||||
"showScheduleNumber": 1,
|
||||
"useCustomShape": 0,
|
||||
"showVehicleCapacity": true
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "UCLUB",
|
||||
"abbr": "UC",
|
||||
"stops": [
|
||||
61,
|
||||
63,
|
||||
64,
|
||||
65,
|
||||
66,
|
||||
67,
|
||||
68,
|
||||
69,
|
||||
134,
|
||||
1
|
||||
],
|
||||
"vType": "Bus",
|
||||
"encLine": "yb{_GxatnMS_BWkA??a@{Ae@oA??a@cAcAgB??c@k@GEg@Yi@I??a@@UFUJg@Z??c@t@Ut@MdAGrB???tAJlC??Br@{@b@c@XUVY`@??}AxC?AWZq@l@I@]N??}Ad@??MFWDg@Iy@S??mAc@??kBi@??cAWu@K??y@MiAG??}@E_@YIKGO??Mw@FaD??HyF??HwF??HyF??HwF??DoCJkB??NsBViC??T_Bl@gD??fAcG??bAqF??bAsF??`AsF?@b@kCn@R??jA\\N?~@X??FB_@|BC???B?m@jD??m@nD??YfBTH??lE`B??jBp@rAT??hDd@??bALXFpAd@??pBv@XXJ`@A?Lp@FbAQ`C??a@nF??Eb@~DY??rEY??`AOpA[??tCaA??~@S^Ev@@h@B??~DJr@AHlB??LvC}BNWDSHKJ??Ud@G\\?nADjCAjAItA[pB??c@~Ae@hAi@~@gAnAm@h@w@l@??qAr@mA`@g@Ji@D_@?a@G??[GI?u@f@RzAH`BH|@M`A",
|
||||
"color": "#BF40BF",
|
||||
"type": "INBOUND",
|
||||
"order": 7,
|
||||
"showDirection": false,
|
||||
"showPlatform": false,
|
||||
"showScheduleNumber": 1,
|
||||
"useCustomShape": 0,
|
||||
"showVehicleCapacity": true
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"name": "University Downtown Center Outbound",
|
||||
"abbr": "UDC O",
|
||||
"stops": [
|
||||
1,
|
||||
21,
|
||||
42
|
||||
],
|
||||
"vType": "Bus",
|
||||
"encLine": "sb{_GnitnMF`@LZ@nCO~CSxCUjBM|@AVIb@??y@lDUp@o@rA}@lAWVa@Tc@J??SCy@?aAAc@G{Aa@g@WYWi@m@e@o@e@aAQMg@mB??c@qBc@mDCY@e@G_BS{AD]G[??MSOIWDy@Ok@OuBs@{Aa@kAU??kAQgCMe@_@M[Kq@HcE??ZgU??NiKPaD`@_E??TgBdCeN??zCoPtAeI??xBkMn@oEX{A??dAsFh@cCd@kBzAcF??l@aBdEuK??bGsOn@yB??fAkEl@mDT}ANuA\\iE??|A{Z??d@iJ@oAC}AC_AU_De@wC??o@eCkHaT??w@{Bq@yBk@aCc@qC]qCk@{D??mDuV??e@wCWoBSo@Wa@GG_@Qa@EY@kFp@??qDb@cCVI@eAISIQO[e@Oq@??Cm@Hw@Vs@d@a@F@b@E`@D\\P^l@??FPHf@t@pL??RfEG~@ERQb@y@d@w@O@?cCc@IIs@M",
|
||||
"color": "#FF7F50",
|
||||
"type": "INBOUND",
|
||||
"order": 8,
|
||||
"showDirection": false,
|
||||
"showPlatform": false,
|
||||
"showScheduleNumber": 1,
|
||||
"useCustomShape": 0,
|
||||
"showVehicleCapacity": true
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"name": "University Downtown Center Inbound",
|
||||
"abbr": "UDC I",
|
||||
"stops": [
|
||||
21,
|
||||
32,
|
||||
118,
|
||||
1,
|
||||
61
|
||||
],
|
||||
"vType": "Bus",
|
||||
"encLine": "qx|_G~~inMD@??zAX??AyE??E}D@u@???AfAe@n@Ov@MN@~AS??xGw@??~Ca@\\BVJPNLN??N^Jl@bAlH??xA`K??hB`M@?x@bGh@tCNl@??XdA`DfJ??dEzL??l@jB^`Bb@bDRxC??B|BCvA_@hI??{@nP??c@vIa@~E??_@vCe@nCa@fBy@|C??w@dCgEzK??kHdRoAhE??q@hC{@~DcAtFcA|G??UzAsBrK_B~I??eF~Y??a@dCQvA]|DMpCSlI??Y|X??G|El@AlBLpAJl@H??nB^dBd@|@f@`At@??h@b@NTxAjEV`CRrA??b@nBb@`BFb@^`AVb@v@|@`An@??f@R^JzAJl@?h@CNERKb@KJE`@Y??hA{AXg@^_Ah@sBXoAJk@@WD[??\\mCRyCNyC?EZOHIBa@AgA??@eAG_B?Ck@D??Ij@Cd@",
|
||||
"color": "#FF7F50",
|
||||
"type": "INBOUND",
|
||||
"order": 9,
|
||||
"showDirection": false,
|
||||
"showPlatform": false,
|
||||
"showScheduleNumber": 1,
|
||||
"useCustomShape": 0,
|
||||
"showVehicleCapacity": true
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"name": "Main Street Outbound",
|
||||
"abbr": "MS O",
|
||||
"stops": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
70,
|
||||
71,
|
||||
72,
|
||||
73,
|
||||
74,
|
||||
75,
|
||||
76,
|
||||
77,
|
||||
155,
|
||||
78,
|
||||
12,
|
||||
132,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
21
|
||||
],
|
||||
"vType": "Bus",
|
||||
"encLine": "sb{_GfitnMFh@LZ@nCO~C[`E??[`CAVEX}@vDk@|Ak@`A??y@bA_@VUHYFSCy@?}@A??cAQs@Qa@Q[S??OOkA|Bg@z@WV??kAnAy@l@mAr@cA^??q@VmAXuCj@YTMV??GJo@Ka@Ek@@sBH??iADHsFRaA??\\}@~AwB??n@}@Ri@Fc@?YCg@??I[O[OS[QYGoBE??mGQ??uHW??qAIoC]_B_@??qA]aA[aE_B??eJ{D??kH_DSWo@YAJeAg@q@e@{@w@gAkAeA_A??m@]o@YeAW{@IeA?_@D??{@Ny@Vm@VsDjB??mBbAkB|@o@RyAX??yADqD?KK[GUKSQ??SYKUMk@UuBK]Wg@[Ya@S[Ei@F??}ARIi@Ay@EmH??GaP??EgFBY?e@OmJ??KaFtGYlHa@OmCIaCIsD?kBCkCcEsCr@oCNSb@yA??|@eDd@{AiD@gCJoFR}DT}C`@s@mBa@gAi@uBOmAAuB?y@f@WXFxEvCz@b@a@|BMz@Ct@FnABXpBKbCIxCMdHQh@AFSjBkG??lCsJ`@}AAMd@}A??x@qCrCiIt@iB??lI_R??fFgLlByD??rImR??zIsR??zIqR?@pCeGr@gBV}@hAoF??h@}BTuA\\mC~AuNJB??KCrA_MDy@@sE????FcIjCd@??RHL?`@IVMLOJUJWHc@??JsAvATxB`@??|AZ",
|
||||
"color": "#6495ED",
|
||||
"type": "INBOUND",
|
||||
"order": 10,
|
||||
"showDirection": false,
|
||||
"showPlatform": false,
|
||||
"showScheduleNumber": 1,
|
||||
"useCustomShape": 0,
|
||||
"showVehicleCapacity": true
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"name": "Main Street Inbound",
|
||||
"abbr": "MS I",
|
||||
"stops": [
|
||||
42,
|
||||
43,
|
||||
44,
|
||||
45,
|
||||
46,
|
||||
47,
|
||||
48,
|
||||
49,
|
||||
50,
|
||||
51,
|
||||
79,
|
||||
155,
|
||||
80,
|
||||
81,
|
||||
82,
|
||||
83,
|
||||
84,
|
||||
85,
|
||||
32,
|
||||
61,
|
||||
1
|
||||
],
|
||||
"vType": "Bus",
|
||||
"encLine": "wv|_G~~inMyCk@??KC_@SwB_@A?}@OSFKtAI`@Uf@UV??IDg@NQHmB]??_@IItL???`BEx@c@vDM~A??sAhLGAF@y@vH]zB?A}BpK??W~@Wr@eBvD??yCtG??{CtG??yCtG??mAjC??uAxC?@gDnH??gDpH??gDpH??uAnCuA|C??gDpH??gDrH??gDnH??]|@_C~G??{@jCw@lCGDW~@??wCjK??oBjH{IRuH^iD`@y@Hg@qAk@{Ac@}AQ{@IcAAmA@kA\\WNGd@RtD`Cd@Vh@TShA]dBAn@BjAHp@zEYtGU`FIs@vB??sA|EGZs@jC?AAHfEjC?T??@pCDhD??RzH??@t@}DP??yFX??}BJIF?fA??DjFJ~F??@x@FXFtH??DzL??@dGDxBKl@?H??BzCTlG??DlA@`GdA@??lBAf@QLIVYP]Po@VsA??TcAZq@RSRMTGZC??XCNGpCE@?t@Ij@KbA]\\Q??`EuB??hAk@lCqA??`@Op@Ql@IfAA|@F??t@LRFhAj@f@\\t@t@??rBrB\\V|Av@??r@\\NLfFvB??dHvC??rErB??pB|@lBr@??THfBVxAP??xCTjAB??vCD~AH??pFN??tCJTJPNLRJX??BTBh@Gf@GRU^MHYJUBOA??SGWSS_@Ig@AiA??CKPwK??JaL??@IPo@DIRMXGjCR??dBT|@RdBf@??|@d@nA`A??ZVNT~@nC??Xz@V`CXhB??`AzDFb@^|@??Vf@v@|@x@j@~@\\??b@HfAFl@?h@CNERK??n@Q^Wx@cAh@}@??Vo@Tq@\\uA^aBDY@WL}@??TkBRyCLqC??@M????`@UDM@U???oCCeAC]_@BK@M~@?Z",
|
||||
"color": "#6495ED",
|
||||
"type": "INBOUND",
|
||||
"order": 11,
|
||||
"showDirection": false,
|
||||
"showPlatform": false,
|
||||
"showScheduleNumber": 1,
|
||||
"useCustomShape": 0,
|
||||
"showVehicleCapacity": true
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"name": "Riviera Ridge - Town Square Mall",
|
||||
"abbr": "RRT",
|
||||
"stops": [
|
||||
118,
|
||||
2,
|
||||
3,
|
||||
119,
|
||||
120,
|
||||
121,
|
||||
122,
|
||||
123,
|
||||
124,
|
||||
125,
|
||||
126,
|
||||
127,
|
||||
128,
|
||||
32,
|
||||
61,
|
||||
1
|
||||
],
|
||||
"vType": "Bus",
|
||||
"encLine": "me{_GbysnMX|@d@vBRzAFxA??JdAFvC@nCC^??K~BSxCUjBM|@AV??e@zBi@rB_@~@Yf@m@x@??]b@_@Vo@PSCy@?aAAq@M??aAUa@Q_@UKMeBbD??MTcBfBy@l@mAr@??uBv@mAXsB`@??g@JSRUb@o@Ka@Ek@@iBF??sAF_@?MjM??EzBe@rL??q@nM??q@vM??KfDGvE??CnI??CfI??EhN?AFvFLrF??D`AXD~BInGa@??rJg@??rCOBbB??VzHPjI??NbJRhD??ZrEyEZ??UBoAZiF~B??}GzC??yC|AwA~@??{AlANbC??bA`P??H~AD^f@fA??F\\m@\\SN??CBFj@Bf@Mf@Kf@EXJX??FZ\\|@??t@fB??RXFDf@GNCIsA??OaC??OcC??MsB??C_@bAO??~AK??|AO??dAKLAMuB??OaC??QgC??OgC??QeC??OgC??OeC?AQcC??OoB??OuB??UkC??U{C??W{C??U{C??U{C??W{C?@U{C??W{C??U{C??Q}C?@MyC??GqC??AmC??CmC??@mC??@oC??BmC???mC??@kC???_B??@qB??@oB??@}B??DaC?@F}B@?A?J_C??JaC??L_C??L_C??J_C??L_C??LaC??L_C??J_C??H_C??J}B??F_C??F}B??D_C??BaC??BmB`AE??jBIA?|@Ah@F??f@HTc@XU??fB]??bB_@??~Ag@??l@Wh@[??b@Wl@c@??jAmA??b@c@j@aA??v@}Ax@j@z@Z??f@Jj@D??h@Br@C??TANERK^I@?LG^Wn@u@??Zg@l@oA??Vu@\\sA?A^aBDg@??VmB??RyB??NwB??JsB??@Y`@UDM@U???cC??EyAAU[BM@A?????Ij@Cf@",
|
||||
"color": "#E37383",
|
||||
"type": "INBOUND",
|
||||
"order": 12,
|
||||
"showDirection": false,
|
||||
"showPlatform": false,
|
||||
"showScheduleNumber": 1,
|
||||
"useCustomShape": 0,
|
||||
"showVehicleCapacity": true
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"name": "Oakdale Commons",
|
||||
"abbr": "OC",
|
||||
"stops": [
|
||||
118,
|
||||
2,
|
||||
3,
|
||||
129,
|
||||
130,
|
||||
32,
|
||||
61,
|
||||
1
|
||||
],
|
||||
"vType": "Bus",
|
||||
"encLine": "me{_GdysnMBFf@jBR~@NhAHz@Bv@H|@FpD??@tBO~CSxCUjB??M|@AVEX}@vDk@|AYf@k@x@??_@b@_@Vo@PSCy@?aAAgAS??mAa@_@UKMkA|Bc@r@??gBnBy@l@mAr@QH??cBl@mAXoB^??k@LSRUb@o@Ka@E{BF??mBHDuB??B}BTcAZ{@rBqC??Zc@L[FUDu@Ek@Og@W_@OKSIQCe@A??{FO??kGQ??{CM{@GsAQ??{@KmAYcBc@yAg@??iCcA{DaB??gIkD??mD}AqCqA??k@a@mDkDm@c@??kAk@eAW{@IeA???aANs@NmAd@qAp@??sGdD??g@To@Ru@Pw@HsAB??uG@??}DGyA???_CAyCDg@BA?iBHsARqAXo@R??q@TyB|@eClA??cHnD??kD`BuBz@??uExB]JYDG^EpA??EtB@tBA`E???hAaBB??Y@Ut@Uf@]n@??UZWXu@f@e@Py@NeER?O???qCEc@??ASEEe@???iBBED??CNCbA??B~B???DfDCz@E??hCMx@OJE??XKt@g@VYT[??\\o@Tg@Ts@??zBGNFv@@??N?r@RRBlAE??n@EE}B??M{G??G_CSa@WZ??y@dAKTEVB~@??J~F??DvBSCs@S??WOWUISKu@@mA??FkF??D}ATi@??d@w@xAo@??pCyA??vCwA??vCuA??vCuA??tCwA??lCoA??~@a@zAg@??x@Q~AS??pAKdCC??|DA??fE???dE???dE???z@Al@EfAU??|@YxBgA??nDkB??|BiAt@Y??l@Ol@IfAAn@D??bANZJ`Af@t@h@??zC|Cj@^??dBx@\\RNLnAh@??jDvA??xD`B??xD`B??bEhB??p@ZpBr@TD??pAPvBTA?|BPdBD??|BBvAH??dFN??~ABhAFTJRP??Tf@DXBh@Gf@Sd@WVYJ_@@??OAWOSWIQIg@?_AEU??HeF??HeF??DgF????DoCPo@DIRMXG\\B??rBNjAP??|Bf@`AZ??t@`@bBpA??V\\\\~@j@hBN`@BX??b@lDd@tB??d@hBBXHTb@bAV`@?Al@r@x@j@x@ZTFn@D??j@Dl@?h@Cl@U??d@MTOVW|@kA??n@uAf@cB??j@eCDY@WF]??ZkCLiB??ToE??ZOHIBa@?a@???kBCw@?@Cm@k@D????Ij@Ch@",
|
||||
"color": "#FFC000",
|
||||
"type": "INBOUND",
|
||||
"order": 13,
|
||||
"showDirection": false,
|
||||
"showPlatform": false,
|
||||
"showScheduleNumber": 1,
|
||||
"useCustomShape": 0,
|
||||
"showVehicleCapacity": true
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"name": "Downtown Southside Outbound",
|
||||
"abbr": "DSO",
|
||||
"stops": [
|
||||
136,
|
||||
137,
|
||||
1,
|
||||
138,
|
||||
139,
|
||||
140,
|
||||
141,
|
||||
142,
|
||||
143,
|
||||
144,
|
||||
145,
|
||||
146,
|
||||
147,
|
||||
148,
|
||||
154,
|
||||
150,
|
||||
151,
|
||||
153,
|
||||
21
|
||||
],
|
||||
"vType": "Bus",
|
||||
"encLine": "ub{_GbitnM@?@VDTLZ?X@tBQtD??QbCUjBM|@AVEX}@vDk@|AYj@A?{@hAWVa@Tc@JSCy@?aAA_AQ_AWA?e@WYW??i@m@e@o@e@aAQMc@aBg@}BYyB??MmA@e@G_B??QuA@M@UAQMWKI??IEWDg@I??}@U}@[??_Cs@{@SoBYgCMe@_@M[??Kq@JmF??ToP??R{M??DsATgDZuC^gC??xBuL??fAeG??pAeHfC{N?AfAsGNgA??^gCz@sE??`@yB@A\\[^gA??n@kB??r@wBVo@j@iA??|BmE??`BkE??|BmG??fCaH??t@sBbB{F??xBsH??V}@\\gBLmAJoB?@TuD`@cE??|A_Q?A`@mEj@gF??V}C?@HsBMyEKaAUgAiAkD??mB{Go@qB??mE{M??gBsF?@}@qC??EOwCK??aDEuAG??[Gi@Sg@_@Y[Sc@Qc@gAsE??gCyL??i@uCk@eF??a@qEUoG??]mNK_C??mAuJqAkG??UmB_AaN??WiDuAcL??_AqH??Ky@mBp@??uFrB??uFrB??yGbC??eBr@s@`@}BzA??uEbDOH[F??e@LsEx@?rC@hBh@lFd@xEb@hCn@`D`@bCf@tCj@pCThAz@bF??TbB??BPEf@PpAjAnG??R`AJHNt@??ZfBb@jG??Dn@vAX??nB^??lB\\@?n@DzE???z@ATNO|B??IrA??CXR@jBZ??hDn@@?j@L",
|
||||
"color": "#CECECE",
|
||||
"type": "INBOUND",
|
||||
"order": 15,
|
||||
"showDirection": false,
|
||||
"showPlatform": false,
|
||||
"showScheduleNumber": 1,
|
||||
"useCustomShape": 0,
|
||||
"showVehicleCapacity": true
|
||||
}
|
||||
]
|
File diff suppressed because it is too large
Load diff
|
@ -1,6 +0,0 @@
|
|||
export "src/data/utils.dart";
|
||||
export "src/data/trip.dart";
|
||||
export "src/data/path.dart";
|
||||
export "src/data/polyline.dart";
|
||||
|
||||
export "package:shared/data.dart";
|
|
@ -1,27 +0,0 @@
|
|||
import "package:flutter/material.dart";
|
||||
|
||||
import "package:client/models.dart";
|
||||
import "package:client/pages.dart";
|
||||
import "package:client/services.dart";
|
||||
|
||||
Future<void> main() async {
|
||||
await services.init();
|
||||
await models.init();
|
||||
await models.initFromOthers();
|
||||
runApp(const ClientApp());
|
||||
}
|
||||
|
||||
/// The main app widget.
|
||||
class ClientApp extends StatelessWidget {
|
||||
/// A const constructor.
|
||||
const ClientApp();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => MaterialApp.router(
|
||||
title: "Flutter Demo",
|
||||
theme: ThemeData(
|
||||
useMaterial3: true,
|
||||
),
|
||||
routerConfig: router,
|
||||
);
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
import "src/models/model.dart";
|
||||
|
||||
export "src/models/model.dart";
|
||||
|
||||
/// A [DataModel] to manage all other data models.
|
||||
class Models extends DataModel {
|
||||
/// Prevents other instances of this class from being created.
|
||||
Models._();
|
||||
|
||||
// List your models here
|
||||
|
||||
/// A list of all models to manage.
|
||||
List<DataModel> get models => [];
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
for (final model in models) {
|
||||
await model.init();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> initFromOthers() async {
|
||||
for (final model in models) {
|
||||
await model.initFromOthers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The global data model singleton.
|
||||
final models = Models._();
|
|
@ -1,22 +0,0 @@
|
|||
import "package:go_router/go_router.dart";
|
||||
export "package:go_router/go_router.dart";
|
||||
|
||||
import "src/pages/home.dart";
|
||||
|
||||
/// Contains all the routes for this app.
|
||||
class Routes {
|
||||
/// The route for the home page.
|
||||
static const home = "/";
|
||||
}
|
||||
|
||||
/// The router for the app.
|
||||
final router = GoRouter(
|
||||
initialLocation: Routes.home,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: Routes.home,
|
||||
name: Routes.home,
|
||||
builder: (_, __) => HomePage(),
|
||||
),
|
||||
],
|
||||
);
|
|
@ -1,31 +0,0 @@
|
|||
/// Defines and manages the different services used by the app.
|
||||
library;
|
||||
|
||||
|
||||
import "src/services/api.dart";
|
||||
import "src/services/service.dart";
|
||||
import "src/services/maps.dart";
|
||||
|
||||
/// A [Service] that manages all other services used by the app.
|
||||
class Services extends Service {
|
||||
/// Prevents other instances of this class from being created.
|
||||
Services._();
|
||||
|
||||
// Define your services here
|
||||
final api = ApiService();
|
||||
|
||||
final maps = GoogleMapsService();
|
||||
|
||||
/// The different services to initialize, in this order.
|
||||
List<Service> get services => [api, maps];
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
for (final service in services) {
|
||||
await service.init();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The global services object.
|
||||
final services = Services._();
|
|
@ -1,15 +0,0 @@
|
|||
import "trip.dart";
|
||||
import "package:shared/data.dart";
|
||||
|
||||
class PathStep {
|
||||
final PathTrip trip;
|
||||
final TripStop enter;
|
||||
final TripStop exit;
|
||||
|
||||
PathStep.fromJson(Json json) :
|
||||
trip = PathTrip.fromJson(json["trip"]),
|
||||
enter = TripStop.fromJson(json["enter_bus"]),
|
||||
exit = TripStop.fromJson(json["exit_bus"]);
|
||||
}
|
||||
|
||||
typedef Path = List<PathStep>;
|
|
@ -1,45 +0,0 @@
|
|||
import "package:fixnum/fixnum.dart";
|
||||
import "package:google_maps_flutter/google_maps_flutter.dart";
|
||||
|
||||
List<LatLng> decodePolyline(String polyline) {
|
||||
final points = <LatLng>[]; // List to hold the decoded coordinates
|
||||
var index = 0;
|
||||
final len = polyline.length;
|
||||
var lat = Int32();
|
||||
var lng = Int32();
|
||||
|
||||
// While there are still characters to process in the polyline
|
||||
while (index < len) {
|
||||
int b;
|
||||
var shift = 0;
|
||||
var result = Int32();
|
||||
final oneF = Int32(0x1F);
|
||||
|
||||
// Decode latitude
|
||||
do {
|
||||
b = polyline.codeUnitAt(index++) - 63; // Get the next character
|
||||
result |= (Int32(b) & oneF) << shift; // Update the result
|
||||
shift += 5; // Increase the shift
|
||||
} while (b >= 0x20); // Continue until a character less than 0x20 is found
|
||||
|
||||
final dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1)); // Decode the latitude delta
|
||||
lat = (lat + dlat) as Int32; // Update the latitude
|
||||
|
||||
shift = 0; // Reset shift for longitude decoding
|
||||
result = Int32(); // Reset result for longitude decoding
|
||||
|
||||
// Decode longitude
|
||||
do {
|
||||
b = polyline.codeUnitAt(index++) - 63; // Get the next character
|
||||
result |= (Int32(b) & oneF) << shift; // Update the result
|
||||
shift += 5; // Increase the shift
|
||||
} while (b >= 0x20); // Continue until a character less than 0x20 is found
|
||||
|
||||
final dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1)); // Decode the longitude delta
|
||||
lng = (lng + dlng) as Int32; // Update the longitude
|
||||
|
||||
// Add the new LatLng poInt32 to the list of points
|
||||
points.add(LatLng(lat.toInt() / 1E5, lng.toInt() / 1E5));
|
||||
}
|
||||
return points;
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
import "package:shared/data.dart";
|
||||
|
||||
import "package:flutter/material.dart" show TimeOfDay;
|
||||
|
||||
extension type TripID(String id) { }
|
||||
|
||||
class Trip {
|
||||
final List<TripStop> stops;
|
||||
final String path;
|
||||
final String name;
|
||||
final String abbrev;
|
||||
final String polyline;
|
||||
|
||||
const Trip({
|
||||
required this.stops,
|
||||
required this.path,
|
||||
required this.name,
|
||||
required this.abbrev,
|
||||
required this.polyline,
|
||||
});
|
||||
|
||||
Trip.fromJson(Json json) :
|
||||
stops = [
|
||||
for (final stop in json["stops"])
|
||||
TripStop.fromJson(stop),
|
||||
],
|
||||
path = json["path"],
|
||||
name = json["name"],
|
||||
abbrev = json["abbrev"],
|
||||
polyline = json["polyline"];
|
||||
}
|
||||
|
||||
class TripStop {
|
||||
final double lat;
|
||||
final double long;
|
||||
// final List<ScheduledTime> times;
|
||||
// For testing, use this:
|
||||
final String time;
|
||||
|
||||
const TripStop({
|
||||
required this.lat,
|
||||
required this.long,
|
||||
// required this.times,
|
||||
required this.time,
|
||||
});
|
||||
|
||||
TripStop.fromJson(Json json) :
|
||||
lat = json["latitude"],
|
||||
long = json["longitude"],
|
||||
time = json["time"];
|
||||
// times = [
|
||||
// for (final time in json["times"])
|
||||
// ScheduledTime.fromJson(time),
|
||||
// ];
|
||||
}
|
||||
|
||||
class ScheduledTime {
|
||||
static const weekdays = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
|
||||
|
||||
final String weekday;
|
||||
final TimeOfDay timeOfDay;
|
||||
|
||||
const ScheduledTime({
|
||||
required this.weekday,
|
||||
required this.timeOfDay,
|
||||
});
|
||||
|
||||
factory ScheduledTime.fromJson(Json json) {
|
||||
final weekday = json["dotw"];
|
||||
final [hour, minute] = (json["time"] as String).split(":");
|
||||
final timeOfDay = TimeOfDay(hour: int.parse(hour), minute: int.parse(minute));
|
||||
return ScheduledTime(weekday: weekday, timeOfDay: timeOfDay);
|
||||
}
|
||||
}
|
||||
|
||||
class PathTrip {
|
||||
final String polyline;
|
||||
PathTrip.fromJson(Json json) :
|
||||
polyline = json["polyline"];
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
import "package:google_maps_flutter/google_maps_flutter.dart";
|
||||
|
||||
import "package:shared/data.dart";
|
||||
|
||||
extension CoordinatesUtils on Coordinates {
|
||||
LatLng toLatLng() => LatLng(lat, long);
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
import "package:flutter/foundation.dart";
|
||||
|
||||
/// A model containing data needed throughout the app.
|
||||
///
|
||||
/// This model may need to be initialized, so [init] should be called before using it. This model
|
||||
/// should also be held as a singleton in some global scope.
|
||||
abstract class DataModel with ChangeNotifier {
|
||||
/// Loads any data needed by the model.
|
||||
///
|
||||
/// This function must not depend on any other model.
|
||||
Future<void> init();
|
||||
|
||||
/// Loads any data from other models.
|
||||
///
|
||||
/// At this point, all models have run [init], so it is safe to use other models.
|
||||
Future<void> initFromOthers() async { }
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
import "package:client/data.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:google_maps_flutter/google_maps_flutter.dart";
|
||||
|
||||
import "package:client/view_models.dart";
|
||||
import "package:client/widgets.dart";
|
||||
|
||||
/// The home page.
|
||||
class HomePage extends ReactiveWidget<HomeModel> {
|
||||
@override
|
||||
HomeModel createModel() => HomeModel();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, HomeModel model) => Scaffold(
|
||||
appBar: AppBar(title: const Text("Counter")),
|
||||
body: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 325,
|
||||
child: Sidebar(model),
|
||||
),
|
||||
Expanded(
|
||||
child: Card(
|
||||
child: Column(
|
||||
children: [
|
||||
SwitchListTile(
|
||||
value: model.markerState == MarkerState.override,
|
||||
onChanged: model.overrideMarkers,
|
||||
title: const Text("Show stops list"),
|
||||
subtitle: const Text("To select a start or end stop, use the buttons below"),
|
||||
),
|
||||
LatLongEditor(
|
||||
latitudeController: model.startLatitudeController,
|
||||
longitudeController: model.startLongitudeController,
|
||||
label: "Start at",
|
||||
isShowingMarkers: model.markerState == MarkerState.start,
|
||||
showMarkers: () => model.showMarkers(MarkerState.start),
|
||||
hideMarkers: model.hideMarkers,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
LatLongEditor(
|
||||
latitudeController: model.endLatitudeController,
|
||||
longitudeController: model.endLongitudeController,
|
||||
label: "End at",
|
||||
isShowingMarkers: model.markerState == MarkerState.end,
|
||||
showMarkers: () => model.showMarkers(MarkerState.end),
|
||||
hideMarkers: model.hideMarkers,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
FilledButton(onPressed: model.search, child: const Text("Search for a path")),
|
||||
const SizedBox(height: 8),
|
||||
const Divider(),
|
||||
const SizedBox(height: 8),
|
||||
if (model.isLoading)
|
||||
const LinearProgressIndicator()
|
||||
else if (model.isSearching && model.path == null)
|
||||
const Text("Could not connect to API")
|
||||
else
|
||||
for (final step in model.path ?? <PathStep>[]) Text(
|
||||
"Get on at ${step.enter.lat}, ${step.enter.long}\n"
|
||||
"Get off at ${step.exit.lat}, ${step.exit.long}",
|
||||
),
|
||||
Expanded(
|
||||
child: model.isGoogleReady
|
||||
? GoogleMap(
|
||||
onMapCreated: (controller) => model.mapController = controller,
|
||||
initialCameraPosition: const CameraPosition(
|
||||
target: LatLng(42.10125081757972, -75.94181323552698),
|
||||
zoom: 14,
|
||||
),
|
||||
markers: model.markers,
|
||||
onTap: model.onMapTapped,
|
||||
polylines: {
|
||||
for (final (index, route) in model.paths.indexed) Polyline(
|
||||
polylineId: PolylineId(index.toString()),
|
||||
color: routeColors[index],
|
||||
points: route,
|
||||
),
|
||||
},
|
||||
)
|
||||
: const Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const routeColors = [Colors.blue, Colors.red, Colors.green, Colors.yellow, Colors.orange];
|
|
@ -1,53 +0,0 @@
|
|||
import "package:flutter/foundation.dart" show debugPrint, kDebugMode;
|
||||
|
||||
import "package:client/data.dart";
|
||||
import "package:http/http.dart" as http;
|
||||
|
||||
import "api_client.dart";
|
||||
import "service.dart";
|
||||
|
||||
class ApiService extends Service {
|
||||
static const usingDocker = !kDebugMode;
|
||||
final _client = ApiClient();
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
debugPrint("Running with Docker? $usingDocker");
|
||||
}
|
||||
|
||||
Uri get _base => usingDocker
|
||||
? Uri(path: "api/")
|
||||
: Uri(scheme: "http", host: "localhost", port: 8001);
|
||||
|
||||
Future<String?> getPath({
|
||||
required Coordinates start,
|
||||
required Coordinates end,
|
||||
}) async {
|
||||
final uri = Uri.parse("/tmp-api/path").replace(
|
||||
queryParameters: {
|
||||
"start_lat": start.lat.toString(),
|
||||
"start_lon": start.long.toString(),
|
||||
"end_lat": end.lat.toString(),
|
||||
"end_lon": end.long.toString(),
|
||||
},
|
||||
);
|
||||
try {
|
||||
final response = await http.get(uri);
|
||||
return response.body;
|
||||
// Any error should show null
|
||||
// ignore: avoid_catches_without_on_clauses
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<Route>?> getRoutes() => _client.getJsonList(
|
||||
_base.resolve("/routes"),
|
||||
Route.fromJson,
|
||||
);
|
||||
|
||||
Future<List<Stop>?> getStops() => _client.getJsonList(
|
||||
_base.resolve("/stops"),
|
||||
Stop.fromJson,
|
||||
);
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
import "dart:convert";
|
||||
|
||||
import "package:client/data.dart";
|
||||
import "package:flutter/foundation.dart";
|
||||
import "package:http/http.dart" as http;
|
||||
|
||||
class ApiClient {
|
||||
final client = http.Client();
|
||||
|
||||
Future<List<T>?> getJsonList<T>(Uri uri, FromJson<T> fromJson, {String? key}) async {
|
||||
try {
|
||||
final response = await client.get(uri);
|
||||
if (response.statusCode != 200) return null;
|
||||
// No key: Assume the entire body is a list
|
||||
// With key: Assume the body is a map with a list at the key
|
||||
final listOfJson = key == null
|
||||
? (jsonDecode(response.body) as List).cast<Json>()
|
||||
: ((jsonDecode(response.body) as Json)[key] as List).cast<Json>();
|
||||
return [
|
||||
for (final json in listOfJson)
|
||||
fromJson(json),
|
||||
];
|
||||
// Want to catch all errors for the UI
|
||||
// ignore: avoid_catches_without_on_clauses
|
||||
} catch (error, stackTrace) {
|
||||
debugPrint("Error: $error\n$stackTrace");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
import "package:web/web.dart";
|
||||
import "service.dart";
|
||||
|
||||
class GoogleMapsService extends Service {
|
||||
@override
|
||||
Future<void> init() async {
|
||||
const key = String.fromEnvironment("GOOGLE_MAPS_API_KEY");
|
||||
final script = document.createElement("script") as HTMLScriptElement;
|
||||
script.src = "https://maps.googleapis.com/maps/api/js?key=$key";
|
||||
document.head?.appendChild(script);
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
/// Defines a service: a singleton object to manage a resource.
|
||||
abstract class Service {
|
||||
/// Initializes any resources needed by the service.
|
||||
///
|
||||
/// This is guaranteed to be called before any other methods.
|
||||
Future<void> init();
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
import "dart:async";
|
||||
|
||||
import "package:client/data.dart";
|
||||
import "package:client/services.dart";
|
||||
import "package:flutter/widgets.dart" hide Path;
|
||||
|
||||
import "package:google_maps_flutter/google_maps_flutter.dart";
|
||||
|
||||
import "home_markers.dart";
|
||||
import "view_model.dart";
|
||||
|
||||
/// The view model for the home page.
|
||||
class HomeModel extends ViewModel with HomeMarkers {
|
||||
final startLatitudeController = TextEditingController();
|
||||
final startLongitudeController = TextEditingController();
|
||||
final endLatitudeController = TextEditingController();
|
||||
final endLongitudeController = TextEditingController();
|
||||
|
||||
Path? path;
|
||||
GoogleMapController? mapController;
|
||||
|
||||
double? get startLatitude => double.tryParse(startLatitudeController.text);
|
||||
double? get startLongitude => double.tryParse(startLongitudeController.text);
|
||||
double? get endLatitude => double.tryParse(endLatitudeController.text);
|
||||
double? get endLongitude => double.tryParse(endLongitudeController.text);
|
||||
|
||||
bool isSearching = false;
|
||||
bool isGoogleReady = false;
|
||||
List<List<LatLng>> paths = [];
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
await Future<void>.delayed(const Duration(seconds: 2));
|
||||
isGoogleReady = true;
|
||||
notifyListeners();
|
||||
await updateMarkers();
|
||||
}
|
||||
|
||||
void onMapTapped(LatLng coordinates) {
|
||||
if (!shouldShowMarkers) return;
|
||||
final marker = Marker(
|
||||
markerId: markerId ?? const MarkerId("Tapped"),
|
||||
position: coordinates,
|
||||
icon: markerIcon ?? BitmapDescriptor.defaultMarker,
|
||||
infoWindow: InfoWindow(title: "$startOrEnd here"),
|
||||
);
|
||||
switch (markerState!) {
|
||||
case MarkerState.start: updateStart(coordinates, marker);
|
||||
case MarkerState.end: updateEnd(coordinates, marker);
|
||||
case MarkerState.override: return;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void updateStart(LatLng coordinates, Marker marker) {
|
||||
startLatitudeController.text = coordinates.latitude.toString();
|
||||
startLongitudeController.text = coordinates.longitude.toString();
|
||||
super.updateStart(coordinates, marker);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateEnd(LatLng coordinates, Marker marker) {
|
||||
endLatitudeController.text = coordinates.latitude.toString();
|
||||
endLongitudeController.text = coordinates.longitude.toString();
|
||||
super.updateEnd(coordinates, marker);
|
||||
}
|
||||
|
||||
String? pathText;
|
||||
Future<void> search() async {
|
||||
final start = (lat: startLatitude, long: startLongitude);
|
||||
final end = (lat: endLatitude, long: endLongitude);
|
||||
if (start.lat == null || start.long == null) return;
|
||||
if (end.lat == null || end.long == null) return;
|
||||
isSearching = true;
|
||||
isLoading = true;
|
||||
final result = await services.api.getPath(start: start as Coordinates, end: end as Coordinates);
|
||||
pathText = result ?? "An error occurred";
|
||||
isLoading = false;
|
||||
if (result == null) return;
|
||||
isSearching = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
|
@ -1,166 +0,0 @@
|
|||
import "package:client/data.dart";
|
||||
import "package:client/services.dart";
|
||||
import "package:flutter/foundation.dart";
|
||||
import "package:google_maps_flutter/google_maps_flutter.dart";
|
||||
|
||||
enum MarkerState {
|
||||
start,
|
||||
end,
|
||||
override;
|
||||
}
|
||||
|
||||
mixin HomeMarkers on ChangeNotifier {
|
||||
MarkerState? markerState;
|
||||
bool get shouldShowMarkers => markerState != null;
|
||||
|
||||
// List<Stop>? stops;
|
||||
Map<StopID, Stop> stops = {};
|
||||
Map<RouteID, Route> routes = {};
|
||||
Marker? _startMarker;
|
||||
Marker? _endMarker;
|
||||
|
||||
Iterable<Stop> getStopsForRoute(RouteID routeID) => routes[routeID]!.stops
|
||||
.map((stopID) => stops[stopID]!);
|
||||
|
||||
Set<Marker> get _filteredMarkers => {
|
||||
// for (final stop in stops ?? <Stop>[])
|
||||
for (final routeID in routesToShow)
|
||||
for (final stop in getStopsForRoute(routeID))
|
||||
Marker(
|
||||
markerId: MarkerId(stop.name),
|
||||
position: stop.coordinates.toLatLng(),
|
||||
onTap: () => onMarkerTapped(stop),
|
||||
consumeTapEvents: true,
|
||||
infoWindow: InfoWindow(
|
||||
title: stop.name,
|
||||
snippet: stop.summary,
|
||||
),
|
||||
),
|
||||
};
|
||||
|
||||
Set<Marker> get markers => shouldShowMarkers ? _filteredMarkers : {
|
||||
if (_startMarker != null) _startMarker!,
|
||||
if (_endMarker != null) _endMarker!,
|
||||
};
|
||||
|
||||
List<Route> bcRouteNames = [];
|
||||
List<Route> occtRouteNames = [];
|
||||
Set<RouteID> routesToShow = {};
|
||||
Iterable<(String, List<Route>)> get providers => [
|
||||
("OCCT", occtRouteNames),
|
||||
("BC Transit", bcRouteNames),
|
||||
];
|
||||
|
||||
int _parseBcNumber(String routeName) {
|
||||
// eg, "53)" --> 53
|
||||
final first = routeName.split(" ").first;
|
||||
final withoutParen = first.substring(0, first.length - 1);
|
||||
return int.parse(withoutParen);
|
||||
}
|
||||
|
||||
int compareBcRoutes(Route a, Route b) =>
|
||||
_parseBcNumber(a.shortName).compareTo(_parseBcNumber(b.shortName));
|
||||
|
||||
int compareOcctRoutes(Route a, Route b) =>
|
||||
a.shortName.compareTo(b.shortName);
|
||||
|
||||
MarkerId? get markerId => switch (markerState!) {
|
||||
MarkerState.start => const MarkerId("start"),
|
||||
MarkerState.end => const MarkerId("end"),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
BitmapDescriptor? get markerIcon => switch (markerState!) {
|
||||
MarkerState.start => BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueCyan),
|
||||
MarkerState.end => BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueGreen),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
String? get startOrEnd => switch (markerState!) {
|
||||
MarkerState.start => "Start",
|
||||
MarkerState.end => "End",
|
||||
_ => null,
|
||||
};
|
||||
|
||||
Future<void> updateMarkers() async {
|
||||
final stopsResponse = await services.api.getStops();
|
||||
if (stopsResponse == null) return;
|
||||
stops = {
|
||||
for (final stop in stopsResponse)
|
||||
stop.id: stop,
|
||||
};
|
||||
final routesResponse = await services.api.getRoutes();
|
||||
if (routesResponse == null) return;
|
||||
routes = {
|
||||
for (final route in routesResponse)
|
||||
route.id: route,
|
||||
};
|
||||
for (final route in routesResponse) {
|
||||
final namesList = switch (route.provider) {
|
||||
Provider.occt => occtRouteNames,
|
||||
Provider.bc => bcRouteNames,
|
||||
};
|
||||
namesList.add(route);
|
||||
}
|
||||
occtRouteNames.sort(compareOcctRoutes);
|
||||
bcRouteNames.sort(compareBcRoutes);
|
||||
}
|
||||
|
||||
void showMarkers(MarkerState newState) {
|
||||
markerState = newState;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void hideMarkers() {
|
||||
markerState = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void onMarkerTapped(Stop stop) {
|
||||
final coordinates = stop.coordinates.toLatLng();
|
||||
final marker = Marker(
|
||||
markerId: markerId ?? MarkerId(stop.name),
|
||||
position: coordinates,
|
||||
infoWindow: InfoWindow(
|
||||
title: "$startOrEnd at ${stop.name}",
|
||||
snippet: stop.description,
|
||||
),
|
||||
icon: markerIcon ?? BitmapDescriptor.defaultMarker,
|
||||
);
|
||||
switch (markerState!) {
|
||||
case MarkerState.start: updateStart(coordinates, marker);
|
||||
case MarkerState.end: updateEnd(coordinates, marker);
|
||||
case MarkerState.override: return;
|
||||
}
|
||||
}
|
||||
|
||||
// Flutter widget
|
||||
// ignore: avoid_positional_boolean_parameters
|
||||
void overrideMarkers(bool value) {
|
||||
markerState = value ? MarkerState.override : null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@mustCallSuper
|
||||
void updateStart(LatLng coordinates, Marker marker) {
|
||||
_startMarker = marker;
|
||||
markerState = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@mustCallSuper
|
||||
void updateEnd(LatLng coordinates, Marker marker) {
|
||||
_endMarker = marker;
|
||||
markerState = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void showRoute(Route route, {required bool shouldShow}) {
|
||||
if (shouldShow) {
|
||||
routesToShow.add(route.id);
|
||||
} else {
|
||||
routesToShow.remove(route.id);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
import "package:flutter/foundation.dart";
|
||||
|
||||
/// A model to load and manage state needed by any piece of UI.
|
||||
///
|
||||
/// [init] is called right away it is *not* awaited. Use [isLoading] and
|
||||
/// [errorText] to convey progress to the user. This allows the UI to load immediately.
|
||||
abstract class ViewModel with ChangeNotifier {
|
||||
/// Calls [init] right away but does not await it.
|
||||
ViewModel() {
|
||||
init();
|
||||
}
|
||||
|
||||
/// Override this method to initializes any data needed by the model.
|
||||
Future<void> init() async {}
|
||||
|
||||
/// Whether this model is currently loading data. Setting this updates the UI.
|
||||
bool _isLoading = false;
|
||||
bool get isLoading => _isLoading;
|
||||
set isLoading(bool value) {
|
||||
_isLoading = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Whether this model has encountered an error. Setting this updates the UI.
|
||||
String? _errorText;
|
||||
String? get errorText => _errorText;
|
||||
set errorText(String? value) {
|
||||
_errorText = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Wether this model is still attached to a widget.
|
||||
bool _isMounted = true;
|
||||
|
||||
@override
|
||||
void notifyListeners() {
|
||||
if (_isMounted) super.notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_isMounted = false;
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// A model to build a value from the UI.
|
||||
abstract class BuilderModel<T> extends ViewModel {
|
||||
/// The value being edited.
|
||||
T get value;
|
||||
|
||||
/// Whether the [value] is ready to be accessed.
|
||||
bool get isReady;
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
import "package:client/widgets.dart";
|
||||
import "package:flutter/material.dart";
|
||||
|
||||
class LatLongEditor extends StatelessWidget {
|
||||
final TextEditingController latitudeController;
|
||||
final TextEditingController longitudeController;
|
||||
final String label;
|
||||
final VoidCallback showMarkers;
|
||||
final VoidCallback hideMarkers;
|
||||
final bool isShowingMarkers;
|
||||
|
||||
const LatLongEditor({
|
||||
required this.latitudeController,
|
||||
required this.longitudeController,
|
||||
required this.label,
|
||||
required this.isShowingMarkers,
|
||||
required this.showMarkers,
|
||||
required this.hideMarkers,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => SizedBox(
|
||||
width: 400,
|
||||
child: Row(
|
||||
children: [
|
||||
Text(label, style: context.textTheme.titleSmall),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: latitudeController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: "Latitude",
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: longitudeController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: "Longitude",
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
if (isShowingMarkers) TextButton(
|
||||
onPressed: hideMarkers,
|
||||
child: const Text("Cancel"),
|
||||
) else TextButton.icon(
|
||||
icon: const Icon(Icons.location_on),
|
||||
onPressed: showMarkers,
|
||||
label: const Text("Pick on map"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
import "package:flutter/material.dart";
|
||||
|
||||
abstract class ReactiveWidgetInterface<T extends ChangeNotifier> extends StatefulWidget {
|
||||
const ReactiveWidgetInterface();
|
||||
|
||||
/// A function to create or find the model. This function will only be called once.
|
||||
T createModel();
|
||||
|
||||
/// Whether the view model should be disposed or not.
|
||||
bool get shouldDispose;
|
||||
|
||||
/// Builds the UI according to the state in [model].
|
||||
Widget build(BuildContext context, T model);
|
||||
|
||||
@override
|
||||
ReactiveWidgetState createState() => ReactiveWidgetState<T>();
|
||||
|
||||
/// This function gives you an opportunity to update the view model when the widget updates.
|
||||
///
|
||||
/// For more details, see [State.didUpdateWidget].
|
||||
void didUpdateWidget(covariant ReactiveWidgetInterface<T> oldWidget, T model) { }
|
||||
}
|
||||
|
||||
/// A widget that creates, subscribes to, and disposes of a [ChangeNotifier].
|
||||
abstract class ReactiveWidget<T extends ChangeNotifier> extends ReactiveWidgetInterface<T> {
|
||||
/// A const constructor.
|
||||
const ReactiveWidget();
|
||||
|
||||
@override
|
||||
T createModel();
|
||||
|
||||
@override
|
||||
bool get shouldDispose => true;
|
||||
}
|
||||
|
||||
/// A [ReactiveWidget] that works with a reusable [ChangeNotifier].
|
||||
abstract class ReusableReactiveWidget<T extends ChangeNotifier> extends ReactiveWidgetInterface<T> {
|
||||
/// The model to listen to.
|
||||
final T model;
|
||||
/// Creates a widget that listens to a view model.
|
||||
const ReusableReactiveWidget(this.model);
|
||||
|
||||
@override
|
||||
T createModel() => model;
|
||||
|
||||
@override
|
||||
bool get shouldDispose => false;
|
||||
}
|
||||
|
||||
/// A state for [ReactiveWidget] that manages the [model].
|
||||
class ReactiveWidgetState<T extends ChangeNotifier> extends State<ReactiveWidgetInterface<T>>{
|
||||
/// The model to listen to.
|
||||
late final T model;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
model = widget.createModel();
|
||||
model.addListener(listener);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
model.removeListener(listener);
|
||||
if (widget.shouldDispose) model.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant ReactiveWidgetInterface<T> oldWidget) {
|
||||
widget.didUpdateWidget(oldWidget, model);
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
/// Updates the UI when [model] updates.
|
||||
void listener() => setState(() {});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => widget.build(context, model);
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
import "package:client/view_models.dart";
|
||||
import "package:client/widgets.dart";
|
||||
import "package:flutter/material.dart";
|
||||
|
||||
class Sidebar extends ReusableReactiveWidget<HomeModel> {
|
||||
const Sidebar(super.model);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, HomeModel model) => DefaultTabController(
|
||||
length: 2,
|
||||
child: Card(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
if (model.markerState != null && model.markerState != MarkerState.override) ...[
|
||||
Text(
|
||||
"Select routes",
|
||||
maxLines: 1,
|
||||
style: context.textTheme.titleLarge,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
Text(
|
||||
"Or click anywhere on the map",
|
||||
maxLines: 1,
|
||||
style: context.textTheme.bodyMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 8),
|
||||
if (model.shouldShowMarkers) TabBar(
|
||||
tabs: [
|
||||
for (final (provider, _) in model.providers)
|
||||
Tab(text: provider),
|
||||
],
|
||||
),
|
||||
if (!model.shouldShowMarkers && model.pathText != null)
|
||||
SingleChildScrollView(child: Center(child: Text(model.pathText!, style: context.textTheme.bodySmall))),
|
||||
if (!model.shouldShowMarkers && model.pathText == null)
|
||||
const Center(child: Text("Choose start or end location")),
|
||||
if (model.shouldShowMarkers) Expanded(
|
||||
child: TabBarView(
|
||||
children: [
|
||||
for (final (_, routesList) in model.providers)
|
||||
ListView(
|
||||
children: [
|
||||
for (final route in routesList) CheckboxListTile(
|
||||
title: Text(route.fullName, maxLines: 1),
|
||||
subtitle: Text(
|
||||
"${route.stops.length} stops",
|
||||
maxLines: 1,
|
||||
),
|
||||
value: model.routesToShow.contains(route.id),
|
||||
onChanged: (value) => model.showRoute(route, shouldShow: value!),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
export "src/view_models/view_model.dart";
|
||||
|
||||
export "src/view_models/home_markers.dart";
|
||||
export "src/view_models/home.dart";
|
|
@ -1,22 +0,0 @@
|
|||
import "package:flutter/material.dart";
|
||||
|
||||
export "package:go_router/go_router.dart";
|
||||
|
||||
export "src/widgets/lat_long_editor.dart";
|
||||
export "src/widgets/reactive_widget.dart";
|
||||
export "src/widgets/sidebar.dart";
|
||||
|
||||
/// Helpful methods on [BuildContext].
|
||||
extension ContextUtils on BuildContext {
|
||||
/// Gets the app's color scheme.
|
||||
ColorScheme get colorScheme => Theme.of(this).colorScheme;
|
||||
|
||||
/// Gets the app's text theme.
|
||||
TextTheme get textTheme => Theme.of(this).textTheme;
|
||||
|
||||
/// Formats a date according to the user's locale.
|
||||
String formatDate(DateTime date) => MaterialLocalizations.of(this).formatCompactDate(date);
|
||||
|
||||
/// Formats a time according to the user's locale.
|
||||
String formatTime(DateTime time) => MaterialLocalizations.of(this).formatTimeOfDay(TimeOfDay.fromDateTime(time));
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
name: client
|
||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
|
||||
version: 0.1.0+1
|
||||
|
||||
environment:
|
||||
sdk: ^3.5.0
|
||||
|
||||
resolution: workspace
|
||||
dependencies:
|
||||
fixnum: ^1.1.1
|
||||
csv: ^6.0.0
|
||||
flutter:
|
||||
sdk: flutter
|
||||
go_router: ^14.2.7
|
||||
google_maps_flutter: ^2.12.0
|
||||
html: ^0.15.5
|
||||
http: ^1.3.0
|
||||
polyline_tools: ^0.0.2
|
||||
web: ^1.1.1
|
||||
shared:
|
||||
path: ../shared
|
||||
|
||||
dev_dependencies:
|
||||
dhttpd: ^4.1.0
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
Binary file not shown.
Before Width: | Height: | Size: 917 B |
Binary file not shown.
Before Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 8.1 KiB |
Binary file not shown.
Before Width: | Height: | Size: 5.5 KiB |
Binary file not shown.
Before Width: | Height: | Size: 20 KiB |
|
@ -1,38 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
If you are serving your web app in a path other than the root, change the
|
||||
href value below to reflect the base path you are serving from.
|
||||
|
||||
The path provided below has to start and end with a slash "/" in order for
|
||||
it to work correctly.
|
||||
|
||||
For more details:
|
||||
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
|
||||
|
||||
This is a placeholder for base href that will be replaced by the value of
|
||||
the `--base-href` argument provided to `flutter build`.
|
||||
-->
|
||||
<base href="$FLUTTER_BASE_HREF">
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||
<meta name="description" content="A new Flutter project.">
|
||||
|
||||
<!-- iOS meta tags & icons -->
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="apple-mobile-web-app-title" content="client">
|
||||
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||
|
||||
<title>client</title>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
</head>
|
||||
<body>
|
||||
<script src="flutter_bootstrap.js" async></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,35 +0,0 @@
|
|||
{
|
||||
"name": "client",
|
||||
"short_name": "client",
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"background_color": "#0175C2",
|
||||
"theme_color": "#0175C2",
|
||||
"description": "A new Flutter project.",
|
||||
"orientation": "portrait-primary",
|
||||
"prefer_related_applications": false,
|
||||
"icons": [
|
||||
{
|
||||
"src": "icons/Icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "icons/Icon-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "icons/Icon-maskable-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "icons/Icon-maskable-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
]
|
||||
}
|
17
src/client/windows/.gitignore
vendored
17
src/client/windows/.gitignore
vendored
|
@ -1,17 +0,0 @@
|
|||
flutter/ephemeral/
|
||||
|
||||
# Visual Studio user-specific files.
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# Visual Studio build-related files.
|
||||
x64/
|
||||
x86/
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
|
@ -1,108 +0,0 @@
|
|||
# Project-level configuration.
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(client LANGUAGES CXX)
|
||||
|
||||
# The name of the executable created for the application. Change this to change
|
||||
# the on-disk name of your application.
|
||||
set(BINARY_NAME "client")
|
||||
|
||||
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
|
||||
# versions of CMake.
|
||||
cmake_policy(VERSION 3.14...3.25)
|
||||
|
||||
# Define build configuration option.
|
||||
get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
|
||||
if(IS_MULTICONFIG)
|
||||
set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
|
||||
CACHE STRING "" FORCE)
|
||||
else()
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
set(CMAKE_BUILD_TYPE "Debug" CACHE
|
||||
STRING "Flutter build mode" FORCE)
|
||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
|
||||
"Debug" "Profile" "Release")
|
||||
endif()
|
||||
endif()
|
||||
# Define settings for the Profile build mode.
|
||||
set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
|
||||
set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
|
||||
set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
|
||||
|
||||
# Use Unicode for all projects.
|
||||
add_definitions(-DUNICODE -D_UNICODE)
|
||||
|
||||
# Compilation settings that should be applied to most targets.
|
||||
#
|
||||
# Be cautious about adding new options here, as plugins use this function by
|
||||
# default. In most cases, you should add new options to specific targets instead
|
||||
# of modifying this function.
|
||||
function(APPLY_STANDARD_SETTINGS TARGET)
|
||||
target_compile_features(${TARGET} PUBLIC cxx_std_17)
|
||||
target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
|
||||
target_compile_options(${TARGET} PRIVATE /EHsc)
|
||||
target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
|
||||
target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
|
||||
endfunction()
|
||||
|
||||
# Flutter library and tool build rules.
|
||||
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
|
||||
add_subdirectory(${FLUTTER_MANAGED_DIR})
|
||||
|
||||
# Application build; see runner/CMakeLists.txt.
|
||||
add_subdirectory("runner")
|
||||
|
||||
|
||||
# Generated plugin build rules, which manage building the plugins and adding
|
||||
# them to the application.
|
||||
include(flutter/generated_plugins.cmake)
|
||||
|
||||
|
||||
# === Installation ===
|
||||
# Support files are copied into place next to the executable, so that it can
|
||||
# run in place. This is done instead of making a separate bundle (as on Linux)
|
||||
# so that building and running from within Visual Studio will work.
|
||||
set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
|
||||
# Make the "install" step default, as it's required to run.
|
||||
set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
|
||||
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
|
||||
endif()
|
||||
|
||||
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
|
||||
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
|
||||
|
||||
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
if(PLUGIN_BUNDLED_LIBRARIES)
|
||||
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
|
||||
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
endif()
|
||||
|
||||
# Copy the native assets provided by the build.dart from all packages.
|
||||
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/")
|
||||
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
|
||||
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
# Fully re-copy the assets directory on each build to avoid having stale files
|
||||
# from a previous install.
|
||||
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
|
||||
install(CODE "
|
||||
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
|
||||
" COMPONENT Runtime)
|
||||
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
|
||||
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
|
||||
|
||||
# Install the AOT library on non-Debug builds only.
|
||||
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||
CONFIGURATIONS Profile;Release
|
||||
COMPONENT Runtime)
|
|
@ -1,109 +0,0 @@
|
|||
# This file controls Flutter-level build steps. It should not be edited.
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
|
||||
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
|
||||
|
||||
# Configuration provided via flutter tool.
|
||||
include(${EPHEMERAL_DIR}/generated_config.cmake)
|
||||
|
||||
# TODO: Move the rest of this into files in ephemeral. See
|
||||
# https://github.com/flutter/flutter/issues/57146.
|
||||
set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
|
||||
|
||||
# Set fallback configurations for older versions of the flutter tool.
|
||||
if (NOT DEFINED FLUTTER_TARGET_PLATFORM)
|
||||
set(FLUTTER_TARGET_PLATFORM "windows-x64")
|
||||
endif()
|
||||
|
||||
# === Flutter Library ===
|
||||
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
|
||||
|
||||
# Published to parent scope for install step.
|
||||
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
|
||||
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
|
||||
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
|
||||
set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
|
||||
|
||||
list(APPEND FLUTTER_LIBRARY_HEADERS
|
||||
"flutter_export.h"
|
||||
"flutter_windows.h"
|
||||
"flutter_messenger.h"
|
||||
"flutter_plugin_registrar.h"
|
||||
"flutter_texture_registrar.h"
|
||||
)
|
||||
list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
|
||||
add_library(flutter INTERFACE)
|
||||
target_include_directories(flutter INTERFACE
|
||||
"${EPHEMERAL_DIR}"
|
||||
)
|
||||
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
|
||||
add_dependencies(flutter flutter_assemble)
|
||||
|
||||
# === Wrapper ===
|
||||
list(APPEND CPP_WRAPPER_SOURCES_CORE
|
||||
"core_implementations.cc"
|
||||
"standard_codec.cc"
|
||||
)
|
||||
list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
|
||||
list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
|
||||
"plugin_registrar.cc"
|
||||
)
|
||||
list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
|
||||
list(APPEND CPP_WRAPPER_SOURCES_APP
|
||||
"flutter_engine.cc"
|
||||
"flutter_view_controller.cc"
|
||||
)
|
||||
list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
|
||||
|
||||
# Wrapper sources needed for a plugin.
|
||||
add_library(flutter_wrapper_plugin STATIC
|
||||
${CPP_WRAPPER_SOURCES_CORE}
|
||||
${CPP_WRAPPER_SOURCES_PLUGIN}
|
||||
)
|
||||
apply_standard_settings(flutter_wrapper_plugin)
|
||||
set_target_properties(flutter_wrapper_plugin PROPERTIES
|
||||
POSITION_INDEPENDENT_CODE ON)
|
||||
set_target_properties(flutter_wrapper_plugin PROPERTIES
|
||||
CXX_VISIBILITY_PRESET hidden)
|
||||
target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
|
||||
target_include_directories(flutter_wrapper_plugin PUBLIC
|
||||
"${WRAPPER_ROOT}/include"
|
||||
)
|
||||
add_dependencies(flutter_wrapper_plugin flutter_assemble)
|
||||
|
||||
# Wrapper sources needed for the runner.
|
||||
add_library(flutter_wrapper_app STATIC
|
||||
${CPP_WRAPPER_SOURCES_CORE}
|
||||
${CPP_WRAPPER_SOURCES_APP}
|
||||
)
|
||||
apply_standard_settings(flutter_wrapper_app)
|
||||
target_link_libraries(flutter_wrapper_app PUBLIC flutter)
|
||||
target_include_directories(flutter_wrapper_app PUBLIC
|
||||
"${WRAPPER_ROOT}/include"
|
||||
)
|
||||
add_dependencies(flutter_wrapper_app flutter_assemble)
|
||||
|
||||
# === Flutter tool backend ===
|
||||
# _phony_ is a non-existent file to force this command to run every time,
|
||||
# since currently there's no way to get a full input/output list from the
|
||||
# flutter tool.
|
||||
set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
|
||||
set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
|
||||
add_custom_command(
|
||||
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
|
||||
${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
|
||||
${CPP_WRAPPER_SOURCES_APP}
|
||||
${PHONY_OUTPUT}
|
||||
COMMAND ${CMAKE_COMMAND} -E env
|
||||
${FLUTTER_TOOL_ENVIRONMENT}
|
||||
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
|
||||
${FLUTTER_TARGET_PLATFORM} $<CONFIG>
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_target(flutter_assemble DEPENDS
|
||||
"${FLUTTER_LIBRARY}"
|
||||
${FLUTTER_LIBRARY_HEADERS}
|
||||
${CPP_WRAPPER_SOURCES_CORE}
|
||||
${CPP_WRAPPER_SOURCES_PLUGIN}
|
||||
${CPP_WRAPPER_SOURCES_APP}
|
||||
)
|
|
@ -1,11 +0,0 @@
|
|||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#ifndef GENERATED_PLUGIN_REGISTRANT_
|
||||
#define GENERATED_PLUGIN_REGISTRANT_
|
||||
|
||||
#include <flutter/plugin_registry.h>
|
||||
|
||||
// Registers Flutter plugins.
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry);
|
||||
|
||||
#endif // GENERATED_PLUGIN_REGISTRANT_
|
|
@ -1,23 +0,0 @@
|
|||
#
|
||||
# Generated file, do not edit.
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
||||
endforeach(plugin)
|
||||
|
||||
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
|
||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
|
||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
|
||||
endforeach(ffi_plugin)
|
|
@ -1,40 +0,0 @@
|
|||
cmake_minimum_required(VERSION 3.14)
|
||||
project(runner LANGUAGES CXX)
|
||||
|
||||
# Define the application target. To change its name, change BINARY_NAME in the
|
||||
# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
|
||||
# work.
|
||||
#
|
||||
# Any new source files that you add to the application should be added here.
|
||||
add_executable(${BINARY_NAME} WIN32
|
||||
"flutter_window.cpp"
|
||||
"main.cpp"
|
||||
"utils.cpp"
|
||||
"win32_window.cpp"
|
||||
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
|
||||
"Runner.rc"
|
||||
"runner.exe.manifest"
|
||||
)
|
||||
|
||||
# Apply the standard set of build settings. This can be removed for applications
|
||||
# that need different build settings.
|
||||
apply_standard_settings(${BINARY_NAME})
|
||||
|
||||
# Add preprocessor definitions for the build version.
|
||||
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"")
|
||||
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}")
|
||||
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}")
|
||||
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}")
|
||||
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}")
|
||||
|
||||
# Disable Windows macros that collide with C++ standard library functions.
|
||||
target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
|
||||
|
||||
# Add dependency libraries and include directories. Add any application-specific
|
||||
# dependencies here.
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
|
||||
target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib")
|
||||
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||
|
||||
# Run the Flutter tool portions of the build. This must not be removed.
|
||||
add_dependencies(${BINARY_NAME} flutter_assemble)
|
|
@ -1,121 +0,0 @@
|
|||
// Microsoft Visual C++ generated resource script.
|
||||
//
|
||||
#pragma code_page(65001)
|
||||
#include "resource.h"
|
||||
|
||||
#define APSTUDIO_READONLY_SYMBOLS
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 2 resource.
|
||||
//
|
||||
#include "winres.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#undef APSTUDIO_READONLY_SYMBOLS
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// English (United States) resources
|
||||
|
||||
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
|
||||
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TEXTINCLUDE
|
||||
//
|
||||
|
||||
1 TEXTINCLUDE
|
||||
BEGIN
|
||||
"resource.h\0"
|
||||
END
|
||||
|
||||
2 TEXTINCLUDE
|
||||
BEGIN
|
||||
"#include ""winres.h""\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
3 TEXTINCLUDE
|
||||
BEGIN
|
||||
"\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
#endif // APSTUDIO_INVOKED
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Icon
|
||||
//
|
||||
|
||||
// Icon with lowest ID value placed first to ensure application icon
|
||||
// remains consistent on all systems.
|
||||
IDI_APP_ICON ICON "resources\\app_icon.ico"
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Version
|
||||
//
|
||||
|
||||
#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD)
|
||||
#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
|
||||
#else
|
||||
#define VERSION_AS_NUMBER 1,0,0,0
|
||||
#endif
|
||||
|
||||
#if defined(FLUTTER_VERSION)
|
||||
#define VERSION_AS_STRING FLUTTER_VERSION
|
||||
#else
|
||||
#define VERSION_AS_STRING "1.0.0"
|
||||
#endif
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION VERSION_AS_NUMBER
|
||||
PRODUCTVERSION VERSION_AS_NUMBER
|
||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS VS_FF_DEBUG
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS VOS__WINDOWS32
|
||||
FILETYPE VFT_APP
|
||||
FILESUBTYPE 0x0L
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904e4"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "com.example" "\0"
|
||||
VALUE "FileDescription", "client" "\0"
|
||||
VALUE "FileVersion", VERSION_AS_STRING "\0"
|
||||
VALUE "InternalName", "client" "\0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2025 com.example. All rights reserved." "\0"
|
||||
VALUE "OriginalFilename", "client.exe" "\0"
|
||||
VALUE "ProductName", "client" "\0"
|
||||
VALUE "ProductVersion", VERSION_AS_STRING "\0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1252
|
||||
END
|
||||
END
|
||||
|
||||
#endif // English (United States) resources
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
#ifndef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 3 resource.
|
||||
//
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#endif // not APSTUDIO_INVOKED
|
|
@ -1,71 +0,0 @@
|
|||
#include "flutter_window.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "flutter/generated_plugin_registrant.h"
|
||||
|
||||
FlutterWindow::FlutterWindow(const flutter::DartProject& project)
|
||||
: project_(project) {}
|
||||
|
||||
FlutterWindow::~FlutterWindow() {}
|
||||
|
||||
bool FlutterWindow::OnCreate() {
|
||||
if (!Win32Window::OnCreate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RECT frame = GetClientArea();
|
||||
|
||||
// The size here must match the window dimensions to avoid unnecessary surface
|
||||
// creation / destruction in the startup path.
|
||||
flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
|
||||
frame.right - frame.left, frame.bottom - frame.top, project_);
|
||||
// Ensure that basic setup of the controller was successful.
|
||||
if (!flutter_controller_->engine() || !flutter_controller_->view()) {
|
||||
return false;
|
||||
}
|
||||
RegisterPlugins(flutter_controller_->engine());
|
||||
SetChildContent(flutter_controller_->view()->GetNativeWindow());
|
||||
|
||||
flutter_controller_->engine()->SetNextFrameCallback([&]() {
|
||||
this->Show();
|
||||
});
|
||||
|
||||
// Flutter can complete the first frame before the "show window" callback is
|
||||
// registered. The following call ensures a frame is pending to ensure the
|
||||
// window is shown. It is a no-op if the first frame hasn't completed yet.
|
||||
flutter_controller_->ForceRedraw();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FlutterWindow::OnDestroy() {
|
||||
if (flutter_controller_) {
|
||||
flutter_controller_ = nullptr;
|
||||
}
|
||||
|
||||
Win32Window::OnDestroy();
|
||||
}
|
||||
|
||||
LRESULT
|
||||
FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) noexcept {
|
||||
// Give Flutter, including plugins, an opportunity to handle window messages.
|
||||
if (flutter_controller_) {
|
||||
std::optional<LRESULT> result =
|
||||
flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
|
||||
lparam);
|
||||
if (result) {
|
||||
return *result;
|
||||
}
|
||||
}
|
||||
|
||||
switch (message) {
|
||||
case WM_FONTCHANGE:
|
||||
flutter_controller_->engine()->ReloadSystemFonts();
|
||||
break;
|
||||
}
|
||||
|
||||
return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
#ifndef RUNNER_FLUTTER_WINDOW_H_
|
||||
#define RUNNER_FLUTTER_WINDOW_H_
|
||||
|
||||
#include <flutter/dart_project.h>
|
||||
#include <flutter/flutter_view_controller.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "win32_window.h"
|
||||
|
||||
// A window that does nothing but host a Flutter view.
|
||||
class FlutterWindow : public Win32Window {
|
||||
public:
|
||||
// Creates a new FlutterWindow hosting a Flutter view running |project|.
|
||||
explicit FlutterWindow(const flutter::DartProject& project);
|
||||
virtual ~FlutterWindow();
|
||||
|
||||
protected:
|
||||
// Win32Window:
|
||||
bool OnCreate() override;
|
||||
void OnDestroy() override;
|
||||
LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
|
||||
LPARAM const lparam) noexcept override;
|
||||
|
||||
private:
|
||||
// The project to run.
|
||||
flutter::DartProject project_;
|
||||
|
||||
// The Flutter instance hosted by this window.
|
||||
std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
|
||||
};
|
||||
|
||||
#endif // RUNNER_FLUTTER_WINDOW_H_
|
|
@ -1,43 +0,0 @@
|
|||
#include <flutter/dart_project.h>
|
||||
#include <flutter/flutter_view_controller.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include "flutter_window.h"
|
||||
#include "utils.h"
|
||||
|
||||
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||
_In_ wchar_t *command_line, _In_ int show_command) {
|
||||
// Attach to console when present (e.g., 'flutter run') or create a
|
||||
// new console when running with a debugger.
|
||||
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
|
||||
CreateAndAttachConsole();
|
||||
}
|
||||
|
||||
// Initialize COM, so that it is available for use in the library and/or
|
||||
// plugins.
|
||||
::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||
|
||||
flutter::DartProject project(L"data");
|
||||
|
||||
std::vector<std::string> command_line_arguments =
|
||||
GetCommandLineArguments();
|
||||
|
||||
project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
|
||||
|
||||
FlutterWindow window(project);
|
||||
Win32Window::Point origin(10, 10);
|
||||
Win32Window::Size size(1280, 720);
|
||||
if (!window.Create(L"client", origin, size)) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
window.SetQuitOnClose(true);
|
||||
|
||||
::MSG msg;
|
||||
while (::GetMessage(&msg, nullptr, 0, 0)) {
|
||||
::TranslateMessage(&msg);
|
||||
::DispatchMessage(&msg);
|
||||
}
|
||||
|
||||
::CoUninitialize();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ generated include file.
|
||||
// Used by Runner.rc
|
||||
//
|
||||
#define IDI_APP_ICON 101
|
||||
|
||||
// Next default values for new objects
|
||||
//
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
#ifndef APSTUDIO_READONLY_SYMBOLS
|
||||
#define _APS_NEXT_RESOURCE_VALUE 102
|
||||
#define _APS_NEXT_COMMAND_VALUE 40001
|
||||
#define _APS_NEXT_CONTROL_VALUE 1001
|
||||
#define _APS_NEXT_SYMED_VALUE 101
|
||||
#endif
|
||||
#endif
|
Binary file not shown.
Before Width: | Height: | Size: 33 KiB |
|
@ -1,14 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- Windows 10 and Windows 11 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
|
@ -1,65 +0,0 @@
|
|||
#include "utils.h"
|
||||
|
||||
#include <flutter_windows.h>
|
||||
#include <io.h>
|
||||
#include <stdio.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
void CreateAndAttachConsole() {
|
||||
if (::AllocConsole()) {
|
||||
FILE *unused;
|
||||
if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
|
||||
_dup2(_fileno(stdout), 1);
|
||||
}
|
||||
if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
|
||||
_dup2(_fileno(stdout), 2);
|
||||
}
|
||||
std::ios::sync_with_stdio();
|
||||
FlutterDesktopResyncOutputStreams();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> GetCommandLineArguments() {
|
||||
// Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
|
||||
int argc;
|
||||
wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
|
||||
if (argv == nullptr) {
|
||||
return std::vector<std::string>();
|
||||
}
|
||||
|
||||
std::vector<std::string> command_line_arguments;
|
||||
|
||||
// Skip the first argument as it's the binary name.
|
||||
for (int i = 1; i < argc; i++) {
|
||||
command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
|
||||
}
|
||||
|
||||
::LocalFree(argv);
|
||||
|
||||
return command_line_arguments;
|
||||
}
|
||||
|
||||
std::string Utf8FromUtf16(const wchar_t* utf16_string) {
|
||||
if (utf16_string == nullptr) {
|
||||
return std::string();
|
||||
}
|
||||
unsigned int target_length = ::WideCharToMultiByte(
|
||||
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
|
||||
-1, nullptr, 0, nullptr, nullptr)
|
||||
-1; // remove the trailing null character
|
||||
int input_length = (int)wcslen(utf16_string);
|
||||
std::string utf8_string;
|
||||
if (target_length == 0 || target_length > utf8_string.max_size()) {
|
||||
return utf8_string;
|
||||
}
|
||||
utf8_string.resize(target_length);
|
||||
int converted_length = ::WideCharToMultiByte(
|
||||
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
|
||||
input_length, utf8_string.data(), target_length, nullptr, nullptr);
|
||||
if (converted_length == 0) {
|
||||
return std::string();
|
||||
}
|
||||
return utf8_string;
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
#ifndef RUNNER_UTILS_H_
|
||||
#define RUNNER_UTILS_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Creates a console for the process, and redirects stdout and stderr to
|
||||
// it for both the runner and the Flutter library.
|
||||
void CreateAndAttachConsole();
|
||||
|
||||
// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
|
||||
// encoded in UTF-8. Returns an empty std::string on failure.
|
||||
std::string Utf8FromUtf16(const wchar_t* utf16_string);
|
||||
|
||||
// Gets the command line arguments passed in as a std::vector<std::string>,
|
||||
// encoded in UTF-8. Returns an empty std::vector<std::string> on failure.
|
||||
std::vector<std::string> GetCommandLineArguments();
|
||||
|
||||
#endif // RUNNER_UTILS_H_
|
|
@ -1,288 +0,0 @@
|
|||
#include "win32_window.h"
|
||||
|
||||
#include <dwmapi.h>
|
||||
#include <flutter_windows.h>
|
||||
|
||||
#include "resource.h"
|
||||
|
||||
namespace {
|
||||
|
||||
/// Window attribute that enables dark mode window decorations.
|
||||
///
|
||||
/// Redefined in case the developer's machine has a Windows SDK older than
|
||||
/// version 10.0.22000.0.
|
||||
/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
|
||||
#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
|
||||
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
|
||||
#endif
|
||||
|
||||
constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
|
||||
|
||||
/// Registry key for app theme preference.
|
||||
///
|
||||
/// A value of 0 indicates apps should use dark mode. A non-zero or missing
|
||||
/// value indicates apps should use light mode.
|
||||
constexpr const wchar_t kGetPreferredBrightnessRegKey[] =
|
||||
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
|
||||
constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme";
|
||||
|
||||
// The number of Win32Window objects that currently exist.
|
||||
static int g_active_window_count = 0;
|
||||
|
||||
using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
|
||||
|
||||
// Scale helper to convert logical scaler values to physical using passed in
|
||||
// scale factor
|
||||
int Scale(int source, double scale_factor) {
|
||||
return static_cast<int>(source * scale_factor);
|
||||
}
|
||||
|
||||
// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
|
||||
// This API is only needed for PerMonitor V1 awareness mode.
|
||||
void EnableFullDpiSupportIfAvailable(HWND hwnd) {
|
||||
HMODULE user32_module = LoadLibraryA("User32.dll");
|
||||
if (!user32_module) {
|
||||
return;
|
||||
}
|
||||
auto enable_non_client_dpi_scaling =
|
||||
reinterpret_cast<EnableNonClientDpiScaling*>(
|
||||
GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
|
||||
if (enable_non_client_dpi_scaling != nullptr) {
|
||||
enable_non_client_dpi_scaling(hwnd);
|
||||
}
|
||||
FreeLibrary(user32_module);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Manages the Win32Window's window class registration.
|
||||
class WindowClassRegistrar {
|
||||
public:
|
||||
~WindowClassRegistrar() = default;
|
||||
|
||||
// Returns the singleton registrar instance.
|
||||
static WindowClassRegistrar* GetInstance() {
|
||||
if (!instance_) {
|
||||
instance_ = new WindowClassRegistrar();
|
||||
}
|
||||
return instance_;
|
||||
}
|
||||
|
||||
// Returns the name of the window class, registering the class if it hasn't
|
||||
// previously been registered.
|
||||
const wchar_t* GetWindowClass();
|
||||
|
||||
// Unregisters the window class. Should only be called if there are no
|
||||
// instances of the window.
|
||||
void UnregisterWindowClass();
|
||||
|
||||
private:
|
||||
WindowClassRegistrar() = default;
|
||||
|
||||
static WindowClassRegistrar* instance_;
|
||||
|
||||
bool class_registered_ = false;
|
||||
};
|
||||
|
||||
WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
|
||||
|
||||
const wchar_t* WindowClassRegistrar::GetWindowClass() {
|
||||
if (!class_registered_) {
|
||||
WNDCLASS window_class{};
|
||||
window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
||||
window_class.lpszClassName = kWindowClassName;
|
||||
window_class.style = CS_HREDRAW | CS_VREDRAW;
|
||||
window_class.cbClsExtra = 0;
|
||||
window_class.cbWndExtra = 0;
|
||||
window_class.hInstance = GetModuleHandle(nullptr);
|
||||
window_class.hIcon =
|
||||
LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
|
||||
window_class.hbrBackground = 0;
|
||||
window_class.lpszMenuName = nullptr;
|
||||
window_class.lpfnWndProc = Win32Window::WndProc;
|
||||
RegisterClass(&window_class);
|
||||
class_registered_ = true;
|
||||
}
|
||||
return kWindowClassName;
|
||||
}
|
||||
|
||||
void WindowClassRegistrar::UnregisterWindowClass() {
|
||||
UnregisterClass(kWindowClassName, nullptr);
|
||||
class_registered_ = false;
|
||||
}
|
||||
|
||||
Win32Window::Win32Window() {
|
||||
++g_active_window_count;
|
||||
}
|
||||
|
||||
Win32Window::~Win32Window() {
|
||||
--g_active_window_count;
|
||||
Destroy();
|
||||
}
|
||||
|
||||
bool Win32Window::Create(const std::wstring& title,
|
||||
const Point& origin,
|
||||
const Size& size) {
|
||||
Destroy();
|
||||
|
||||
const wchar_t* window_class =
|
||||
WindowClassRegistrar::GetInstance()->GetWindowClass();
|
||||
|
||||
const POINT target_point = {static_cast<LONG>(origin.x),
|
||||
static_cast<LONG>(origin.y)};
|
||||
HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
|
||||
UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
|
||||
double scale_factor = dpi / 96.0;
|
||||
|
||||
HWND window = CreateWindow(
|
||||
window_class, title.c_str(), WS_OVERLAPPEDWINDOW,
|
||||
Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
|
||||
Scale(size.width, scale_factor), Scale(size.height, scale_factor),
|
||||
nullptr, nullptr, GetModuleHandle(nullptr), this);
|
||||
|
||||
if (!window) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UpdateTheme(window);
|
||||
|
||||
return OnCreate();
|
||||
}
|
||||
|
||||
bool Win32Window::Show() {
|
||||
return ShowWindow(window_handle_, SW_SHOWNORMAL);
|
||||
}
|
||||
|
||||
// static
|
||||
LRESULT CALLBACK Win32Window::WndProc(HWND const window,
|
||||
UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) noexcept {
|
||||
if (message == WM_NCCREATE) {
|
||||
auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
|
||||
SetWindowLongPtr(window, GWLP_USERDATA,
|
||||
reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
|
||||
|
||||
auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
|
||||
EnableFullDpiSupportIfAvailable(window);
|
||||
that->window_handle_ = window;
|
||||
} else if (Win32Window* that = GetThisFromHandle(window)) {
|
||||
return that->MessageHandler(window, message, wparam, lparam);
|
||||
}
|
||||
|
||||
return DefWindowProc(window, message, wparam, lparam);
|
||||
}
|
||||
|
||||
LRESULT
|
||||
Win32Window::MessageHandler(HWND hwnd,
|
||||
UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) noexcept {
|
||||
switch (message) {
|
||||
case WM_DESTROY:
|
||||
window_handle_ = nullptr;
|
||||
Destroy();
|
||||
if (quit_on_close_) {
|
||||
PostQuitMessage(0);
|
||||
}
|
||||
return 0;
|
||||
|
||||
case WM_DPICHANGED: {
|
||||
auto newRectSize = reinterpret_cast<RECT*>(lparam);
|
||||
LONG newWidth = newRectSize->right - newRectSize->left;
|
||||
LONG newHeight = newRectSize->bottom - newRectSize->top;
|
||||
|
||||
SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
|
||||
newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
case WM_SIZE: {
|
||||
RECT rect = GetClientArea();
|
||||
if (child_content_ != nullptr) {
|
||||
// Size and position the child window.
|
||||
MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
|
||||
rect.bottom - rect.top, TRUE);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
case WM_ACTIVATE:
|
||||
if (child_content_ != nullptr) {
|
||||
SetFocus(child_content_);
|
||||
}
|
||||
return 0;
|
||||
|
||||
case WM_DWMCOLORIZATIONCOLORCHANGED:
|
||||
UpdateTheme(hwnd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return DefWindowProc(window_handle_, message, wparam, lparam);
|
||||
}
|
||||
|
||||
void Win32Window::Destroy() {
|
||||
OnDestroy();
|
||||
|
||||
if (window_handle_) {
|
||||
DestroyWindow(window_handle_);
|
||||
window_handle_ = nullptr;
|
||||
}
|
||||
if (g_active_window_count == 0) {
|
||||
WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
|
||||
}
|
||||
}
|
||||
|
||||
Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
|
||||
return reinterpret_cast<Win32Window*>(
|
||||
GetWindowLongPtr(window, GWLP_USERDATA));
|
||||
}
|
||||
|
||||
void Win32Window::SetChildContent(HWND content) {
|
||||
child_content_ = content;
|
||||
SetParent(content, window_handle_);
|
||||
RECT frame = GetClientArea();
|
||||
|
||||
MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
|
||||
frame.bottom - frame.top, true);
|
||||
|
||||
SetFocus(child_content_);
|
||||
}
|
||||
|
||||
RECT Win32Window::GetClientArea() {
|
||||
RECT frame;
|
||||
GetClientRect(window_handle_, &frame);
|
||||
return frame;
|
||||
}
|
||||
|
||||
HWND Win32Window::GetHandle() {
|
||||
return window_handle_;
|
||||
}
|
||||
|
||||
void Win32Window::SetQuitOnClose(bool quit_on_close) {
|
||||
quit_on_close_ = quit_on_close;
|
||||
}
|
||||
|
||||
bool Win32Window::OnCreate() {
|
||||
// No-op; provided for subclasses.
|
||||
return true;
|
||||
}
|
||||
|
||||
void Win32Window::OnDestroy() {
|
||||
// No-op; provided for subclasses.
|
||||
}
|
||||
|
||||
void Win32Window::UpdateTheme(HWND const window) {
|
||||
DWORD light_mode;
|
||||
DWORD light_mode_size = sizeof(light_mode);
|
||||
LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey,
|
||||
kGetPreferredBrightnessRegValue,
|
||||
RRF_RT_REG_DWORD, nullptr, &light_mode,
|
||||
&light_mode_size);
|
||||
|
||||
if (result == ERROR_SUCCESS) {
|
||||
BOOL enable_dark_mode = light_mode == 0;
|
||||
DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE,
|
||||
&enable_dark_mode, sizeof(enable_dark_mode));
|
||||
}
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
#ifndef RUNNER_WIN32_WINDOW_H_
|
||||
#define RUNNER_WIN32_WINDOW_H_
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
// A class abstraction for a high DPI-aware Win32 Window. Intended to be
|
||||
// inherited from by classes that wish to specialize with custom
|
||||
// rendering and input handling
|
||||
class Win32Window {
|
||||
public:
|
||||
struct Point {
|
||||
unsigned int x;
|
||||
unsigned int y;
|
||||
Point(unsigned int x, unsigned int y) : x(x), y(y) {}
|
||||
};
|
||||
|
||||
struct Size {
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
Size(unsigned int width, unsigned int height)
|
||||
: width(width), height(height) {}
|
||||
};
|
||||
|
||||
Win32Window();
|
||||
virtual ~Win32Window();
|
||||
|
||||
// Creates a win32 window with |title| that is positioned and sized using
|
||||
// |origin| and |size|. New windows are created on the default monitor. Window
|
||||
// sizes are specified to the OS in physical pixels, hence to ensure a
|
||||
// consistent size this function will scale the inputted width and height as
|
||||
// as appropriate for the default monitor. The window is invisible until
|
||||
// |Show| is called. Returns true if the window was created successfully.
|
||||
bool Create(const std::wstring& title, const Point& origin, const Size& size);
|
||||
|
||||
// Show the current window. Returns true if the window was successfully shown.
|
||||
bool Show();
|
||||
|
||||
// Release OS resources associated with window.
|
||||
void Destroy();
|
||||
|
||||
// Inserts |content| into the window tree.
|
||||
void SetChildContent(HWND content);
|
||||
|
||||
// Returns the backing Window handle to enable clients to set icon and other
|
||||
// window properties. Returns nullptr if the window has been destroyed.
|
||||
HWND GetHandle();
|
||||
|
||||
// If true, closing this window will quit the application.
|
||||
void SetQuitOnClose(bool quit_on_close);
|
||||
|
||||
// Return a RECT representing the bounds of the current client area.
|
||||
RECT GetClientArea();
|
||||
|
||||
protected:
|
||||
// Processes and route salient window messages for mouse handling,
|
||||
// size change and DPI. Delegates handling of these to member overloads that
|
||||
// inheriting classes can handle.
|
||||
virtual LRESULT MessageHandler(HWND window,
|
||||
UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) noexcept;
|
||||
|
||||
// Called when CreateAndShow is called, allowing subclass window-related
|
||||
// setup. Subclasses should return false if setup fails.
|
||||
virtual bool OnCreate();
|
||||
|
||||
// Called when Destroy is called.
|
||||
virtual void OnDestroy();
|
||||
|
||||
private:
|
||||
friend class WindowClassRegistrar;
|
||||
|
||||
// OS callback called by message pump. Handles the WM_NCCREATE message which
|
||||
// is passed when the non-client area is being created and enables automatic
|
||||
// non-client DPI scaling so that the non-client area automatically
|
||||
// responds to changes in DPI. All other messages are handled by
|
||||
// MessageHandler.
|
||||
static LRESULT CALLBACK WndProc(HWND const window,
|
||||
UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) noexcept;
|
||||
|
||||
// Retrieves a class instance pointer for |window|
|
||||
static Win32Window* GetThisFromHandle(HWND const window) noexcept;
|
||||
|
||||
// Update the window frame's theme to match the system theme.
|
||||
static void UpdateTheme(HWND const window);
|
||||
|
||||
bool quit_on_close_ = false;
|
||||
|
||||
// window handle for top level window.
|
||||
HWND window_handle_ = nullptr;
|
||||
|
||||
// window handle for hosted content.
|
||||
HWND child_content_ = nullptr;
|
||||
};
|
||||
|
||||
#endif // RUNNER_WIN32_WINDOW_H_
|
6
src/data/azure_function_timer/.gitignore
vendored
6
src/data/azure_function_timer/.gitignore
vendored
|
@ -1,6 +0,0 @@
|
|||
# Created by venv; see https://docs.python.org/3/library/venv.html
|
||||
bin
|
||||
include
|
||||
lib
|
||||
lib64
|
||||
local.settings.json
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"ms-azuretools.vscode-azurefunctions"
|
||||
]
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
#!/bin/sh
|
||||
func azure functionapp publish cron-map-scrape
|
|
@ -1,41 +0,0 @@
|
|||
import azure.functions as func
|
||||
import datetime
|
||||
import json
|
||||
import urllib.request
|
||||
import logging
|
||||
|
||||
app = func.FunctionApp()
|
||||
|
||||
|
||||
# https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-table?tabs=isolated-process%2Ctable-api%2Cextensionv3&pivots=programming-language-python#output
|
||||
@app.timer_trigger(
|
||||
schedule="*/5 * * * * *",
|
||||
arg_name="myTimer",
|
||||
run_on_startup=False,
|
||||
use_monitor=False,
|
||||
)
|
||||
def Timer(myTimer: func.TimerRequest) -> None:
|
||||
if myTimer.past_due:
|
||||
logging.info("The timer is past due!")
|
||||
logging.info("Python timer trigger function executed.")
|
||||
|
||||
endpoint = "https://binghamtonupublic.etaspot.net/service.php?service=get_vehicles&token=TESTING"
|
||||
contents = urllib.request.urlopen(endpoint).read()
|
||||
try:
|
||||
vehicles = json.loads(contents)["get_vehicles"]
|
||||
time = datetime.datetime.now()
|
||||
seconds = int(
|
||||
(time - datetime.datetime(1970, 1, 1)).total_seconds() % (3600 * 24)
|
||||
)
|
||||
weekday = time.weekday()
|
||||
for vehicle in vehicles:
|
||||
route = vehicle["routeID"]
|
||||
equipment = vehicle["equipmentID"]
|
||||
lattitude = vehicle["lat"]
|
||||
longitude = vehicle["lng"]
|
||||
logging.info(
|
||||
f"{route},{equipment},{lattitude},{longitude},{seconds},{weekday}"
|
||||
)
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
raise e
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"version": "2.0",
|
||||
"logging": {
|
||||
"applicationInsights": {
|
||||
"samplingSettings": {
|
||||
"isEnabled": true,
|
||||
"excludedTypes": "Request"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extensionBundle": {
|
||||
"id": "Microsoft.Azure.Functions.ExtensionBundle",
|
||||
"version": "[4.*, 5.0.0)"
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
home = /usr/sbin
|
||||
include-system-site-packages = false
|
||||
version = 3.13.1
|
||||
executable = /usr/bin/python3.13
|
||||
command = /usr/sbin/python -m venv /home/pagwin/projects/school/spring2025/cs445_soft_eng/portfolio-team4/src/data/azure_function_timer
|
|
@ -1,5 +0,0 @@
|
|||
# Do not include azure-functions-worker in this file
|
||||
# The Python Worker is managed by the Azure Functions platform
|
||||
# Manually managing azure-functions-worker may cause unexpected issues
|
||||
|
||||
azure-functions
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue