35 Commits
Author SHA1 Message Date
drake 509bf06128 run 2024-06-13 16:00:28 -05:00
drake 2c85accb7f Fix API Delete 2024-06-13 15:52:01 -05:00
drake cfade0e075 API Rewrite with UI 2024-06-13 15:30:48 -05:00
drake 64e493012a API Rewrite 1 2024-06-13 15:27:13 -05:00
drake 9116876f7b API Rewrite 2024-06-13 14:27:36 -05:00
drake 0a1250dfd2 API Listing half-rewrite 2024-06-12 14:28:13 -05:00
drake 68edb2c3a1 fixes 4 2024-06-06 21:55:56 -05:00
drake ee8b419887 fixes 3 2024-06-06 21:40:28 -05:00
drake d350dde994 fixes 2 2024-06-06 21:31:30 -05:00
drake 00a4965efc fixes 2024-06-06 21:17:31 -05:00
drake 013dc5572b test 2 2024-06-06 21:00:43 -05:00
drake 086f47cab0 test 2024-06-06 20:25:15 -05:00
drake dd0b7460fb fix env vars 2024-06-06 20:10:38 -05:00
drake 6bdb53ecd9 test3 2024-06-06 20:08:26 -05:00
drake 8084532ba1 test2 2024-06-06 20:06:43 -05:00
drake 8efffef7c9 test 2024-06-06 20:05:34 -05:00
drake 9ac5b280d0 fix api address & adjust linux build 2024-06-06 19:48:26 -05:00
drake 96b608d0c1 reformat jenkinsfile again x6 2024-06-06 18:49:18 -05:00
drake bf53db3524 reformat jenkinsfile again x5 2024-06-06 18:32:51 -05:00
drake bdc69a5dc0 reformat jenkinsfile again x4 2024-06-06 18:14:12 -05:00
drake a81cb9ab73 reformat jenkinsfile again x3 2024-06-06 18:12:43 -05:00
drake 5fe4201060 reformat jenkinsfile again x2 2024-06-06 18:06:47 -05:00
drake 355a5b2532 reformat jenkinsfile again 2024-06-06 18:03:17 -05:00
drake abf893cef3 reformat jenkinsfile 2024-06-06 18:00:33 -05:00
drake 339f64ac0d reformat jenkinsfile 2024-06-06 16:41:37 -05:00
jenkins d5e7de8f50 security updates part 2 2024-06-06 15:44:28 -05:00
jenkins e1ab786140 security updates 2024-06-06 15:13:53 -05:00
drake 8f8797fbe6 readme fixes 2024-04-30 03:01:27 +00:00
drake e440438a8a after SLC rubric and README.md 2024-04-29 21:58:55 -05:00
drake 9b8dcda882 Update fbla-api/README.md 2024-04-22 14:05:35 +00:00
drake 5e1b1e3a1f Update fbla_ui/README.md 2024-04-17 15:37:23 +00:00
drake 501a8113b2 Update readme formatting and packages 2024-04-16 13:56:13 +00:00
drake 9b30a0c16d Update readme formatting 2024-04-10 14:48:33 +00:00
drake 5a560fbae8 Update Readme for WI SLC 2024-04-10 14:43:46 +00:00
drake 2728d612d4 lots of fixes 2024-04-07 19:51:58 -05:00
32 changed files with 1433 additions and 865 deletions
Vendored
+137
View File
@@ -0,0 +1,137 @@
pipeline {
agent any
stages {
stage('Flutter Cleanup/Update') {
steps {
sh '''cd fbla_ui
flutter upgrade --force
flutter pub upgrade
flutter --version
flutter doctor
flutter clean'''
}
}
stage('Build API & UI') {
parallel {
stage('Build API') {
steps {
sh '''cd fbla-api
docker image prune -f
docker build --no-cache -t fbla-api .'''
}
}
stage('UI Web Build') {
steps {
sh '''cd fbla_ui
flutter build web --release --base-href /fbla/'''
}
}
stage('UI Linux Build') {
steps {
sh '''cd fbla_ui
flutter build linux --release'''
}
}
stage('UI APK Build') {
steps {
sh '''cd fbla_ui
flutter build apk --release'''
}
}
}
}
stage('Deploy and Save') {
parallel {
stage('Save Other Builds') {
steps {
script {
def remote = [
name : 'HostServer',
host : '192.168.0.216',
user : "${env.JOBLINK_LOCAL_USER}",
password : "${env.JOBLINK_LOCAL_PASSWD}",
allowAnyHosts: true,
]
if (env.BRANCH_NAME == 'main') {
sshRemove(path: "/home/${env.JOBLINK_LOCAL_USER}/builds/main/linux", remote: remote)
sshCommand remote: remote, command: "mkdir /home/${env.JOBLINK_LOCAL_USER}/builds/main/linux"
sshPut(from: 'fbla_ui/build/linux/x64/release', into: "/home/${env.JOBLINK_LOCAL_USER}/builds/main/linux", remote: remote)
sshCommand remote: remote, command: "mv /home/${env.JOBLINK_LOCAL_USER}/builds/main/linux/release/* /home/${env.JOBLINK_LOCAL_USER}/builds/main/linux"
sshCommand remote: remote, command: "rm -R /home/${env.JOBLINK_LOCAL_USER}/builds/main/linux/release/"
sshRemove(path: "/home/${env.JOBLINK_LOCAL_USER}/builds/main/apk", remote: remote)
sshCommand remote: remote, command: "mkdir /home/${env.JOBLINK_LOCAL_USER}/builds/main/apk"
sshPut(from: 'fbla_ui/build/app/outputs/apk/release', into: "/home/${env.JOBLINK_LOCAL_USER}/builds/main/apk", remote: remote)
sshCommand remote: remote, command: "mv /home/${env.JOBLINK_LOCAL_USER}/builds/main/apk/release/* /home/${env.JOBLINK_LOCAL_USER}/builds/main/apk"
sshCommand remote: remote, command: "rm -R /home/${env.JOBLINK_LOCAL_USER}/builds/main/apk/release/"
} else {
sshRemove(path: "/home/${env.JOBLINK_LOCAL_USER}/builds/dev/linux", remote: remote)
sshCommand remote: remote, command: "mkdir /home/${env.JOBLINK_LOCAL_USER}/builds/dev/linux"
sshPut(from: 'fbla_ui/build/linux/x64/release', into: "/home/${env.JOBLINK_LOCAL_USER}/builds/dev/linux", remote: remote)
sshCommand remote: remote, command: "mv /home/${env.JOBLINK_LOCAL_USER}/builds/dev/linux/release/* /home/${env.JOBLINK_LOCAL_USER}/builds/dev/linux"
sshCommand remote: remote, command: "rm -R /home/${env.JOBLINK_LOCAL_USER}/builds/dev/linux/release/"
sshRemove(path: "/home/${env.JOBLINK_LOCAL_USER}/builds/dev/apk", remote: remote)
sshCommand remote: remote, command: "mkdir /home/${env.JOBLINK_LOCAL_USER}/builds/dev/apk"
sshPut(from: 'fbla_ui/build/app/outputs/apk/release', into: "/home/${env.JOBLINK_LOCAL_USER}/builds/dev/apk", remote: remote)
sshCommand remote: remote, command: "mv /home/${env.JOBLINK_LOCAL_USER}/builds/dev/apk/release/* /home/${env.JOBLINK_LOCAL_USER}/builds/dev/apk"
sshCommand remote: remote, command: "rm -R /home/${env.JOBLINK_LOCAL_USER}/builds/dev/apk/release/"
}
}
}
}
stage('Deploy Remote Web UI') {
when {
expression {
env.BRANCH_NAME == 'main'
}
}
steps {
script {
def remote = [
name : 'MarinoDev',
host : 'marinodev.com',
port : 21098,
user : "${env.JOBLINK_REMOTE_USER}",
identityFile : '/var/jenkins_home/marinoDevPrivateKey',
passphrase : "${env.JOBLINK_REMOTE_PASSWD}",
allowAnyHosts: true,
]
sshRemove(path: "/home/${env.JOBLINK_REMOTE_USER}/public_html/fbla", remote: remote)
sshPut(from: 'fbla_ui/build/web/', into: "/home/${env.JOBLINK_REMOTE_USER}/public_html/", remote: remote)
sshCommand remote: remote, command: "mv /home/${env.JOBLINK_REMOTE_USER}/public_html/web /home/${env.JOBLINK_REMOTE_USER}/public_html/fbla"
}
}
}
stage('Deploy Local Web UI') {
steps {
script {
def remote = [
name : 'HostServer',
host : '192.168.0.216',
user : "${env.JOBLINK_LOCAL_USER}",
password : "${env.JOBLINK_LOCAL_PASSWD}",
allowAnyHosts: true,
]
sshRemove(path: "/home/${env.JOBLINK_LOCAL_USER}/fbla-webserver/webfiles/fbla", remote: remote)
sshPut(from: 'fbla_ui/build/web/', into: "/home/${env.JOBLINK_LOCAL_USER}/fbla-webserver", remote: remote)
sshCommand remote: remote, command: "mv /home/${env.JOBLINK_LOCAL_USER}/fbla-webserver/web /home/${env.JOBLINK_LOCAL_USER}/fbla-webserver/webfiles/fbla"
}
}
}
stage('Start API') {
steps {
sh '''cd fbla-api
docker-compose down
docker-compose up -d'''
}
}
}
}
stage('Run API Tests') {
steps {
sh '''cd fbla-api
dart pub get
dart run ./test/fbla_api_test.dart'''
}
}
}
}
+54 -3
View File
@@ -1,5 +1,56 @@
This is my app `Job Link` for the 2023-2024 FBLA Coding and Programming event.
It uses the [Flutter Framework](https://flutter.dev/) for the front end and [Dart Language](https://dart.dev/) for both the front end and API. # Job Link
Please view the README in [fbla_ui](fbla_ui/README.md) and [fbla-api](fbla-api/README.md) for specific instructions for installation and usage for each part of the app This is my app `Job Link` for the 2023-2024 FBLA Coding and Programming event.\
WI SLC Winner.
It uses the [Flutter Framework](https://flutter.dev/) for the front end and the [Dart Language](https://dart.dev/) for both the front end and API.
## 3rd Party Libraries
- [dart_jsonwebtoken](https://pub.dev/packages/dart_jsonwebtoken)
- [sliver_tools](https://pub.dev/packages/sliver_tools)
- [flutter_sticky_header](https://pub.dev/packages/flutter_sticky_header)
- [pdf](https://pub.dev/packages/pdf)
- [printing](https://pub.dev/packages/printing)
- [open_filex](https://pub.dev/packages/open_filex)
- [postgres](https://pub.dev/packages/postgres)
- [argon2](https://pub.dev/packages/argon2)
## Requirements
### Job Link
- **OS**: Windows, Linux, MacOS, Android, IOS. Note that some releases may not contain a MacOS or IOS build.
- Stable internet connection.
### API
- **OS**: Windows, Linux, MacOS.
- Stable internet connection.
## Installation/Usage
Please view the README in [fbla_ui](fbla_ui/README.md) and [fbla-api](fbla-api/README.md) for specific instructions for installation and usage for each part of the app.
## Competitions
[Here](https://docs.google.com/presentation/d/1ZbSE9RqobU2T-NDIm3CUtT_9nEhOm3_B47NZl1-c_QA) is the presentation used for competitions.
### WI State Leadership Conference
1st Place\
Used release 0.1.1\
[Here](SLC) are my score sheets.
Questions asked (with my answers):
**Q**: Why did you decide to use the apps (framework and tools I assume) you used?\
**A**: I decided to use Flutter primarily because of its cross-platform capabilities and its integration with the [Material design specification](https://m3.material.io/). I also decided to use it because I had some previous experience with Flutter and Dart, and Dart is somewhat similar to Java which I also had experience with.
This one seems to just be a clarification for the `User input is validated` category of the [scoring sheet](https://connect.fbla.org/headquarters/files/High%20School%20Competitive%20Events%20Resources/Individual%20Guidelines/Presentation%20Events/Coding--Programming.pdf) (page 5-6)\
**Q**: You mentioned that you validated that the description [of the business] is required, do you validate all the inputs or just that?\
**A**: I validate that all of the required fields are inputted, and I also check for a valid email address and website format.
This seems to be for the [scoring sheet](https://connect.fbla.org/headquarters/files/High%20School%20Competitive%20Events%20Resources/Individual%20Guidelines/Presentation%20Events/Coding--Programming.pdf) (page 5-6) `Code Quality` section.\
While looking through a binder of the presentation and source code:\
**Q**: Is your source code all properly commented?\
**A**: While it is hard to comment UI code because of the nature of it, I did my best to use comments where applicable, and I also provide extensive documentation of the installation and usage of the app in the documentation in the README files.
Binary file not shown.
Binary file not shown.
+2 -2
View File
@@ -9,9 +9,9 @@ RUN echo 'deb [signed-by=/usr/share/keyrings/dart.gpg arch=amd64] https://storag
RUN apt-get update && apt-get install -y dart RUN apt-get update && apt-get install -y dart
ENV PATH="$PATH:/usr/lib/dart/bin" ENV PATH="$PATH:/usr/lib/dart/bin"
RUN cd /root && git clone https://https://git.marinodev.com/MarinoDev/FBLA24.git RUN cd /root && git clone https://git.marinodev.com/MarinoDev/FBLA24.git
WORKDIR /root/FBLA24/fbla-api WORKDIR /root/FBLA24/fbla-api
RUN dart pub install RUN dart pub install
EXPOSE 8000 EXPOSE 8000
ENTRYPOINT dart run ./lib/fbla_api.dart ENTRYPOINT dart run ./lib/fbla_api.dart
-1
View File
@@ -16,6 +16,5 @@ docker-compose up -d'''
dart run ./test/fbla_api_test.dart''' dart run ./test/fbla_api_test.dart'''
} }
} }
} }
} }
+30 -20
View File
@@ -2,19 +2,16 @@ This is the API for my 2023-2024 FBLA Coding & Programming App
## Installation ## Installation
1. Install [Dart SDK](https://dart.dev/get-dart) 1. Install [Dart SDK](https://dart.dev/get-dart).
2. Install [PostgreSQL](https://www.postgresql.org/) and set it up 2. Clone the repo .
3. Clone the repo ```bash
```
git clone https://git.marinodev.com/MarinoDev/FBLA24.git git clone https://git.marinodev.com/MarinoDev/FBLA24.git
cd FBLA24/fbla-api/ cd FBLA24/fbla-api/
``` ```
4. Run `dart pub install` to install dart packages 3. Run `dart pub install` to install dart packages.
4. Install [PostgreSQL](https://www.postgresql.org/) and set it up.
## Usage 5. Create `fbla` database in postgres.
```SQL
1. Create `fbla` database in postgres
```
CREATE DATABASE fbla CREATE DATABASE fbla
WITH WITH
OWNER = [username] OWNER = [username]
@@ -24,8 +21,8 @@ CREATE DATABASE fbla
``` ```
Make sure to change [username] to the actual username of your postgres instance. Make sure to change [username] to the actual username of your postgres instance.
2. Create `businesses` table 6. Create `businesses` table.
``` ```SQL
-- Table: public.businesses -- Table: public.businesses
-- DROP TABLE IF EXISTS public.businesses; -- DROP TABLE IF EXISTS public.businesses;
@@ -51,10 +48,10 @@ TABLESPACE pg_default;
ALTER TABLE IF EXISTS public.businesses ALTER TABLE IF EXISTS public.businesses
OWNER to [username]; OWNER to [username];
``` ```
Make sure to change [username] to the actual username of your postgres instance Make sure to change [username] to the actual username of your postgres instance.
3. Create `users` table 7. Create `users` table.
``` ```SQL
-- Table: public.users -- Table: public.users
-- DROP TABLE IF EXISTS public.users; -- DROP TABLE IF EXISTS public.users;
@@ -73,9 +70,22 @@ TABLESPACE pg_default;
ALTER TABLE IF EXISTS public.users ALTER TABLE IF EXISTS public.users
OWNER to postgres; OWNER to postgres;
``` ```
4. Set environment variables `POSTGRES_ADDRESS` (IP address), `POSTGRES_PORT` (default 5432), `POSTGRES_USERNAME`, and `POSTGRES_PASSWORD` to appropriate information for your postgres database. Also set `SECRET_KEY` to anything you want to genera te JSON Web Tokens. 8. Set environment variables `JOBLINK_POSTGRES_ADDRESS` (IP address), `JOBLINK_POSTGRES_PORT` (default 5432), `JOBLINK_POSTGRES_USERNAME`, and `JOBLINK_POSTGRES_PASSWORD` to appropriate information for your postgres database. Also set `JOBLINK_SECRET_KEY` to anything you want to generate JSON Web Tokens.
5. Set lib/create_first_user.dart username and password variables at top of file and run with `dart run lib/create_first_user.dart` 9. Set lib/create_first_user.dart username and password variables at top of file and run with `dart run lib/create_first_user.dart`.\
Note: the username and password you use here will be what you use to log into the app as an admin Note: the username and password you use here will be what you use to log into the app as an admin.
6. Your database should be all set up! Start the API with `dart run lib/fbla_api.dart` or alternatively, run it in a docker container with [Docker Build](https://docs.docker.com/reference/cli/docker/image/build/) and [Docker Compose](https://docs.docker.com/compose/) using the included [Dockerfile](Dockerfile) and [docker-compose.yml](docker-compose.yml) files. If using `Docker Compose`, change the first portion of the volumes path on line 9 to any desired storage location.
## Usage
### Installed
Start the API with `dart run lib/fbla_api.dart`\
Note the address it is serving at, you will need this to connect it to the UI, and to run tests. Note the address it is serving at, you will need this to connect it to the UI, and to run tests.
7. Test your api with `dart run test/fbla_api_test.dart`, setting `apiAddress` on line 8 to your api address.
### Docker
Run it in a docker container with [Docker Build](https://docs.docker.com/reference/cli/docker/image/build/) and [Docker Compose](https://docs.docker.com/compose/) using the included [Dockerfile](Dockerfile) and [docker-compose.yml](docker-compose.yml) files. If using `Docker Compose`, change the first portion of the volumes path on line 9 to any desired storage location.\
Note the address it is serving at, you will need this to connect it to the UI, and to run tests.
### Testing
You can test all functionality of your api with `dart run test/fbla_api_test.dart`, setting `apiAddress` on line 8 to your api address.
-25
View File
@@ -1,25 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIEQzCCAyugAwIBAgISA1MOaRzFLZim3nvjG7iscMdEMA0GCSqGSIb3DQEBCwUA
MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD
EwJSMzAeFw0yMzEyMTQyMjI5NTVaFw0yNDAzMTMyMjI5NTRaMCAxHjAcBgNVBAMT
FWhvbWVsYWIubWFyaW5vZGV2LmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA
BER7NDX7FANFf72Pqwl5haoczQchiOhMGNyMa5foT0aYG2DeMQeEhpl+17vBzEwu
KewujegQNWZdNHVbGxqdUmWjggIuMIICKjAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0l
BBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYE
FOsys6fx3sqsaEaqklTWasLzlNJzMB8GA1UdIwQYMBaAFBQusxe3WFbLrlAJQOYf
r52LFMLGMFUGCCsGAQUFBwEBBEkwRzAhBggrBgEFBQcwAYYVaHR0cDovL3IzLm8u
bGVuY3Iub3JnMCIGCCsGAQUFBzAChhZodHRwOi8vcjMuaS5sZW5jci5vcmcvMDgG
A1UdEQQxMC+CFWhvbWVsYWIubWFyaW5vZGV2LmNvbYIWbWFyaW5vLW5hcy5zeW5v
bG9neS5tZTATBgNVHSAEDDAKMAgGBmeBDAECATCCAQMGCisGAQQB1nkCBAIEgfQE
gfEA7wB2AEiw42vapkc0D+VqAvqdMOscUgHLVt0sgdm7v6s52IRzAAABjGqqUTkA
AAQDAEcwRQIhAJncD2v9CgQii1KCLJhILJ96Jy7mE3a1ptaZ8sip8k3xAiBXw7ML
wbp/ikIzl0xYTeJsjU7lp7/VI26K+luw++dsMgB1AO7N0GTV2xrOxVy3nbTNE6Iy
h0Z8vOzew1FIWUZxH7WbAAABjGqqUTsAAAQDAEYwRAIgSXZW95Cafz5olbebYbSH
xlvPXhmJaspB7XzEsQP2i6kCIHa1QcGg9vyXJ6apl7WSPIz4DLJb6/ZeaHkPqAC5
NjKPMA0GCSqGSIb3DQEBCwUAA4IBAQBSKsqvA8v8h1WmTjudd8cRWc3RdU6lgq/E
pTXX6XWYFpU+D7jprp/NeW8RmVToQRUi6A3Bv9KnrsHG5HpwJ37SLh6Gv8s2t1A/
8FyNyndjuBKxFSaoJKkqlKUA657dXh67if5WLZYZYQdArRw3Q+fQOfSit8+RomTY
xmNVnRVO5+JRwxwbk/WT8tZHOb3Cd7Ozo7d2vpwH/KqY4RM7ajV8shzulMlAtqfT
ahi4ph8eUm43nCJPsVa4ddQLaV4z8EXoQ/SAsjrMkbAyg4G3uKoCqPUX4lnvqAAW
7kxmH8lJpkh76sz7gT1j/vzk9Fbslk3+8oa8PUO9PYQWfYwzWIs2
-----END CERTIFICATE-----
-61
View File
@@ -1,61 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw
WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg
RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP
R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx
sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm
NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg
Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG
/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC
AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB
Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA
FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw
AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw
Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB
gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W
PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl
ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz
CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm
lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4
avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2
yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O
yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids
hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+
HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv
MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX
nLRbwHOoq7hHwg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFYDCCBEigAwIBAgIQQAF3ITfU6UK47naqPGQKtzANBgkqhkiG9w0BAQsFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTIxMDEyMDE5MTQwM1oXDTI0MDkzMDE4MTQwM1ow
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQCt6CRz9BQ385ueK1coHIe+3LffOJCMbjzmV6B493XC
ov71am72AE8o295ohmxEk7axY/0UEmu/H9LqMZshftEzPLpI9d1537O4/xLxIZpL
wYqGcWlKZmZsj348cL+tKSIG8+TA5oCu4kuPt5l+lAOf00eXfJlII1PoOK5PCm+D
LtFJV4yAdLbaL9A4jXsDcCEbdfIwPPqPrt3aY6vrFk/CjhFLfs8L6P+1dy70sntK
4EwSJQxwjQMpoOFTJOwT2e4ZvxCzSow/iaNhUd6shweU9GNx7C7ib1uYgeGJXDR5
bHbvO5BieebbpJovJsXQEOEO3tkQjhb7t/eo98flAgeYjzYIlefiN5YNNnWe+w5y
sR2bvAP5SQXYgd0FtCrWQemsAXaVCg/Y39W9Eh81LygXbNKYwagJZHduRze6zqxZ
Xmidf3LWicUGQSk+WT7dJvUkyRGnWqNMQB9GoZm1pzpRboY7nn1ypxIFeFntPlF4
FQsDj43QLwWyPntKHEtzBRL8xurgUBN8Q5N0s8p0544fAQjQMNRbcTa0B7rBMDBc
SLeCO5imfWCKoqMpgsy6vYMEG6KDA0Gh1gXxG8K28Kh8hjtGqEgqiNx2mna/H2ql
PRmP6zjzZN7IKw0KKP/32+IVQtQi0Cdd4Xn+GOdwiK1O5tmLOsbdJ1Fu/7xk9TND
TwIDAQABo4IBRjCCAUIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw
SwYIKwYBBQUHAQEEPzA9MDsGCCsGAQUFBzAChi9odHRwOi8vYXBwcy5pZGVudHJ1
c3QuY29tL3Jvb3RzL2RzdHJvb3RjYXgzLnA3YzAfBgNVHSMEGDAWgBTEp7Gkeyxx
+tvhS5B1/8QVYIWJEDBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEB
ATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQu
b3JnMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9jcmwuaWRlbnRydXN0LmNvbS9E
U1RST09UQ0FYM0NSTC5jcmwwHQYDVR0OBBYEFHm0WeZ7tuXkAXOACIjIGlj26Ztu
MA0GCSqGSIb3DQEBCwUAA4IBAQAKcwBslm7/DlLQrt2M51oGrS+o44+/yQoDFVDC
5WxCu2+b9LRPwkSICHXM6webFGJueN7sJ7o5XPWioW5WlHAQU7G75K/QosMrAdSW
9MUgNTP52GE24HGNtLi1qoJFlcDyqSMo59ahy2cI2qBDLKobkx/J3vWraV0T9VuG
WCLKTVXkcGdtwlfFRjlBz4pYg1htmf5X6DYO8A4jqv2Il9DjXA6USbW1FzXSLr9O
he8Y4IWS6wY7bCkjCWDcRQJMEhg76fsO3txE+FiYruq9RUWhiF1myv4Q6W+CyBFC
Dfvp7OOGAN6dEOM4+qR9sdjoSYKEBpsr6GtPAQw4dy753ec5
-----END CERTIFICATE-----
-86
View File
@@ -1,86 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIEQzCCAyugAwIBAgISA1MOaRzFLZim3nvjG7iscMdEMA0GCSqGSIb3DQEBCwUA
MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD
EwJSMzAeFw0yMzEyMTQyMjI5NTVaFw0yNDAzMTMyMjI5NTRaMCAxHjAcBgNVBAMT
FWhvbWVsYWIubWFyaW5vZGV2LmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA
BER7NDX7FANFf72Pqwl5haoczQchiOhMGNyMa5foT0aYG2DeMQeEhpl+17vBzEwu
KewujegQNWZdNHVbGxqdUmWjggIuMIICKjAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0l
BBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYE
FOsys6fx3sqsaEaqklTWasLzlNJzMB8GA1UdIwQYMBaAFBQusxe3WFbLrlAJQOYf
r52LFMLGMFUGCCsGAQUFBwEBBEkwRzAhBggrBgEFBQcwAYYVaHR0cDovL3IzLm8u
bGVuY3Iub3JnMCIGCCsGAQUFBzAChhZodHRwOi8vcjMuaS5sZW5jci5vcmcvMDgG
A1UdEQQxMC+CFWhvbWVsYWIubWFyaW5vZGV2LmNvbYIWbWFyaW5vLW5hcy5zeW5v
bG9neS5tZTATBgNVHSAEDDAKMAgGBmeBDAECATCCAQMGCisGAQQB1nkCBAIEgfQE
gfEA7wB2AEiw42vapkc0D+VqAvqdMOscUgHLVt0sgdm7v6s52IRzAAABjGqqUTkA
AAQDAEcwRQIhAJncD2v9CgQii1KCLJhILJ96Jy7mE3a1ptaZ8sip8k3xAiBXw7ML
wbp/ikIzl0xYTeJsjU7lp7/VI26K+luw++dsMgB1AO7N0GTV2xrOxVy3nbTNE6Iy
h0Z8vOzew1FIWUZxH7WbAAABjGqqUTsAAAQDAEYwRAIgSXZW95Cafz5olbebYbSH
xlvPXhmJaspB7XzEsQP2i6kCIHa1QcGg9vyXJ6apl7WSPIz4DLJb6/ZeaHkPqAC5
NjKPMA0GCSqGSIb3DQEBCwUAA4IBAQBSKsqvA8v8h1WmTjudd8cRWc3RdU6lgq/E
pTXX6XWYFpU+D7jprp/NeW8RmVToQRUi6A3Bv9KnrsHG5HpwJ37SLh6Gv8s2t1A/
8FyNyndjuBKxFSaoJKkqlKUA657dXh67if5WLZYZYQdArRw3Q+fQOfSit8+RomTY
xmNVnRVO5+JRwxwbk/WT8tZHOb3Cd7Ozo7d2vpwH/KqY4RM7ajV8shzulMlAtqfT
ahi4ph8eUm43nCJPsVa4ddQLaV4z8EXoQ/SAsjrMkbAyg4G3uKoCqPUX4lnvqAAW
7kxmH8lJpkh76sz7gT1j/vzk9Fbslk3+8oa8PUO9PYQWfYwzWIs2
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw
WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg
RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP
R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx
sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm
NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg
Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG
/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC
AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB
Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA
FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw
AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw
Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB
gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W
PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl
ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz
CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm
lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4
avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2
yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O
yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids
hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+
HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv
MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX
nLRbwHOoq7hHwg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFYDCCBEigAwIBAgIQQAF3ITfU6UK47naqPGQKtzANBgkqhkiG9w0BAQsFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTIxMDEyMDE5MTQwM1oXDTI0MDkzMDE4MTQwM1ow
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQCt6CRz9BQ385ueK1coHIe+3LffOJCMbjzmV6B493XC
ov71am72AE8o295ohmxEk7axY/0UEmu/H9LqMZshftEzPLpI9d1537O4/xLxIZpL
wYqGcWlKZmZsj348cL+tKSIG8+TA5oCu4kuPt5l+lAOf00eXfJlII1PoOK5PCm+D
LtFJV4yAdLbaL9A4jXsDcCEbdfIwPPqPrt3aY6vrFk/CjhFLfs8L6P+1dy70sntK
4EwSJQxwjQMpoOFTJOwT2e4ZvxCzSow/iaNhUd6shweU9GNx7C7ib1uYgeGJXDR5
bHbvO5BieebbpJovJsXQEOEO3tkQjhb7t/eo98flAgeYjzYIlefiN5YNNnWe+w5y
sR2bvAP5SQXYgd0FtCrWQemsAXaVCg/Y39W9Eh81LygXbNKYwagJZHduRze6zqxZ
Xmidf3LWicUGQSk+WT7dJvUkyRGnWqNMQB9GoZm1pzpRboY7nn1ypxIFeFntPlF4
FQsDj43QLwWyPntKHEtzBRL8xurgUBN8Q5N0s8p0544fAQjQMNRbcTa0B7rBMDBc
SLeCO5imfWCKoqMpgsy6vYMEG6KDA0Gh1gXxG8K28Kh8hjtGqEgqiNx2mna/H2ql
PRmP6zjzZN7IKw0KKP/32+IVQtQi0Cdd4Xn+GOdwiK1O5tmLOsbdJ1Fu/7xk9TND
TwIDAQABo4IBRjCCAUIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw
SwYIKwYBBQUHAQEEPzA9MDsGCCsGAQUFBzAChi9odHRwOi8vYXBwcy5pZGVudHJ1
c3QuY29tL3Jvb3RzL2RzdHJvb3RjYXgzLnA3YzAfBgNVHSMEGDAWgBTEp7Gkeyxx
+tvhS5B1/8QVYIWJEDBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEB
ATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQu
b3JnMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9jcmwuaWRlbnRydXN0LmNvbS9E
U1RST09UQ0FYM0NSTC5jcmwwHQYDVR0OBBYEFHm0WeZ7tuXkAXOACIjIGlj26Ztu
MA0GCSqGSIb3DQEBCwUAA4IBAQAKcwBslm7/DlLQrt2M51oGrS+o44+/yQoDFVDC
5WxCu2+b9LRPwkSICHXM6webFGJueN7sJ7o5XPWioW5WlHAQU7G75K/QosMrAdSW
9MUgNTP52GE24HGNtLi1qoJFlcDyqSMo59ahy2cI2qBDLKobkx/J3vWraV0T9VuG
WCLKTVXkcGdtwlfFRjlBz4pYg1htmf5X6DYO8A4jqv2Il9DjXA6USbW1FzXSLr9O
he8Y4IWS6wY7bCkjCWDcRQJMEhg76fsO3txE+FiYruq9RUWhiF1myv4Q6W+CyBFC
Dfvp7OOGAN6dEOM4+qR9sdjoSYKEBpsr6GtPAQw4dy753ec5
-----END CERTIFICATE-----
-5
View File
@@ -1,5 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgBRfv5A/Ky/0a239v
+dD1tqdxLpNTW4S1Xnu7FhizFyWhRANCAAREezQ1+xQDRX+9j6sJeYWqHM0HIYjo
TBjcjGuX6E9GmBtg3jEHhIaZfte7wcxMLinsLo3oEDVmXTR1WxsanVJl
-----END PRIVATE KEY-----
+6 -5
View File
@@ -3,13 +3,14 @@ version: '3'
services: services:
fbla_api: fbla_api:
image: fbla-api image: fbla-api
restart: unless-stopped
ports: ports:
- "8000:8000" - "8000:8000"
volumes: volumes:
- /var/jenkins_home/logos:/root/FBLA24/fbla-api/logos - /var/jenkins_home/logos:/root/FBLA24/fbla-api/logos
environment: environment:
- POSTGRES_USERNAME - JOBLINK_POSTGRES_USERNAME
- POSTGRES_PASSWORD - JOBLINK_POSTGRES_PASSWORD
- SECRET_KEY - JOBLINK_SECRET_KEY
- POSTGRES_ADDRESS - JOBLINK_POSTGRES_ADDRESS
- POSTGRES_PORT - JOBLINK_POSTGRES_PORT
+4 -4
View File
@@ -20,11 +20,11 @@ var parameters = Argon2Parameters(
); );
final postgres = PostgreSQLConnection( final postgres = PostgreSQLConnection(
Platform.environment['POSTGRES_ADDRESS']!, Platform.environment['JOBLINK_POSTGRES_ADDRESS']!,
int.parse(Platform.environment['POSTGRES_PORT']!), int.parse(Platform.environment['JOBLINK_POSTGRES_PORT']!),
'fbla', 'fbla',
username: Platform.environment['POSTGRES_USERNAME'], username: Platform.environment['JOBLINK_POSTGRES_USERNAME'],
password: Platform.environment['POSTGRES_PASSWORD'], password: Platform.environment['JOBLINK_POSTGRES_PASSWORD'],
); );
Future<void> main() async { Future<void> main() async {
+307 -133
View File
@@ -2,66 +2,57 @@ import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:argon2/argon2.dart';
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart'; import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
import 'package:http/http.dart' as http;
import 'package:postgres/postgres.dart'; import 'package:postgres/postgres.dart';
import 'package:shelf/shelf.dart'; import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io; import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf_router/shelf_router.dart'; import 'package:shelf_router/shelf_router.dart';
import 'package:http/http.dart' as http;
import 'package:argon2/argon2.dart';
SecretKey secretKey = SecretKey(Platform.environment['JOBLINK_SECRET_KEY']!);
SecretKey secretKey = SecretKey(Platform.environment['SECRET_KEY']!);
enum BusinessType { enum BusinessType {
food, food,
shop, shop,
outdoors, outdoors,
manufacturing, manufacturing,
entertainment,
other, other,
} }
enum JobType { cashier, server, mechanic, other }
class Business { class Business {
int id; int id;
String name; String name;
String description; String description;
BusinessType type; String? website;
String website; String? contactName;
String contactName; String? contactEmail;
String contactEmail; String? contactPhone;
String contactPhone; String? notes;
String notes; String? locationName;
String locationName; String? locationAddress;
String locationAddress;
Business({ Business(
required this.id, {required this.id,
required this.name, required this.name,
required this.description, required this.description,
required this.type, this.website,
required this.website, this.contactName,
required this.contactName, this.contactEmail,
required this.contactEmail, this.contactPhone,
required this.contactPhone, this.notes,
required this.notes, this.locationName,
required this.locationName, this.locationAddress});
required this.locationAddress,
});
factory Business.fromJson(Map<String, dynamic> json) { factory Business.fromJson(Map<String, dynamic> json) {
bool typeValid = true;
try {
BusinessType.values.byName(json['type']);
} catch (e) {
typeValid = false;
}
return Business( return Business(
id: json['id'], id: json['id'],
name: json['name'], name: json['name'],
description: json['description'], description: json['description'],
type: typeValid
? BusinessType.values.byName(json['type'])
: BusinessType.other,
website: json['website'], website: json['website'],
contactName: json['contactName'], contactName: json['contactName'],
contactEmail: json['contactEmail'], contactEmail: json['contactEmail'],
@@ -73,9 +64,46 @@ class Business {
} }
} }
class JobListing {
String? id;
String? businessId;
String name;
String description;
JobType type;
String? wage;
String? link;
JobListing(
{this.id,
this.businessId,
required this.name,
required this.description,
required this.type,
this.wage,
this.link});
factory JobListing.fromJson(Map<String, dynamic> json) {
bool typeValid = true;
try {
JobType.values.byName(json['type']);
} catch (e) {
typeValid = false;
}
return JobListing(
id: json['id'],
businessId: json['businessId'],
name: json['name'],
description: json['description'],
type: typeValid ? JobType.values.byName(json['type']) : JobType.other,
wage: json['wage'],
link: json['link'],
);
}
}
Future<String> fetchBusinessData() async { Future<String> fetchBusinessData() async {
final result = await postgres.query( final result = await postgres.query('''
'''
SELECT json_agg( SELECT json_agg(
json_build_object( json_build_object(
'id', id, 'id', id,
@@ -91,12 +119,9 @@ Future<String> fetchBusinessData() async {
'locationAddress', "locationAddress" 'locationAddress', "locationAddress"
) )
) FROM businesses ) FROM businesses
''' ''');
);
var encoded = json.encode(result); var encoded = json.encode(result[0][0]);
var decoded = json.decode(encoded);
encoded = json.encode(decoded[0][0]);
return encoded; return encoded;
} }
@@ -104,17 +129,14 @@ Future<String> fetchBusinessData() async {
String _hostname = 'localhost'; String _hostname = 'localhost';
const _port = 8000; const _port = 8000;
final postgres = PostgreSQLConnection( final postgres = PostgreSQLConnection(
Platform.environment['POSTGRES_ADDRESS']!, Platform.environment['JOBLINK_POSTGRES_ADDRESS']!,
int.parse(Platform.environment['POSTGRES_PORT']!), int.parse(Platform.environment['JOBLINK_POSTGRES_PORT']!),
'fbla', 'fbla',
username: Platform.environment['POSTGRES_USERNAME'], username: Platform.environment['JOBLINK_POSTGRES_USERNAME'],
password: Platform.environment['POSTGRES_PASSWORD'], password: Platform.environment['JOBLINK_POSTGRES_PASSWORD'],
); );
void main() async { void main() async {
await postgres.open(); await postgres.open();
@@ -125,8 +147,88 @@ void main() async {
print('Hello received'); print('Hello received');
return Response.ok( return Response.ok(
'Hello, World!', 'Hello, World!',
headers: {'Access-Control-Allow-Origin': '*'}, headers: {'Access-Control-Allow-Origin': '*'},
);
});
app.get('/fbla-api/businessdata/overview', (Request request) async {
print('business overview request received');
var filters = request.url.queryParameters['filters']?.split(',') ??
JobType.values.asNameMap().keys;
// List<Map<String, List<Map<String, dynamic>>>> this is the real type lol
List<dynamic> output = [];
for (int i = 0; i < filters.length; i++) {
var postgresResult = (await postgres.query('''
SELECT json_agg(
json_build_object(
'id', id,
'name', name,
'description', description,
'website', website,
'contactEmail', "contactEmail",
'contactPhone', "contactPhone",
'locationName', "locationName"
)
) FROM public.businesses WHERE id IN (SELECT "businessId" FROM public.listings WHERE type='${filters.elementAt(i)}')
'''))[0][0];
if (postgresResult != null) {
output.add({filters.elementAt(i): postgresResult});
}
}
return Response.ok(
json.encode(output),
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'text/plain'
},
);
});
app.get('/fbla-api/businessdata/business/<business>',
(Request request, String business) async {
print('idividual business data request received');
var result = (await postgres.query('''
SELECT
json_build_object(
'id', b.id,
'name', b.name,
'description', b.description,
'website', b.website,
'contactName', b."contactName",
'contactEmail', b."contactEmail",
'contactPhone', b."contactPhone",
'notes', b.notes,
'locationName', b."locationName",
'locationAddress', b."locationAddress",
'listings',
json_agg(
json_build_object(
'id', l.id,
'name', l.name,
'description', l.description,
'type', l.type,
'wage', l.wage,
'link', l.link
)
)
)
FROM businesses b
LEFT JOIN listings l ON b.id = l.business_id
WHERE b.id = $business
GROUP BY b.id;
'''))[0][0];
return Response.ok(
json.encode(result),
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'text/plain'
},
); );
}); });
app.get('/fbla-api/businessdata', (Request request) async { app.get('/fbla-api/businessdata', (Request request) async {
@@ -134,7 +236,10 @@ void main() async {
final output = await fetchBusinessData(); final output = await fetchBusinessData();
return Response.ok( return Response.ok(
output.toString(), output.toString(),
headers: {'Access-Control-Allow-Origin': '*'}, headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'text/plain'
},
); );
}); });
app.get('/fbla-api/logos/<logo>', (Request request, String logoId) { app.get('/fbla-api/logos/<logo>', (Request request, String logoId) {
@@ -161,15 +266,12 @@ void main() async {
var json = jsonDecode(payload); var json = jsonDecode(payload);
Business business = Business.fromJson(json); Business business = Business.fromJson(json);
await postgres.query( await postgres.query('''
''' INSERT INTO businesses (name, description, website, "contactName", "contactPhone", "contactEmail", notes, "locationName", "locationAddress")
INSERT INTO businesses (name, description, type, website, "contactName", "contactPhone", "contactEmail", notes, "locationName", "locationAddress") VALUES ('${business.name.replaceAll("'", "''")}', '${business.description.replaceAll("'", "''")}', '${business.website ?? 'NULL'}', '${business.contactName?.replaceAll("'", "''") ?? 'NULL'}', '${business.contactPhone ?? 'NULL'}', '${business.contactEmail ?? 'NULL'}', '${business.notes?.replaceAll("'", "''") ?? 'NULL'}', '${business.locationName?.replaceAll("'", "''") ?? 'NULL'}', '${business.locationAddress?.replaceAll("'", "''") ?? 'NULL'}')
VALUES ('${business.name.replaceAll("'", "''")}', '${business.description.replaceAll("'", "''")}', '${business.type.name}', '${business.website}', '${business.contactName.replaceAll("'", "''")}', '${business.contactPhone}', '${business.contactEmail}', '${business.notes.replaceAll("'", "''")}', '${business.locationName.replaceAll("'", "''")}', '${business.locationAddress.replaceAll("'", "''")}') ''');
'''
);
final dbBusiness = await postgres.query( final dbBusiness = await postgres.query('''SELECT * FROM public.businesses
'''SELECT * FROM public.businesses
ORDER BY id DESC LIMIT 1'''); ORDER BY id DESC LIMIT 1''');
var id = dbBusiness[0][0]; var id = dbBusiness[0][0];
var logoResponse = await http.get( var logoResponse = await http.get(
@@ -180,8 +282,8 @@ void main() async {
await File('logos/$id.png').writeAsBytes(logoResponse.bodyBytes); await File('logos/$id.png').writeAsBytes(logoResponse.bodyBytes);
} }
return Response.ok( return Response.ok(
id.toString(), id.toString(),
headers: {'Access-Control-Allow-Origin': '*'}, headers: {'Access-Control-Allow-Origin': '*'},
); );
} on JWTExpiredException { } on JWTExpiredException {
print('JWT Expired'); print('JWT Expired');
@@ -190,14 +292,47 @@ void main() async {
} }
return Response.unauthorized( return Response.unauthorized(
'unauthorized', 'unauthorized',
headers: {'Access-Control-Allow-Origin': '*'},
);
});
app.post('/fbla-api/createlisting', (Request request) async {
print('create business request received');
final payload = await request.readAsString();
var auth = request.headers['Authorization']?.replaceAll('Bearer ', '');
try {
JWT.verify(auth!, secretKey);
var json = jsonDecode(payload);
JobListing listing = JobListing.fromJson(json);
await postgres.query('''
INSERT INTO listings ("businessId", name, description, type, wage, link)
VALUES ('${listing.businessId}' '${listing.name.replaceAll("'", "''")}', '${listing.description.replaceAll("'", "''")}', '${listing.type.name}', '${listing.wage ?? 'NULL'}', '${listing.link?.replaceAll("'", "''") ?? 'NULL'}')
''');
final dbListing = await postgres.query('''SELECT id FROM public.listings
ORDER BY id DESC LIMIT 1''');
var id = dbListing[0][0];
return Response.ok(
id.toString(),
headers: {'Access-Control-Allow-Origin': '*'}, headers: {'Access-Control-Allow-Origin': '*'},
);
} on JWTExpiredException {
print('JWT Expired');
} on JWTException catch (e) {
print(e.message);
}
return Response.unauthorized(
'unauthorized',
headers: {'Access-Control-Allow-Origin': '*'},
); );
}); });
app.post('/fbla-api/deletebusiness', (Request request) async { app.post('/fbla-api/deletebusiness', (Request request) async {
print('delete business request received'); print('delete business request received');
final payload = await request.readAsString(); final payload = await request.readAsString();
var auth = request.headers['Authorization']?.replaceAll('Bearer ', ''); var auth = request.headers['Authorization']?.replaceAll('Bearer ', '');
try { try {
@@ -205,33 +340,54 @@ void main() async {
var json = jsonDecode(payload); var json = jsonDecode(payload);
var id = json['id']; var id = json['id'];
await postgres.query( await postgres.query('DELETE FROM public.businesses WHERE id=$id;');
'''
DELETE FROM public.businesses
WHERE id IN
($id);
'''
);
try{ try {
await File('logos/$id.png').delete(); await File('logos/$id.png').delete();
} catch(e) { } catch (e) {
print(e); print('Failure to delete logo! $e');
} }
return Response.ok( return Response.ok(
id.toString(), id.toString(),
headers: {'Access-Control-Allow-Origin': '*'}, headers: {'Access-Control-Allow-Origin': '*'},
); );
} on JWTExpiredException { } on JWTExpiredException {
print('JWT Expired'); print('JWT Expired');
} on JWTException catch (e) { } on JWTException catch (e) {
print(e.message); print(e.message);
} }
return Response.unauthorized( return Response.unauthorized(
'unauthorized', 'unauthorized',
headers: {'Access-Control-Allow-Origin': '*'},
);
});
app.post('/fbla-api/deletelisting', (Request request) async {
print('delete listing request received');
final payload = await request.readAsString();
var auth = request.headers['Authorization']?.replaceAll('Bearer ', '');
try {
JWT.verify(auth!, secretKey);
var json = jsonDecode(payload);
var id = json['id'];
await postgres.query('DELETE FROM public.listings WHERE id=$id;');
return Response.ok(
id.toString(),
headers: {'Access-Control-Allow-Origin': '*'}, headers: {'Access-Control-Allow-Origin': '*'},
);
} on JWTExpiredException {
print('JWT Expired');
} on JWTException catch (e) {
print(e.message);
}
return Response.unauthorized(
'unauthorized',
headers: {'Access-Control-Allow-Origin': '*'},
); );
}); });
app.post('/fbla-api/editbusiness', (Request request) async { app.post('/fbla-api/editbusiness', (Request request) async {
@@ -245,13 +401,11 @@ void main() async {
var json = jsonDecode(payload); var json = jsonDecode(payload);
Business business = Business.fromJson(json); Business business = Business.fromJson(json);
await postgres.query( await postgres.query('''
'''
UPDATE businesses SET UPDATE businesses SET
name = '${business.name.replaceAll("'", "''")}'::text, description = '${business.description.replaceAll("'", "''")}'::text, website = '${business.website}'::text, type = '${business.type.name}'::text, "contactName" = '${business.contactName.replaceAll("'", "''")}'::text, "contactPhone" = '${business.contactPhone}'::text, "contactEmail" = '${business.contactEmail}'::text, notes = '${business.notes.replaceAll("'", "''")}'::text, "locationName" = '${business.locationName.replaceAll("'", "''")}'::text, "locationAddress" = '${business.locationAddress.replaceAll("'", "''")}'::text WHERE name = '${business.name.replaceAll("'", "''").replaceAll("\"", "\"\"")}'::text, description = '${business.description.replaceAll("'", "''").replaceAll("\"", "\"\"")}'::text, website = '${business.website!}'::text, "contactName" = '${business.contactName!.replaceAll("'", "''").replaceAll("\"", "\"\"")}'::text, "contactPhone" = '${business.contactPhone!}'::text, "contactEmail" = '${business.contactEmail!}'::text, notes = '${business.notes!.replaceAll("'", "''").replaceAll("\"", "\"\"")}'::text, "locationName" = '${business.locationName!.replaceAll("'", "''").replaceAll("\"", "\"\"")}'::text, "locationAddress" = '${business.locationAddress!.replaceAll("'", "''").replaceAll("\"", "\"\"")}'::text WHERE
id = ${business.id}; id = ${business.id};
''' ''');
);
var logoResponse = await http.get( var logoResponse = await http.get(
Uri.http('logo.clearbit.com', '/${business.website}'), Uri.http('logo.clearbit.com', '/${business.website}'),
@@ -259,18 +413,17 @@ void main() async {
try { try {
await File('logos/${business.id}.png').delete(); await File('logos/${business.id}.png').delete();
} catch(e) { } catch (e) {
print(e); print('Failure to delete logo! $e');
} }
if (logoResponse.headers.toString().contains('image/png')) { if (logoResponse.headers.toString().contains('image/png')) {
await File('logos/${business.id}.png').writeAsBytes(logoResponse.bodyBytes); await File('logos/${business.id}.png')
.writeAsBytes(logoResponse.bodyBytes);
} }
return Response.ok( return Response.ok(
business.id.toString(), business.id.toString(),
headers: {'Access-Control-Allow-Origin': '*'}, headers: {'Access-Control-Allow-Origin': '*'},
); );
} on JWTExpiredException { } on JWTExpiredException {
print('JWT Expired'); print('JWT Expired');
@@ -279,8 +432,40 @@ void main() async {
} }
return Response.unauthorized( return Response.unauthorized(
'unauthorized', 'unauthorized',
headers: {'Access-Control-Allow-Origin': '*'},
);
});
app.post('/fbla-api/editlisting', (Request request) async {
print('edit listing request received');
final payload = await request.readAsString();
var auth = request.headers['Authorization']?.replaceAll('Bearer ', '');
try {
JWT.verify(auth!, secretKey);
var json = jsonDecode(payload);
JobListing listing = JobListing.fromJson(json);
await postgres.query('''
UPDATE listings SET
"businessId" = ${listing.businessId}, name = '${listing.name.replaceAll("'", "''")}'::text, description = '${listing.description.replaceAll("'", "''")}'::text, type = '${listing.type.name}'::text, wage = '${listing.wage ?? 'NULL'}'::text, link = '${listing.link?.replaceAll("'", "''") ?? 'NULL'}'::text WHERE
id = ${listing.id};
''');
return Response.ok(
listing.id.toString(),
headers: {'Access-Control-Allow-Origin': '*'}, headers: {'Access-Control-Allow-Origin': '*'},
);
} on JWTExpiredException {
print('JWT Expired');
} on JWTException catch (e) {
print(e.message);
}
return Response.unauthorized(
'unauthorized',
headers: {'Access-Control-Allow-Origin': '*'},
); );
}); });
app.post('/fbla-api/signin', (Request request) async { app.post('/fbla-api/signin', (Request request) async {
@@ -291,13 +476,12 @@ void main() async {
var username = json['username']; var username = json['username'];
var password = json['password']; var password = json['password'];
var saltDb = await postgres.query( var saltDb = await postgres
'SELECT salt FROM users WHERE username=\'$username\'' .query('SELECT salt FROM users WHERE username=\'$username\'');
);
if (saltDb.isEmpty) { if (saltDb.isEmpty) {
return Response.unauthorized( return Response.unauthorized(
'invalid username', 'invalid username',
headers: {'Access-Control-Allow-Origin': '*'}, headers: {'Access-Control-Allow-Origin': '*'},
); );
} }
@@ -319,17 +503,13 @@ void main() async {
argon2.generateBytes(passwordBytes, result); argon2.generateBytes(passwordBytes, result);
var resultHex = result.toHexString(); var resultHex = result.toHexString();
var passwordHashDb = await postgres.query( var passwordHashDb = await postgres
'SELECT password_hash FROM users WHERE username=\'$username\'' .query('SELECT password_hash FROM users WHERE username=\'$username\'');
);
var passwordHash = passwordHashDb[0][0].toString(); var passwordHash = passwordHashDb[0][0].toString();
if (passwordHash == resultHex) { if (passwordHash == resultHex) {
final jwt = JWT( final jwt = JWT(
{ {'username': username},
'username': username
},
); );
final token = jwt.sign(secretKey); final token = jwt.sign(secretKey);
try { try {
@@ -341,14 +521,13 @@ void main() async {
} }
return Response.ok( return Response.ok(
token.toString(), token.toString(),
headers: {'Access-Control-Allow-Origin': '*'}, headers: {'Access-Control-Allow-Origin': '*'},
); );
} else { } else {
return Response.unauthorized( return Response.unauthorized(
'invalid password', 'invalid password',
headers: {'Access-Control-Allow-Origin': '*'}, headers: {'Access-Control-Allow-Origin': '*'},
); );
} }
}); });
@@ -366,7 +545,8 @@ void main() async {
var password = json['password']; var password = json['password'];
var r = Random.secure(); var r = Random.secure();
String randomSalt = String.fromCharCodes(List.generate(32, (index) => r.nextInt(33) + 89)); String randomSalt = String.fromCharCodes(
List.generate(32, (index) => r.nextInt(33) + 89));
final salt = randomSalt.toBytesLatin1(); final salt = randomSalt.toBytesLatin1();
var parameters = Argon2Parameters( var parameters = Argon2Parameters(
@@ -384,27 +564,24 @@ void main() async {
argon2.generateBytes(passwordBytes, result); argon2.generateBytes(passwordBytes, result);
var resultHex = result.toHexString(); var resultHex = result.toHexString();
postgres.query( postgres.query('''
'''
INSERT INTO public.users (username, password_hash, salt) INSERT INTO public.users (username, password_hash, salt)
VALUES ('$username', '$resultHex', '$randomSalt') VALUES ('$username', '$resultHex', '$randomSalt')
''' ''');
);
return Response.ok( return Response.ok(
username, username,
headers: {'Access-Control-Allow-Origin': '*'}, headers: {'Access-Control-Allow-Origin': '*'},
); );
} on JWTExpiredException { } on JWTExpiredException {
print('JWT Expired'); print('JWT Expired');
} on JWTException catch (e) { } on JWTException catch (e) {
print(e.message); print(e.message);
} }
return Response.unauthorized( return Response.unauthorized(
'unauthorized', 'unauthorized',
headers: {'Access-Control-Allow-Origin': '*'}, headers: {'Access-Control-Allow-Origin': '*'},
); );
}); });
app.post('/fbla-api/deleteuser', (Request request) async { app.post('/fbla-api/deleteuser', (Request request) async {
@@ -419,18 +596,15 @@ void main() async {
var json = jsonDecode(payload); var json = jsonDecode(payload);
var username = json['username']; var username = json['username'];
postgres.query( postgres.query('''
'''
DELETE FROM public.users DELETE FROM public.users
WHERE username IN ('$username'); WHERE username IN ('$username');
''' ''');
);
return Response.ok( return Response.ok(
username, username,
headers: {'Access-Control-Allow-Origin': '*'}, headers: {'Access-Control-Allow-Origin': '*'},
); );
} on JWTExpiredException { } on JWTExpiredException {
print('JWT Expired'); print('JWT Expired');
} on JWTException catch (e) { } on JWTException catch (e) {
@@ -438,8 +612,8 @@ void main() async {
} }
return Response.unauthorized( return Response.unauthorized(
'unauthorized', 'unauthorized',
headers: {'Access-Control-Allow-Origin': '*'}, headers: {'Access-Control-Allow-Origin': '*'},
); );
}); });
app.get('/fbla-api/marinodev', (Request request) async { app.get('/fbla-api/marinodev', (Request request) async {
@@ -449,11 +623,11 @@ void main() async {
List<int> content = logo.readAsBytesSync(); List<int> content = logo.readAsBytesSync();
return Response.ok( return Response.ok(
content, content,
headers: { headers: {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
'Content-Type': 'image/svg+xml' 'Content-Type': 'image/svg+xml'
}, },
); );
}); });
+82 -45
View File
@@ -5,10 +5,15 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: _fe_analyzer_shared name: _fe_analyzer_shared
sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 sha256: "5aaf60d96c4cd00fe7f21594b5ad6a1b699c80a27420f8a837f4d68473ef09e3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "64.0.0" version: "68.0.0"
_macros:
dependency: transitive
description: dart
source: sdk
version: "0.1.0"
adaptive_number: adaptive_number:
dependency: transitive dependency: transitive
description: description:
@@ -21,10 +26,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: analyzer name: analyzer
sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" sha256: "21f1d3720fd1c70316399d5e2bccaebb415c434592d778cce8acb967b8578808"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.2.0" version: "6.5.0"
argon2: argon2:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -37,10 +42,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: args name: args
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.2" version: "2.5.0"
async: async:
dependency: transitive dependency: transitive
description: description:
@@ -61,10 +66,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: buffer name: buffer
sha256: "8962c12174f53e2e848a6acd7ac7fd63d8a1a6a316c20c458a832d87eba5422a" sha256: "389da2ec2c16283c8787e0adaede82b1842102f8c8aae2f49003a766c5c6b3d1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.2.3"
charcode: charcode:
dependency: transitive dependency: transitive
description: description:
@@ -73,6 +78,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.1" version: "1.3.1"
clock:
dependency: transitive
description:
name: clock
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
url: "https://pub.dev"
source: hosted
version: "1.1.1"
collection: collection:
dependency: transitive dependency: transitive
description: description:
@@ -93,10 +106,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: coverage name: coverage
sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097" sha256: "3945034e86ea203af7a056d98e98e42a5518fff200d6e8e6647e1886b07e936e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.6.3" version: "1.8.0"
crypto: crypto:
dependency: transitive dependency: transitive
description: description:
@@ -109,10 +122,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: dart_jsonwebtoken name: dart_jsonwebtoken
sha256: "6703695f581fc54d0a7e5f281c5538735167605bb9e5abd208c8b330625a92b1" sha256: "346e9a21e4bf6e6a431e19ece00ebb2e3668e1e339cabdf6f46d18d88692a848"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.12.1" version: "2.14.0"
ed25519_edwards: ed25519_edwards:
dependency: transitive dependency: transitive
description: description:
@@ -141,10 +154,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: frontend_server_client name: frontend_server_client
sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.2.0" version: "4.0.0"
glob: glob:
dependency: transitive dependency: transitive
description: description:
@@ -165,10 +178,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: http name: http
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.2.1"
http_methods: http_methods:
dependency: transitive dependency: transitive
description: description:
@@ -205,10 +218,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: js name: js
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.7" version: "0.7.1"
lints: lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
@@ -225,30 +238,38 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.2.0"
macros:
dependency: transitive
description:
name: macros
sha256: "12e8a9842b5a7390de7a781ec63d793527582398d16ea26c60fed58833c9ae79"
url: "https://pub.dev"
source: hosted
version: "0.1.0-main.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.16" version: "0.12.16+1"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.15.0"
mime: mime:
dependency: transitive dependency: transitive
description: description:
name: mime name: mime
sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.4" version: "1.0.5"
node_preamble: node_preamble:
dependency: transitive dependency: transitive
description: description:
@@ -269,10 +290,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.3" version: "1.9.0"
pedantic: pedantic:
dependency: transitive dependency: transitive
description: description:
@@ -285,10 +306,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: pointycastle name: pointycastle
sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.7.3" version: "3.9.1"
pool: pool:
dependency: transitive dependency: transitive
description: description:
@@ -301,10 +322,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: postgres name: postgres
sha256: "98457afc06dd3f9d6892c178ea03ca9659e454107c9be90111e607691998d70d" sha256: f8e4f14734d096277f77ed5dddefcbc1ce18f8f7db5b7ff4b5dd6df2d9db2730
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.6.2" version: "2.6.4"
pub_semver: pub_semver:
dependency: transitive dependency: transitive
description: description:
@@ -365,10 +386,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shelf_web_socket name: shelf_web_socket
sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.4" version: "2.0.0"
source_map_stack_trace: source_map_stack_trace:
dependency: transitive dependency: transitive
description: description:
@@ -429,26 +450,26 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: test name: test
sha256: "9b0dd8e36af4a5b1569029949d50a52cb2a2a2fdaa20cebb96e6603b9ae241f9" sha256: "7ee44229615f8f642b68120165ae4c2a75fe77ae2065b1e55ae4711f6cf0899e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.24.6" version: "1.25.7"
test_api: test_api:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.1" version: "0.7.2"
test_core: test_core:
dependency: transitive dependency: transitive
description: description:
name: test_core name: test_core
sha256: "4bef837e56375537055fdbbbf6dd458b1859881f4c7e6da936158f77d61ab265" sha256: "55ea5a652e38a1dfb32943a7973f3681a60f872f8c3a05a14664ad54ef9c6696"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.6" version: "0.6.4"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@@ -469,10 +490,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vm_service name: vm_service
sha256: "0fae432c85c4ea880b33b497d32824b97795b04cdaa74d270219572a1f50268d" sha256: "360c4271613beb44db559547d02f8b0dc044741d0eeb9aa6ccdb47e8ec54c63a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "11.9.0" version: "14.2.3"
watcher: watcher:
dependency: transitive dependency: transitive
description: description:
@@ -481,22 +502,38 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
web:
dependency: transitive
description:
name: web
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
url: "https://pub.dev"
source: hosted
version: "0.5.1"
web_socket:
dependency: transitive
description:
name: web_socket
sha256: "24301d8c293ce6fe327ffe6f59d8fd8834735f0ec36e4fd383ec7ff8a64aa078"
url: "https://pub.dev"
source: hosted
version: "0.1.5"
web_socket_channel: web_socket_channel:
dependency: transitive dependency: transitive
description: description:
name: web_socket_channel name: web_socket_channel
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b sha256: a2d56211ee4d35d9b344d9d4ce60f362e4f5d1aafb988302906bd732bc731276
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.0" version: "3.0.0"
webkit_inspection_protocol: webkit_inspection_protocol:
dependency: transitive dependency: transitive
description: description:
name: webkit_inspection_protocol name: webkit_inspection_protocol
sha256: "67d3a8b6c79e1987d19d848b0892e582dbb0c66c57cc1fef58a177dd2aa2823d" sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.2.1"
yaml: yaml:
dependency: transitive dependency: transitive
description: description:
@@ -506,4 +543,4 @@ packages:
source: hosted source: hosted
version: "3.1.2" version: "3.1.2"
sdks: sdks:
dart: ">=3.0.6 <4.0.0" dart: ">=3.4.0-256.0.dev <4.0.0"
+16 -19
View File
@@ -1,18 +1,17 @@
import 'dart:convert'; import 'dart:convert';
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
import 'package:fbla_api/fbla_api.dart';
import 'package:test/test.dart';
import 'package:http/http.dart' as http;
import 'dart:io'; import 'dart:io';
final apiAddress = 'https://homelab.marinodev.com'; import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
SecretKey secretKey = SecretKey(Platform.environment['SECRET_KEY']!); import 'package:fbla_api/fbla_api.dart';
import 'package:http/http.dart' as http;
import 'package:test/test.dart';
void main() async{ final apiAddress = 'https://homelab.marinodev.com';
SecretKey secretKey = SecretKey(Platform.environment['JOBLINK_SECRET_KEY']!);
void main() async {
final jwt = JWT( final jwt = JWT(
{ {'username': 'tmp'},
'username': 'tmp'
},
); );
final token = jwt.sign(secretKey); final token = jwt.sign(secretKey);
@@ -43,13 +42,13 @@ void main() async{
"password": "tmp" "password": "tmp"
} }
'''; ''';
var response = await http.post(Uri.parse('$apiAddress, /fbla-api/createuser'), var response = await http.post(Uri.parse('$apiAddress/fbla-api/createuser'),
body: json, body: json,
headers: {'Authorization': token}).timeout(const Duration(seconds: 20)); headers: {'Authorization': token}).timeout(const Duration(seconds: 20));
expect(response.statusCode, 200); expect(response.statusCode, 200);
expect(response.body, 'tmp'); expect(response.body, 'tmp');
}); });
test('sign-in', () async{ test('sign-in', () async {
var json = ''' var json = '''
{ {
"username": "tmp", "username": "tmp",
@@ -64,17 +63,15 @@ void main() async{
expect(response.statusCode, 200); expect(response.statusCode, 200);
expect(JWT.decode(response.body).payload['username'], 'tmp'); expect(JWT.decode(response.body).payload['username'], 'tmp');
}); });
test('delete-user', () async{ test('delete-user', () async {
var json = ''' var json = '''
{ {
"username": "tmp" "username": "tmp"
} }
'''; ''';
var response = await http.post( var response = await http.post(Uri.parse('$apiAddress/fbla-api/deleteuser'),
Uri.parse('$apiAddress/fbla-api/deleteuser'),
body: json, body: json,
headers: {'Authorization': token}).timeout(const Duration(seconds: 20) headers: {'Authorization': token}).timeout(const Duration(seconds: 20));
);
expect(response.statusCode, 200); expect(response.statusCode, 200);
expect(response.body, 'tmp'); expect(response.body, 'tmp');
}); });
@@ -84,7 +81,6 @@ void main() async{
"id": 0, "id": 0,
"name": "tmp", "name": "tmp",
"description": "tmp", "description": "tmp",
"type": "business",
"website": "tmp", "website": "tmp",
"contactName": "tmp", "contactName": "tmp",
"contactEmail": "tmp", "contactEmail": "tmp",
@@ -94,7 +90,8 @@ void main() async{
"locationAddress": "tmp" "locationAddress": "tmp"
} }
'''; ''';
var response = await http.post(Uri.parse('$apiAddress/fbla-api/createbusiness'), var response = await http.post(
Uri.parse('$apiAddress/fbla-api/createbusiness'),
body: json, body: json,
headers: {'Authorization': token}).timeout(const Duration(seconds: 20)); headers: {'Authorization': token}).timeout(const Duration(seconds: 20));
+17
View File
@@ -6,6 +6,8 @@ My version of the app can be found at [marinodev.com/fbla/](https://marinodev.co
Pre-built files for several operating systems (built with my API) can be found in the releases tab, Pre-built files for several operating systems (built with my API) can be found in the releases tab,
alternatively, you can compile it yourself for use with your own API and database (see API readme): alternatively, you can compile it yourself for use with your own API and database (see API readme):
### Windows/Linux
1. Install [Flutter](https://docs.flutter.dev/get-started/install) 1. Install [Flutter](https://docs.flutter.dev/get-started/install)
2. Use `flutter doctor` to make sure your environment is fully set up for your platform 2. Use `flutter doctor` to make sure your environment is fully set up for your platform
3. Clone the repo 3. Clone the repo
@@ -18,3 +20,18 @@ cd FBLA24/fbla_ui/
4. Run `flutter pub get` to install all dependencies 4. Run `flutter pub get` to install all dependencies
5. Optional: set `apiAddress` at the top of `lib/api_logic.dart` 5. Optional: set `apiAddress` at the top of `lib/api_logic.dart`
6. Build app with `flutter build --release` 6. Build app with `flutter build --release`
### MacOS
*These instructions are temporary and will change as I try MacOS*
1. Install [Flutter](https://docs.flutter.dev/get-started/install)
2. Use `flutter doctor` to make sure your environment is fully set up for your platform
3. Clone the repo
```
git clone https://git.marinodev.com/MarinoDev/FBLA24.git
cd FBLA24/fbla_ui/
```
4. Run `flutter pub get` to install all dependencies
5. Optional: set `apiAddress` at the top of `lib/api_logic.dart`
6. Build app with `flutter build --release`
+49 -7
View File
@@ -8,8 +8,6 @@ import 'package:http/http.dart' as http;
var apiAddress = 'https://homelab.marinodev.com/fbla-api'; var apiAddress = 'https://homelab.marinodev.com/fbla-api';
var client = http.Client(); var client = http.Client();
// var apiAddress = '192.168.0.114:8000'; // var apiAddress = '192.168.0.114:8000';
// var apiLogoAddress = 'https://homelab.marinodev.com/fbla-api';
// var apiLogoAddress = 'http://192.168.0.114:8000';
Future fetchBusinessData() async { Future fetchBusinessData() async {
try { try {
@@ -31,13 +29,59 @@ Future fetchBusinessData() async {
} }
} }
Future fetchBusinessDataOverview() async {
try {
var response = await http
.get(Uri.parse('$apiAddress/businessdata/overview'))
.timeout(const Duration(seconds: 20));
if (response.statusCode == 200) {
var decodedResponse = json.decode(response.body);
Map<JobType, List<Business>> groupedBusinesses = {};
for (int i = 0; i < decodedResponse.length; i++) {
groupedBusinesses.addAll({
JobType.values.byName(decodedResponse[i]):
decodedResponse.map((json) => Business.fromJson(json)).toList()
});
}
return groupedBusinesses;
} else {
return 'Error ${response.statusCode}! Please try again later!';
}
} on TimeoutException {
return 'Unable to connect to server (timeout).\nPlease try again later.';
} on SocketException {
return 'Unable to connect to server (socket exception).\nPlease check your internet connection.\n';
}
}
Future fetchBusiness(int id) async {
try {
var response = await http
.get(Uri.parse('$apiAddress/businessdata/business/$id'))
.timeout(const Duration(seconds: 20));
if (response.statusCode == 200) {
var decodedResponse = json.decode(response.body);
Business business = Business.fromJson(decodedResponse);
return business;
} else {
return 'Error ${response.statusCode}! Please try again later!';
}
} on TimeoutException {
return 'Unable to connect to server (timeout).\nPlease try again later.';
} on SocketException {
return 'Unable to connect to server (socket exception).\nPlease check your internet connection.\n';
}
}
Future createBusiness(Business business, String jwt) async { Future createBusiness(Business business, String jwt) async {
var json = ''' var json = '''
{ {
"id": ${business.id}, "id": ${business.id},
"name": "${business.name}", "name": "${business.name}",
"description": "${business.description}", "description": "${business.description}",
"type": "${business.type.name}",
"website": "${business.website}", "website": "${business.website}",
"contactName": "${business.contactName}", "contactName": "${business.contactName}",
"contactEmail": "${business.contactEmail}", "contactEmail": "${business.contactEmail}",
@@ -62,10 +106,10 @@ Future createBusiness(Business business, String jwt) async {
} }
} }
Future deleteBusiness(Business business, String jwt) async { Future deleteBusiness(int id, String jwt) async {
var json = ''' var json = '''
{ {
"id": ${business.id} "id": $id
} }
'''; ''';
@@ -89,7 +133,6 @@ Future editBusiness(Business business, String jwt) async {
"id": ${business.id}, "id": ${business.id},
"name": "${business.name}", "name": "${business.name}",
"description": "${business.description}", "description": "${business.description}",
"type": "${business.type.name}",
"website": "${business.website}", "website": "${business.website}",
"contactName": "${business.contactName}", "contactName": "${business.contactName}",
"contactEmail": "${business.contactEmail}", "contactEmail": "${business.contactEmail}",
@@ -99,7 +142,6 @@ Future editBusiness(Business business, String jwt) async {
"locationAddress": "${business.locationAddress}" "locationAddress": "${business.locationAddress}"
} }
'''; ''';
print(json);
try { try {
var response = await http.post(Uri.parse('$apiAddress/editbusiness'), var response = await http.post(Uri.parse('$apiAddress/editbusiness'),
body: json, body: json,
+17 -15
View File
@@ -22,15 +22,15 @@ class Home extends StatefulWidget {
} }
class _HomeState extends State<Home> { class _HomeState extends State<Home> {
late Future refreshBusinessDataFuture; late Future refreshBusinessDataOverviewFuture;
bool _isPreviousData = false; bool _isPreviousData = false;
late List<Business> businesses; late Map<JobType, List<Business>> overviewBusinesses;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
refreshBusinessDataFuture = fetchBusinessData(); refreshBusinessDataOverviewFuture = fetchBusinessDataOverview();
initialLogin(); initialLogin();
} }
@@ -65,10 +65,10 @@ class _HomeState extends State<Home> {
body: RefreshIndicator( body: RefreshIndicator(
edgeOffset: 120, edgeOffset: 120,
onRefresh: () async { onRefresh: () async {
var refreshedData = fetchBusinessData(); var refreshedData = fetchBusinessDataOverview();
await refreshedData; await refreshedData;
setState(() { setState(() {
refreshBusinessDataFuture = refreshedData; refreshBusinessDataOverviewFuture = refreshedData;
}); });
}, },
child: CustomScrollView( child: CustomScrollView(
@@ -80,7 +80,7 @@ class _HomeState extends State<Home> {
scrolledUnderElevation: 0, scrolledUnderElevation: 0,
centerTitle: true, centerTitle: true,
expandedHeight: widescreen ? 70 : 120, expandedHeight: widescreen ? 70 : 120,
backgroundColor: Theme.of(context).colorScheme.background, backgroundColor: Theme.of(context).colorScheme.surface,
bottom: _getBottom(), bottom: _getBottom(),
leading: IconButton( leading: IconButton(
icon: getIconFromThemeMode(themeMode), icon: getIconFromThemeMode(themeMode),
@@ -185,8 +185,8 @@ class _HomeState extends State<Home> {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => builder: (context) => ExportData(
ExportData(businesses: businesses))); groupedBusinesses: overviewBusinesses)));
} }
}, },
), ),
@@ -245,7 +245,7 @@ class _HomeState extends State<Home> {
], ],
), ),
FutureBuilder( FutureBuilder(
future: refreshBusinessDataFuture, future: refreshBusinessDataOverviewFuture,
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) { if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasData) { if (snapshot.hasData) {
@@ -263,9 +263,11 @@ class _HomeState extends State<Home> {
child: FilledButton( child: FilledButton(
child: const Text('Retry'), child: const Text('Retry'),
onPressed: () { onPressed: () {
var refreshedData = fetchBusinessData(); var refreshedData =
fetchBusinessDataOverview();
setState(() { setState(() {
refreshBusinessDataFuture = refreshedData; refreshBusinessDataOverviewFuture =
refreshedData;
}); });
}, },
), ),
@@ -274,11 +276,11 @@ class _HomeState extends State<Home> {
)); ));
} }
businesses = snapshot.data; overviewBusinesses = snapshot.data;
_isPreviousData = true; _isPreviousData = true;
return BusinessDisplayPanel( return BusinessDisplayPanel(
businesses: businesses, groupedBusinesses: overviewBusinesses,
widescreen: widescreen, widescreen: widescreen,
selectable: false); selectable: false);
} else if (snapshot.hasError) { } else if (snapshot.hasError) {
@@ -293,7 +295,7 @@ class _HomeState extends State<Home> {
ConnectionState.waiting) { ConnectionState.waiting) {
if (_isPreviousData) { if (_isPreviousData) {
return BusinessDisplayPanel( return BusinessDisplayPanel(
businesses: businesses, groupedBusinesses: overviewBusinesses,
widescreen: widescreen, widescreen: widescreen,
selectable: false); selectable: false);
} else { } else {
@@ -339,7 +341,7 @@ class _HomeState extends State<Home> {
Widget? _getFAB() { Widget? _getFAB() {
if (loggedIn) { if (loggedIn) {
return FloatingActionButton( return FloatingActionButton(
child: const Icon(Icons.add), child: const Icon(Icons.add_business),
onPressed: () { onPressed: () {
Navigator.push( Navigator.push(
context, context,
+1 -1
View File
@@ -89,7 +89,7 @@ class _MainAppState extends State<MainApp> {
primary: Colors.blue, primary: Colors.blue,
onPrimary: Colors.white, onPrimary: Colors.white,
secondary: Colors.blue.shade200, secondary: Colors.blue.shade200,
background: Colors.white, background: Colors.grey.shade300,
tertiary: Colors.green, tertiary: Colors.green,
), ),
iconTheme: const IconThemeData(color: Colors.black), iconTheme: const IconThemeData(color: Colors.black),
+271 -141
View File
@@ -4,181 +4,311 @@ import 'package:fbla_ui/pages/create_edit_business.dart';
import 'package:fbla_ui/pages/signin_page.dart'; import 'package:fbla_ui/pages/signin_page.dart';
import 'package:fbla_ui/shared.dart'; import 'package:fbla_ui/shared.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class BusinessDetail extends StatefulWidget { class BusinessDetail extends StatefulWidget {
final Business inputBusiness; final int id;
final String name;
final JobType clickFromType;
const BusinessDetail({super.key, required this.inputBusiness}); const BusinessDetail(
{super.key,
required this.id,
required this.name,
required this.clickFromType});
@override @override
State<BusinessDetail> createState() => _CreateBusinessDetailState(); State<BusinessDetail> createState() => _CreateBusinessDetailState();
} }
class _CreateBusinessDetailState extends State<BusinessDetail> { class _CreateBusinessDetailState extends State<BusinessDetail> {
late Future loadBusiness;
@override
void initState() {
super.initState();
loadBusiness = fetchBusiness(widget.id);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Business business = Business.copy(widget.inputBusiness); return FutureBuilder(
return Scaffold( future: loadBusiness,
appBar: AppBar( builder: (context, snapshot) {
title: Text(business.name), if (snapshot.connectionState == ConnectionState.done) {
actions: _getActions(business), if (snapshot.hasData) {
), if (snapshot.data.runtimeType != String) {
body: ListView( return Scaffold(
children: [ appBar: AppBar(
Card( title: Text(snapshot.data.name),
actions: _getActions(snapshot.data, widget.clickFromType),
),
body: _detailBody(snapshot.data),
);
} else {
return Scaffold(
appBar: AppBar(
title: Text(widget.name),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(children: [
Center(
child:
Text(snapshot.data, textAlign: TextAlign.center)),
Padding(
padding: const EdgeInsets.all(8.0),
child: FilledButton(
child: const Text('Retry'),
onPressed: () {
var refreshedData = fetchBusiness(widget.id);
setState(() {
loadBusiness = refreshedData;
});
},
),
),
]),
),
);
}
}
} else if (snapshot.connectionState == ConnectionState.waiting) {
return Scaffold(
appBar: AppBar(
title: Text(widget.name),
),
body: Container(
padding: const EdgeInsets.all(8.0),
alignment: Alignment.center,
child: const SizedBox(
width: 75,
height: 75,
child:
RiveAnimation.asset('assets/mdev_triangle_loading.riv'),
)),
);
}
return Padding(
padding: const EdgeInsets.all(8.0),
child: Scaffold(
appBar: AppBar(
title: Text(widget.name),
),
body: Text(
'\nError: ${snapshot.error}',
style: const TextStyle(fontSize: 18),
textAlign: TextAlign.center,
),
),
);
});
}
ListView _detailBody(Business business) {
return ListView(
children: [
// Title, logo, desc, website
Card(
clipBehavior: Clip.antiAlias,
child: Column(
children: [
ListTile(
title: Text(business.name,
textAlign: TextAlign.left,
style: const TextStyle(
fontSize: 24, fontWeight: FontWeight.bold)),
subtitle: Text(
business.description,
textAlign: TextAlign.left,
),
leading: ClipRRect(
borderRadius: BorderRadius.circular(6.0),
child: Image.network('$apiAddress/logos/${business.id}',
width: 48,
height: 48, errorBuilder: (BuildContext context,
Object exception, StackTrace? stackTrace) {
return getIconFromJobType(widget.clickFromType, 48,
Theme.of(context).colorScheme.onSurface);
}),
),
),
ListTile(
leading: const Icon(Icons.link),
title: const Text('Website'),
subtitle: Text(business.website!,
style: const TextStyle(color: Colors.blue)),
onTap: () {
launchUrl(Uri.parse('https://${business.website}'));
},
),
],
),
),
// Available positions
Card(
child: Padding(
padding: const EdgeInsets.only(left: 16.0, top: 8.0),
child:
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
const Text(
'Available Postitions',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
// Container(
// height: 400,
// width: 300,
ListView(
scrollDirection: Axis.vertical,
shrinkWrap: true,
children: [
ListTile(
title: Text('Postition 1'),
leading: Icon(Icons.work),
onTap: () {
// launchUrl(Uri.parse(''));
},
),
ListTile(
title: Text('Postition 2'),
leading: Icon(Icons.work),
onTap: () {
// launchUrl(Uri.parse(''));
},
),
ListTile(
title: Text('Postition 3'),
leading: Icon(Icons.work),
onTap: () {
// launchUrl(Uri.parse(''));
},
),
],
),
]),
),
),
// Contact info
Visibility(
visible:
(business.contactEmail != null || business.contactPhone != null),
child: Card(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
child: Column( child: Column(
children: [ children: [
ListTile( Row(
title: Text(business.name, children: [
textAlign: TextAlign.left, Padding(
style: const TextStyle( padding: const EdgeInsets.only(left: 16.0, top: 8.0),
fontSize: 24, fontWeight: FontWeight.bold)), child: Text(
subtitle: Text( business.contactName ?? 'Contact ${business.name}',
business.description, textAlign: TextAlign.left,
textAlign: TextAlign.left, style: const TextStyle(
), fontSize: 20, fontWeight: FontWeight.bold),
leading: ClipRRect( ),
borderRadius: BorderRadius.circular(6.0), ),
child: Image.network( ],
'https://$apiAddress/fbla-api/logos/${business.id}', ),
width: 48, Visibility(
height: 48, errorBuilder: (BuildContext context, visible: business.contactPhone != null,
Object exception, StackTrace? stackTrace) { child: ListTile(
return getIconFromType(business.type, 48, leading: Icon(Icons.phone),
Theme.of(context).colorScheme.onSurface); title: Text(business.contactPhone!),
}), // maybe replace ! with ?? ''. same is true for below
onTap: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor:
Theme.of(context).colorScheme.background,
title: Text(business.contactName!.isEmpty
? 'Contact ${business.name}?'
: 'Contact ${business.contactName}'),
content: Text(business.contactName!.isEmpty
? 'Would you like to call or text ${business.name}?'
: 'Would you like to call or text ${business.contactName}?'),
actions: [
TextButton(
child: const Text('Text'),
onPressed: () {
launchUrl(Uri.parse(
'sms:${business.contactPhone}'));
Navigator.of(context).pop();
}),
TextButton(
child: const Text('Call'),
onPressed: () async {
launchUrl(Uri.parse(
'tel:${business.contactPhone}'));
Navigator.of(context).pop();
}),
],
);
});
},
), ),
), ),
ListTile( Visibility(
leading: const Icon(Icons.link), visible: business.contactEmail != null,
title: const Text('Website'), child: ListTile(
subtitle: Text(business.website, leading: const Icon(Icons.email),
style: const TextStyle(color: Colors.blue)), title: Text(business.contactEmail!),
onTap: () { onTap: () {
launchUrl(Uri.parse('https://${business.website}')); launchUrl(Uri.parse('mailto:${business.contactEmail}'));
}, },
),
), ),
], ],
), ),
), ),
Visibility( ),
visible: (business.contactEmail.isNotEmpty || // Location
business.contactPhone.isNotEmpty), Visibility(
child: Card( child: Card(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
child: Column( child: ListTile(
children: [ leading: const Icon(Icons.location_on),
Row( title: Text(business.locationName!),
children: [ subtitle: Text(business.locationAddress!),
Padding( onTap: () {
padding: const EdgeInsets.only(left: 16.0, top: 8.0), launchUrl(Uri.parse(Uri.encodeFull(
child: Text( 'https://www.google.com/maps/search/?api=1&query=${business.locationName}')));
business.contactName.isEmpty },
? 'Contact ${business.name}'
: business.contactName,
textAlign: TextAlign.left,
style: const TextStyle(
fontSize: 20, fontWeight: FontWeight.bold),
),
),
],
),
Visibility(
visible: business.contactPhone.isNotEmpty,
child: ListTile(
leading: Icon(Icons.phone),
title: Text(business.contactPhone),
onTap: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor:
Theme.of(context).colorScheme.background,
title: Text(business.contactName.isEmpty
? 'Contact ${business.name}?'
: 'Contact ${business.contactName}'),
content: Text(business.contactName.isEmpty
? 'Would you like to call or text ${business.name}?'
: 'Would you like to call or text ${business.contactName}?'),
actions: [
TextButton(
child: const Text('Text'),
onPressed: () {
launchUrl(Uri.parse(
'sms:${business.contactPhone}'));
Navigator.of(context).pop();
}),
TextButton(
child: const Text('Call'),
onPressed: () async {
launchUrl(Uri.parse(
'tel:${business.contactPhone}'));
Navigator.of(context).pop();
}),
],
);
});
},
),
),
Visibility(
visible: business.contactEmail.isNotEmpty,
child: ListTile(
leading: const Icon(Icons.email),
title: Text(business.contactEmail),
onTap: () {
launchUrl(Uri.parse('mailto:${business.contactEmail}'));
},
),
),
],
),
), ),
), ),
Visibility( ),
child: Card( // Notes
clipBehavior: Clip.antiAlias, Visibility(
child: ListTile( visible: business.notes != null,
leading: const Icon(Icons.location_on), child: Card(
title: Text(business.locationName), child: ListTile(
subtitle: Text(business.locationAddress), leading: const Icon(Icons.notes),
onTap: () { title: const Text(
launchUrl(Uri.parse(Uri.encodeFull( 'Additional Notes:',
'https://www.google.com/maps/search/?api=1&query=${business.locationName}'))); style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
},
), ),
subtitle: Text(business.notes!),
), ),
), ),
Visibility( ),
visible: business.notes.isNotEmpty, ],
child: Card(
child: ListTile(
leading: const Icon(Icons.notes),
title: const Text(
'Additional Notes:',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
subtitle: Text(business.notes),
),
),
),
],
),
); );
} }
List<Widget>? _getActions(Business business) { List<Widget>? _getActions(Business business, JobType clickFromType) {
if (loggedIn) { if (loggedIn) {
return [ return [
IconButton( IconButton(
icon: const Icon(Icons.edit), icon: const Icon(Icons.edit),
onPressed: () { onPressed: () {
Navigator.of(context).push(MaterialPageRoute( Navigator.of(context).push(MaterialPageRoute(
builder: (context) => builder: (context) => CreateEditBusiness(
CreateEditBusiness(inputBusiness: business))); inputBusiness: business,
clickFromType: clickFromType,
)));
}, },
), ),
IconButton( IconButton(
@@ -202,7 +332,7 @@ class _CreateBusinessDetailState extends State<BusinessDetail> {
child: const Text('Yes'), child: const Text('Yes'),
onPressed: () async { onPressed: () async {
String? deleteResult = String? deleteResult =
await deleteBusiness(business, jwt); await deleteBusiness(business.id, jwt);
if (deleteResult != null) { if (deleteResult != null) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
+66 -54
View File
@@ -6,8 +6,9 @@ import 'package:flutter/services.dart';
class CreateEditBusiness extends StatefulWidget { class CreateEditBusiness extends StatefulWidget {
final Business? inputBusiness; final Business? inputBusiness;
final JobType? clickFromType;
const CreateEditBusiness({super.key, this.inputBusiness}); const CreateEditBusiness({super.key, this.inputBusiness, this.clickFromType});
@override @override
State<CreateEditBusiness> createState() => _CreateEditBusinessState(); State<CreateEditBusiness> createState() => _CreateEditBusinessState();
@@ -27,7 +28,6 @@ class _CreateEditBusinessState extends State<CreateEditBusiness> {
id: 0, id: 0,
name: 'Business', name: 'Business',
description: 'Add details about the business below.', description: 'Add details about the business below.',
type: BusinessType.other,
website: '', website: '',
contactName: '', contactName: '',
contactEmail: '', contactEmail: '',
@@ -155,7 +155,9 @@ class _CreateEditBusinessState extends State<CreateEditBusiness> {
'https://logo.clearbit.com/${business.website}', 'https://logo.clearbit.com/${business.website}',
errorBuilder: (BuildContext context, errorBuilder: (BuildContext context,
Object exception, StackTrace? stackTrace) { Object exception, StackTrace? stackTrace) {
return getIconFromType(business.type, 48, return getIconFromJobType(
widget.clickFromType ?? JobType.other,
48,
Theme.of(context).colorScheme.onBackground); Theme.of(context).colorScheme.onBackground);
}), }),
), ),
@@ -180,7 +182,7 @@ class _CreateEditBusinessState extends State<CreateEditBusiness> {
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
}, },
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Business Name', labelText: 'Business Name (required)',
), ),
validator: (value) { validator: (value) {
if (value != null && value.isEmpty) { if (value != null && value.isEmpty) {
@@ -199,17 +201,19 @@ class _CreateEditBusinessState extends State<CreateEditBusiness> {
AutovalidateMode.onUserInteraction, AutovalidateMode.onUserInteraction,
keyboardType: TextInputType.url, keyboardType: TextInputType.url,
onChanged: (inputUrl) { onChanged: (inputUrl) {
business.website = Uri.encodeFull(inputUrl setState(() {
.toLowerCase() business.website = Uri.encodeFull(inputUrl
.replaceAll('https://', '') .toLowerCase()
.replaceAll('http://', '') .replaceAll('https://', '')
.replaceAll('www.', '')); .replaceAll('http://', '')
.replaceAll('www.', ''));
});
}, },
onTapOutside: (PointerDownEvent event) { onTapOutside: (PointerDownEvent event) {
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
}, },
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Website', labelText: 'Website (required)',
), ),
validator: (value) { validator: (value) {
if (value != null && value.isEmpty) { if (value != null && value.isEmpty) {
@@ -227,6 +231,7 @@ class _CreateEditBusinessState extends State<CreateEditBusiness> {
autovalidateMode: autovalidateMode:
AutovalidateMode.onUserInteraction, AutovalidateMode.onUserInteraction,
maxLength: 500, maxLength: 500,
maxLines: null,
onChanged: (inputDesc) { onChanged: (inputDesc) {
setState(() { setState(() {
business.description = inputDesc; business.description = inputDesc;
@@ -236,7 +241,8 @@ class _CreateEditBusinessState extends State<CreateEditBusiness> {
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
}, },
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Business Description', labelText:
'Business Description (required)',
), ),
validator: (value) { validator: (value) {
if (value != null && value.isEmpty) { if (value != null && value.isEmpty) {
@@ -332,7 +338,7 @@ class _CreateEditBusinessState extends State<CreateEditBusiness> {
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
}, },
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Location Name (optional)', labelText: 'Location Name',
), ),
), ),
), ),
@@ -350,47 +356,53 @@ class _CreateEditBusinessState extends State<CreateEditBusiness> {
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
}, },
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Location Address (optional)', labelText: 'Location Address',
), ),
), ),
), ),
Padding(
padding: const EdgeInsets.only( // Business Type Dropdown
left: 8.0, right: 8.0, bottom: 8.0),
child: Row( // Padding(
mainAxisAlignment: // padding: const EdgeInsets.only(
MainAxisAlignment.spaceBetween, // left: 8.0, right: 8.0, bottom: 8.0),
children: [ // child: Row(
const Text('Type of Business', // mainAxisAlignment:
style: TextStyle(fontSize: 16)), // MainAxisAlignment.spaceBetween,
DropdownMenu<BusinessType>( // children: [
initialSelection: business.type, // const Text('Type of Business',
controller: businessTypeController, // style: TextStyle(fontSize: 16)),
label: const Text('Business Type'), // DropdownMenu<BusinessType>(
dropdownMenuEntries: const [ // initialSelection: business.type,
DropdownMenuEntry( // controller: businessTypeController,
value: BusinessType.food, // label: const Text('Business Type'),
label: 'Food Related'), // dropdownMenuEntries: const [
DropdownMenuEntry( // DropdownMenuEntry(
value: BusinessType.shop, // value: BusinessType.food,
label: 'Shop'), // label: 'Food Related'),
DropdownMenuEntry( // DropdownMenuEntry(
value: BusinessType.outdoors, // value: BusinessType.shop,
label: 'Outdoors'), // label: 'Shop'),
DropdownMenuEntry( // DropdownMenuEntry(
value: BusinessType.manufacturing, // value: BusinessType.outdoors,
label: 'Manufacturing'), // label: 'Outdoors'),
DropdownMenuEntry( // DropdownMenuEntry(
value: BusinessType.other, // value: BusinessType.manufacturing,
label: 'Other'), // label: 'Manufacturing'),
], // DropdownMenuEntry(
onSelected: (inputType) { // value: BusinessType.entertainment,
business.type = inputType!; // label: 'Entertainment'),
}, // DropdownMenuEntry(
), // value: BusinessType.other,
], // label: 'Other'),
), // ],
), // onSelected: (inputType) {
// business.type = inputType!;
// },
// ),
// ],
// ),
// ),
Padding( Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
left: 8.0, right: 8.0, bottom: 8.0), left: 8.0, right: 8.0, bottom: 8.0),
@@ -403,8 +415,7 @@ class _CreateEditBusinessState extends State<CreateEditBusiness> {
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
}, },
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: labelText: 'Contact Information Name',
'Contact Information Name (optional)',
), ),
), ),
), ),
@@ -439,7 +450,7 @@ class _CreateEditBusinessState extends State<CreateEditBusiness> {
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
}, },
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Contact Email (optional)', labelText: 'Contact Email',
), ),
validator: (value) { validator: (value) {
if (value != null) { if (value != null) {
@@ -463,6 +474,7 @@ class _CreateEditBusinessState extends State<CreateEditBusiness> {
child: TextFormField( child: TextFormField(
controller: _notesController, controller: _notesController,
maxLength: 300, maxLength: 300,
maxLines: null,
onSaved: (inputText) { onSaved: (inputText) {
business.notes = inputText!; business.notes = inputText!;
}, },
@@ -470,7 +482,7 @@ class _CreateEditBusinessState extends State<CreateEditBusiness> {
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
}, },
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Other Notes (optional)', labelText: 'Other Notes',
), ),
), ),
), ),
+42 -31
View File
@@ -16,9 +16,9 @@ bool isBusinessesFiltered = true;
bool _isLoading = false; bool _isLoading = false;
class ExportData extends StatefulWidget { class ExportData extends StatefulWidget {
final List<Business> businesses; final Map<JobType, List<Business>> groupedBusinesses;
const ExportData({super.key, required this.businesses}); const ExportData({super.key, required this.groupedBusinesses});
@override @override
State<ExportData> createState() => _ExportDataState(); State<ExportData> createState() => _ExportDataState();
@@ -31,7 +31,7 @@ class _ExportDataState extends State<ExportData> {
void initState() { void initState() {
super.initState(); super.initState();
refreshBusinessDataFuture = fetchBusinessData(); refreshBusinessDataFuture = fetchBusinessDataOverview();
_isLoading = false; _isLoading = false;
selectedBusinesses = <Business>{}; selectedBusinesses = <Business>{};
} }
@@ -39,7 +39,7 @@ class _ExportDataState extends State<ExportData> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
floatingActionButton: _FAB(businesses: widget.businesses), floatingActionButton: _FAB(groupedBusinesses: widget.groupedBusinesses),
body: CustomScrollView( body: CustomScrollView(
slivers: [ slivers: [
SliverAppBar( SliverAppBar(
@@ -196,7 +196,7 @@ class _ExportDataState extends State<ExportData> {
), ),
), ),
BusinessDisplayPanel( BusinessDisplayPanel(
businesses: widget.businesses, groupedBusinesses: widget.groupedBusinesses,
widescreen: MediaQuery.sizeOf(context).width >= 1000, widescreen: MediaQuery.sizeOf(context).width >= 1000,
selectable: true), selectable: true),
const SliverToBoxAdapter( const SliverToBoxAdapter(
@@ -211,15 +211,26 @@ class _ExportDataState extends State<ExportData> {
} }
class _FAB extends StatefulWidget { class _FAB extends StatefulWidget {
final List<Business> businesses; final Map<JobType, List<Business>> groupedBusinesses;
const _FAB({required this.businesses}); const _FAB({required this.groupedBusinesses});
@override @override
State<_FAB> createState() => _FABState(); State<_FAB> createState() => _FABState();
} }
class _FABState extends State<_FAB> { class _FABState extends State<_FAB> {
List<Business> allBusinesses = [];
@override
void initState() {
super.initState();
for (JobType type in widget.groupedBusinesses.keys) {
allBusinesses.addAll(widget.groupedBusinesses[type]!);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FloatingActionButton( return FloatingActionButton(
@@ -273,7 +284,7 @@ class _FABState extends State<_FAB> {
List<pw.TableRow> rows = []; List<pw.TableRow> rows = [];
if (selectedBusinesses.isEmpty) { if (selectedBusinesses.isEmpty) {
selectedBusinesses.addAll(widget.businesses); selectedBusinesses.addAll(allBusinesses);
isBusinessesFiltered = false; isBusinessesFiltered = false;
} else { } else {
isBusinessesFiltered = true; isBusinessesFiltered = true;
@@ -284,9 +295,9 @@ class _FABState extends State<_FAB> {
if (dataTypeFilters.contains(DataType.logo)) { if (dataTypeFilters.contains(DataType.logo)) {
remainingSpace -= 32; remainingSpace -= 32;
} }
if (dataTypeFilters.contains(DataType.type)) { // if (dataTypeFilters.contains(DataType.type)) {
remainingSpace -= 56; // remainingSpace -= 56;
} // }
if (dataTypeFilters.contains(DataType.contactName)) { if (dataTypeFilters.contains(DataType.contactName)) {
remainingSpace -= 72; remainingSpace -= 72;
} }
@@ -331,9 +342,9 @@ class _FABState extends State<_FAB> {
} else if (dataType == DataType.description) { } else if (dataType == DataType.description) {
width = pw.FixedColumnWidth(descriptionWidth); width = pw.FixedColumnWidth(descriptionWidth);
columnNum++; columnNum++;
} else if (dataType == DataType.type) { // } else if (dataType == DataType.type) {
width = const pw.FixedColumnWidth(56); // width = const pw.FixedColumnWidth(56);
columnNum++; // columnNum++;
} else if (dataType == DataType.website) { } else if (dataType == DataType.website) {
width = pw.FixedColumnWidth(websiteWidth); width = pw.FixedColumnWidth(websiteWidth);
columnNum++; columnNum++;
@@ -394,18 +405,18 @@ class _FABState extends State<_FAB> {
), ),
padding: const pw.EdgeInsets.all(4.0))); padding: const pw.EdgeInsets.all(4.0)));
} }
if (dataTypeFilters.contains(DataType.type)) { // if (dataTypeFilters.contains(DataType.type)) {
data.add(pw.Padding( // data.add(pw.Padding(
child: pw.Text( // child: pw.Text(
business.type.name, // business.type.name,
// style: const pw.TextStyle(fontSize: 10) // // style: const pw.TextStyle(fontSize: 10)
), // ),
padding: const pw.EdgeInsets.all(4.0))); // padding: const pw.EdgeInsets.all(4.0)));
} // }
if (dataTypeFilters.contains(DataType.website)) { if (dataTypeFilters.contains(DataType.website)) {
data.add(pw.Padding( data.add(pw.Padding(
child: pw.Text( child: pw.Text(
business.website, business.website ?? '',
// style: const pw.TextStyle(fontSize: 10) // style: const pw.TextStyle(fontSize: 10)
), ),
padding: const pw.EdgeInsets.all(4.0))); padding: const pw.EdgeInsets.all(4.0)));
@@ -413,7 +424,7 @@ class _FABState extends State<_FAB> {
if (dataTypeFilters.contains(DataType.contactName)) { if (dataTypeFilters.contains(DataType.contactName)) {
data.add(pw.Padding( data.add(pw.Padding(
child: pw.Text( child: pw.Text(
business.contactName, business.contactName ?? '',
// style: const pw.TextStyle(fontSize: 10) // style: const pw.TextStyle(fontSize: 10)
), ),
padding: const pw.EdgeInsets.all(4.0))); padding: const pw.EdgeInsets.all(4.0)));
@@ -421,7 +432,7 @@ class _FABState extends State<_FAB> {
if (dataTypeFilters.contains(DataType.contactEmail)) { if (dataTypeFilters.contains(DataType.contactEmail)) {
data.add(pw.Padding( data.add(pw.Padding(
child: pw.Text( child: pw.Text(
business.contactEmail, business.contactEmail ?? '',
// style: const pw.TextStyle(fontSize: 10) // style: const pw.TextStyle(fontSize: 10)
), ),
padding: const pw.EdgeInsets.all(4.0))); padding: const pw.EdgeInsets.all(4.0)));
@@ -429,7 +440,7 @@ class _FABState extends State<_FAB> {
if (dataTypeFilters.contains(DataType.contactPhone)) { if (dataTypeFilters.contains(DataType.contactPhone)) {
data.add(pw.Padding( data.add(pw.Padding(
child: pw.Text( child: pw.Text(
business.contactPhone, business.contactPhone ?? '',
// style: const pw.TextStyle(fontSize: 10) // style: const pw.TextStyle(fontSize: 10)
), ),
padding: const pw.EdgeInsets.all(4.0))); padding: const pw.EdgeInsets.all(4.0)));
@@ -440,7 +451,7 @@ class _FABState extends State<_FAB> {
style = const pw.TextStyle(fontSize: 8); style = const pw.TextStyle(fontSize: 8);
} }
data.add(pw.Padding( data.add(pw.Padding(
child: pw.Text(business.notes, style: style), child: pw.Text(business.notes ?? '', style: style),
padding: const pw.EdgeInsets.all(4.0))); padding: const pw.EdgeInsets.all(4.0)));
} }
@@ -461,10 +472,10 @@ class _FABState extends State<_FAB> {
} else { } else {
rows.add(pw.TableRow( rows.add(pw.TableRow(
children: [ children: [
pw.Padding( // pw.Padding(
child: getPwIconFromType( // child: getPwIconFromType(
business.type, 24, PdfColors.black), // business.type, 24, PdfColors.black),
padding: const pw.EdgeInsets.all(4.0)), // padding: const pw.EdgeInsets.all(4.0)),
...data ...data
], ],
)); ));
+222 -111
View File
@@ -1,4 +1,3 @@
import 'package:collection/collection.dart';
import 'package:fbla_ui/api_logic.dart'; import 'package:fbla_ui/api_logic.dart';
import 'package:fbla_ui/pages/business_detail.dart'; import 'package:fbla_ui/pages/business_detail.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -21,7 +20,7 @@ enum DataType {
logo, logo,
name, name,
description, description,
type, // type,
website, website,
contactName, contactName,
contactEmail, contactEmail,
@@ -33,7 +32,7 @@ Map<DataType, int> dataTypeValues = {
DataType.logo: 0, DataType.logo: 0,
DataType.name: 1, DataType.name: 1,
DataType.description: 2, DataType.description: 2,
DataType.type: 3, // DataType.type: 3,
DataType.website: 4, DataType.website: 4,
DataType.contactName: 5, DataType.contactName: 5,
DataType.contactEmail: 6, DataType.contactEmail: 6,
@@ -45,7 +44,7 @@ Map<DataType, String> dataTypeFriendly = {
DataType.logo: 'Logo', DataType.logo: 'Logo',
DataType.name: 'Name', DataType.name: 'Name',
DataType.description: 'Description', DataType.description: 'Description',
DataType.type: 'Type', // DataType.type: 'Type',
DataType.website: 'Website', DataType.website: 'Website',
DataType.contactName: 'Contact Name', DataType.contactName: 'Contact Name',
DataType.contactEmail: 'Contact Email', DataType.contactEmail: 'Contact Email',
@@ -67,83 +66,104 @@ enum BusinessType {
shop, shop,
outdoors, outdoors,
manufacturing, manufacturing,
entertainment,
other, other,
} }
enum JobType { cashier, server, mechanic, other }
class JobListing {
String? id;
String? businessId;
String name;
String description;
JobType type;
String? wage;
String? link;
JobListing(
{this.id,
this.businessId,
required this.name,
required this.description,
required this.type,
this.wage,
this.link});
}
class Business { class Business {
int id; int id;
String name; String name;
String description; String description;
BusinessType type; String? website;
String website; String? contactName;
String contactName; String? contactEmail;
String contactEmail; String? contactPhone;
String contactPhone; String? notes;
String notes; String? locationName;
String locationName; String? locationAddress;
String locationAddress; List<JobListing>? listings;
Business({ Business(
required this.id, {required this.id,
required this.name, required this.name,
required this.description, required this.description,
required this.type, this.website,
required this.website, this.contactName,
required this.contactName, this.contactEmail,
required this.contactEmail, this.contactPhone,
required this.contactPhone, this.notes,
required this.notes, this.locationName,
required this.locationName, this.locationAddress,
required this.locationAddress, this.listings});
});
factory Business.fromJson(Map<String, dynamic> json) { factory Business.fromJson(Map<String, dynamic> json) {
bool typeValid = true; List<JobListing>? listings = [];
try { for (int i = 0; i < json['listings'].length; i++) {
BusinessType.values.byName(json['type']); listings.add(JobListing(
} catch (e) { name: json['listings']['name'],
typeValid = false; description: json['listings']['description'],
type: json['listings']['type'],
wage: json['listings']['wage'],
link: json['listings']['link']));
} }
return Business( return Business(
id: json['id'], id: json['id'],
name: json['name'], name: json['name'],
description: json['description'], description: json['description'],
type: typeValid website: json['website'],
? BusinessType.values.byName(json['type']) contactName: json['contactName'],
: BusinessType.other, contactEmail: json['contactEmail'],
website: json['website'], contactPhone: json['contactPhone'],
contactName: json['contactName'], notes: json['notes'],
contactEmail: json['contactEmail'], locationName: json['locationName'],
contactPhone: json['contactPhone'], locationAddress: json['locationAddress'],
notes: json['notes'], listings: listings);
locationName: json['locationName'],
locationAddress: json['locationAddress'],
);
} }
factory Business.copy(Business input) { factory Business.copy(Business input) {
return Business( return Business(
id: input.id, id: input.id,
name: input.name, name: input.name,
description: input.description, description: input.description,
type: input.type, website: input.website,
website: input.website, contactName: input.contactName,
contactName: input.contactName, contactEmail: input.contactEmail,
contactEmail: input.contactEmail, contactPhone: input.contactPhone,
contactPhone: input.contactPhone, notes: input.notes,
notes: input.notes, locationName: input.locationName,
locationName: input.locationName, locationAddress: input.locationAddress,
locationAddress: input.locationAddress, listings: input.listings);
);
} }
} }
Map<BusinessType, List<Business>> groupBusinesses(List<Business> businesses) { // Map<BusinessType, List<Business>> groupBusinesses(List<Business> businesses) {
Map<BusinessType, List<Business>> groupedBusinesses = // Map<BusinessType, List<Business>> groupedBusinesses =
groupBy<Business, BusinessType>(businesses, (business) => business.type); // groupBy<Business, BusinessType>(businesses, (business) => business.type!);
//
return groupedBusinesses; // return groupedBusinesses;
} // }
Icon getIconFromType(BusinessType type, double size, Color color) { Icon getIconFromType(BusinessType type, double size, Color color) {
switch (type) { switch (type) {
@@ -171,6 +191,12 @@ Icon getIconFromType(BusinessType type, double size, Color color) {
size: size, size: size,
color: color, color: color,
); );
case BusinessType.entertainment:
return Icon(
Icons.live_tv,
size: size,
color: color,
);
case BusinessType.other: case BusinessType.other:
return Icon( return Icon(
Icons.business, Icons.business,
@@ -180,6 +206,35 @@ Icon getIconFromType(BusinessType type, double size, Color color) {
} }
} }
Icon getIconFromJobType(JobType type, double size, Color color) {
switch (type) {
case JobType.cashier:
return Icon(
Icons.shopping_bag,
size: size,
color: color,
);
case JobType.server:
return Icon(
Icons.restaurant,
size: size,
color: color,
);
case JobType.mechanic:
return Icon(
Icons.construction,
size: size,
color: color,
);
case JobType.other:
return Icon(
Icons.work,
size: size,
color: color,
);
}
}
pw.Icon getPwIconFromType(BusinessType type, double size, PdfColor color) { pw.Icon getPwIconFromType(BusinessType type, double size, PdfColor color) {
switch (type) { switch (type) {
case BusinessType.food: case BusinessType.food:
@@ -190,11 +245,26 @@ pw.Icon getPwIconFromType(BusinessType type, double size, PdfColor color) {
return pw.Icon(const pw.IconData(0xea99), size: size, color: color); return pw.Icon(const pw.IconData(0xea99), size: size, color: color);
case BusinessType.manufacturing: case BusinessType.manufacturing:
return pw.Icon(const pw.IconData(0xebbc), size: size, color: color); return pw.Icon(const pw.IconData(0xebbc), size: size, color: color);
case BusinessType.entertainment:
return pw.Icon(const pw.IconData(0xe639), size: size, color: color);
case BusinessType.other: case BusinessType.other:
return pw.Icon(const pw.IconData(0xe0af), size: size, color: color); return pw.Icon(const pw.IconData(0xe0af), size: size, color: color);
} }
} }
pw.Icon getPwIconFromJobType(JobType type, double size, PdfColor color) {
switch (type) {
case JobType.cashier:
return pw.Icon(const pw.IconData(0xf1cc), size: size, color: color);
case JobType.server:
return pw.Icon(const pw.IconData(0xe56c), size: size, color: color);
case JobType.mechanic:
return pw.Icon(const pw.IconData(0xea3c), size: size, color: color);
case JobType.other:
return pw.Icon(const pw.IconData(0xe8f9), size: size, color: color);
}
}
Text getNameFromType(BusinessType type, Color color) { Text getNameFromType(BusinessType type, Color color) {
switch (type) { switch (type) {
case BusinessType.food: case BusinessType.food:
@@ -205,11 +275,26 @@ Text getNameFromType(BusinessType type, Color color) {
return Text('Outdoors', style: TextStyle(color: color)); return Text('Outdoors', style: TextStyle(color: color));
case BusinessType.manufacturing: case BusinessType.manufacturing:
return Text('Manufacturing', style: TextStyle(color: color)); return Text('Manufacturing', style: TextStyle(color: color));
case BusinessType.entertainment:
return Text('Entertainment', style: TextStyle(color: color));
case BusinessType.other: case BusinessType.other:
return Text('Other', style: TextStyle(color: color)); return Text('Other', style: TextStyle(color: color));
} }
} }
Text getNameFromJobType(JobType type, Color color) {
switch (type) {
case JobType.cashier:
return Text('Cashier', style: TextStyle(color: color));
case JobType.server:
return Text('Server', style: TextStyle(color: color));
case JobType.mechanic:
return Text('Mechanic', style: TextStyle(color: color));
case JobType.other:
return Text('Other', style: TextStyle(color: color));
}
}
Icon getIconFromThemeMode(ThemeMode theme) { Icon getIconFromThemeMode(ThemeMode theme) {
switch (theme) { switch (theme) {
case ThemeMode.dark: case ThemeMode.dark:
@@ -222,13 +307,13 @@ Icon getIconFromThemeMode(ThemeMode theme) {
} }
class BusinessDisplayPanel extends StatefulWidget { class BusinessDisplayPanel extends StatefulWidget {
final List<Business> businesses; final Map<JobType, List<Business>> groupedBusinesses;
final bool widescreen; final bool widescreen;
final bool selectable; final bool selectable;
const BusinessDisplayPanel( const BusinessDisplayPanel(
{super.key, {super.key,
required this.businesses, required this.groupedBusinesses,
required this.widescreen, required this.widescreen,
required this.selectable}); required this.selectable});
@@ -242,40 +327,42 @@ class _BusinessDisplayPanelState extends State<BusinessDisplayPanel> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
List<BusinessHeader> headers = []; List<BusinessHeader> headers = [];
List<Business> filteredBusinesses = []; // List<Business> filteredBusinesses = [];
for (var business in widget.businesses) { // for (var business in widget.groupedBusinesses.) {
if (business.name.toLowerCase().contains(searchFilter.toLowerCase())) { // if (business.name.toLowerCase().contains(searchFilter.toLowerCase())) {
filteredBusinesses.add(business); // filteredBusinesses.add(business);
} // }
} // }
var groupedBusinesses = groupBusinesses(filteredBusinesses);
var businessTypes = groupedBusinesses.keys.toList();
for (var i = 0; i < businessTypes.length; i++) { if (filters.isNotEmpty) {
if (filters.contains(businessTypes[i])) { isFiltered = true;
isFiltered = true;
}
} }
// for (var i = 0; i < businessTypes.length; i++) {
// if (filters.contains(businessTypes[i])) {
// isFiltered = true;
// }
// }
if (isFiltered) { if (isFiltered) {
for (var i = 0; i < businessTypes.length; i++) { for (JobType jobType in widget.groupedBusinesses.keys) {
if (filters.contains(businessTypes[i])) { if (filters.contains(jobType)) {
headers.add(BusinessHeader( headers.add(BusinessHeader(
type: businessTypes[i], type: jobType,
widescreen: widget.widescreen, widescreen: widget.widescreen,
selectable: widget.selectable, selectable: widget.selectable,
selectedBusinesses: selectedBusinesses, selectedBusinesses: selectedBusinesses,
businesses: groupedBusinesses[businessTypes[i]]!)); businesses: widget.groupedBusinesses[jobType]!));
} }
} }
} else { } else {
for (var i = 0; i < businessTypes.length; i++) { for (JobType jobType in widget.groupedBusinesses.keys) {
headers.add(BusinessHeader( headers.add(BusinessHeader(
type: businessTypes[i], type: jobType,
widescreen: widget.widescreen, widescreen: widget.widescreen,
selectable: widget.selectable, selectable: widget.selectable,
selectedBusinesses: selectedBusinesses, selectedBusinesses: selectedBusinesses,
businesses: groupedBusinesses[businessTypes[i]]!)); businesses: widget.groupedBusinesses[jobType]!));
} }
} }
headers.sort((a, b) => a.type.index.compareTo(b.type.index)); headers.sort((a, b) => a.type.index.compareTo(b.type.index));
@@ -284,7 +371,7 @@ class _BusinessDisplayPanelState extends State<BusinessDisplayPanel> {
} }
class BusinessHeader extends StatefulWidget { class BusinessHeader extends StatefulWidget {
final BusinessType type; final JobType type;
final List<Business> businesses; final List<Business> businesses;
final Set<Business> selectedBusinesses; final Set<Business> selectedBusinesses;
final bool widescreen; final bool widescreen;
@@ -332,10 +419,10 @@ class _BusinessHeaderState extends State<BusinessHeader> {
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.only(left: 4.0, right: 12.0), padding: const EdgeInsets.only(left: 4.0, right: 12.0),
child: getIconFromType( child: getIconFromJobType(
widget.type, 24, Theme.of(context).colorScheme.onPrimary), widget.type, 24, Theme.of(context).colorScheme.onPrimary),
), ),
getNameFromType( getNameFromJobType(
widget.type, Theme.of(context).colorScheme.onPrimary), widget.type, Theme.of(context).colorScheme.onPrimary),
], ],
), ),
@@ -365,10 +452,11 @@ class _BusinessHeaderState extends State<BusinessHeader> {
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.only(left: 4.0, right: 12.0), padding: const EdgeInsets.only(left: 4.0, right: 12.0),
child: getIconFromType( child: getIconFromJobType(
widget.type, 24, Theme.of(context).colorScheme.onPrimary), widget.type, 24, Theme.of(context).colorScheme.onPrimary),
), ),
getNameFromType(widget.type, Theme.of(context).colorScheme.onPrimary), getNameFromJobType(
widget.type, Theme.of(context).colorScheme.onPrimary),
], ],
); );
} }
@@ -393,6 +481,7 @@ class _BusinessHeaderState extends State<BusinessHeader> {
selectable: selectable, selectable: selectable,
widescreen: widescreen, widescreen: widescreen,
callback: refresh, callback: refresh,
type: widget.type,
); );
}, },
), ),
@@ -407,6 +496,7 @@ class _BusinessHeaderState extends State<BusinessHeader> {
selectable: selectable, selectable: selectable,
widescreen: widescreen, widescreen: widescreen,
callback: refresh, callback: refresh,
type: widget.type,
); );
}, },
), ),
@@ -420,13 +510,15 @@ class BusinessCard extends StatefulWidget {
final bool widescreen; final bool widescreen;
final bool selectable; final bool selectable;
final Function callback; final Function callback;
final JobType type;
const BusinessCard( const BusinessCard(
{super.key, {super.key,
required this.business, required this.business,
required this.widescreen, required this.widescreen,
required this.selectable, required this.selectable,
required this.callback}); required this.callback,
required this.type});
@override @override
State<BusinessCard> createState() => _BusinessCardState(); State<BusinessCard> createState() => _BusinessCardState();
@@ -436,27 +528,31 @@ class _BusinessCardState extends State<BusinessCard> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (widget.widescreen) { if (widget.widescreen) {
return _businessTile(widget.business, widget.selectable); return _businessTile(widget.business, widget.selectable, widget.type);
} else { } else {
return _businessListItem( return _businessListItem(
widget.business, widget.selectable, widget.callback); widget.business, widget.selectable, widget.callback, widget.type);
} }
} }
Widget _businessTile(Business business, bool selectable) { Widget _businessTile(Business business, bool selectable, JobType type) {
return MouseRegion( return MouseRegion(
cursor: SystemMouseCursors.click, cursor: SystemMouseCursors.click,
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
Navigator.of(context).push(MaterialPageRoute( Navigator.of(context).push(MaterialPageRoute(
builder: (context) => BusinessDetail(inputBusiness: business))); builder: (context) => BusinessDetail(
id: business.id,
name: business.name,
clickFromType: type,
)));
}, },
child: Card( child: Card(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
_getTileRow(business, selectable, widget.callback), _getTileRow(business, selectable, widget.callback, type),
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Text( child: Text(
@@ -479,7 +575,8 @@ class _BusinessCardState extends State<BusinessCard> {
Uri.parse('https://${business.website}')); Uri.parse('https://${business.website}'));
}, },
), ),
if (business.locationName.isNotEmpty) if ((business.locationName != null) &&
(business.locationName != ''))
IconButton( IconButton(
icon: const Icon(Icons.location_on), icon: const Icon(Icons.location_on),
onPressed: () { onPressed: () {
@@ -487,7 +584,8 @@ class _BusinessCardState extends State<BusinessCard> {
'https://www.google.com/maps/search/?api=1&query=${business.locationName}'))); 'https://www.google.com/maps/search/?api=1&query=${business.locationName}')));
}, },
), ),
if (business.contactPhone.isNotEmpty) if ((business.contactPhone != null) &&
(business.contactPhone != ''))
IconButton( IconButton(
icon: const Icon(Icons.phone), icon: const Icon(Icons.phone),
onPressed: () { onPressed: () {
@@ -498,10 +596,16 @@ class _BusinessCardState extends State<BusinessCard> {
backgroundColor: Theme.of(context) backgroundColor: Theme.of(context)
.colorScheme .colorScheme
.background, .background,
title: Text( title: Text((business.contactName ==
'Contact ${business.contactName}?'), null ||
content: Text( business.contactName == '')
'Would you like to call or text ${business.contactName}?'), ? 'Contact ${business.name}?'
: 'Contact ${business.contactName}'),
content: Text((business.contactName ==
null ||
business.contactName == '')
? 'Would you like to call or text ${business.name}?'
: 'Would you like to call or text ${business.contactName}?'),
actions: [ actions: [
TextButton( TextButton(
child: const Text('Text'), child: const Text('Text'),
@@ -522,7 +626,8 @@ class _BusinessCardState extends State<BusinessCard> {
}); });
}, },
), ),
if (business.contactEmail.isNotEmpty) if ((business.contactEmail != null) &&
(business.contactEmail != ''))
IconButton( IconButton(
icon: const Icon(Icons.email), icon: const Icon(Icons.email),
onPressed: () { onPressed: () {
@@ -540,7 +645,8 @@ class _BusinessCardState extends State<BusinessCard> {
); );
} }
Widget _getTileRow(Business business, bool selectable, Function callback) { Widget _getTileRow(
Business business, bool selectable, Function callback, JobType type) {
if (selectable) { if (selectable) {
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -552,8 +658,8 @@ class _BusinessCardState extends State<BusinessCard> {
child: Image.network('$apiAddress/logos/${business.id}', child: Image.network('$apiAddress/logos/${business.id}',
height: 48, width: 48, errorBuilder: (BuildContext context, height: 48, width: 48, errorBuilder: (BuildContext context,
Object exception, StackTrace? stackTrace) { Object exception, StackTrace? stackTrace) {
return getIconFromType( return getIconFromJobType(
business.type, 48, Theme.of(context).colorScheme.onSurface); type, 48, Theme.of(context).colorScheme.onSurface);
}), }),
), ),
), ),
@@ -585,8 +691,8 @@ class _BusinessCardState extends State<BusinessCard> {
child: Image.network('$apiAddress/logos/${business.id}', child: Image.network('$apiAddress/logos/${business.id}',
height: 48, width: 48, errorBuilder: (BuildContext context, height: 48, width: 48, errorBuilder: (BuildContext context,
Object exception, StackTrace? stackTrace) { Object exception, StackTrace? stackTrace) {
return getIconFromType(business.type, 48, return getIconFromJobType(
Theme.of(context).colorScheme.onSurface); type, 48, Theme.of(context).colorScheme.onSurface);
}), }),
)), )),
Flexible( Flexible(
@@ -607,7 +713,7 @@ class _BusinessCardState extends State<BusinessCard> {
} }
Widget _businessListItem( Widget _businessListItem(
Business business, bool selectable, Function callback) { Business business, bool selectable, Function callback, JobType type) {
return Card( return Card(
child: ListTile( child: ListTile(
leading: ClipRRect( leading: ClipRRect(
@@ -615,8 +721,8 @@ class _BusinessCardState extends State<BusinessCard> {
child: Image.network('$apiAddress/logos/${business.id}', child: Image.network('$apiAddress/logos/${business.id}',
height: 24, width: 24, errorBuilder: (BuildContext context, height: 24, width: 24, errorBuilder: (BuildContext context,
Object exception, StackTrace? stackTrace) { Object exception, StackTrace? stackTrace) {
return getIconFromType( return getIconFromJobType(
business.type, 24, Theme.of(context).colorScheme.onSurface); type, 24, Theme.of(context).colorScheme.onSurface);
})), })),
title: Text(business.name), title: Text(business.name),
subtitle: Text(business.description, subtitle: Text(business.description,
@@ -624,7 +730,11 @@ class _BusinessCardState extends State<BusinessCard> {
trailing: _getCheckbox(selectable, callback), trailing: _getCheckbox(selectable, callback),
onTap: () { onTap: () {
Navigator.of(context).push(MaterialPageRoute( Navigator.of(context).push(MaterialPageRoute(
builder: (context) => BusinessDetail(inputBusiness: business))); builder: (context) => BusinessDetail(
id: business.id,
name: business.name,
clickFromType: type,
)));
}, },
), ),
); );
@@ -675,7 +785,8 @@ class _FilterChipsState extends State<FilterChips> {
showCheckmark: false, showCheckmark: false,
shape: shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
label: Text(type.name), label:
getNameFromType(type, Theme.of(context).colorScheme.onSurface),
selected: selectedChips.contains(type), selected: selectedChips.contains(type),
onSelected: (bool selected) { onSelected: (bool selected) {
setState(() { setState(() {
+39 -39
View File
@@ -17,22 +17,22 @@ cmake_policy(SET CMP0063 NEW)
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
# Root filesystem for cross-building. # Root filesystem for cross-building.
if(FLUTTER_TARGET_PLATFORM_SYSROOT) if (FLUTTER_TARGET_PLATFORM_SYSROOT)
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
endif() endif ()
# Define build configuration options. # Define build configuration options.
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE) STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release") "Debug" "Profile" "Release")
endif() endif ()
# Compilation settings that should be applied to most targets. # Compilation settings that should be applied to most targets.
# #
@@ -40,10 +40,10 @@ endif()
# default. In most cases, you should add new options to specific targets instead # default. In most cases, you should add new options to specific targets instead
# of modifying this function. # of modifying this function.
function(APPLY_STANDARD_SETTINGS TARGET) function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_14) target_compile_features(${TARGET} PUBLIC cxx_std_14)
target_compile_options(${TARGET} PRIVATE -Wall -Werror) target_compile_options(${TARGET} PRIVATE -Wall) # removed -Werror to avoid error from libraries on unused vars
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>") target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>") target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
endfunction() endfunction()
# Flutter library and tool build rules. # Flutter library and tool build rules.
@@ -61,9 +61,9 @@ add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
# #
# Any new source files that you add to the application should be added here. # Any new source files that you add to the application should be added here.
add_executable(${BINARY_NAME} add_executable(${BINARY_NAME}
"main.cc" "main.cc"
"my_application.cc" "my_application.cc"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
) )
# Apply the standard set of build settings. This can be removed for applications # Apply the standard set of build settings. This can be removed for applications
@@ -82,8 +82,8 @@ add_dependencies(${BINARY_NAME} flutter_assemble)
# people trying to run the unbundled copy, put it in a subdirectory instead of # people trying to run the unbundled copy, put it in a subdirectory instead of
# the default top-level location. # the default top-level location.
set_target_properties(${BINARY_NAME} set_target_properties(${BINARY_NAME}
PROPERTIES PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
) )
@@ -96,9 +96,9 @@ include(flutter/generated_plugins.cmake)
# By default, "installing" just makes a relocatable bundle in the build # By default, "installing" just makes a relocatable bundle in the build
# directory. # directory.
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif() endif ()
# Start with a clean build bundle directory every time. # Start with a clean build bundle directory every time.
install(CODE " install(CODE "
@@ -109,19 +109,19 @@ set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime) COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime) COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime) COMPONENT Runtime)
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) foreach (bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
install(FILES "${bundled_library}" install(FILES "${bundled_library}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime) COMPONENT Runtime)
endforeach(bundled_library) endforeach (bundled_library)
# Fully re-copy the assets directory on each build to avoid having stale files # Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install. # from a previous install.
@@ -130,10 +130,10 @@ install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime) " COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only. # Install the AOT library on non-Debug builds only.
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") if (NOT CMAKE_BUILD_TYPE MATCHES "Debug")
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime) COMPONENT Runtime)
endif() endif ()
@@ -7,12 +7,16 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <printing/printing_plugin.h> #include <printing/printing_plugin.h>
#include <rive_common/rive_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h> #include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) { void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) printing_registrar = g_autoptr(FlPluginRegistrar) printing_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "PrintingPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "PrintingPlugin");
printing_plugin_register_with_registrar(printing_registrar); printing_plugin_register_with_registrar(printing_registrar);
g_autoptr(FlPluginRegistrar) rive_common_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "RivePlugin");
rive_plugin_register_with_registrar(rive_common_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
@@ -4,6 +4,7 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
printing printing
rive_common
url_launcher_linux url_launcher_linux
) )
+56 -56
View File
@@ -13,10 +13,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: archive name: archive
sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d" sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.4.10" version: "3.6.1"
async: async:
dependency: transitive dependency: transitive
description: description:
@@ -29,10 +29,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: barcode name: barcode
sha256: "91b143666f7bb13636f716b6d4e412e372ab15ff7969799af8c9e30a382e9385" sha256: ab180ce22c6555d77d45f0178a523669db67f95856e3378259ef2ffeb43e6003
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.6" version: "2.2.8"
bidi: bidi:
dependency: transitive dependency: transitive
description: description:
@@ -93,18 +93,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: cupertino_icons name: cupertino_icons
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.6" version: "1.0.8"
dart_jsonwebtoken: dart_jsonwebtoken:
dependency: "direct main" dependency: "direct main"
description: description:
name: dart_jsonwebtoken name: dart_jsonwebtoken
sha256: "40dc3a4788c02a44bc97ea0c8c4a078ae58c9a45acc2312ee6a689b0e8f5b5b9" sha256: "346e9a21e4bf6e6a431e19ece00ebb2e3668e1e339cabdf6f46d18d88692a848"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.13.0" version: "2.14.0"
ed25519_edwards: ed25519_edwards:
dependency: transitive dependency: transitive
description: description:
@@ -204,10 +204,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: image name: image
sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e" sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.7" version: "4.2.0"
js: js:
dependency: transitive dependency: transitive
description: description:
@@ -220,26 +220,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.0.0" version: "10.0.4"
leak_tracker_flutter_testing: leak_tracker_flutter_testing:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker_flutter_testing name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "3.0.3"
leak_tracker_testing: leak_tracker_testing:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker_testing name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "3.0.1"
lints: lints:
dependency: transitive dependency: transitive
description: description:
@@ -268,10 +268,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.11.0" version: "1.12.0"
open_filex: open_filex:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -300,26 +300,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: path_provider name: path_provider
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.1.3"
path_provider_android: path_provider_android:
dependency: transitive dependency: transitive
description: description:
name: path_provider_android name: path_provider_android
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" sha256: "9c96da072b421e98183f9ea7464898428e764bc0ce5567f27ec8693442e72514"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.2" version: "2.2.5"
path_provider_foundation: path_provider_foundation:
dependency: transitive dependency: transitive
description: description:
name: path_provider_foundation name: path_provider_foundation
sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.2" version: "2.4.0"
path_provider_linux: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
@@ -356,10 +356,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: pdf_widget_wrapper name: pdf_widget_wrapper
sha256: "9c3ca36e5000c9682d52bbdc486867ba7c5ee4403d1a5d6d03ed72157753377b" sha256: c930860d987213a3d58c7ec3b7ecf8085c3897f773e8dc23da9cae60a5d6d0f5
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.3" version: "1.0.4"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
@@ -388,10 +388,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: pointycastle name: pointycastle
sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29" sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.7.4" version: "3.9.1"
printing: printing:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -412,42 +412,42 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: rive name: rive
sha256: ec44b6cf7341e21727c4b0e762f4ac82f9a45f7e52df3ebad2d1289a726fbaaf sha256: "03bae056e67a5c98f9523c34f8ea63c15b323426a7e8d76d7ebb93066d4df6dc"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.13.1" version: "0.13.5"
rive_common: rive_common:
dependency: transitive dependency: transitive
description: description:
name: rive_common name: rive_common
sha256: "0f070bc0e764c570abd8b34d744ef30d1292bd4051f2e0951bbda755875fce6a" sha256: "3fe76ba4680787741688ee393e47b63417e8643816795e4eac01021683af1d84"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.3" version: "0.4.9"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:
name: shared_preferences name: shared_preferences
sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.2" version: "2.2.3"
shared_preferences_android: shared_preferences_android:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_android name: shared_preferences_android
sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" sha256: "93d0ec9dd902d85f326068e6a899487d1f65ffcd5798721a95330b26c8131577"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.1" version: "2.2.3"
shared_preferences_foundation: shared_preferences_foundation:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_foundation name: shared_preferences_foundation
sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.5" version: "2.4.0"
shared_preferences_linux: shared_preferences_linux:
dependency: transitive dependency: transitive
description: description:
@@ -537,10 +537,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.1" version: "0.7.0"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@@ -553,26 +553,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: url_launcher name: url_launcher
sha256: "0ecc004c62fd3ed36a2ffcbe0dd9700aee63bd7532d0b642a488b1ec310f492e" sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.2.5" version: "6.3.0"
url_launcher_android: url_launcher_android:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_android name: url_launcher_android
sha256: d4ed0711849dd8e33eb2dd69c25db0d0d3fdc37e0a62e629fe32f57a22db2745 sha256: ceb2625f0c24ade6ef6778d1de0b2e44f2db71fded235eb52295247feba8c5cf
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.3.0" version: "6.3.3"
url_launcher_ios: url_launcher_ios:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_ios name: url_launcher_ios
sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5" sha256: "7068716403343f6ba4969b4173cbf3b84fc768042124bc2c011e5d782b24fe89"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.2.5" version: "6.3.0"
url_launcher_linux: url_launcher_linux:
dependency: transitive dependency: transitive
description: description:
@@ -585,10 +585,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_macos name: url_launcher_macos
sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.0" version: "3.2.0"
url_launcher_platform_interface: url_launcher_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -601,10 +601,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_web name: url_launcher_web
sha256: "3692a459204a33e04bc94f5fb91158faf4f2c8903281ddd82915adecdb1a901d" sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.3.1"
url_launcher_windows: url_launcher_windows:
dependency: transitive dependency: transitive
description: description:
@@ -633,10 +633,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vm_service name: vm_service
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "13.0.0" version: "14.2.1"
web: web:
dependency: transitive dependency: transitive
description: description:
@@ -649,10 +649,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: "8cb58b45c47dcb42ab3651533626161d6b67a2921917d8d429791f76972b3480" sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.3.0" version: "5.5.1"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
@@ -670,5 +670,5 @@ packages:
source: hosted source: hosted
version: "6.5.0" version: "6.5.0"
sdks: sdks:
dart: ">=3.3.0 <4.0.0" dart: ">=3.4.0 <4.0.0"
flutter: ">=3.19.0" flutter: ">=3.22.0"
+9
View File
@@ -101,3 +101,12 @@ flutter:
# #
# For details regarding fonts from package dependencies, # For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages # see https://flutter.dev/custom-fonts/#from-packages
msix_config:
display_name: Job Link
publisher_display_name: MarinoDev
identity_name: com.marinodev.joblink
msix_version: 0.1.0.0
logo_path: C:\Users\Drake Marino\Code\FBLA24\fbla24\fbla_ui\windows\windows_logo.png
capabilities: internetClient
install_certificate: true
+1 -1
View File
@@ -27,7 +27,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
FlutterWindow window(project); FlutterWindow window(project);
Win32Window::Point origin(10, 10); Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720); Win32Window::Size size(1280, 720);
if (!window.Create(L"fbla_ui", origin, size)) { if (!window.Create(L"Job Link", origin, size)) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
window.SetQuitOnClose(true); window.SetQuitOnClose(true);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 166 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB