Compare commits

...

233 Commits

Author SHA1 Message Date
55nknown
2afad1cc89 update gitmodules 2022-11-21 21:16:52 +01:00
55nknown
94b2fe3cb7 fix app not starting when no users 2022-11-21 21:06:46 +01:00
55nknown
8a75642ec9 update gitmodules 2022-11-21 21:01:34 +01:00
55nknown
f4b03b0304 add premium submodule 2022-11-21 20:08:28 +01:00
annon
5281b9f202 Update README.md 2022-11-21 13:33:17 +01:00
55nknown
db58b54754 bump version 2022-11-21 13:27:39 +01:00
55nknown
7de3d211bb set user to last selected one 2022-11-21 12:41:22 +01:00
55nknown
d3e5fc99ea html fallback 2022-11-21 10:11:24 +01:00
55nknown
17cffc0576 revert goalplanner avghelper 2022-11-21 09:34:51 +01:00
55nknown
f38ff3b862 fix build 2022-11-20 23:03:50 +01:00
55nknown
89e67c369e fix build script 2022-11-20 22:26:27 +01:00
55nknown
6dd68c8f5d changelog 2022-11-20 22:18:21 +01:00
55nknown
97b01e9f14 fix build script 2022-11-20 22:03:46 +01:00
55nknown
556ba9d289 disable widget 2022-11-20 21:55:21 +01:00
55nknown
64868e85f3 widget setup 2022-11-20 21:02:32 +01:00
55nknown
30ac155b4b grade value nocolor 2022-11-20 20:18:58 +01:00
55nknown
36bd679644 cleanup 2022-11-20 19:27:06 +01:00
55nknown
75b03b95bc add premium backend 2022-11-20 19:25:04 +01:00
55nknown
ac18cf62c3 custom theme 2022-11-20 11:55:34 +01:00
55nknown
3619a7a4a7 nickname 2022-11-20 11:54:54 +01:00
55nknown
a71b365e4a deps 2022-11-20 11:38:31 +01:00
55nknown
20fa9a8aef migrate goalplanner 2022-11-17 13:44:12 +01:00
annon
da12ac8646 Goal planner backend (#132)
* refactor avghelper

* #59 backend

Co-authored-by: DarK-rtfm <44683230+DarK-rtfm@users.noreply.github.com>
2022-11-17 13:41:16 +01:00
55nknown
4b40692fe1 logic 2022-11-16 07:42:02 +01:00
55nknown
26f65a4144 fix livecard filter label lessons #128 2022-11-16 07:31:40 +01:00
55nknown
f8cfa04d04 nickname changing 2022-11-15 09:00:38 +01:00
55nknown
82671f5ec9 fix #125 2022-11-14 14:05:51 +01:00
55nknown
5d37de897f icon packs 2022-11-14 10:08:05 +01:00
55nknown
fcb1d8d6d9 fix #122 2022-11-14 09:14:08 +01:00
55nknown
446fad4c5f fix #124 2022-11-14 08:09:34 +01:00
55nknown
0f799375a1 fix #118 2022-11-14 08:02:50 +01:00
55nknown
29aa356cd0 Merge branch 'master' of https://github.com/filc/naplo 2022-11-14 07:39:07 +01:00
55nknown
3abaf2f96a ios livecard id 2022-11-14 07:38:49 +01:00
annon
5b48847cf2 Update FUNDING.yml 2022-11-07 18:19:11 +01:00
55nknown
b82a56ca65 new version 2022-11-02 08:08:49 +01:00
55nknown
94c57abdea mobile 2022-11-02 08:07:37 +01:00
55nknown
e3c26987ec kreta 2022-11-02 08:07:33 +01:00
55nknown
eac710a5b4 fix filters 2022-11-02 08:06:15 +01:00
55nknown
b8299c4daf mobile 2022-10-31 15:57:53 +01:00
55nknown
7a671b6aa6 desktop 2022-10-31 15:57:49 +01:00
55nknown
f51de83c88 fix updates 2022-10-31 15:57:25 +01:00
55nknown
d77cc081a9 changelog 2022-10-31 15:57:20 +01:00
55nknown
18fa9a6de7 build fix 2022-10-31 15:57:15 +01:00
55nknown
4467267e61 move i18n 2022-10-31 14:50:26 +01:00
55nknown
f39b9ffeb6 version bump 2022-10-31 05:13:14 +01:00
55nknown
0df9de97d4 changelog 2022-10-31 05:12:47 +01:00
55nknown
69a3090f16 surprise grades 2022-10-31 05:07:38 +01:00
55nknown
818060bbcb fix goodstudent 2022-10-26 13:10:52 +02:00
55nknown
38eb8c440b gradeopening db 2022-10-26 13:10:46 +02:00
55nknown
47a18c1ec2 animation 2022-10-26 12:06:08 +02:00
55nknown
cc33550331 mobile 2022-10-30 20:24:22 +01:00
55nknown
4af3e51300 👏🏿 👏🏿 👏🏿 2022-10-03 11:24:25 +02:00
55nknown
ea33d00f54 ios live activity 2022-10-03 19:37:39 +02:00
55nknown
75eba2c83f char limit formatter 2022-10-03 11:39:30 +02:00
55nknown
4a81722747 revert shit 2022-10-03 10:10:27 +02:00
55nknown
ed67551164 foreground_service setup 2022-09-18 18:40:40 +02:00
55nknown
c53502f16a sub 2022-09-18 18:29:51 +02:00
El Koulali András
d13de96ae9 support refresh tokens (dkt fix) (#113) 2022-09-18 15:31:01 +02:00
55nknown
53e9aca376 common lesson tile 2022-09-11 21:15:56 +02:00
55nknown
8a39086ca6 common filter 2022-09-11 14:46:58 +02:00
55nknown
fd7793a20d desktop login 2022-09-08 22:35:25 +02:00
55nknown
7a793a3af0 add macos, desktop 2022-09-07 22:12:27 +02:00
55nknown
cac21a4849 new version 2022-09-05 21:02:21 +02:00
55nknown
4c558157e8 open_file dep git 2022-09-05 21:00:42 +02:00
55nknown
043b669737 implement nonce v2 2022-09-05 21:00:31 +02:00
55nknown
7b9ec6de2e flplug_linux 2022-09-05 17:26:00 +02:00
55nknown
b14821901c vnbmp 2022-08-17 21:28:44 +02:00
55nknown
cc05524bea changelog 2022-06-30 03:15:34 +02:00
55nknown
b3d791a4c3 disable m3 2022-06-30 02:10:34 +02:00
55nknown
9525d7d1df clean up 2022-06-30 01:28:42 +02:00
55nknown
bc040185d0 update pods 2022-06-30 01:21:31 +02:00
55nknown
65e98bf8a8 update ios project 2022-06-30 00:45:53 +02:00
unknown
1a0558485e Create dependabot.yml 2022-06-23 06:15:28 -07:00
unknown
8f7c46d2d4 new version 2022-05-10 18:16:32 +02:00
Brúnó Salomon
4f3d44dfed fix black bar (#107) 2022-05-10 18:00:37 +02:00
unknown
922e8984f8 ios fix try 1 2022-05-02 22:52:30 +02:00
unknown
ee475f8ee8 version bump 2022-05-02 22:09:08 +02:00
unknown
3c431cbce1 Livecardrework (#104) 2022-05-02 22:07:06 +02:00
unknown
708c411339 changelog 2022-03-16 14:26:19 +01:00
unknown
1e9247652a build fix 2022-03-16 12:33:45 +01:00
unknown
551ed6ebdb Merge branch 'master' of ssh://github.com/filc/naplo 2022-03-16 11:51:38 +01:00
unknown
c86ac68007 changelog 2022-03-16 11:51:26 +01:00
DarK-rtfm
d309f11f19 fix graph interval 2022-03-14 08:25:30 +01:00
unknown
e4acd4f872 changelog 2022-03-01 17:43:46 +01:00
unknown
3ee91e7543 settings class avg graph 2022-02-28 17:31:03 +01:00
unknown
f147ae328e changelog 2022-02-28 15:52:40 +01:00
unknown
aab1f605d4 run release script 2022-02-28 15:37:04 +01:00
Brúnó Salomon
c71a6d9468 dev: add version in launch config 2022-02-25 18:52:58 +01:00
unknown
165f836d93 kreten 2022-02-04 16:09:07 +01:00
unknown
6356206291 mobile 2022-02-04 16:09:01 +01:00
unknown
bf8ca49f98 fix build script 2022-02-01 18:37:34 +01:00
unknown
0fca636311 compile time version definition 2022-02-01 18:33:13 +01:00
Brúnó Salomon
8673a9e42a chore: version + changelog 2022-01-22 21:55:49 +01:00
Brúnó Salomon
0390d0df39 feat(quick-actions): add localization 2022-01-17 16:50:32 +01:00
Brúnó Salomon
4ec2f74fee chore(quick-actions): add ios icons 2022-01-17 16:45:55 +01:00
ezyyeah
e2078db34b Merge branch 'master' of https://github.com/filc/naplo 2022-01-16 23:08:29 +01:00
ezyyeah
1f39bdc301 quick actions 2022-01-16 23:08:16 +01:00
unknown
029e841d7b version bump 2022-01-16 21:26:55 +01:00
unknown
6a16f93884 version bump 2022-01-16 21:07:08 +01:00
unknown
cfc0229f09 changelog 2022-01-16 21:03:45 +01:00
unknown
a68dd759d8 homework attachments 2022-01-16 18:15:26 +01:00
unknown
e7d0e3805d Merge branch 'master' of ssh://github.com/filc/naplo 2022-01-16 14:08:07 +01:00
unknown
d617d9ef47 changelog 2022-01-16 14:07:05 +01:00
Unknown
2564224a6e Update changelog.md 2022-01-15 01:52:03 +01:00
unknown
5c2b690bc8 mobile 2022-01-15 01:50:42 +01:00
unknown
ff72d146c0 version bump 2022-01-14 23:50:11 +01:00
unknown
088b6e4580 mobile 2022-01-14 23:44:47 +01:00
Unknown
66793607e8 Update README.md 2022-01-14 23:19:04 +01:00
unknown
c91e792c6c changelog 2022-01-14 23:09:50 +01:00
unknown
16d0bd4163 mobile 2022-01-14 23:09:38 +01:00
Unknown
e559338483 Update README.md 2022-01-08 18:28:47 +01:00
unknown
f2c29aa81b 3.2.0 2022-01-08 15:42:49 +01:00
unknown
e44a3b7330 mobile 2022-01-07 07:05:54 +01:00
unknown
64606311bf mobile 2022-01-07 06:49:17 +01:00
unknown
43c4d2e454 mobile 2022-01-05 16:11:10 +01:00
unknown
9b579ad196 cleanup 2022-01-05 16:10:21 +01:00
unknown
425a4aaa91 cleanup 2022-01-05 15:39:24 +01:00
unknown
506c04aea9 build fix 2022-01-05 13:47:53 +01:00
unknown
cd18cfb220 mobile 2022-01-05 13:03:02 +01:00
unknown
f071e59e7a kreten 2022-01-05 13:02:57 +01:00
unknown
49f3a447b3 desktop 2022-01-05 13:02:52 +01:00
unknown
339dbea1ef flutter linting 2022-01-05 13:02:22 +01:00
unknown
79f6ef4c50 kreta 2022-01-05 12:32:26 +01:00
unknown
ae18fbab2e mobile 2022-01-05 12:32:18 +01:00
unknown
9003e13aa1 updater update 2022-01-05 12:30:43 +01:00
unknown
e58f4859b4 changelog 2022-01-05 12:30:07 +01:00
unknown
be809c6aaf Merge branch 'master' of ssh://github.com/filc/naplo 2022-01-05 12:18:48 +01:00
Brúnó Salomon
a6b012035b mobile 2021-12-30 17:26:43 +01:00
unknown
a0ef512c55 changelog 2021-12-03 23:02:12 +01:00
unknown
4e1c1be6e4 better updates 2021-12-03 23:00:13 +01:00
unknown
92a93941ab changelog 2021-12-03 22:08:27 +01:00
Unknown
ece621455c Update README.md 2021-11-20 15:55:44 +01:00
Unknown
0787f97a6f Update README.md 2021-11-17 22:52:29 +01:00
unknown
c74975e3bc kreten 2021-11-07 11:26:29 +01:00
unknown
4b669b0069 mobile 2021-11-07 11:26:21 +01:00
unknown
d24286b61c mobile 2021-11-07 10:35:57 +01:00
unknown
40f3d2159a revert build fixes, downgrade permission_handler (sdk 30) 2021-11-02 22:10:35 +01:00
unknown
8df77b5c06 3.1.1 changelog 2021-11-02 21:30:49 +01:00
unknown
fd88b5ee55 build fix (gradle ver) 2021-11-02 21:05:11 +01:00
unknown
ceaafb4897 build fix 2021-11-02 20:28:12 +01:00
unknown
6617882892 build fix 2021-11-02 19:57:56 +01:00
unknown
1b16b748d4 bump android sdk version 2021-11-02 19:57:28 +01:00
unknown
7014255b89 bump 3.1.1 2021-11-02 16:12:34 +01:00
unknown
d86f2fffe5 kreten 2021-11-02 16:07:38 +01:00
unknown
de1b0444b6 kreten 2021-11-02 14:12:33 +01:00
unknown
bd1a8358f6 mobile 2021-11-02 14:12:23 +01:00
unknown
8a670b3a4c bump 2021-10-03 20:14:02 +02:00
unknown
2917a2e913 mobile 2021-10-03 16:19:45 +02:00
unknown
414c183399 mobile 2021-10-03 15:13:29 +02:00
unknown
428d6ff975 wait for progress animation to finish 2021-10-03 15:04:47 +02:00
unknown
c418a61133 cleanup 2021-10-03 14:20:43 +02:00
unknown
7e83d7b969 sqflite fix 2021-10-03 12:17:32 +02:00
unknown
bdf6cc20c7 linux .desktop file 2021-10-03 01:11:02 +02:00
unknown
da089da22b add linux desktop support 2021-10-03 01:04:45 +02:00
unknown
fa3c0954c4 mobile 2021-10-03 00:44:50 +02:00
unknown
f4843211f5 changelog 2021-10-03 00:42:03 +02:00
unknown
5e1727eadd kreten 2021-10-03 00:28:35 +02:00
unknown
dd103e7474 mobile 2021-10-03 00:28:33 +02:00
unknown
aa61301b17 format weekday on current week 2021-10-03 00:27:58 +02:00
unknown
af360fda53 refactor 2021-10-03 00:27:40 +02:00
unknown
a56453ab9d status 2021-10-02 21:47:41 +02:00
unknown
c9aed14d7c changelog 2021-10-02 15:46:52 +02:00
unknown
0cef766ee6 bump 3.1.0 2021-10-02 15:45:43 +02:00
unknown
c8cd6bf9b8 subject icon fix 2021-10-02 15:44:33 +02:00
unknown
d9e8b4f4ed add tomorrow 2021-10-02 15:35:36 +02:00
unknown
8ab96a32c3 changelog 2021-10-02 00:14:11 +02:00
unknown
22c8a285ab kreten 2021-10-02 00:07:15 +02:00
unknown
d819245e31 bump 3.0.6 2021-09-30 23:09:45 +02:00
unknown
6b55721ec5 mobile 2021-09-30 22:58:57 +02:00
unknown
13062c4a9b kreten 2021-09-30 22:58:50 +02:00
unknown
2188eaf1c6 high refresh rate patch 2021-09-30 22:37:30 +02:00
unknown
922d252c57 mobile 2021-09-30 21:57:55 +02:00
unknown
7a5290efe5 kreten 2021-09-30 21:57:52 +02:00
unknown
d1e6cc1fbb connectivity testing 2021-09-30 21:57:45 +02:00
unknown
b6a8696911 fix network bug 2021-09-30 21:57:21 +02:00
unknown
f7efd65f5e mobile 2021-09-30 19:51:29 +02:00
unknown
9963f65ab2 mobile 2021-09-30 19:16:53 +02:00
unknown
8d8c3a54c2 use default user-agent for config 2021-09-29 18:18:50 +02:00
unknown
c49d93c7b1 mobile 2021-09-26 20:59:04 +02:00
unknown
b0a7ab20d9 changelog 2021-09-26 01:45:10 +02:00
unknown
59a9e0e236 bump 3.0.5 2021-09-26 01:40:29 +02:00
unknown
e83202ba29 cleanup 2021-09-26 01:39:41 +02:00
unknown
d4489dd9d4 kreten 2021-09-26 01:33:36 +02:00
Unknown
debb4cac5b Merge pull request #26 from filc/analytics
Analytics
2021-09-26 01:32:15 +02:00
unknown
acbdf2aecb Merge branch 'master' into analytics 2021-09-26 01:31:39 +02:00
unknown
5d9ea4311a mobile 2021-09-20 12:53:04 +02:00
unknown
8aa24d7a32 mobile 2021-09-20 12:39:07 +02:00
unknown
9fbcef357e error report 2021-09-20 12:38:06 +02:00
unknown
e9423a8535 mobile 2021-09-19 18:47:20 +02:00
unknown
2a7265256d analytics client 2021-09-19 17:56:38 +02:00
unknown
d5ba231fcc fix database migration 2021-09-18 16:30:17 +02:00
unknown
68d92c4462 changelog 2021-09-18 14:27:44 +02:00
unknown
a46f6130cb ios dist fix 2021-09-18 14:25:20 +02:00
unknown
c6021e7a69 version bump 2021-09-18 12:24:56 +02:00
unknown
3ceb40ddb8 mobile 2021-09-18 12:24:38 +02:00
unknown
593292e860 kreten 2021-09-18 12:24:36 +02:00
unknown
ab952daa10 vibration cleanup 2021-09-18 11:57:01 +02:00
unknown
56b6eb3d2b haptics 2021-09-18 11:17:41 +02:00
unknown
26be20ac28 version bump 2021-09-12 20:25:22 +02:00
unknown
afd004b980 migrate users db 2021-09-12 20:24:39 +02:00
unknown
903cbba69a basics 2021-09-12 19:33:26 +02:00
unknown
6eb6a0cf16 kreten 2021-09-12 18:32:43 +02:00
unknown
abf9f9b13d todo 2021-09-11 19:26:53 +02:00
unknown
0471843919 mobile 2021-09-11 19:13:04 +02:00
unknown
bb83dd2137 version bump 2021-09-11 19:11:04 +02:00
unknown
d9b07525a4 mobile 2021-09-11 19:10:40 +02:00
unknown
92334406c5 kreten 2021-09-11 19:10:35 +02:00
unknown
506af7fa71 mobile 2021-09-11 18:41:52 +02:00
unknown
f5be03e5ce add role 2021-09-11 18:40:11 +02:00
unknown
2047784d8f mobile 2021-09-11 17:58:31 +02:00
unknown
037cf1df48 kreten 2021-09-11 17:58:27 +02:00
unknown
8238cc0678 role 2021-09-11 17:55:27 +02:00
unknown
f52a747268 mobile 2021-09-11 16:01:56 +02:00
unknown
c37bdc7aff kreten 2021-09-11 16:01:52 +02:00
unknown
9015ff2d6b mobile 2021-09-11 15:53:47 +02:00
unknown
a801503838 trim 2021-09-11 15:53:37 +02:00
55nknown
b0565ddcd9 mobile 2021-09-06 09:54:22 +02:00
55nknown
d8b6ba9a64 ios 2021-09-06 09:39:45 +02:00
unknown
549570a48c version bump 2021-09-05 19:54:30 +02:00
unknown
930e8d86b8 mobile 2021-09-05 19:36:14 +02:00
unknown
4aff7df96f kreten 2021-09-05 19:36:10 +02:00
unknown
c839ac178a android build fix 2021-09-05 19:35:08 +02:00
unknown
2c7efc0a18 kreten 2021-09-05 18:49:59 +02:00
unknown
5252813721 mobile 2021-09-05 18:48:23 +02:00
unknown
15d3daefed kreten 2021-09-05 18:48:18 +02:00
unknown
0729c13362 mobile 2021-09-05 16:58:33 +02:00
unknown
4bb978ba63 mobile 2021-09-05 16:55:43 +02:00
unknown
d9c071b5ae update gradle version 2021-09-05 16:53:13 +02:00
unknown
6ffe4b6c33 gitignore 2021-09-04 20:29:46 +02:00
unknown
7bd5d8bd5a version bump 2021-09-04 20:24:03 +02:00
unknown
c0ffa5d726 commit 2021-09-04 20:05:48 +02:00
unknown
befb107183 mobile 2021-09-04 12:11:15 +02:00
unknown
e56488094e mobile 2021-09-02 20:15:02 +02:00
172 changed files with 6137 additions and 588 deletions

2
.github/FUNDING.yml vendored
View File

@@ -1 +1 @@
patreon: filcnaplo
github: filc

11
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "pub" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "daily"

5
.gitignore vendored
View File

@@ -1,5 +1,8 @@
# See https://www.dartlang.org/guides/libraries/private-files
termek.txt
.DS_Store
# Files and directories created by pub
.dart_tool/
.packages
@@ -19,3 +22,5 @@ doc/api/
*.js_
*.js.deps
*.js.map
*.txt

4
.gitmodules vendored
View File

@@ -9,3 +9,7 @@
[submodule "Kreta API"]
path = filcnaplo_kreta_api
url = https://github.com/filc/kreten
[submodule "Premium"]
path = filcnaplo_premium
url = git@github.com:filc/premium

5
.vscode/launch.json vendored
View File

@@ -8,7 +8,10 @@
"name": "filcnaplo",
"cwd": "filcnaplo",
"request": "launch",
"type": "dart"
"type": "dart",
"toolArgs": [
"--dart-define=APPVER=$(cat pubspec.yaml | grep version: | cut -d' ' -f2 | cut -d+ -f1)"
]
}
]
}

View File

@@ -5,20 +5,20 @@
#### Nem hivatalos e-napló alkalmazás az eKRÉTA rendszerhez
[![Downloads](https://img.shields.io/github/downloads-pre/filc/naplo/latest/total?color=%23&label=Downloads&logo=github&sort=semver)](https://github.com/filc/naplo/releases) &nbsp; [![discord](https://img.shields.io/discord/712698455193157643?label=Discord)](http://filcnaplo.hu/discord) &nbsp; [![Codemagic build status](https://api.codemagic.io/apps/612cc79b35b443d1b2c638ec/612cc79b35b443d1b2c638eb/status_badge.svg)](https://codemagic.io/apps/612cc79b35b443d1b2c638ec/612cc79b35b443d1b2c638eb/latest_build)
[![Downloads](https://img.shields.io/github/downloads-pre/filc/naplo/latest/total?color=%23&label=Downloads&logo=github&sort=semver)](https://github.com/filc/naplo/releases) &nbsp; [![discord](https://img.shields.io/discord/712698455193157643?label=Discord)](http://filcnaplo.hu/discord)
## Setup
### Clone the project:
### Clone the project
```
$ git clone --recursive https://github.com/filc/naplo
$ cd naplo
```sh
git clone --recursive https://github.com/filc/naplo
cd naplo
```
### Run the app:
### Run the app
```
$ cd filcnaplo
$ flutter run
```sh
cd filcnaplo
flutter run
```

4
changelog.md Normal file
View File

@@ -0,0 +1,4 @@
What's new:
- Hibajavítások 🐛
- **Megérkezett a Filc Premium!** ✨

View File

@@ -8,7 +8,6 @@
.buildlog/
.history
.svn/
build.sh
# IntelliJ related
*.iml

View File

@@ -1,10 +1,30 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
# This file should be version controlled.
version:
revision: 06e2fd63574bad2edafbe4653104ed76871ee0b1
revision: 3c0bee85b8e43b860877922bdc411a7333db4d32
channel: beta
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 3c0bee85b8e43b860877922bdc411a7333db4d32
base_revision: 3c0bee85b8e43b860877922bdc411a7333db4d32
- platform: macos
create_revision: 3c0bee85b8e43b860877922bdc411a7333db4d32
base_revision: 3c0bee85b8e43b860877922bdc411a7333db4d32
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

View File

@@ -0,0 +1,29 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View File

@@ -9,3 +9,4 @@ GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
.project

View File

@@ -26,13 +26,8 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file("$System.env.ANDROID_SIGNING")
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
} else {
keystoreProperties.load(new FileInputStream(rootProject.file("signing/signing.properties")))
}
def keystorePropertiesFile = rootProject.file("$System.env.HOME/keys/filc3.properties")
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
android {
compileSdkVersion rootProject.ext.compileSdkVersion
@@ -52,6 +47,7 @@ android {
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true
}
signingConfigs {
@@ -77,6 +73,7 @@ flutter {
}
dependencies {
implementation 'com.android.support:multidex:2.0.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'

View File

@@ -1,19 +1,62 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="hu.filc.naplo">
<application android:label="Filc Napló" android:icon="@mipmap/ic_launcher" android:requestLegacyExternalStorage="true">
<activity android:name=".MainActivity" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
<meta-data android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/NormalTheme" />
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<meta-data android:name="flutterEmbedding" android:value="2" />
</application>
<application android:label="Filc Napló" android:icon="@mipmap/ic_launcher" android:requestLegacyExternalStorage="true">
<activity android:exported="true" android:name=".MainActivity" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
<meta-data android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/NormalTheme" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Accepts URIs that begin with https://api.filcnaplo.hu -->
<data
android:scheme="https"
android:host="api.filcnaplo.hu"
android:pathPrefix="/callback" />
</intent-filter>
</activity>
<meta-data android:name="flutterEmbedding" android:value="2" />
<!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<!-- <receiver android:name=".WidgetTimetable.widget_timetable"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="list_widget.ACTION_WIDGET_CLICK_NAV_LEFT" />
<action android:name="list_widget.ACTION_WIDGET_CLICK_NAV_RIGHT" />
<action android:name="list_widget.ACTION_WIDGET_CLICK_NAV_TODAY" />
<action android:name="list_widget.ACTION_WIDGET_CLICK_NAV_REFRESH" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/home_widget_test_info" />
</receiver>
<service android:name=".WidgetTimetable.widget_timetable_service"
android:permission="android.permission.BIND_REMOTEVIEWS" />
<receiver android:name="es.antonborri.home_widget.HomeWidgetBackgroundReceiver"
android:exported="true">
<intent-filter>
<action android:name="es.antonborri.home_widget.action.BACKGROUND" />
</intent-filter>
</receiver>
<service android:name="es.antonborri.home_widget.HomeWidgetBackgroundService"
android:permission="android.permission.BIND_JOB_SERVICE" android:exported="true" />
-->
</application>
<!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@@ -1,9 +1,9 @@
buildscript {
ext.kotlin_version = '1.3.50'
ext.kotlin_version = '1.6.10'
ext {
compileSdkVersion = 30
targetSdkVersion = 30
compileSdkVersion = 33
targetSdkVersion = 33
appCompatVersion = "1.1.0"
}
@@ -13,7 +13,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.3'
classpath 'com.android.tools.build:gradle:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
@@ -33,8 +33,8 @@ subprojects {
afterEvaluate {project ->
if (project.plugins.hasPlugin('android') || project.plugins.hasPlugin('android-library')) {
android {
compileSdkVersion 30
buildToolsVersion '30.0.3'
compileSdkVersion 33
buildToolsVersion '31.0.0'
}
}
}

View File

@@ -1,4 +0,0 @@
keyAlias=test
keyPassword=test123
storeFile=../signing/signing.keystore
storePassword=test123

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,5 +0,0 @@
<svg width="20" height="22" viewBox="0 0 20 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M1 8L10 1L19 8V19C19 19.5304 18.7893 20.0391 18.4142 20.4142C18.0391 20.7893 17.5304 21 17 21H3C2.46957 21 1.96086 20.7893 1.58579 20.4142C1.21071 20.0391 1 19.5304 1 19V8Z"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>

Before

Width:  |  Height:  |  Size: 384 B

View File

@@ -1,3 +0,0 @@
<svg width="288" height="288" viewBox="0 0 288 288" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M159.653 207.26C136.598 217.973 115.171 217.858 100.771 216.821C83.6064 215.583 69.7824 210.888 63.72 206.77C59.976 204.236 54.8928 205.215 52.3584 208.959C49.824 212.703 50.8032 217.786 54.5472 220.32C64.3104 226.93 81.5616 231.84 99.5904 233.136C102.643 233.367 105.998 233.496 109.613 233.496C125.309 233.496 145.238 231.999 166.55 222.092C170.64 220.176 172.426 215.324 170.51 211.22C168.61 207.13 163.742 205.344 159.653 207.26ZM250.574 195.135C251.352 124.057 258.667 -10.2371 122.616 0.620487C-11.7072 11.4348 23.904 153.332 21.9024 200.852C20.1312 225.994 11.7936 256.723 0 288H36.3024C40.0464 274.752 42.7824 261.634 43.9632 249.149C46.152 250.675 48.4992 252.144 51.0048 253.555C55.0656 255.96 58.5648 259.143 62.2656 262.512C70.9056 270.389 80.712 279.331 99.864 280.44C101.146 280.512 102.442 280.555 103.709 280.555C123.091 280.555 136.339 272.074 146.995 265.263C152.093 261.994 156.499 259.171 160.646 257.818C172.44 254.131 182.736 248.17 190.426 240.595C191.635 239.415 192.744 238.191 193.795 236.952C198.086 252.663 203.962 270.36 210.47 288H288C269.366 259.258 250.171 231.077 250.574 195.135ZM34.9056 156.471V156.457C33.5664 133.258 44.6688 113.761 59.688 112.882C74.7072 112.004 87.9696 130.105 89.3088 153.289C89.3088 153.303 89.3088 153.303 89.3088 153.303C89.3808 154.556 89.4096 155.794 89.4096 157.018C84.6576 158.213 80.352 159.956 76.5072 161.986C76.4928 161.813 76.4784 161.641 76.464 161.468V161.453C75.1824 148.292 68.1552 138.356 60.7536 139.249C53.3664 140.156 48.4128 151.561 49.7088 164.722C50.2704 170.468 51.912 175.594 54.216 179.482C53.64 179.929 52.0272 181.109 50.1696 182.477C48.7728 183.5 47.0592 184.738 45.0144 186.25C39.4272 178.906 35.5968 168.365 34.9056 156.471ZM187.099 213.797C186.566 226.051 170.525 237.586 155.722 242.208L155.635 242.237C149.472 244.239 143.986 247.752 138.168 251.467C128.405 257.717 118.296 264.183 103.709 264.183C102.744 264.183 101.765 264.154 100.8 264.096C87.4368 263.319 81.1872 257.631 73.2816 250.416C69.1056 246.615 64.7856 242.669 59.2272 239.415L59.0976 239.343C47.088 232.56 39.6288 224.136 39.1536 216.778C38.9232 213.135 40.5504 209.967 43.992 207.389C51.48 201.773 56.5056 198.101 59.8176 195.668C63.504 192.975 64.6272 192.168 65.448 191.376C66.0384 190.829 66.672 190.21 67.3632 189.533C74.232 182.852 85.7376 171.663 103.406 171.663C114.206 171.663 126.158 175.825 138.888 184.004C144.878 187.906 150.106 189.706 156.715 191.996C161.251 193.565 166.406 195.336 173.304 198.288L173.419 198.346C179.842 200.981 187.445 205.805 187.099 213.783V213.797ZM183.557 184.997C182.318 184.378 181.022 183.788 179.683 183.226C173.462 180.576 168.48 178.762 164.347 177.322C166.637 172.858 168.048 167.285 168.178 161.223C168.494 146.492 161.064 134.525 151.589 134.511C142.099 134.482 134.165 146.405 133.848 161.137C133.834 161.626 133.834 162.101 133.848 162.577C128.002 159.898 122.256 157.94 116.64 156.745C116.611 156.183 116.582 155.636 116.568 155.074V155.06C116.021 128.233 132.494 106.014 153.346 105.452C174.197 104.89 191.549 126.174 192.096 153.015V153.029C192.341 165.169 189.101 176.329 183.557 184.997Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.2 KiB

13
filcnaplo/build.sh Executable file
View File

@@ -0,0 +1,13 @@
#!/usr/bin/env fish
# With build number
function get_version_bn
cat pubspec.yaml | grep version: | cut -d' ' -f2
end
function get_version
cat pubspec.yaml | grep version: | cut -d' ' -f2 | cut -d+ -f1
end
flutter build apk --release --dart-define=APPVER=(get_version) --no-tree-shake-icons && \
cp -v "build/app/outputs/flutter-apk/app-release.apk" ~/"Desktop/hu.filc.naplo_"(get_version_bn).apk

View File

@@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>8.0</string>
<string>11.0</string>
</dict>
</plist>

View File

@@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"

View File

@@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"

43
filcnaplo/ios/Podfile Normal file
View File

@@ -0,0 +1,43 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '11.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
pod 'DKImagePickerController/PhotoGallery', :git => 'https://github.com/zhangao0086/DKImagePickerController.git'
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end

164
filcnaplo/ios/Podfile.lock Normal file
View File

@@ -0,0 +1,164 @@
PODS:
- connectivity_plus (0.0.1):
- Flutter
- ReachabilitySwift
- DKImagePickerController/Core (4.3.4):
- DKImagePickerController/ImageDataManager
- DKImagePickerController/Resource
- DKImagePickerController/ImageDataManager (4.3.4)
- DKImagePickerController/PhotoGallery (4.3.4):
- DKImagePickerController/Core
- DKPhotoGallery
- DKImagePickerController/Resource (4.3.4)
- DKPhotoGallery (0.0.17):
- DKPhotoGallery/Core (= 0.0.17)
- DKPhotoGallery/Model (= 0.0.17)
- DKPhotoGallery/Preview (= 0.0.17)
- DKPhotoGallery/Resource (= 0.0.17)
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Core (0.0.17):
- DKPhotoGallery/Model
- DKPhotoGallery/Preview
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Model (0.0.17):
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Preview (0.0.17):
- DKPhotoGallery/Model
- DKPhotoGallery/Resource
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Resource (0.0.17):
- SDWebImage
- SwiftyGif
- file_picker (0.0.1):
- DKImagePickerController/PhotoGallery
- Flutter
- Flutter (1.0.0)
- flutter_custom_tabs (0.0.1):
- Flutter
- FMDB (2.7.5):
- FMDB/standard (= 2.7.5)
- FMDB/standard (2.7.5)
- home_widget (0.0.1):
- Flutter
- live_activities (0.0.1):
- Flutter
- open_file (0.0.1):
- Flutter
- path_provider_ios (0.0.1):
- Flutter
- permission_handler_apple (9.0.4):
- Flutter
- quick_actions_ios (0.0.1):
- Flutter
- ReachabilitySwift (5.0.0)
- SDWebImage (5.13.2):
- SDWebImage/Core (= 5.13.2)
- SDWebImage/Core (5.13.2)
- share_plus (0.0.1):
- Flutter
- sqflite (0.0.2):
- Flutter
- FMDB (>= 2.7.5)
- SwiftyGif (5.4.3)
- uni_links (0.0.1):
- Flutter
- url_launcher_ios (0.0.1):
- Flutter
- workmanager (0.0.1):
- Flutter
DEPENDENCIES:
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- DKImagePickerController/PhotoGallery (from `https://github.com/zhangao0086/DKImagePickerController.git`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`)
- flutter_custom_tabs (from `.symlinks/plugins/flutter_custom_tabs/ios`)
- home_widget (from `.symlinks/plugins/home_widget/ios`)
- live_activities (from `.symlinks/plugins/live_activities/ios`)
- open_file (from `.symlinks/plugins/open_file/ios`)
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- quick_actions_ios (from `.symlinks/plugins/quick_actions_ios/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- sqflite (from `.symlinks/plugins/sqflite/ios`)
- uni_links (from `.symlinks/plugins/uni_links/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- workmanager (from `.symlinks/plugins/workmanager/ios`)
SPEC REPOS:
trunk:
- DKPhotoGallery
- FMDB
- ReachabilitySwift
- SDWebImage
- SwiftyGif
EXTERNAL SOURCES:
connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/ios"
DKImagePickerController:
:git: https://github.com/zhangao0086/DKImagePickerController.git
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
Flutter:
:path: Flutter
flutter_custom_tabs:
:path: ".symlinks/plugins/flutter_custom_tabs/ios"
home_widget:
:path: ".symlinks/plugins/home_widget/ios"
live_activities:
:path: ".symlinks/plugins/live_activities/ios"
open_file:
:path: ".symlinks/plugins/open_file/ios"
path_provider_ios:
:path: ".symlinks/plugins/path_provider_ios/ios"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
quick_actions_ios:
:path: ".symlinks/plugins/quick_actions_ios/ios"
share_plus:
:path: ".symlinks/plugins/share_plus/ios"
sqflite:
:path: ".symlinks/plugins/sqflite/ios"
uni_links:
:path: ".symlinks/plugins/uni_links/ios"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
workmanager:
:path: ".symlinks/plugins/workmanager/ios"
CHECKOUT OPTIONS:
DKImagePickerController:
:commit: a727e44718a67e300089174e5591166045815ba4
:git: https://github.com/zhangao0086/DKImagePickerController.git
SPEC CHECKSUMS:
connectivity_plus: 413a8857dd5d9f1c399a39130850d02fe0feaf7e
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
file_picker: 817ab1d8cd2da9d2da412a417162deee3500fc95
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_custom_tabs: 7a10a08686955cb748e5d26e0ae586d30689bf89
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
home_widget: 2829415127ee92e876f816cbbe44c0b6601b8a37
live_activities: 9ff56a06a2d43ecd68f56deeed13b18a8304789c
open_file: 02eb5cb6b21264bd3a696876f5afbfb7ca4f4b7d
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce
quick_actions_ios: 56c03753992beabaa6c598e470e12d430d6e5e2f
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
SDWebImage: 72f86271a6f3139cc7e4a89220946489d4b9a866
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de
workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6
PODFILE CHECKSUM: 862f939bb7e5390bdb8b2534eb81a9457ea9fbdc
COCOAPODS: 1.11.3

View File

@@ -3,11 +3,19 @@
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objectVersion = 51;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3127F78D28EAEDE200C2EFB3 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3127F75528EAECC800C2EFB3 /* WidgetKit.framework */; };
3127F78E28EAEDE200C2EFB3 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3127F75728EAECC800C2EFB3 /* SwiftUI.framework */; };
3127F79828EAEDE300C2EFB3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3127F79728EAEDE300C2EFB3 /* Assets.xcassets */; };
3127F79E28EAEDE300C2EFB3 /* livecard.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 3127F78C28EAEDE200C2EFB3 /* livecard.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
3127F7A428EAEE3D00C2EFB3 /* livecard.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 3127F7A328EAEE3D00C2EFB3 /* livecard.intentdefinition */; };
3127F7A628EAEE5900C2EFB3 /* livecard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3127F7A528EAEE5900C2EFB3 /* livecard.swift */; };
3127F7A828EAEE8500C2EFB3 /* lesson_model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3127F7A728EAEE8500C2EFB3 /* lesson_model.swift */; };
373A6ECB5FC71FE9D8AF2EDB /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F0ADD56276103500A3016C8 /* Pods_Runner.framework */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
@@ -15,7 +23,28 @@
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
3127F79C28EAEDE300C2EFB3 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 3127F78B28EAEDE200C2EFB3;
remoteInfo = livecardExtension;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
3127F74F28EAEC8A00C2EFB3 /* Embed Foundation Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 13;
files = (
3127F79E28EAEDE300C2EFB3 /* livecard.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
@@ -31,10 +60,23 @@
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
1F0ADD56276103500A3016C8 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3127F73928EAEC3200C2EFB3 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
3127F73F28EAEC8A00C2EFB3 /* IntentsUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IntentsUI.framework; path = System/Library/Frameworks/IntentsUI.framework; sourceTree = SDKROOT; };
3127F75528EAECC800C2EFB3 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
3127F75728EAECC800C2EFB3 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
3127F78C28EAEDE200C2EFB3 /* livecard.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = livecard.appex; sourceTree = BUILT_PRODUCTS_DIR; };
3127F79728EAEDE300C2EFB3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
3127F79928EAEDE300C2EFB3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
3127F7A328EAEE3D00C2EFB3 /* livecard.intentdefinition */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.intentdefinition; path = livecard.intentdefinition; sourceTree = "<group>"; };
3127F7A528EAEE5900C2EFB3 /* livecard.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = livecard.swift; sourceTree = "<group>"; tabWidth = 2; };
3127F7A728EAEE8500C2EFB3 /* lesson_model.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = lesson_model.swift; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
707F8089D970F81C480F73C4 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
80777CF254888CE770D5F909 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -42,19 +84,63 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
98578F0EBCC6D3FF8391AAEB /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
3127F78928EAEDE200C2EFB3 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
3127F78E28EAEDE200C2EFB3 /* SwiftUI.framework in Frameworks */,
3127F78D28EAEDE200C2EFB3 /* WidgetKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
373A6ECB5FC71FE9D8AF2EDB /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
3127F78F28EAEDE200C2EFB3 /* livecard */ = {
isa = PBXGroup;
children = (
3127F7A728EAEE8500C2EFB3 /* lesson_model.swift */,
3127F7A328EAEE3D00C2EFB3 /* livecard.intentdefinition */,
3127F79728EAEDE300C2EFB3 /* Assets.xcassets */,
3127F79928EAEDE300C2EFB3 /* Info.plist */,
3127F7A528EAEE5900C2EFB3 /* livecard.swift */,
);
path = livecard;
sourceTree = "<group>";
};
6640A963014A9D4F31026053 /* Frameworks */ = {
isa = PBXGroup;
children = (
1F0ADD56276103500A3016C8 /* Pods_Runner.framework */,
3127F73F28EAEC8A00C2EFB3 /* IntentsUI.framework */,
3127F75528EAECC800C2EFB3 /* WidgetKit.framework */,
3127F75728EAECC800C2EFB3 /* SwiftUI.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
91FEB6212755D596FFFFEC73 /* Pods */ = {
isa = PBXGroup;
children = (
80777CF254888CE770D5F909 /* Pods-Runner.debug.xcconfig */,
98578F0EBCC6D3FF8391AAEB /* Pods-Runner.release.xcconfig */,
707F8089D970F81C480F73C4 /* Pods-Runner.profile.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
@@ -71,7 +157,10 @@
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
3127F78F28EAEDE200C2EFB3 /* livecard */,
97C146EF1CF9000F007C117D /* Products */,
91FEB6212755D596FFFFEC73 /* Pods */,
6640A963014A9D4F31026053 /* Frameworks */,
);
sourceTree = "<group>";
};
@@ -79,6 +168,7 @@
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
3127F78C28EAEDE200C2EFB3 /* livecard.appex */,
);
name = Products;
sourceTree = "<group>";
@@ -86,6 +176,7 @@
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
3127F73928EAEC3200C2EFB3 /* Runner.entitlements */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
@@ -101,20 +192,41 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
3127F78B28EAEDE200C2EFB3 /* livecard */ = {
isa = PBXNativeTarget;
buildConfigurationList = 3127F79F28EAEDE300C2EFB3 /* Build configuration list for PBXNativeTarget "livecard" */;
buildPhases = (
3127F78828EAEDE200C2EFB3 /* Sources */,
3127F78928EAEDE200C2EFB3 /* Frameworks */,
3127F78A28EAEDE200C2EFB3 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = livecard;
productName = livecardExtension;
productReference = 3127F78C28EAEDE200C2EFB3 /* livecard.appex */;
productType = "com.apple.product-type.app-extension";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
779338C8D92BCBC36F75F791 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
71459C0EB905E05018E3D78F /* [CP] Embed Pods Frameworks */,
3127F74F28EAEC8A00C2EFB3 /* Embed Foundation Extensions */,
);
buildRules = (
);
dependencies = (
3127F79D28EAEDE300C2EFB3 /* PBXTargetDependency */,
);
name = Runner;
productName = Runner;
@@ -127,9 +239,13 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1020;
LastSwiftUpdateCheck = 1410;
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = "";
TargetAttributes = {
3127F78B28EAEDE200C2EFB3 = {
CreatedOnToolsVersion = 14.1;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
@@ -150,11 +266,20 @@
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
3127F78B28EAEDE200C2EFB3 /* livecard */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
3127F78A28EAEDE200C2EFB3 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
3127F79828EAEDE300C2EFB3 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -183,6 +308,45 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
71459C0EB905E05018E3D78F /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
779338C8D92BCBC36F75F791 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -200,6 +364,16 @@
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
3127F78828EAEDE200C2EFB3 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
3127F7A828EAEE8500C2EFB3 /* lesson_model.swift in Sources */,
3127F7A428EAEE3D00C2EFB3 /* livecard.intentdefinition in Sources */,
3127F7A628EAEE5900C2EFB3 /* livecard.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -211,6 +385,14 @@
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
3127F79D28EAEDE300C2EFB3 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 3127F78B28EAEDE200C2EFB3 /* livecard */;
targetProxy = 3127F79C28EAEDE300C2EFB3 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
@@ -254,6 +436,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@@ -272,7 +455,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -285,12 +468,18 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = MYUTW2GF6J;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = hu.filc.naplo;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -299,6 +488,125 @@
};
name = Profile;
};
3127F7A028EAEDE300C2EFB3 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = MYUTW2GF6J;
GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = livecard/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = livecard;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_NSSupportsLiveActivities = YES;
IPHONEOS_DEPLOYMENT_TARGET = 16.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = hu.filc.naplo.livecardpro;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
3127F7A128EAEDE300C2EFB3 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = MYUTW2GF6J;
GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = livecard/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = livecard;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_NSSupportsLiveActivities = YES;
IPHONEOS_DEPLOYMENT_TARGET = 16.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = hu.filc.naplo.livecardpro;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
3127F7A228EAEDE300C2EFB3 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = MYUTW2GF6J;
GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = livecard/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = livecard;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_NSSupportsLiveActivities = YES;
IPHONEOS_DEPLOYMENT_TARGET = 16.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = hu.filc.naplo.livecardpro;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -322,6 +630,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@@ -346,7 +655,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -377,6 +686,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@@ -395,10 +705,11 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
@@ -409,12 +720,18 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = MYUTW2GF6J;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = hu.filc.naplo;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -428,12 +745,18 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = MYUTW2GF6J;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = hu.filc.naplo;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -445,6 +768,16 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
3127F79F28EAEDE300C2EFB3 /* Build configuration list for PBXNativeTarget "livecard" */ = {
isa = XCConfigurationList;
buildConfigurations = (
3127F7A028EAEDE300C2EFB3 /* Debug */,
3127F7A128EAEDE300C2EFB3 /* Release */,
3127F7A228EAEDE300C2EFB3 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
LastUpgradeVersion = "1300"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -27,8 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
@@ -38,8 +36,8 @@
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@@ -61,8 +59,6 @@
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"

View File

@@ -4,4 +4,7 @@
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -8,6 +8,18 @@ import Flutter
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
// here, Without this code the task will not work.
//SwiftFlutterForegroundTaskPlugin.setPluginRegistrantCallback(registerPlugins)
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
// here
func registerPlugins(registry: FlutterPluginRegistry) {
GeneratedPluginRegistrant.register(with: registry)
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "icon.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 B

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "icon.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 B

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "icon.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 B

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "icon.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 B

View File

@@ -22,6 +22,8 @@
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSSupportsLiveActivities</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
@@ -50,5 +52,9 @@
</array>
<key>NSPhotoLibraryUsageDescription</key>
<string>The app requires the photo library to set a custom profile picture.</string>
</dict>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
</dict>
</plist>

View File

@@ -1,8 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>production</string>
</dict>
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:api.filcnaplo.hu</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,13 @@
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.widgetkit-extension</string>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,27 @@
import Foundation
class LessonData {
var icon: String
var index: String
var title: String
var subtitle: String
var description: String
var startDate: Date
var endDate: Date
var date: ClosedRange<Date>
var nextSubject: String
var nextRoom: String
init?(JSONData data:[String: String]) {
self.icon = data["icon"]!
self.index = data["index"]!
self.title = data["title"]!
self.subtitle = data["subtitle"]!
self.description = data["description"]!
self.startDate = Date(timeIntervalSince1970: Double(data["startDate"]!)! / 1000)
self.endDate = Date(timeIntervalSince1970: Double(data["endDate"]!)! / 1000)
date = self.startDate...self.endDate
self.nextSubject = data["nextSubject"]!
self.nextRoom = data["nextRoom"]!
}
}

View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>INEnums</key>
<array/>
<key>INIntentDefinitionModelVersion</key>
<string>1.2</string>
<key>INIntentDefinitionNamespace</key>
<string>88xZPY</string>
<key>INIntentDefinitionSystemVersion</key>
<string>20A294</string>
<key>INIntentDefinitionToolsBuildVersion</key>
<string>12A6144</string>
<key>INIntentDefinitionToolsVersion</key>
<string>12.0</string>
<key>INIntents</key>
<array>
<dict>
<key>INIntentCategory</key>
<string>information</string>
<key>INIntentDescriptionID</key>
<string>tVvJ9c</string>
<key>INIntentEligibleForWidgets</key>
<true/>
<key>INIntentIneligibleForSuggestions</key>
<true/>
<key>INIntentName</key>
<string>Configuration</string>
<key>INIntentResponse</key>
<dict>
<key>INIntentResponseCodes</key>
<array>
<dict>
<key>INIntentResponseCodeName</key>
<string>success</string>
<key>INIntentResponseCodeSuccess</key>
<true/>
</dict>
<dict>
<key>INIntentResponseCodeName</key>
<string>failure</string>
</dict>
</array>
</dict>
<key>INIntentTitle</key>
<string>Configuration</string>
<key>INIntentTitleID</key>
<string>gpCwrM</string>
<key>INIntentType</key>
<string>Custom</string>
<key>INIntentVerb</key>
<string>View</string>
</dict>
</array>
<key>INTypes</key>
<array/>
</dict>
</plist>

View File

@@ -0,0 +1,137 @@
import ActivityKit
import WidgetKit
import SwiftUI
@main
struct Widgets: WidgetBundle {
var body: some Widget {
if #available(iOS 16.1, *) {
LiveCardWidget()
}
}
}
// We need to redefined live activities pipe
struct LiveActivitiesAppAttributes: ActivityAttributes, Identifiable {
public typealias LiveDeliveryData = ContentState
public struct ContentState: Codable, Hashable {
var data: Dictionary<String, String>
}
var id = UUID()
}
@available(iOSApplicationExtension 16.1, *)
struct LiveCardWidget: Widget {
var body: some WidgetConfiguration {
/// Live Activity Notification
ActivityConfiguration(for: LiveActivitiesAppAttributes.self) { context in
let lesson = LessonData(JSONData: context.state.data)
HStack(alignment: .center) {
Image(systemName: lesson!.icon)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: CGFloat(30), height: CGFloat(30))
.padding(.leading, CGFloat(8))
VStack(alignment: .leading) {
Text(lesson!.index + lesson!.title)
.font(.title3)
.bold()
Text(lesson!.description)
.font(.subheadline)
Spacer()
HStack {
Image(systemName: "arrow.right")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: CGFloat(12), height: CGFloat(12))
Text(lesson!.nextSubject)
.font(.caption)
Text(lesson!.nextRoom)
.font(.caption2)
}
}.padding(15)
Spacer()
Text(lesson!.subtitle)
.font(.subheadline)
.padding(.trailing, 12)
}.padding(12)
/// Dynamic Island
} dynamicIsland: { context in
let lesson = LessonData(JSONData: context.state.data)
/// Expanded
return DynamicIsland {
DynamicIslandExpandedRegion(.leading) {
VStack {
Spacer()
Image(systemName: lesson!.icon)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: CGFloat(30), height: CGFloat(30))
.padding(.leading, CGFloat(6))
.padding(.bottom, CGFloat(6))
}
}
DynamicIslandExpandedRegion(.center) {
VStack(alignment: .leading) {
Text(lesson!.index + lesson!.title)
.lineLimit(1)
.font(.title3)
.bold()
Text(lesson!.description)
.lineLimit(2)
.font(.caption)
}
}
DynamicIslandExpandedRegion(.trailing) {
VStack {
Spacer()
Text(lesson!.subtitle)
.lineLimit(1)
.font(.subheadline)
Spacer()
}
}
/// Compact
} compactLeading: {
Label {
Text(lesson!.title)
} icon: {
Image(systemName: lesson!.icon)
}
.font(.caption2)
} compactTrailing: {
Text(timerInterval: lesson!.date, countsDown: true)
.multilineTextAlignment(.center)
.frame(width: 40)
.font(.caption2)
/// Collapsed
} minimal: {
VStack(alignment: .center) {
Image(systemName: lesson!.icon)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: CGFloat(12), height: CGFloat(12))
Text(timerInterval: lesson!.date, countsDown: true)
.multilineTextAlignment(.center)
.monospacedDigit()
.font(.system(size: CGFloat(10)))
}
}
.keylineTint(.accentColor)
}
}
}

View File

@@ -1,23 +1,38 @@
// ignore_for_file: avoid_print
import 'dart:convert';
import 'dart:developer';
import 'package:filcnaplo/models/config.dart';
import 'package:filcnaplo/models/news.dart';
import 'package:filcnaplo/models/release.dart';
import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo/models/supporter.dart';
import 'package:filcnaplo_kreta_api/models/school.dart';
import 'package:http/http.dart' as http;
import 'package:connectivity_plus/connectivity_plus.dart';
class FilcAPI {
static const SCHOOL_LIST = "https://filcnaplo.hu/v2/school_list.json";
static const CONFIG = "https://filcnaplo.hu/v2/config.json";
static const NEWS = "https://filcnaplo.hu/v2/news.json";
static const SUPPORTERS = "https://filcnaplo.hu/v2/supporters.json";
static const REPO = "filc/naplo";
static const RELEASES = "https://api.github.com/repos/$REPO/releases";
// Public API
static const schoolList = "https://filcnaplo.hu/v2/school_list.json";
static const news = "https://filcnaplo.hu/v2/news.json";
static const supporters = "https://filcnaplo.hu/v2/supporters.json";
// Private API
static const config = "https://api.filcnaplo.hu/config";
static const reportApi = "https://api.filcnaplo.hu/report";
static const premiumApi = "https://api.filcnaplo.hu/premium/activate";
static const premiumScopesApi = "https://api.filcnaplo.hu/premium/scopes";
// Updates
static const repo = "filc/naplo";
static const releases = "https://api.github.com/repos/$repo/releases";
static Future<bool> checkConnectivity() async => (await Connectivity().checkConnectivity()) != ConnectivityResult.none;
static Future<List<School>?> getSchools() async {
try {
http.Response res = await http.get(Uri.parse(SCHOOL_LIST));
http.Response res = await http.get(Uri.parse(schoolList));
if (res.statusCode == 200) {
List<School> schools = (jsonDecode(res.body) as List).cast<Map>().map((json) => School.fromJson(json)).toList();
@@ -33,25 +48,39 @@ class FilcAPI {
} catch (error) {
print("ERROR: FilcAPI.getSchools: $error");
}
return null;
}
static Future<Config?> getConfig() async {
static Future<Config?> getConfig(SettingsProvider settings) async {
final userAgent = SettingsProvider.defaultSettings().config.userAgent;
Map<String, String> headers = {
"x-filc-id": settings.xFilcId,
"user-agent": userAgent,
};
log("[CONFIG] x-filc-id: \"${settings.xFilcId}\"");
log("[CONFIG] user-agent: \"$userAgent\"");
try {
http.Response res = await http.get(Uri.parse(CONFIG));
http.Response res = await http.get(Uri.parse(config), headers: headers);
if (res.statusCode == 200) {
return Config.fromJson(jsonDecode(res.body));
} else {
throw "HTTP ${res.statusCode}: ${res.body}";
} else if (res.statusCode == 429) {
res = await http.get(Uri.parse(config));
if (res.statusCode == 200) return Config.fromJson(jsonDecode(res.body));
}
throw "HTTP ${res.statusCode}: ${res.body}";
} catch (error) {
print("ERROR: FilcAPI.getConfig: $error");
}
return null;
}
static Future<List<News>?> getNews() async {
try {
http.Response res = await http.get(Uri.parse(NEWS));
http.Response res = await http.get(Uri.parse(news));
if (res.statusCode == 200) {
return (jsonDecode(res.body) as List).cast<Map>().map((e) => News.fromJson(e)).toList();
@@ -61,11 +90,12 @@ class FilcAPI {
} catch (error) {
print("ERROR: FilcAPI.getNews: $error");
}
return null;
}
static Future<Supporters?> getSupporters() async {
try {
http.Response res = await http.get(Uri.parse(SUPPORTERS));
http.Response res = await http.get(Uri.parse(supporters));
if (res.statusCode == 200) {
return Supporters.fromJson(jsonDecode(res.body));
@@ -75,11 +105,12 @@ class FilcAPI {
} catch (error) {
print("ERROR: FilcAPI.getSupporters: $error");
}
return null;
}
static Future<List<Release>?> getReleases() async {
try {
http.Response res = await http.get(Uri.parse(RELEASES));
http.Response res = await http.get(Uri.parse(releases));
if (res.statusCode == 200) {
return (jsonDecode(res.body) as List).cast<Map>().map((e) => Release.fromJson(e)).toList();
@@ -89,19 +120,48 @@ class FilcAPI {
} catch (error) {
print("ERROR: FilcAPI.getReleases: $error");
}
return null;
}
static Future<http.StreamedResponse?> downloadRelease(Release release) {
if (release.downloads.length > 0) {
try {
var client = http.Client();
var request = http.Request('GET', Uri.parse(release.downloads.first));
return client.send(request);
} catch (error) {
print("ERROR: FilcAPI.downloadRelease: $error");
}
static Future<http.StreamedResponse?> downloadRelease(ReleaseDownload release) {
try {
var client = http.Client();
var request = http.Request('GET', Uri.parse(release.url));
return client.send(request);
} catch (error) {
print("ERROR: FilcAPI.downloadRelease: $error");
return Future.value(null);
}
}
return Future.value(null);
static Future<void> sendReport(ErrorReport report) async {
try {
http.Response res = await http.post(Uri.parse(reportApi), body: {
"os": report.os,
"version": report.version,
"error": report.error,
"stack_trace": report.stack,
});
if (res.statusCode != 200) {
throw "HTTP ${res.statusCode}: ${res.body}";
}
} catch (error) {
print("ERROR: FilcAPI.sendReport: $error");
}
}
}
class ErrorReport {
String stack;
String os;
String version;
String error;
ErrorReport({
required this.stack,
required this.os,
required this.version,
required this.error,
});
}

View File

@@ -1,3 +1,6 @@
// ignore_for_file: avoid_print, use_build_context_synchronously
import 'package:filcnaplo/utils/jwt.dart';
import 'package:filcnaplo_kreta_api/providers/absence_provider.dart';
import 'package:filcnaplo_kreta_api/providers/event_provider.dart';
import 'package:filcnaplo_kreta_api/providers/exam_provider.dart';
@@ -10,10 +13,8 @@ import 'package:filcnaplo/api/providers/user_provider.dart';
import 'package:filcnaplo/api/providers/database_provider.dart';
import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo/models/user.dart';
import 'package:filcnaplo/utils/jwt.dart';
import 'package:filcnaplo_kreta_api/client/api.dart';
import 'package:filcnaplo_kreta_api/client/client.dart';
import 'package:filcnaplo_kreta_api/models/message.dart';
import 'package:filcnaplo_kreta_api/models/student.dart';
import 'package:filcnaplo_kreta_api/models/week.dart';
import 'package:flutter/material.dart';
@@ -22,9 +23,9 @@ import 'package:filcnaplo/api/nonce.dart';
enum LoginState { missingFields, invalidGrant, failed, normal, inProgress, success }
Nonce getNonce(BuildContext context, String nonce, String username, String instituteCode) {
Nonce nonceEncoder = Nonce(key: [53, 75, 109, 112, 109, 103, 100, 53, 102, 74], nonce: nonce);
nonceEncoder.encode(username.toLowerCase() + instituteCode.toLowerCase() + nonce);
Nonce getNonce(String nonce, String username, String instituteCode) {
Nonce nonceEncoder = Nonce(key: [98, 97, 83, 115, 120, 79, 119, 108, 85, 49, 106, 77], nonce: nonce);
nonceEncoder.encode(instituteCode.toUpperCase() + nonce + username.toUpperCase());
return nonceEncoder;
}
@@ -45,7 +46,7 @@ Future loginApi({
String nonceStr = await Provider.of<KretaClient>(context, listen: false).getAPI(KretaAPI.nonce, json: false);
Nonce nonce = getNonce(context, nonceStr, username, instituteCode);
Nonce nonce = getNonce(nonceStr, username, instituteCode);
headers.addAll(nonce.header());
Map? res = await Provider.of<KretaClient>(context, listen: false).postAPI(KretaAPI.login,
@@ -65,12 +66,14 @@ Future loginApi({
try {
Provider.of<KretaClient>(context, listen: false).accessToken = res["access_token"];
Map? studentJson = await Provider.of<KretaClient>(context, listen: false).getAPI(KretaAPI.student(instituteCode));
Student student = Student.fromJson(studentJson!);
var user = User(
username: username,
password: password,
instituteCode: instituteCode,
name: JwtUtils.getNameFromJWT(res["access_token"]) ?? "?",
student: Student.fromJson(studentJson!),
name: student.name,
student: student,
role: JwtUtils.getRoleFromJWT(res["access_token"])!,
);
if (onLogin != null) onLogin(user);
@@ -82,14 +85,16 @@ Future loginApi({
// Get user data
try {
await Provider.of<GradeProvider>(context, listen: false).fetch();
await Provider.of<TimetableProvider>(context, listen: false).fetch(week: Week.current());
await Provider.of<ExamProvider>(context, listen: false).fetch();
await Provider.of<HomeworkProvider>(context, listen: false).fetch();
await Provider.of<MessageProvider>(context, listen: false).fetch(type: MessageType.inbox);
await Provider.of<NoteProvider>(context, listen: false).fetch();
await Provider.of<EventProvider>(context, listen: false).fetch();
await Provider.of<AbsenceProvider>(context, listen: false).fetch();
await Future.wait([
Provider.of<GradeProvider>(context, listen: false).fetch(),
Provider.of<TimetableProvider>(context, listen: false).fetch(week: Week.current()),
Provider.of<ExamProvider>(context, listen: false).fetch(),
Provider.of<HomeworkProvider>(context, listen: false).fetch(),
Provider.of<MessageProvider>(context, listen: false).fetchAll(),
Provider.of<NoteProvider>(context, listen: false).fetch(),
Provider.of<EventProvider>(context, listen: false).fetch(),
Provider.of<AbsenceProvider>(context, listen: false).fetch(),
]);
} catch (error) {
print("WARNING: failed to fetch user data: $error");
}

View File

@@ -19,7 +19,7 @@ class Nonce {
return {
"X-Authorizationpolicy-Nonce": nonce,
"X-Authorizationpolicy-Key": encoded ?? "",
"X-Authorizationpolicy-Version": "v1",
"X-Authorizationpolicy-Version": "v2",
};
}
}

View File

@@ -1,6 +1,10 @@
import 'dart:io';
import 'package:filcnaplo/database/query.dart';
import 'package:filcnaplo/database/store.dart';
import 'package:sqflite/sqflite.dart';
// ignore: depend_on_referenced_packages
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
class DatabaseProvider {
// late Database _database;
@@ -10,8 +14,14 @@ class DatabaseProvider {
late UserDatabaseStore userStore;
Future<void> init() async {
var db = await openDatabase("app.db");
// _database = db;
Database db;
if (Platform.isLinux || Platform.isWindows) {
db = await databaseFactoryFfi.openDatabase("app.db");
} else {
db = await openDatabase("app.db");
}
query = DatabaseQuery(db: db);
store = DatabaseStore(db: db);
userQuery = UserDatabaseQuery(db: db);

View File

@@ -0,0 +1,199 @@
// ignore_for_file: no_leading_underscores_for_local_identifiers
import 'dart:async';
import 'dart:io';
import 'package:filcnaplo/helpers/subject.dart';
import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo_kreta_api/models/lesson.dart';
import 'package:filcnaplo_kreta_api/models/week.dart';
import 'package:filcnaplo/utils/format.dart';
import 'package:filcnaplo_kreta_api/providers/timetable_provider.dart';
import 'package:flutter/widgets.dart';
import 'package:live_activities/live_activities.dart';
enum LiveCardState { empty, duringLesson, duringBreak, morning, afternoon, night }
class LiveCardProvider extends ChangeNotifier {
Lesson? currentLesson;
Lesson? nextLesson;
Lesson? prevLesson;
List<Lesson>? nextLessons;
LiveCardState currentState = LiveCardState.empty;
late Timer _timer;
late final TimetableProvider _timetable;
late final SettingsProvider _settings;
late Duration _delay;
final _liveActivitiesPlugin = LiveActivities();
String? _latestActivityId;
Map<String, String> _lastActivity = {};
LiveCardProvider({
required TimetableProvider timetable,
required SettingsProvider settings,
}) : _timetable = timetable,
_settings = settings {
_timer = Timer.periodic(const Duration(seconds: 1), (timer) => update());
timetable.restore().then((_) => update());
_delay = settings.bellDelayEnabled ? Duration(seconds: settings.bellDelay) : Duration.zero;
}
@override
void dispose() {
_timer.cancel();
if (_latestActivityId != null && Platform.isIOS) _liveActivitiesPlugin.endActivity(_latestActivityId!);
super.dispose();
}
// Debugging
static DateTime _now() {
return DateTime.now();
}
String getFloorDifference() {
final prevFloor = prevLesson!.getFloor();
final nextFloor = nextLesson!.getFloor();
if (prevFloor == null || nextFloor == null || prevFloor == nextFloor) {
return "to room";
}
if (nextFloor == 0) {
return "ground floor";
}
if (nextFloor > prevFloor) {
return "up floor";
} else {
return "down floor";
}
}
Map<String, String> toMap() {
switch (currentState) {
case LiveCardState.duringLesson:
return {
"icon": currentLesson != null ? SubjectIcon.resolveName(subject: currentLesson?.subject) : "book",
"index": currentLesson != null ? '${currentLesson!.lessonIndex}. ' : "",
"title": currentLesson != null ? ShortSubject.resolve(subject: currentLesson?.subject).capital() : "",
"subtitle": currentLesson?.room.replaceAll("_", " ") ?? "",
"description": currentLesson?.description ?? "",
"startDate": ((currentLesson?.start.millisecondsSinceEpoch ?? 0) - _delay.inMilliseconds).toString(),
"endDate": ((currentLesson?.end.millisecondsSinceEpoch ?? 0) - _delay.inMilliseconds).toString(),
"nextSubject": nextLesson != null ? ShortSubject.resolve(subject: nextLesson?.subject).capital() : "",
"nextRoom": nextLesson?.room.replaceAll("_", " ") ?? "",
};
case LiveCardState.duringBreak:
final iconFloorMap = {
"to room": "chevron.right.2",
"up floor": "arrow.up.right",
"down floor": "arrow.down.left",
"ground floor": "arrow.down.left",
};
final diff = getFloorDifference();
return {
"icon": iconFloorMap[diff] ?? "cup.and.saucer",
"title": "Szünet",
"description": "Maradj ebben a teremben.",
"startDate": ((prevLesson?.end.millisecondsSinceEpoch ?? 0) - _delay.inMilliseconds).toString(),
"endDate": ((nextLesson?.start.millisecondsSinceEpoch ?? 0) - _delay.inMilliseconds).toString(),
"nextSubject": nextLesson != null ? ShortSubject.resolve(subject: nextLesson?.subject) : "",
"nextRoom": nextLesson?.room.replaceAll("_", " ") ?? "",
"index": "",
"subtitle": "",
};
default:
return {};
}
}
void update() async {
if (Platform.isIOS) {
final cmap = toMap();
if (cmap != _lastActivity) {
_lastActivity = cmap;
if (_lastActivity != {}) {
if (_latestActivityId == null) {
_liveActivitiesPlugin.createActivity(_lastActivity).then((value) => _latestActivityId = value);
} else {
_liveActivitiesPlugin.updateActivity(_latestActivityId!, _lastActivity);
}
} else {
if (_latestActivityId != null) _liveActivitiesPlugin.endActivity(_latestActivityId!);
}
}
}
List<Lesson> today = _today(_timetable);
if (today.isEmpty) {
await _timetable.fetch(week: Week.current());
today = _today(_timetable);
}
_delay = _settings.bellDelayEnabled ? Duration(seconds: _settings.bellDelay) : Duration.zero;
final now = _now().add(_delay);
// Filter cancelled lessons #20
// Filter label lessons #128
today = today.where((lesson) => lesson.status?.name != "Elmaradt" && lesson.subject.id != '' && !lesson.isEmpty).toList();
if (today.isNotEmpty) {
// sort
today.sort((a, b) => a.start.compareTo(b.start));
final _lesson = today.firstWhere((l) => l.start.isBefore(now) && l.end.isAfter(now), orElse: () => Lesson.fromJson({}));
if (_lesson.start.year != 0) {
currentLesson = _lesson;
} else {
currentLesson = null;
}
final _next = today.firstWhere((l) => l.start.isAfter(now), orElse: () => Lesson.fromJson({}));
nextLessons = today.where((l) => l.start.isAfter(now)).toList();
if (_next.start.year != 0) {
nextLesson = _next;
} else {
nextLesson = null;
}
final _prev = today.lastWhere((l) => l.end.isBefore(now), orElse: () => Lesson.fromJson({}));
if (_prev.start.year != 0) {
prevLesson = _prev;
} else {
prevLesson = null;
}
}
if (currentLesson != null) {
currentState = LiveCardState.duringLesson;
} else if (nextLesson != null && prevLesson != null) {
currentState = LiveCardState.duringBreak;
} else if (now.hour >= 12 && now.hour < 20) {
currentState = LiveCardState.afternoon;
} else if (now.hour >= 20) {
currentState = LiveCardState.night;
} else if (now.hour >= 5 && now.hour <= 10) {
currentState = LiveCardState.morning;
} else {
currentState = LiveCardState.empty;
}
notifyListeners();
}
bool get show => currentState != LiveCardState.empty;
Duration get delay => _delay;
bool _sameDate(DateTime a, DateTime b) => (a.year == b.year && a.month == b.month && a.day == b.day);
List<Lesson> _today(TimetableProvider p) => p.lessons.where((l) => _sameDate(l.date, _now())).toList();
}

View File

@@ -1,3 +1,5 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:math';
import 'package:filcnaplo/api/client.dart';
@@ -39,7 +41,7 @@ class NewsProvider extends ChangeNotifier {
}
_state = state_;
Provider.of<SettingsProvider>(_context, listen: false).update(_context, newsState: _state);
Provider.of<SettingsProvider>(_context, listen: false).update(newsState: _state);
}
Future<void> fetch() async {
@@ -51,7 +53,7 @@ class NewsProvider extends ChangeNotifier {
if (_fresh < 0) {
_state = news_.length;
Provider.of<SettingsProvider>(_context, listen: false).update(_context, newsState: _state);
Provider.of<SettingsProvider>(_context, listen: false).update(newsState: _state);
}
_fresh = max(_fresh, 0);
@@ -70,12 +72,13 @@ class NewsProvider extends ChangeNotifier {
_fresh--;
_state++;
Provider.of<SettingsProvider>(_context, listen: false).update(_context, newsState: _state);
Provider.of<SettingsProvider>(_context, listen: false).update(newsState: _state);
if (_fresh > 0)
if (_fresh > 0) {
show = true;
else
} else {
show = false;
}
notifyListeners();
}

View File

@@ -0,0 +1,79 @@
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/widgets.dart';
import 'package:http/http.dart' as http;
enum Status { network, maintenance, syncing }
class StatusProvider extends ChangeNotifier {
final List<Status> _stack = [];
double _progress = 0.0;
ConnectivityResult _networkType = ConnectivityResult.none;
ConnectivityResult get networkType => _networkType;
StatusProvider() {
_handleNetworkChanges();
Connectivity().checkConnectivity().then((value) => _networkType = value);
}
Status? getStatus() => _stack.isNotEmpty ? _stack[0] : null;
// Status progress from 0.0 to 1.0
double get progress => _progress;
void _handleNetworkChanges() {
Connectivity().onConnectivityChanged.listen((event) {
_networkType = event;
if (event == ConnectivityResult.none) {
if (!_stack.contains(Status.network)) {
_stack.insert(0, Status.network);
notifyListeners();
}
} else {
if (_stack.contains(Status.network)) {
_stack.remove(Status.network);
notifyListeners();
}
}
});
}
void triggerRequest(http.Response res) {
if (res.headers.containsKey("x-maintenance-mode") || res.statusCode == 503) {
if (!_stack.contains(Status.maintenance)) {
_stack.insert(0, Status.maintenance);
notifyListeners();
}
} else {
if (_stack.contains(Status.maintenance)) {
_stack.remove(Status.maintenance);
notifyListeners();
}
}
}
void triggerSync({required int current, required int max}) {
double prev = _progress;
if (!_stack.contains(Status.syncing)) {
_stack.add(Status.syncing);
_progress = 0.0;
notifyListeners();
}
if (max == 0) {
_progress = 0.0;
} else {
_progress = current / max;
}
if (_progress == 1.0) {
notifyListeners();
// Wait for animation
Future.delayed(const Duration(milliseconds: 250), () {
_stack.remove(Status.syncing);
notifyListeners();
});
} else if (progress != prev) {
notifyListeners();
}
}
}

View File

@@ -0,0 +1,71 @@
// ignore_for_file: use_build_context_synchronously
import 'package:filcnaplo/api/providers/database_provider.dart';
import 'package:filcnaplo/api/providers/status_provider.dart';
import 'package:filcnaplo/api/providers/user_provider.dart';
import 'package:filcnaplo_kreta_api/client/api.dart';
import 'package:filcnaplo_kreta_api/client/client.dart';
import 'package:filcnaplo_kreta_api/models/student.dart';
import 'package:filcnaplo_kreta_api/models/week.dart';
import 'package:filcnaplo_kreta_api/providers/absence_provider.dart';
import 'package:filcnaplo_kreta_api/providers/event_provider.dart';
import 'package:filcnaplo_kreta_api/providers/exam_provider.dart';
import 'package:filcnaplo_kreta_api/providers/grade_provider.dart';
import 'package:filcnaplo_kreta_api/providers/homework_provider.dart';
import 'package:filcnaplo_kreta_api/providers/message_provider.dart';
import 'package:filcnaplo_kreta_api/providers/note_provider.dart';
import 'package:filcnaplo_kreta_api/providers/timetable_provider.dart';
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';
// Mutex
bool lock = false;
Future<void> syncAll(BuildContext context) {
if (lock) return Future.value();
// Lock
lock = true;
// ignore: avoid_print
print("INFO Syncing all");
UserProvider user = Provider.of<UserProvider>(context, listen: false);
StatusProvider statusProvider = Provider.of<StatusProvider>(context, listen: false);
List<Future<void>> tasks = [];
int taski = 0;
Future<void> syncStatus(Future<void> future) async {
await future.onError((error, stackTrace) => null);
taski++;
statusProvider.triggerSync(current: taski, max: tasks.length);
}
tasks = [
syncStatus(Provider.of<GradeProvider>(context, listen: false).fetch()),
syncStatus(Provider.of<TimetableProvider>(context, listen: false).fetch(week: Week.current())),
syncStatus(Provider.of<ExamProvider>(context, listen: false).fetch()),
syncStatus(Provider.of<HomeworkProvider>(context, listen: false).fetch(from: DateTime.now().subtract(const Duration(days: 30)))),
syncStatus(Provider.of<MessageProvider>(context, listen: false).fetchAll()),
syncStatus(Provider.of<NoteProvider>(context, listen: false).fetch()),
syncStatus(Provider.of<EventProvider>(context, listen: false).fetch()),
syncStatus(Provider.of<AbsenceProvider>(context, listen: false).fetch()),
// Sync student
syncStatus(() async {
if (user.user == null) return;
Map? studentJson = await Provider.of<KretaClient>(context, listen: false).getAPI(KretaAPI.student(user.instituteCode!));
if (studentJson == null) return;
Student student = Student.fromJson(studentJson);
user.user?.name = student.name;
// Store user
await Provider.of<DatabaseProvider>(context, listen: false).store.storeUser(user.user!);
}()),
];
return Future.wait(tasks)
// Unlock
.then((value) => lock = false);
}

View File

@@ -3,14 +3,12 @@ import 'dart:io';
import 'package:filcnaplo/api/client.dart';
import 'package:filcnaplo/models/release.dart';
import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
class UpdateProvider extends ChangeNotifier {
// Private
late List<Release> _releases;
bool _available = false;
bool get available => _available && _releases.length > 0;
PackageInfo? _packageInfo;
bool get available => _available && _releases.isNotEmpty;
// Public
List<Release> get releases => _releases;
@@ -20,9 +18,10 @@ class UpdateProvider extends ChangeNotifier {
required BuildContext context,
}) {
_releases = List.castFrom(initialReleases);
PackageInfo.fromPlatform().then((value) => _packageInfo = value);
}
static const currentVersion = String.fromEnvironment("APPVER", defaultValue: "1.0");
Future<void> fetch() async {
if (!Platform.isAndroid) return;
@@ -30,8 +29,9 @@ class UpdateProvider extends ChangeNotifier {
_releases.sort((a, b) => -a.version.compareTo(b.version));
// Check for new releases
if (_releases.length > 0) {
_available = _packageInfo != null && _releases.first.version.compareTo(Version.fromString(_packageInfo?.version ?? "")) == 1;
if (_releases.isNotEmpty) {
_available = _releases.first.version.compareTo(Version.fromString(currentVersion)) == 1;
// ignore: avoid_print
if (_available) print("INFO: New update: ${releases.first.version}");
notifyListeners();
}

View File

@@ -1,9 +1,10 @@
import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo/models/user.dart';
import 'package:filcnaplo_kreta_api/models/student.dart';
import 'package:flutter/foundation.dart';
class UserProvider with ChangeNotifier {
Map<String, User> _users = {};
final Map<String, User> _users = {};
String? _selectedUserId;
User? get user => _users[_selectedUserId];
@@ -13,16 +14,26 @@ class UserProvider with ChangeNotifier {
String? get name => user?.name;
String? get username => user?.username;
String? get password => user?.password;
Role? get role => user?.role;
Student? get student => user?.student;
String? get nickname => user?.nickname;
String? get displayName => user?.displayName;
final SettingsProvider _settings;
UserProvider({required SettingsProvider settings}) : _settings = settings;
void setUser(String userId) {
_selectedUserId = userId;
_settings.update(lastAccountId: userId);
notifyListeners();
}
void addUser(User user) {
_users[user.id] = user;
print("DEBUG: Added User: ${user.id} ${user.name}");
if (kDebugMode) {
print("DEBUG: Added User: ${user.id}");
}
}
void removeUser(String userId) {

View File

@@ -1,23 +1,36 @@
import 'dart:io';
import 'dart:math';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:filcnaplo/api/client.dart';
import 'package:filcnaplo/api/providers/live_card_provider.dart';
import 'package:filcnaplo/api/providers/news_provider.dart';
import 'package:filcnaplo/api/providers/database_provider.dart';
import 'package:filcnaplo/api/providers/status_provider.dart';
import 'package:filcnaplo/models/config.dart';
import 'package:filcnaplo/theme.dart';
import 'package:filcnaplo/theme/observer.dart';
import 'package:filcnaplo/theme/theme.dart';
import 'package:filcnaplo_kreta_api/client/client.dart';
import 'package:filcnaplo_mobile_ui/common/system_chrome.dart';
import 'package:filcnaplo_mobile_ui/screens/login/login_route.dart';
import 'package:filcnaplo_mobile_ui/screens/login/login_screen.dart';
import 'package:filcnaplo_mobile_ui/screens/navigation/navigation_screen.dart';
import 'package:filcnaplo_mobile_ui/screens/settings/settings_route.dart';
import 'package:filcnaplo_mobile_ui/screens/settings/settings_screen.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:i18n_extension/i18n_widget.dart';
import 'package:material_color_utilities/palettes/core_palette.dart';
import 'package:provider/provider.dart';
// Mobile UI
import 'package:filcnaplo_mobile_ui/common/system_chrome.dart' as mobile;
import 'package:filcnaplo_mobile_ui/screens/login/login_route.dart' as mobile;
import 'package:filcnaplo_mobile_ui/screens/login/login_screen.dart' as mobile;
import 'package:filcnaplo_mobile_ui/screens/navigation/navigation_screen.dart' as mobile;
import 'package:filcnaplo_mobile_ui/screens/settings/settings_route.dart' as mobile;
import 'package:filcnaplo_mobile_ui/screens/settings/settings_screen.dart' as mobile;
// Desktop UI
import 'package:filcnaplo_desktop_ui/screens/navigation/navigation_screen.dart' as desktop;
import 'package:filcnaplo_desktop_ui/screens/login/login_screen.dart' as desktop;
import 'package:filcnaplo_desktop_ui/screens/login/login_route.dart' as desktop;
// Providers
import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo_kreta_api/providers/absence_provider.dart';
@@ -31,79 +44,99 @@ import 'package:filcnaplo_kreta_api/providers/timetable_provider.dart';
import 'package:filcnaplo/api/providers/user_provider.dart';
import 'package:filcnaplo/api/providers/update_provider.dart';
import 'package:filcnaplo_mobile_ui/pages/grades/calculator/grade_calculator_provider.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:filcnaplo_premium/providers/premium_provider.dart';
class App extends StatelessWidget {
final SettingsProvider settings;
final UserProvider user;
final DatabaseProvider database;
App({Key? key, required this.database, required this.settings, required this.user}) : super(key: key) {
if (user.getUsers().length > 0) user.setUser(user.getUsers().first.id);
}
const App({Key? key, required this.database, required this.settings, required this.user}) : super(key: key);
@override
Widget build(BuildContext context) {
setSystemChrome(context);
mobile.setSystemChrome(context);
WidgetsBinding.instance?.addPostFrameCallback((_) {
FilcAPI.getConfig().then((Config? config) {
settings.update(context, database: database, config: config ?? Config.fromJson({}));
// Set high refresh mode #28
if (Platform.isAndroid) FlutterDisplayMode.setHighRefreshRate();
CorePalette? corePalette;
final status = StatusProvider();
final kreta = KretaClient(user: user, settings: settings, status: status);
final timetable = TimetableProvider(user: user, database: database, kreta: kreta);
final premium = PremiumProvider(settings: settings);
WidgetsBinding.instance.addPostFrameCallback((_) {
FilcAPI.getConfig(settings).then((Config? config) {
if (config != null) settings.update(config: config);
});
premium.activate();
});
return I18n(
initialLocale: Locale(settings.language, settings.language.toUpperCase()),
child: MultiProvider(
providers: [
ChangeNotifierProvider<SettingsProvider>(create: (_) => settings),
ChangeNotifierProvider<UserProvider>(create: (_) => user),
Provider<KretaClient>(create: (context) => KretaClient(context: context, userAgent: settings.config.userAgent)),
Provider<DatabaseProvider>(create: (context) => database),
ChangeNotifierProvider<ThemeModeObserver>(create: (context) => ThemeModeObserver(initialTheme: settings.theme)),
ChangeNotifierProvider<NewsProvider>(create: (context) => NewsProvider(context: context)),
ChangeNotifierProvider<UpdateProvider>(create: (context) => UpdateProvider(context: context)),
return MultiProvider(
providers: [
ChangeNotifierProvider<PremiumProvider>(create: (_) => premium),
ChangeNotifierProvider<SettingsProvider>(create: (_) => settings),
ChangeNotifierProvider<UserProvider>(create: (_) => user),
ChangeNotifierProvider<StatusProvider>(create: (_) => status),
Provider<KretaClient>(create: (_) => kreta),
Provider<DatabaseProvider>(create: (context) => database),
ChangeNotifierProvider<ThemeModeObserver>(create: (context) => ThemeModeObserver(initialTheme: settings.theme)),
ChangeNotifierProvider<NewsProvider>(create: (context) => NewsProvider(context: context)),
ChangeNotifierProvider<UpdateProvider>(create: (context) => UpdateProvider(context: context)),
// User data providers
ChangeNotifierProvider<GradeProvider>(create: (context) => GradeProvider(context: context)),
ChangeNotifierProvider<TimetableProvider>(create: (context) => TimetableProvider(context: context)),
ChangeNotifierProvider<ExamProvider>(create: (context) => ExamProvider(context: context)),
ChangeNotifierProvider<HomeworkProvider>(create: (context) => HomeworkProvider(context: context)),
ChangeNotifierProvider<MessageProvider>(create: (context) => MessageProvider(context: context)),
ChangeNotifierProvider<NoteProvider>(create: (context) => NoteProvider(context: context)),
ChangeNotifierProvider<EventProvider>(create: (context) => EventProvider(context: context)),
ChangeNotifierProvider<AbsenceProvider>(create: (context) => AbsenceProvider(context: context)),
// User data providers
ChangeNotifierProvider<GradeProvider>(create: (_) => GradeProvider(settings: settings, user: user, database: database, kreta: kreta)),
ChangeNotifierProvider<TimetableProvider>(create: (_) => timetable),
ChangeNotifierProvider<ExamProvider>(create: (context) => ExamProvider(context: context)),
ChangeNotifierProvider<HomeworkProvider>(create: (context) => HomeworkProvider(context: context)),
ChangeNotifierProvider<MessageProvider>(create: (context) => MessageProvider(context: context)),
ChangeNotifierProvider<NoteProvider>(create: (context) => NoteProvider(context: context)),
ChangeNotifierProvider<EventProvider>(create: (context) => EventProvider(context: context)),
ChangeNotifierProvider<AbsenceProvider>(create: (context) => AbsenceProvider(context: context)),
ChangeNotifierProvider<GradeCalculatorProvider>(create: (context) => GradeCalculatorProvider(context)),
],
child: Consumer<ThemeModeObserver>(
builder: (context, themeMode, child) {
return MaterialApp(
ChangeNotifierProvider<GradeCalculatorProvider>(
create: (_) => GradeCalculatorProvider(settings: settings, user: user, database: database, kreta: kreta)),
ChangeNotifierProvider<LiveCardProvider>(create: (context) => LiveCardProvider(timetable: timetable, settings: settings))
],
child: Consumer<ThemeModeObserver>(
builder: (context, themeMode, child) {
return FutureBuilder<CorePalette?>(
future: DynamicColorPlugin.getCorePalette(),
builder: (context, snapshot) {
corePalette = snapshot.data;
return MaterialApp(
builder: (context, child) {
// Limit font size scaling to 1.0
double textScaleFactor = min(MediaQuery.of(context).textScaleFactor, 1.0);
return MediaQuery(
data: MediaQuery.of(context).copyWith(textScaleFactor: textScaleFactor),
child: child ?? Container(),
return I18n(
initialLocale: Locale(settings.language, settings.language.toUpperCase()),
child: MediaQuery(
data: MediaQuery.of(context).copyWith(textScaleFactor: textScaleFactor),
child: child ?? Container(),
),
);
},
title: "Filc Napló",
debugShowCheckedModeBanner: false,
theme: AppTheme.lightTheme(context),
darkTheme: AppTheme.darkTheme(context),
theme: AppTheme.lightTheme(context, palette: corePalette),
darkTheme: AppTheme.darkTheme(context, palette: corePalette),
themeMode: themeMode.themeMode,
localizationsDelegates: [
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
const Locale('en', 'EN'),
const Locale('hu', 'HU'),
const Locale('de', 'DE'),
supportedLocales: const [
Locale('en', 'EN'),
Locale('hu', 'HU'),
Locale('de', 'DE'),
],
localeListResolutionCallback: (locales, supported) {
Locale locale = Locale('hu', 'HU');
Locale locale = const Locale('hu', 'HU');
for (var loc in locales ?? []) {
if (supported.contains(loc)) {
@@ -115,28 +148,43 @@ class App extends StatelessWidget {
return locale;
},
onGenerateRoute: (settings) => rootNavigator(settings),
initialRoute: user.getUsers().length > 0 ? "navigation" : "login");
},
),
initialRoute: user.getUsers().isNotEmpty ? "navigation" : "login",
);
},
);
},
),
);
}
Route? rootNavigator(RouteSettings route) {
// if platform == android || platform == ios
switch (route.name) {
case "login_back":
return CupertinoPageRoute(builder: (context) => LoginScreen(back: true));
case "login":
return _rootRoute(LoginScreen());
case "navigation":
return _rootRoute(Navigation());
case "login_to_navigation":
return loginRoute(Navigation());
case "settings":
return settingsRoute(SettingsScreen());
if (Platform.isAndroid || Platform.isIOS) {
switch (route.name) {
case "login_back":
return CupertinoPageRoute(builder: (context) => const mobile.LoginScreen(back: true));
case "login":
return _rootRoute(const mobile.LoginScreen());
case "navigation":
return _rootRoute(const mobile.NavigationScreen());
case "login_to_navigation":
return mobile.loginRoute(const mobile.NavigationScreen());
case "settings":
return mobile.settingsRoute(const mobile.SettingsScreen());
}
} else if (Platform.isWindows || Platform.isMacOS || Platform.isLinux) {
switch (route.name) {
case "login_back":
return CupertinoPageRoute(builder: (context) => const desktop.LoginScreen(back: true));
case "login":
return _rootRoute(const desktop.LoginScreen());
case "navigation":
return _rootRoute(const desktop.NavigationScreen());
case "login_to_navigation":
return desktop.loginRoute(const desktop.NavigationScreen());
}
}
// else if platform == windows || ...
return null;
}
Route _rootRoute(Widget widget) {

View File

@@ -1,72 +1,134 @@
// ignore_for_file: avoid_print
import 'dart:io';
import 'package:filcnaplo/api/providers/database_provider.dart';
import 'package:filcnaplo/database/struct.dart';
import 'package:filcnaplo/models/settings.dart';
import 'package:sqflite/sqflite.dart';
// ignore: depend_on_referenced_packages
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
Future<Database> initDB() async {
// await deleteDatabase('app.db'); // for debugging
var db = await openDatabase('app.db');
const settingsDB = DatabaseStruct("settings", {
"language": String, "start_page": int, "rounding": int, "theme": int, "accent_color": int, "news": int, "news_state": int, "developer_mode": int,
"update_channel": int, "config": String, "custom_accent_color": int, "custom_background_color": int, "custom_highlight_color": int, // general
"grade_color1": int, "grade_color2": int, "grade_color3": int, "grade_color4": int, "grade_color5": int, // grade colors
"vibration_strength": int, "ab_weeks": int, "swap_ab_weeks": int,
"notifications": int, "notifications_bitfield": int, "notification_poll_interval": int, // notifications
"x_filc_id": String, "graph_class_avg": int, "presentation_mode": int, "bell_delay": int, "bell_delay_enabled": int,
"grade_opening_fun": int, "icon_pack": String, "premium_scopes": String, "premium_token": String, "last_account_id": String,
});
const usersDB = DatabaseStruct("users", {
"id": String, "name": String, "username": String, "password": String, "institute_code": String, "student": String, "role": int,
"nickname": String // premium only
});
const userDataDB = DatabaseStruct("user_data", {
"id": String, "grades": String, "timetable": String, "exams": String, "homework": String, "messages": String, "notes": String,
"events": String, "absences": String, "group_averages": String,
// "subject_lesson_count": String, // non kreta data
"last_seen_grade": int,
});
var settingsDB = await createSettingsTable(db);
Future<void> createTable(Database db, DatabaseStruct struct) => db.execute("CREATE TABLE IF NOT EXISTS ${struct.table} ($struct)");
// Create table Users
await db.execute("CREATE TABLE IF NOT EXISTS users (id TEXT NOT NULL, name TEXT, username TEXT, password TEXT, institute_code TEXT, student TEXT)");
await db.execute("CREATE TABLE IF NOT EXISTS user_data ("
"id TEXT NOT NULL, grades TEXT, timetable TEXT, exams TEXT, homework TEXT, messages TEXT, notes TEXT, events TEXT, absences TEXT)");
Future<Database> initDB(DatabaseProvider database) async {
Database db;
if (Platform.isLinux || Platform.isWindows) {
sqfliteFfiInit();
db = await databaseFactoryFfi.openDatabase("app.db");
} else {
db = await openDatabase("app.db");
}
await createTable(db, settingsDB);
await createTable(db, usersDB);
await createTable(db, userDataDB);
if ((await db.rawQuery("SELECT COUNT(*) FROM settings"))[0].values.first == 0) {
// Set default values for table Settings
await db.insert("settings", SettingsProvider.defaultSettings().toMap());
await db.insert("settings", SettingsProvider.defaultSettings(database: database).toMap());
}
await migrateDB(db, settingsDB.struct.keys);
// Migrate Databases
try {
await migrateDB(
db,
struct: settingsDB,
defaultValues: SettingsProvider.defaultSettings(database: database).toMap(),
);
await migrateDB(
db,
struct: usersDB,
defaultValues: {"role": 0},
);
await migrateDB(db, struct: userDataDB, defaultValues: {
"grades": "[]", "timetable": "[]", "exams": "[]", "homework": "[]", "messages": "[]", "notes": "[]", "events": "[]", "absences": "[]",
"group_averages": "[]",
// "subject_lesson_count": "{}", // non kreta data
"last_seen_grade": 0,
});
} catch (error) {
print("ERROR: migrateDB: $error");
}
return db;
}
Future<DatabaseStruct> createSettingsTable(Database db) async {
var settingsDB = DatabaseStruct({
"language": String, "start_page": int, "rounding": int, "theme": int, "accent_color": int, "news": int, "news_state": int, "developer_mode": int,
"update_channel": int, "config": String, // general
"grade_color1": int, "grade_color2": int, "grade_color3": int, "grade_color4": int, "grade_color5": int, // grade colors
"vibration_strength": int, "ab_weeks": int, "swap_ab_weeks": int,
"notifications": int, "notifications_bitfield": int, "notification_poll_interval": int, // notifications
Future<void> migrateDB(
Database db, {
required DatabaseStruct struct,
required Map<String, Object?> defaultValues,
}) async {
var originalRows = await db.query(struct.table);
if (originalRows.isEmpty) {
await db.execute("drop table ${struct.table}");
await createTable(db, struct);
return;
}
List<Map<String, dynamic>> migrated = [];
// go through each row and add missing keys or delete non existing keys
await Future.forEach<Map<String, Object?>>(originalRows, (original) async {
bool migrationRequired = struct.struct.keys.any((key) => !original.containsKey(key) || original[key] == null) ||
original.keys.any((key) => !struct.struct.containsKey(key));
if (migrationRequired) {
print("INFO: Migrating ${struct.table}");
var copy = Map<String, Object?>.from(original);
// Fill missing columns
for (var key in struct.struct.keys) {
if (!original.containsKey(key) || original[key] == null) {
print("DEBUG: migrating $key");
copy[key] = defaultValues[key];
}
}
for (var key in original.keys) {
if (!struct.struct.keys.contains(key)) {
print("DEBUG: dropping $key");
copy.remove(key);
}
}
migrated.add(copy);
}
});
// Create table Settings
await db.execute("CREATE TABLE IF NOT EXISTS settings ($settingsDB)");
// replace the old table with the migrated one
if (migrated.isNotEmpty) {
// Delete table
await db.execute("drop table ${struct.table}");
return settingsDB;
}
Future<void> migrateDB(Database db, Iterable<String> keys) async {
var settings = (await db.query("settings"))[0];
bool migrationRequired = keys.any((key) => !settings.containsKey(key) || settings[key] == null);
if (migrationRequired) {
var defaultSettings = SettingsProvider.defaultSettings();
var settingsCopy = Map<String, dynamic>.from(settings);
// Delete settings
await db.execute("drop table settings");
// Fill missing columns
keys.forEach((key) {
if (!keys.contains(key)) {
print("debug: dropping $key");
settingsCopy.remove(key);
}
if (!settings.containsKey(key) || settings[key] == null) {
print("DEBUG: migrating $key");
settingsCopy[key] = defaultSettings.toMap()[key];
}
// Recreate table
await createTable(db, struct);
await Future.forEach(migrated, (Map<String, Object?> copy) async {
await db.insert(struct.table, copy);
});
// Recreate settings
await createSettingsTable(db);
await db.insert("settings", settingsCopy);
print("INFO: Database migrated");
}
}

View File

@@ -1,6 +1,9 @@
import 'dart:convert';
import 'package:filcnaplo/api/providers/database_provider.dart';
import 'package:filcnaplo/models/subject_lesson_count.dart';
import 'package:filcnaplo/models/user.dart';
import 'package:sqflite/sqflite.dart';
// ignore: depend_on_referenced_packages
import 'package:sqflite_common/sqlite_api.dart';
// Models
import 'package:filcnaplo/models/settings.dart';
@@ -13,24 +16,33 @@ import 'package:filcnaplo_kreta_api/models/message.dart';
import 'package:filcnaplo_kreta_api/models/note.dart';
import 'package:filcnaplo_kreta_api/models/event.dart';
import 'package:filcnaplo_kreta_api/models/absence.dart';
import 'package:filcnaplo_kreta_api/models/group_average.dart';
class DatabaseQuery {
DatabaseQuery({required this.db});
final Database db;
Future<SettingsProvider> getSettings() async {
Future<SettingsProvider> getSettings(DatabaseProvider database) async {
Map settingsMap = (await db.query("settings")).elementAt(0);
SettingsProvider settings = SettingsProvider.fromMap(settingsMap);
SettingsProvider settings = SettingsProvider.fromMap(settingsMap, database: database);
return settings;
}
Future<UserProvider> getUsers() async {
var userProvider = UserProvider();
Future<UserProvider> getUsers(SettingsProvider settings) async {
var userProvider = UserProvider(settings: settings);
List<Map> usersMap = await db.query("users");
usersMap.forEach((user) {
for (var user in usersMap) {
userProvider.addUser(User.fromMap(user));
});
}
if (userProvider.getUsers().map((e) => e.id).contains(settings.lastAccountId)) {
userProvider.setUser(settings.lastAccountId);
} else {
if (usersMap.isNotEmpty) {
userProvider.setUser(userProvider.getUsers().first.id);
settings.update(lastAccountId: userProvider.id);
}
}
return userProvider;
}
}
@@ -42,7 +54,7 @@ class UserDatabaseQuery {
Future<List<Grade>> getGrades({required String userId}) async {
List<Map> userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]);
if (userData.length == 0) return [];
if (userData.isEmpty) return [];
String? gradesJson = userData.elementAt(0)["grades"] as String?;
if (gradesJson == null) return [];
List<Grade> grades = (jsonDecode(gradesJson) as List).map((e) => Grade.fromJson(e)).toList();
@@ -51,7 +63,7 @@ class UserDatabaseQuery {
Future<List<Lesson>> getLessons({required String userId}) async {
List<Map> userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]);
if (userData.length == 0) return [];
if (userData.isEmpty) return [];
String? lessonsJson = userData.elementAt(0)["timetable"] as String?;
if (lessonsJson == null) return [];
List<Lesson> lessons = (jsonDecode(lessonsJson) as List).map((e) => Lesson.fromJson(e)).toList();
@@ -60,7 +72,7 @@ class UserDatabaseQuery {
Future<List<Exam>> getExams({required String userId}) async {
List<Map> userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]);
if (userData.length == 0) return [];
if (userData.isEmpty) return [];
String? examsJson = userData.elementAt(0)["exams"] as String?;
if (examsJson == null) return [];
List<Exam> exams = (jsonDecode(examsJson) as List).map((e) => Exam.fromJson(e)).toList();
@@ -69,7 +81,7 @@ class UserDatabaseQuery {
Future<List<Homework>> getHomework({required String userId}) async {
List<Map> userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]);
if (userData.length == 0) return [];
if (userData.isEmpty) return [];
String? homeworkJson = userData.elementAt(0)["homework"] as String?;
if (homeworkJson == null) return [];
List<Homework> homework = (jsonDecode(homeworkJson) as List).map((e) => Homework.fromJson(e)).toList();
@@ -78,7 +90,7 @@ class UserDatabaseQuery {
Future<List<Message>> getMessages({required String userId}) async {
List<Map> userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]);
if (userData.length == 0) return [];
if (userData.isEmpty) return [];
String? messagesJson = userData.elementAt(0)["messages"] as String?;
if (messagesJson == null) return [];
List<Message> messages = (jsonDecode(messagesJson) as List).map((e) => Message.fromJson(e)).toList();
@@ -87,7 +99,7 @@ class UserDatabaseQuery {
Future<List<Note>> getNotes({required String userId}) async {
List<Map> userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]);
if (userData.length == 0) return [];
if (userData.isEmpty) return [];
String? notesJson = userData.elementAt(0)["notes"] as String?;
if (notesJson == null) return [];
List<Note> notes = (jsonDecode(notesJson) as List).map((e) => Note.fromJson(e)).toList();
@@ -96,7 +108,7 @@ class UserDatabaseQuery {
Future<List<Event>> getEvents({required String userId}) async {
List<Map> userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]);
if (userData.length == 0) return [];
if (userData.isEmpty) return [];
String? eventsJson = userData.elementAt(0)["events"] as String?;
if (eventsJson == null) return [];
List<Event> events = (jsonDecode(eventsJson) as List).map((e) => Event.fromJson(e)).toList();
@@ -105,10 +117,37 @@ class UserDatabaseQuery {
Future<List<Absence>> getAbsences({required String userId}) async {
List<Map> userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]);
if (userData.length == 0) return [];
String? absebcesJson = userData.elementAt(0)["absences"] as String?;
if (absebcesJson == null) return [];
List<Absence> absebces = (jsonDecode(absebcesJson) as List).map((e) => Absence.fromJson(e)).toList();
return absebces;
if (userData.isEmpty) return [];
String? absencesJson = userData.elementAt(0)["absences"] as String?;
if (absencesJson == null) return [];
List<Absence> absences = (jsonDecode(absencesJson) as List).map((e) => Absence.fromJson(e)).toList();
return absences;
}
Future<List<GroupAverage>> getGroupAverages({required String userId}) async {
List<Map> userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]);
if (userData.isEmpty) return [];
String? groupAveragesJson = userData.elementAt(0)["group_averages"] as String?;
if (groupAveragesJson == null) return [];
List<GroupAverage> groupAverages = (jsonDecode(groupAveragesJson) as List).map((e) => GroupAverage.fromJson(e)).toList();
return groupAverages;
}
Future<SubjectLessonCount> getSubjectLessonCount({required String userId}) async {
List<Map> userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]);
if (userData.isEmpty) return SubjectLessonCount.fromMap({});
String? lessonCountJson = userData.elementAt(0)["subject_lesson_count"] as String?;
if (lessonCountJson == null) return SubjectLessonCount.fromMap({});
SubjectLessonCount lessonCount = SubjectLessonCount.fromMap(jsonDecode(lessonCountJson) as Map);
return lessonCount;
}
Future<DateTime> lastSeenGrade({required String userId}) async {
List<Map> userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]);
if (userData.isEmpty) return DateTime(0);
int? lastSeenDate = userData.elementAt(0)["last_seen_grade"] as int?;
if (lastSeenDate == null) return DateTime(0);
DateTime lastSeen = DateTime.fromMillisecondsSinceEpoch(lastSeenDate);
return lastSeen;
}
}

View File

@@ -1,5 +1,7 @@
import 'dart:convert';
import 'package:sqflite/sqflite.dart';
import 'package:filcnaplo/models/subject_lesson_count.dart';
// ignore: depend_on_referenced_packages
import 'package:sqflite_common/sqlite_api.dart';
// Models
import 'package:filcnaplo/models/settings.dart';
@@ -12,6 +14,7 @@ import 'package:filcnaplo_kreta_api/models/message.dart';
import 'package:filcnaplo_kreta_api/models/note.dart';
import 'package:filcnaplo_kreta_api/models/event.dart';
import 'package:filcnaplo_kreta_api/models/absence.dart';
import 'package:filcnaplo_kreta_api/models/group_average.dart';
class DatabaseStore {
DatabaseStore({required this.db});
@@ -24,7 +27,7 @@ class DatabaseStore {
Future<void> storeUser(User user) async {
List userRes = await db.query("users", where: "id = ?", whereArgs: [user.id]);
if (userRes.length > 0) {
if (userRes.isNotEmpty) {
await db.update("users", user.toMap(), where: "id = ?", whereArgs: [user.id]);
} else {
await db.insert("users", user.toMap());
@@ -43,43 +46,58 @@ class UserDatabaseStore {
final Database db;
Future storeGrades(List<Grade> grades, {required String userId}) async {
Future<void> storeGrades(List<Grade> grades, {required String userId}) async {
String gradesJson = jsonEncode(grades.map((e) => e.json).toList());
await db.update("user_data", {"grades": gradesJson}, where: "id = ?", whereArgs: [userId]);
}
Future storeLessons(List<Lesson> lessons, {required String userId}) async {
Future<void> storeLessons(List<Lesson> lessons, {required String userId}) async {
String lessonsJson = jsonEncode(lessons.map((e) => e.json).toList());
await db.update("user_data", {"timetable": lessonsJson}, where: "id = ?", whereArgs: [userId]);
}
Future storeExams(List<Exam> exams, {required String userId}) async {
Future<void> storeExams(List<Exam> exams, {required String userId}) async {
String examsJson = jsonEncode(exams.map((e) => e.json).toList());
await db.update("user_data", {"exams": examsJson}, where: "id = ?", whereArgs: [userId]);
}
Future storeHomework(List<Homework> homework, {required String userId}) async {
Future<void> storeHomework(List<Homework> homework, {required String userId}) async {
String homeworkJson = jsonEncode(homework.map((e) => e.json).toList());
await db.update("user_data", {"homework": homeworkJson}, where: "id = ?", whereArgs: [userId]);
}
Future storeMessages(List<Message> messages, {required String userId}) async {
Future<void> storeMessages(List<Message> messages, {required String userId}) async {
String messagesJson = jsonEncode(messages.map((e) => e.json).toList());
await db.update("user_data", {"messages": messagesJson}, where: "id = ?", whereArgs: [userId]);
}
Future storeNotes(List<Note> notes, {required String userId}) async {
Future<void> storeNotes(List<Note> notes, {required String userId}) async {
String notesJson = jsonEncode(notes.map((e) => e.json).toList());
await db.update("user_data", {"notes": notesJson}, where: "id = ?", whereArgs: [userId]);
}
Future storeEvents(List<Event> events, {required String userId}) async {
Future<void> storeEvents(List<Event> events, {required String userId}) async {
String eventsJson = jsonEncode(events.map((e) => e.json).toList());
await db.update("user_data", {"events": eventsJson}, where: "id = ?", whereArgs: [userId]);
}
Future storeAbsences(List<Absence> absences, {required String userId}) async {
Future<void> storeAbsences(List<Absence> absences, {required String userId}) async {
String absencesJson = jsonEncode(absences.map((e) => e.json).toList());
await db.update("user_data", {"absences": absencesJson}, where: "id = ?", whereArgs: [userId]);
}
Future<void> storeGroupAverages(List<GroupAverage> groupAverages, {required String userId}) async {
String groupAveragesJson = jsonEncode(groupAverages.map((e) => e.json).toList());
await db.update("user_data", {"group_averages": groupAveragesJson}, where: "id = ?", whereArgs: [userId]);
}
Future<void> storeSubjectLessonCount(SubjectLessonCount lessonCount, {required String userId}) async {
String lessonCountJson = jsonEncode(lessonCount.toMap());
await db.update("user_data", {"subject_lesson_count": lessonCountJson}, where: "id = ?", whereArgs: [userId]);
}
Future<void> storeLastSeenGrade(DateTime date, {required String userId}) async {
int lastSeenDate = date.millisecondsSinceEpoch;
await db.update("user_data", {"last_seen_grade": lastSeenDate}, where: "id = ?", whereArgs: [userId]);
}
}

View File

@@ -1,7 +1,8 @@
class DatabaseStruct {
final String table;
final Map<String, dynamic> struct;
DatabaseStruct(this.struct);
const DatabaseStruct(this.table, this.struct);
String _toDBfield(String name, dynamic type) {
String typeName = "";
@@ -15,7 +16,7 @@ class DatabaseStruct {
break;
}
return "${name} ${typeName.toUpperCase()}";
return "$name ${typeName.toUpperCase()} ${name == 'id' ? 'NOT NULL' : ''}";
}
@override

View File

@@ -1,9 +1,13 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:io';
import 'dart:typed_data';
import 'package:filcnaplo/api/providers/user_provider.dart';
import 'package:filcnaplo/helpers/storage_helper.dart';
import 'package:filcnaplo_kreta_api/client/client.dart';
import 'package:filcnaplo_kreta_api/models/attachment.dart';
import 'package:filcnaplo_kreta_api/models/homework.dart';
import 'package:flutter/widgets.dart';
import 'package:open_file/open_file.dart';
import 'package:provider/provider.dart';
@@ -28,3 +32,25 @@ extension AttachmentHelper on Attachment {
return result.type == ResultType.done;
}
}
extension HomeworkAttachmentHelper on HomeworkAttachment {
Future<String> download(BuildContext context, {bool overwrite = false}) async {
String downloads = await StorageHelper.downloadsPath();
if (!overwrite && await File("$downloads/$name").exists()) return "$downloads/$name";
String url = downloadUrl(Provider.of<UserProvider>(context, listen: false).instituteCode ?? "");
Uint8List data = await Provider.of<KretaClient>(context, listen: false).getAPI(url, rawResponse: true);
if (!await StorageHelper.write("$downloads/$name", data)) return "";
return "$downloads/$name";
}
Future<bool> open(BuildContext context) async {
String downloads = await StorageHelper.downloadsPath();
if (!await File("$downloads/$name").exists()) await download(context);
var result = await OpenFile.open("$downloads/$name");
return result.type == ResultType.done;
}
}

View File

@@ -6,19 +6,15 @@ class AverageHelper {
List<String> ignoreInFinal = ["5,SzorgalomErtek", "4,MagatartasErtek"];
if (finalAvg)
grades.removeWhere((e) =>
(e.value.value == 0) ||
(ignoreInFinal.contains(e.gradeType?.id)));
if (finalAvg) {
grades.removeWhere((e) => (e.value.value == 0) || (ignoreInFinal.contains(e.gradeType?.id)));
}
grades.forEach((e) {
for (var e in grades) {
average += e.value.value * ((finalAvg ? 100 : e.value.weight) / 100);
});
}
average = average /
grades
.map((e) => (finalAvg ? 100 : e.value.weight) / 100)
.fold(0.0, (a, b) => a + b);
average = average / grades.map((e) => (finalAvg ? 100 : e.value.weight) / 100).fold(0.0, (a, b) => a + b);
return average.isNaN ? 0.0 : average;
}

View File

@@ -0,0 +1,36 @@
import 'package:flutter/cupertino.dart';
import 'package:quick_actions/quick_actions.dart';
import 'package:filcnaplo_mobile_ui/common/screens.i18n.dart';
const QuickActions quickActions = QuickActions();
void setupQuickActions() {
quickActions.setShortcutItems(<ShortcutItem>[
ShortcutItem(type: 'action_grades', localizedTitle: 'grades'.i18n, icon: 'ic_grades'),
ShortcutItem(type: 'action_timetable', localizedTitle: 'timetable'.i18n, icon: 'ic_timetable'),
ShortcutItem(type: 'action_messages', localizedTitle: 'messages'.i18n, icon: 'ic_messages'),
ShortcutItem(type: 'action_absences', localizedTitle: 'absences'.i18n, icon: 'ic_absences')
]);
}
void handleQuickActions(BuildContext context, void Function(String) callback) {
quickActions.initialize((shortcutType) {
switch (shortcutType) {
case 'action_home':
callback("home");
break;
case 'action_grades':
callback("grades");
break;
case 'action_timetable':
callback("timetable");
break;
case 'action_messages':
callback("messages");
break;
case 'action_absences':
callback("absences");
break;
}
});
}

View File

@@ -5,6 +5,7 @@ import 'package:share_plus/share_plus.dart';
class ShareHelper {
static Future<void> shareText(String text, {String? subject}) => Share.share(text, subject: subject);
// ignore: deprecated_member_use
static Future<void> shareFile(String path, {String? text, String? subject}) => Share.shareFiles([path], text: text, subject: subject);
static Future<void> shareAttachment(Attachment attachment, {required BuildContext context}) async {

View File

@@ -1,3 +1,5 @@
// ignore_for_file: avoid_print
import 'dart:io';
import 'dart:typed_data';

View File

@@ -0,0 +1,144 @@
import 'package:filcnaplo/icons/filc_icons.dart';
import 'package:filcnaplo/models/icon_pack.dart';
import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo/utils/format.dart';
import 'package:filcnaplo_kreta_api/models/subject.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
typedef SubjectIconVariants = Map<IconPack, IconData>;
class SubjectIconData {
final SubjectIconVariants data;
final String name; // for iOS live activities compatibilty
SubjectIconData({
this.data = const {
IconPack.material: Icons.widgets_outlined,
IconPack.cupertino: CupertinoIcons.rectangle_grid_2x2,
},
this.name = "square.grid.2x2",
});
}
SubjectIconVariants createIcon({required IconData material, required IconData cupertino}) {
return {
IconPack.material: material,
IconPack.cupertino: cupertino,
};
}
class SubjectIcon {
static String resolveName({Subject? subject, String? subjectName}) => _resolve(subject: subject, subjectName: subjectName).name;
static IconData resolveVariant({Subject? subject, String? subjectName, required BuildContext context}) =>
_resolve(subject: subject, subjectName: subjectName).data[Provider.of<SettingsProvider>(context, listen: false).iconPack]!;
static SubjectIconData _resolve({Subject? subject, String? subjectName}) {
assert(!(subject == null && subjectName == null));
String name = (subject?.name ?? subjectName ?? "").toLowerCase().specialChars().trim();
String category = subject?.category.description.toLowerCase().specialChars() ?? "";
// todo: check for categories
if (RegExp("mate(k|matika)").hasMatch(name) || category == "matematika") {
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.function, material: Icons.calculate_outlined), name: "function");
} else if (RegExp("magyar nyelv|nyelvtan").hasMatch(name)) {
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.textformat_alt, material: Icons.spellcheck_outlined), name: "textformat.alt");
} else if (RegExp("irodalom").hasMatch(name)) {
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.book, material: Icons.menu_book_outlined), name: "book");
} else if (RegExp("tor(i|tenelem)").hasMatch(name)) {
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.compass, material: Icons.hourglass_empty_outlined), name: "safari");
} else if (RegExp("foldrajz").hasMatch(name)) {
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.map, material: Icons.public_outlined), name: "map");
} else if (RegExp("rajz|muvtori|muveszet|vizualis").hasMatch(name)) {
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.paintbrush, material: Icons.palette_outlined), name: "paintbrush");
} else if (RegExp("fizika").hasMatch(name)) {
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.lightbulb, material: Icons.emoji_objects_outlined), name: "lightbulb");
} else if (RegExp("^enek|zene|szolfezs|zongora|korus").hasMatch(name)) {
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.music_note, material: Icons.music_note_outlined), name: "music.note");
} else if (RegExp("^tes(i|tneveles)|sport").hasMatch(name)) {
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.sportscourt, material: Icons.sports_soccer_outlined), name: "sportscourt");
} else if (RegExp("kemia").hasMatch(name)) {
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.lab_flask, material: Icons.science_outlined), name: "testtube.2");
} else if (RegExp("biologia").hasMatch(name)) {
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.paw, material: Icons.pets_outlined), name: "pawprint");
} else if (RegExp("kornyezet|termeszet ?(tudomany|ismeret)|hon( es nep)?ismeret").hasMatch(name)) {
return SubjectIconData(
data: createIcon(cupertino: CupertinoIcons.arrow_3_trianglepath, material: Icons.eco_outlined), name: "arrow.3.trianglepath");
} else if (RegExp("(hit|erkolcs)tan|vallas|etika").hasMatch(name)) {
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.heart, material: Icons.favorite_border_outlined), name: "heart");
} else if (RegExp("penzugy").hasMatch(name)) {
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.money_dollar, material: Icons.savings_outlined), name: "dollarsign");
} else if (RegExp("informatika|szoftver|iroda|digitalis").hasMatch(name)) {
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.device_laptop, material: Icons.computer_outlined), name: "laptopcomputer");
} else if (RegExp("prog").hasMatch(name)) {
return SubjectIconData(
data: createIcon(cupertino: CupertinoIcons.chevron_left_slash_chevron_right, material: Icons.code_outlined),
name: "chevron.left.forwardslash.chevron.right");
} else if (RegExp("halozat").hasMatch(name)) {
return SubjectIconData(
data: createIcon(cupertino: CupertinoIcons.antenna_radiowaves_left_right, material: Icons.wifi_tethering_outlined),
name: "antenna.radiowaves.left.and.right");
} else if (RegExp("szinhaz").hasMatch(name)) {
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.hifispeaker, material: Icons.theater_comedy_outlined), name: "hifispeaker");
} else if (RegExp("film|media").hasMatch(name)) {
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.film, material: Icons.theaters_outlined), name: "film");
} else if (RegExp("elektro(tech)?nika").hasMatch(name)) {
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.bolt, material: Icons.electrical_services_outlined), name: "bolt");
} else if (RegExp("gepesz|mernok|ipar").hasMatch(name)) {
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.wrench, material: Icons.precision_manufacturing_outlined), name: "wrench");
} else if (RegExp("technika").hasMatch(name)) {
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.hammer, material: Icons.build_outlined), name: "hammer");
} else if (RegExp("tanc").hasMatch(name)) {
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.music_mic, material: Icons.speaker_outlined), name: "music.mic");
} else if (RegExp("filozofia").hasMatch(name)) {
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.bubble_left, material: Icons.psychology_outlined), name: "bubble.left");
} else if (RegExp("osztaly(fonoki|kozosseg)").hasMatch(name) || name == "ofo") {
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.group, material: Icons.groups_outlined), name: "person.3");
} else if (RegExp("gazdasag").hasMatch(name)) {
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.chart_pie, material: Icons.account_balance_outlined), name: "chart.pie");
} else if (RegExp("szorgalom").hasMatch(name)) {
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.checkmark_seal, material: Icons.verified_outlined), name: "checkmark.seal");
} else if (RegExp("magatartas").hasMatch(name)) {
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.smiley, material: Icons.emoji_people_outlined), name: "face.smiling");
} else if (RegExp("angol|nemet|francia|olasz|orosz|spanyol|latin|kinai|nyelv").hasMatch(name)) {
return SubjectIconData(data: createIcon(cupertino: CupertinoIcons.globe, material: Icons.translate_outlined), name: "globe");
} else if (RegExp("linux").hasMatch(name)) {
return SubjectIconData(data: createIcon(material: FilcIcons.linux, cupertino: FilcIcons.linux));
}
return SubjectIconData();
}
}
class ShortSubject {
static String resolve({Subject? subject, String? subjectName}) {
assert(!(subject == null && subjectName == null));
String name = (subject?.name ?? subjectName ?? "").toLowerCase().specialChars().trim();
// String category = subject?.category.description.toLowerCase().specialChars() ?? "";
if (RegExp("magyar irodalom").hasMatch(name)) {
return "Irodalom";
} else if (RegExp("magyar nyelv").hasMatch(name)) {
return "Nyelvtan";
} else if (RegExp("matematika").hasMatch(name)) {
return "Matek";
} else if (RegExp("digitalis kultura").hasMatch(name)) {
return "Dig. kult.";
} else if (RegExp("testneveles").hasMatch(name)) {
return "Tesi";
} else if (RegExp("tortenelem").hasMatch(name)) {
return "Töri";
} else if (RegExp("(angol|nemet|francia|olasz|orosz|spanyol|latin|kinai) nyelv").hasMatch(name)) {
return (subject?.name ?? subjectName ?? "?").replaceAll(" nyelv", "");
} else if (RegExp("informatika").hasMatch(name)) {
return "Infó";
} else if (RegExp("osztalyfonoki").hasMatch(name)) {
return "Ofő";
}
return subject?.name.capital() ?? subjectName?.capital() ?? "?";
}
}

View File

@@ -1,46 +0,0 @@
import 'package:filcnaplo/icons/filc_icons.dart';
import 'package:filcnaplo/utils/format.dart';
import 'package:filcnaplo_kreta_api/models/subject.dart';
import 'package:flutter/material.dart';
class SubjectIcon {
static IconData? lookup({Subject? subject, String? subjectName}) {
assert(!(subject == null && subjectName == null));
String name = subject?.name.toLowerCase().specialChars() ?? subjectName ?? "";
String category = subject?.category.description.toLowerCase().specialChars() ?? "";
// todo: check for categories
if (RegExp("mate(k|matika)").hasMatch(name) || category == "matematika") return Icons.calculate_outlined;
if (RegExp("magyar nyelv|nyelvtan").hasMatch(name)) return Icons.spellcheck_outlined;
if (RegExp("irodalom").hasMatch(name)) return Icons.menu_book_outlined;
if (RegExp("tor(i|tenelem)").hasMatch(name)) return Icons.hourglass_empty_outlined;
if (RegExp("foldrajz").hasMatch(name)) return Icons.public_outlined;
if (RegExp("rajz|muvtori|muveszet|kultura").hasMatch(name)) return Icons.palette_outlined;
if (RegExp("fizika").hasMatch(name)) return Icons.emoji_objects_outlined;
if (RegExp("^enek|zene|szolfezs|zongora|korus").hasMatch(name)) return Icons.music_note_outlined;
if (RegExp("^tes(i|tneveles)|sport").hasMatch(name)) return Icons.sports_soccer_outlined;
if (RegExp("kemia").hasMatch(name)) return Icons.science_outlined;
if (RegExp("biologia").hasMatch(name)) return Icons.pets_outlined;
if (RegExp("kornyezet|termeszet(tudomany|ismeret)|hon( es nep)?ismeret").hasMatch(name)) return Icons.eco_outlined;
if (RegExp("(hit|erkolcs)tan|vallas|etika").hasMatch(name)) return Icons.favorite_border_outlined;
if (RegExp("penzugy").hasMatch(name)) return Icons.savings_outlined;
if (RegExp("informatika|szoftver|iroda").hasMatch(name)) return Icons.computer_outlined;
if (RegExp("prog").hasMatch(name)) return Icons.code_outlined;
if (RegExp("halozat").hasMatch(name)) return Icons.wifi_tethering_outlined;
if (RegExp("szinhaz").hasMatch(name)) return Icons.theater_comedy_outlined;
if (RegExp("film|media").hasMatch(name)) return Icons.theaters_outlined;
if (RegExp("elektro(tech)?nika").hasMatch(name)) return Icons.electrical_services_outlined;
if (RegExp("gepesz|mernok|ipar").hasMatch(name)) return Icons.precision_manufacturing_outlined;
if (RegExp("technika").hasMatch(name)) return Icons.build_outlined;
if (RegExp("tanc").hasMatch(name)) return Icons.speaker_outlined;
if (RegExp("filozofia").hasMatch(name)) return Icons.psychology_outlined;
if (RegExp("osztaly(fonoki|kozosseg)").hasMatch(name)) return Icons.groups_outlined;
if (RegExp("gazdasag").hasMatch(name)) return Icons.account_balance_outlined;
if (RegExp("szorgalom").hasMatch(name)) return Icons.verified_outlined;
if (RegExp("magatartas").hasMatch(name)) return Icons.emoji_people_outlined;
if (RegExp("angol|nemet|francia|olasz|orosz|spanyol|latin|kinai|nyelv").hasMatch(name)) return Icons.translate_outlined;
if (RegExp("linux").hasMatch(name)) return FilcIcons.linux;
return Icons.widgets_outlined;
}
}

View File

@@ -7,35 +7,40 @@ import 'package:filcnaplo/helpers/storage_helper.dart';
import 'package:filcnaplo/models/release.dart';
import 'package:open_file/open_file.dart';
enum UpdateState { prepare, downloading, installing }
enum UpdateState { none, preparing, downloading, installing }
typedef UpdateCallback = Function(double progress, UpdateState state);
// TODO: cleanup old apk files
extension UpdateHelper on Release {
Future<void> install({UpdateCallback? updateCallback}) async {
updateCallback!(-1, UpdateState.preparing);
String downloads = await StorageHelper.downloadsPath();
File apk = File("$downloads/filcnaplo-${version}.apk");
File apk = File("$downloads/filcnaplo-$version.apk");
if (!await apk.exists()) {
updateCallback!(-1, UpdateState.downloading);
updateCallback(-1, UpdateState.downloading);
var bytes = await download(updateCallback: updateCallback);
if (!await StorageHelper.write(apk.path, bytes)) throw "failed to write apk: permission denied";
}
updateCallback!(-1, UpdateState.installing);
updateCallback(-1, UpdateState.installing);
var result = await OpenFile.open(apk.path);
if (result.type != ResultType.done) {
print("ERROR: installUpdate.openFile: " + result.message);
// ignore: avoid_print
print("ERROR: installUpdate.openFile: ${result.message}");
throw result.message;
}
updateCallback(-1, UpdateState.prepare);
updateCallback(-1, UpdateState.none);
}
Future<Uint8List> download({UpdateCallback? updateCallback}) async {
var response = await FilcAPI.downloadRelease(this);
var response = await FilcAPI.downloadRelease(downloads.first);
List<List<int>> chunks = [];
int downloaded = 0;

View File

@@ -1,10 +1,22 @@
import 'package:flutter/widgets.dart';
class FilcIcons {
static const IconData home = const FilcIconData(0x41);
static const IconData linux = const FilcIconData(0x42);
}
FilcIcons._();
class FilcIconData extends IconData {
const FilcIconData(int codePoint) : super(codePoint, fontFamily: "FilcIcons");
static const iconFontFamily = 'FilcIcons';
/// home
static const IconData home = IconData(0x00, fontFamily: iconFontFamily);
/// linux
static const IconData linux = IconData(0x01, fontFamily: iconFontFamily);
/// upstairs
static const IconData upstairs = IconData(0x02, fontFamily: iconFontFamily);
/// downstairs
static const IconData downstairs = IconData(0x03, fontFamily: iconFontFamily);
/// premium
static const IconData premium = IconData(0x04, fontFamily: iconFontFamily);
}

View File

@@ -2,10 +2,12 @@ import 'package:filcnaplo/api/providers/user_provider.dart';
import 'package:filcnaplo/api/providers/database_provider.dart';
import 'package:filcnaplo/database/init.dart';
import 'package:filcnaplo/models/settings.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:filcnaplo/app.dart';
import 'package:flutter/services.dart';
import 'package:filcnaplo_mobile_ui/screens/error_screen.dart';
import 'package:filcnaplo_mobile_ui/screens/error_report_screen.dart';
void main() async {
// Initalize
@@ -30,21 +32,34 @@ class Startup {
late DatabaseProvider database;
Future<void> start() async {
var db = await initDB();
await db.close();
database = DatabaseProvider();
var db = await initDB(database);
await db.close();
await database.init();
settings = await database.query.getSettings();
user = await database.query.getUsers();
settings = await database.query.getSettings(database);
user = await database.query.getUsers(settings);
}
}
bool errorShown = false;
String lastException = '';
Widget errorBuilder(FlutterErrorDetails details) {
return Builder(builder: (context) {
if (Navigator.of(context).canPop()) Navigator.pop(context);
WidgetsBinding.instance?.addPostFrameCallback((_) {
Navigator.of(context, rootNavigator: true).push(MaterialPageRoute(builder: (ctx) => ErrorScreen(details)));
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!errorShown && details.exceptionAsString() != lastException) {
errorShown = true;
lastException = details.exceptionAsString();
Navigator.of(context, rootNavigator: true).push(MaterialPageRoute(builder: (context) {
if (kReleaseMode) {
return ErrorReportScreen(details);
} else {
return ErrorScreen(details);
}
})).then((_) => errorShown = false);
}
});
return Container();

View File

@@ -1,15 +1,11 @@
import 'dart:io';
import 'package:package_info_plus/package_info_plus.dart';
class Config {
String _userAgent;
String? _version;
Map? json;
static const String _version = String.fromEnvironment("APPVER", defaultValue: "2.2.0");
Config({required String userAgent, this.json}) : _userAgent = userAgent {
PackageInfo.fromPlatform().then((value) => _version = value.version);
}
Config({required String userAgent, this.json}) : _userAgent = userAgent;
factory Config.fromJson(Map json) {
return Config(
@@ -18,7 +14,7 @@ class Config {
);
}
String get userAgent => _userAgent.replaceAll("\$0", _version ?? "0").replaceAll("\$1", platform).replaceAll("\$2", "0");
String get userAgent => _userAgent.replaceAll("\$0", _version).replaceAll("\$1", platform).replaceAll("\$2", "0");
static String get platform {
if (Platform.isAndroid) {

View File

@@ -0,0 +1 @@
enum IconPack { material, cupertino }

View File

@@ -1,9 +1,26 @@
class ReleaseDownload {
String url;
int size;
ReleaseDownload({
required this.url,
required this.size,
});
factory ReleaseDownload.fromJson(Map json) {
return ReleaseDownload(
url: json["browser_download_url"] ?? "",
size: json["size"] ?? 0,
);
}
}
class Release {
String tag;
Version version;
String author;
String body;
List<String> downloads;
List<ReleaseDownload> downloads;
bool prerelease;
Release({
@@ -20,7 +37,7 @@ class Release {
tag: json["tag_name"] ?? Version.zero.toString(),
author: json["author"] != null ? json["author"]["login"] ?? "" : "",
body: json["body"] ?? "",
downloads: json["assets"] != null ? json["assets"].map((a) => a["browser_download_url"] ?? "").toList().cast<String>() : [],
downloads: json["assets"] != null ? json["assets"].map((a) => ReleaseDownload.fromJson(a)).toList().cast<ReleaseDownload>() : [],
prerelease: json["prerelease"] ?? false,
version: Version.fromString(json["tag_name"] ?? ""),
);
@@ -58,10 +75,11 @@ class Version {
// check for valid prerelease name
if (p[0] != "") {
if (prereleases.contains(p[0].toLowerCase().trim()))
if (prereleases.contains(p[0].toLowerCase().trim())) {
pre = p[0];
else
} else {
throw "invalid prerelease name: ${p[0]}";
}
}
// core
@@ -74,6 +92,7 @@ class Version {
return Version(x, y, z, prerelease: pre, prever: prev);
} catch (error) {
// ignore: avoid_print
print("WARNING: Failed to parse version ($o): $error");
return Version.zero;
}
@@ -125,5 +144,8 @@ class Version {
}
static const zero = Version(0, 0, 0);
static const List<String> prereleases = ["dev", "pre", "alpha", "beta", "rc"];
static const List<String> prereleases = ["dev", "pre", "alpha", "beta", "rc", "nightly", "test"];
@override
int get hashCode => toString().hashCode;
}

View File

@@ -1,18 +1,22 @@
import 'dart:convert';
import 'dart:developer';
import 'package:filcnaplo/api/providers/database_provider.dart';
import 'package:filcnaplo/models/config.dart';
import 'package:filcnaplo/theme.dart';
import 'package:filcnaplo/models/icon_pack.dart';
import 'package:filcnaplo/theme/colors/accent.dart';
import 'package:filcnaplo/theme/colors/dark_mobile.dart';
import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:provider/provider.dart';
import 'package:uuid/uuid.dart';
enum Pages { home, grades, timetable, messages, absences }
enum UpdateChannel { stable, beta, dev }
enum VibrationStrength { off, light, medium, strong }
class SettingsProvider extends ChangeNotifier {
PackageInfo? _packageInfo;
final DatabaseProvider? _database;
// en_en, hu_hu, de_de
String _language;
@@ -43,12 +47,27 @@ class SettingsProvider extends ChangeNotifier {
int _notificationPollInterval;
bool _developerMode;
VibrationStrength _vibrate;
bool _ABweeks;
bool _abWeeks;
bool _swapABweeks;
UpdateChannel _updateChannel;
Config _config;
String _xFilcId;
bool _graphClassAvg;
bool _goodStudent;
bool _presentationMode;
bool _bellDelayEnabled;
int _bellDelay;
bool _gradeOpeningFun;
IconPack _iconPack;
Color _customAccentColor;
Color _customBackgroundColor;
Color _customHighlightColor;
List<String> _premiumScopes;
String _premiumAccessToken;
String _lastAccountId;
SettingsProvider({
DatabaseProvider? database,
required String language,
required Pages startPage,
required int rounding,
@@ -62,11 +81,26 @@ class SettingsProvider extends ChangeNotifier {
required bool developerMode,
required int notificationPollInterval,
required VibrationStrength vibrate,
required bool ABweeks,
required bool abWeeks,
required bool swapABweeks,
required UpdateChannel updateChannel,
required Config config,
}) : _language = language,
required String xFilcId,
required bool graphClassAvg,
required bool goodStudent,
required bool presentationMode,
required bool bellDelayEnabled,
required int bellDelay,
required bool gradeOpeningFun,
required IconPack iconPack,
required Color customAccentColor,
required Color customBackgroundColor,
required Color customHighlightColor,
required List<String> premiumScopes,
required String premiumAccessToken,
required String lastAccountId,
}) : _database = database,
_language = language,
_startPage = startPage,
_rounding = rounding,
_theme = theme,
@@ -79,17 +113,36 @@ class SettingsProvider extends ChangeNotifier {
_developerMode = developerMode,
_notificationPollInterval = notificationPollInterval,
_vibrate = vibrate,
_ABweeks = ABweeks,
_abWeeks = abWeeks,
_swapABweeks = swapABweeks,
_updateChannel = updateChannel,
_config = config {
PackageInfo.fromPlatform().then((PackageInfo packageInfo) {
_packageInfo = packageInfo;
});
}
_config = config,
_xFilcId = xFilcId,
_graphClassAvg = graphClassAvg,
_goodStudent = goodStudent,
_presentationMode = presentationMode,
_bellDelayEnabled = bellDelayEnabled,
_bellDelay = bellDelay,
_gradeOpeningFun = gradeOpeningFun,
_iconPack = iconPack,
_customAccentColor = customAccentColor,
_customBackgroundColor = customBackgroundColor,
_customHighlightColor = customHighlightColor,
_premiumScopes = premiumScopes,
_premiumAccessToken = premiumAccessToken,
_lastAccountId = lastAccountId;
factory SettingsProvider.fromMap(Map map, {required DatabaseProvider database}) {
Map<String, Object?>? configMap;
try {
configMap = jsonDecode(map["config"] ?? "{}");
} catch (e) {
log("[ERROR] SettingsProvider.fromMap: $e");
}
factory SettingsProvider.fromMap(Map map) {
return SettingsProvider(
database: database,
language: map["language"],
startPage: Pages.values[map["start_page"]],
rounding: map["rounding"],
@@ -102,17 +155,31 @@ class SettingsProvider extends ChangeNotifier {
Color(map["grade_color4"]),
Color(map["grade_color5"]),
],
newsEnabled: map["news"] == 1 ? true : false,
newsEnabled: map["news"] == 1,
newsState: map["news_state"],
notificationsEnabled: map["notifications"] == 1 ? true : false,
notificationsEnabled: map["notifications"] == 1,
notificationsBitfield: map["notifications_bitfield"],
notificationPollInterval: map["notification_poll_interval"],
developerMode: map["developer_mode"] == 1 ? true : false,
developerMode: map["developer_mode"] == 1,
vibrate: VibrationStrength.values[map["vibration_strength"]],
ABweeks: map["ab_weeks"] == 1 ? true : false,
swapABweeks: map["swap_ab_weeks"] == 1 ? true : false,
abWeeks: map["ab_weeks"] == 1,
swapABweeks: map["swap_ab_weeks"] == 1,
updateChannel: UpdateChannel.values[map["update_channel"]],
config: Config.fromJson(jsonDecode(map["config"] ?? "{}")),
config: Config.fromJson(configMap ?? {}),
xFilcId: map["x_filc_id"],
graphClassAvg: map["graph_class_avg"] == 1,
goodStudent: false,
presentationMode: map["presentation_mode"] == 1,
bellDelayEnabled: map["bell_delay_enabled"] == 1,
bellDelay: map["bell_delay"],
gradeOpeningFun: map["grade_opening_fun"] == 1,
iconPack: Map.fromEntries(IconPack.values.map((e) => MapEntry(e.name, e)))[map["icon_pack"]]!,
customAccentColor: Color(map["custom_accent_color"]),
customBackgroundColor: Color(map["custom_background_color"]),
customHighlightColor: Color(map["custom_highlight_color"]),
premiumScopes: jsonDecode(map["premium_scopes"]).cast<String>(),
premiumAccessToken: map["premium_token"],
lastAccountId: map["last_account_id"],
);
}
@@ -135,26 +202,40 @@ class SettingsProvider extends ChangeNotifier {
"grade_color5": _gradeColors[4].value,
"update_channel": _updateChannel.index,
"vibration_strength": _vibrate.index,
"ab_weeks": _ABweeks ? 1 : 0,
"ab_weeks": _abWeeks ? 1 : 0,
"swap_ab_weeks": _swapABweeks ? 1 : 0,
"notification_poll_interval": _notificationPollInterval,
"config": jsonEncode(config.json),
"x_filc_id": _xFilcId,
"graph_class_avg": _graphClassAvg ? 1 : 0,
"presentation_mode": _presentationMode ? 1 : 0,
"bell_delay_enabled": _bellDelayEnabled ? 1 : 0,
"bell_delay": _bellDelay,
"grade_opening_fun": _gradeOpeningFun ? 1 : 0,
"icon_pack": _iconPack.name,
"custom_accent_color": _customAccentColor.value,
"custom_background_color": _customBackgroundColor.value,
"custom_highlight_color": _customHighlightColor.value,
"premium_scopes": jsonEncode(_premiumScopes),
"premium_token": _premiumAccessToken,
"last_account_id": _lastAccountId,
};
}
factory SettingsProvider.defaultSettings() {
factory SettingsProvider.defaultSettings({DatabaseProvider? database}) {
return SettingsProvider(
database: database,
language: "hu",
startPage: Pages.home,
rounding: 5,
theme: ThemeMode.system,
accentColor: AccentColor.filc,
gradeColors: [
DarkAppColors().red,
DarkAppColors().orange,
DarkAppColors().yellow,
DarkAppColors().green,
DarkAppColors().filc,
DarkMobileAppColors().red,
DarkMobileAppColors().orange,
DarkMobileAppColors().yellow,
DarkMobileAppColors().green,
DarkMobileAppColors().filc,
],
newsEnabled: true,
newsState: -1,
@@ -163,10 +244,24 @@ class SettingsProvider extends ChangeNotifier {
developerMode: false,
notificationPollInterval: 1,
vibrate: VibrationStrength.medium,
ABweeks: false,
abWeeks: false,
swapABweeks: false,
updateChannel: UpdateChannel.stable,
config: Config.fromJson({}),
xFilcId: const Uuid().v4(),
graphClassAvg: false,
goodStudent: false,
presentationMode: false,
bellDelayEnabled: false,
bellDelay: 0,
gradeOpeningFun: true,
iconPack: IconPack.cupertino,
customAccentColor: const Color(0xff20AC9B),
customBackgroundColor: const Color(0xff000000),
customHighlightColor: const Color(0xff222222),
premiumScopes: [],
premiumAccessToken: "",
lastAccountId: "",
);
}
@@ -184,15 +279,27 @@ class SettingsProvider extends ChangeNotifier {
bool get developerMode => _developerMode;
int get notificationPollInterval => _notificationPollInterval;
VibrationStrength get vibrate => _vibrate;
bool get ABweeks => _ABweeks;
bool get abWeeks => _abWeeks;
bool get swapABweeks => _swapABweeks;
UpdateChannel get updateChannel => _updateChannel;
PackageInfo? get packageInfo => _packageInfo;
Config get config => _config;
String get xFilcId => _xFilcId;
bool get graphClassAvg => _graphClassAvg;
bool get goodStudent => _goodStudent;
bool get presentationMode => _presentationMode;
bool get bellDelayEnabled => _bellDelayEnabled;
int get bellDelay => _bellDelay;
bool get gradeOpeningFun => _gradeOpeningFun;
IconPack get iconPack => _iconPack;
Color? get customAccentColor => _customAccentColor == accentColorMap[AccentColor.custom] ? null : _customAccentColor;
Color? get customBackgroundColor => _customBackgroundColor;
Color? get customHighlightColor => _customHighlightColor;
List<String> get premiumScopes => _premiumScopes;
String get premiumAccessToken => _premiumAccessToken;
String get lastAccountId => _lastAccountId;
Future<void> update(
BuildContext context, {
DatabaseProvider? database,
Future<void> update({
bool store = true,
String? language,
Pages? startPage,
int? rounding,
@@ -206,10 +313,24 @@ class SettingsProvider extends ChangeNotifier {
bool? developerMode,
int? notificationPollInterval,
VibrationStrength? vibrate,
bool? ABweeks,
bool? abWeeks,
bool? swapABweeks,
UpdateChannel? updateChannel,
Config? config,
String? xFilcId,
bool? graphClassAvg,
bool? goodStudent,
bool? presentationMode,
bool? bellDelayEnabled,
int? bellDelay,
bool? gradeOpeningFun,
IconPack? iconPack,
Color? customAccentColor,
Color? customBackgroundColor,
Color? customHighlightColor,
List<String>? premiumScopes,
String? premiumAccessToken,
String? lastAccountId,
}) async {
if (language != null && language != _language) _language = language;
if (startPage != null && startPage != _startPage) _startPage = startPage;
@@ -222,16 +343,30 @@ class SettingsProvider extends ChangeNotifier {
if (notificationsEnabled != null && notificationsEnabled != _notificationsEnabled) _notificationsEnabled = notificationsEnabled;
if (notificationsBitfield != null && notificationsBitfield != _notificationsBitfield) _notificationsBitfield = notificationsBitfield;
if (developerMode != null && developerMode != _developerMode) _developerMode = developerMode;
if (notificationPollInterval != null && notificationPollInterval != _notificationPollInterval)
if (notificationPollInterval != null && notificationPollInterval != _notificationPollInterval) {
_notificationPollInterval = notificationPollInterval;
}
if (vibrate != null && vibrate != _vibrate) _vibrate = vibrate;
if (ABweeks != null && ABweeks != _ABweeks) _ABweeks = ABweeks;
if (abWeeks != null && abWeeks != _abWeeks) _abWeeks = abWeeks;
if (swapABweeks != null && swapABweeks != _swapABweeks) _swapABweeks = swapABweeks;
if (updateChannel != null && updateChannel != _updateChannel) _updateChannel = updateChannel;
if (config != null && config != _config) _config = config;
if (xFilcId != null && xFilcId != _xFilcId) _xFilcId = xFilcId;
if (graphClassAvg != null && graphClassAvg != _graphClassAvg) _graphClassAvg = graphClassAvg;
if (goodStudent != null) _goodStudent = goodStudent;
if (presentationMode != null && presentationMode != _presentationMode) _presentationMode = presentationMode;
if (bellDelay != null && bellDelay != _bellDelay) _bellDelay = bellDelay;
if (bellDelayEnabled != null && bellDelayEnabled != _bellDelayEnabled) _bellDelayEnabled = bellDelayEnabled;
if (gradeOpeningFun != null && gradeOpeningFun != _gradeOpeningFun) _gradeOpeningFun = gradeOpeningFun;
if (iconPack != null && iconPack != _iconPack) _iconPack = iconPack;
if (customAccentColor != null && customAccentColor != _customAccentColor) _customAccentColor = customAccentColor;
if (customBackgroundColor != null && customBackgroundColor != _customBackgroundColor) _customBackgroundColor = customBackgroundColor;
if (customHighlightColor != null && customHighlightColor != _customHighlightColor) _customHighlightColor = customHighlightColor;
if (premiumScopes != null && premiumScopes != _premiumScopes) _premiumScopes = premiumScopes;
if (premiumAccessToken != null && premiumAccessToken != _premiumAccessToken) _premiumAccessToken = premiumAccessToken;
if (lastAccountId != null && lastAccountId != _lastAccountId) _lastAccountId = lastAccountId;
if (database == null) database = Provider.of<DatabaseProvider>(context, listen: false);
await database.store.storeSettings(this);
if (store) await _database?.store.storeSettings(this);
notifyListeners();
}
}

View File

@@ -0,0 +1,31 @@
import 'package:filcnaplo_kreta_api/models/category.dart';
import 'package:filcnaplo_kreta_api/models/subject.dart';
enum SubjectLessonCountUpdateState { ready, updating }
class SubjectLessonCount {
DateTime lastUpdated;
Map<Subject, int> subjects;
SubjectLessonCountUpdateState state;
SubjectLessonCount({required this.lastUpdated, required this.subjects, this.state = SubjectLessonCountUpdateState.ready});
factory SubjectLessonCount.fromMap(Map json) {
return SubjectLessonCount(
lastUpdated: DateTime.fromMillisecondsSinceEpoch(json["last_updated"] ?? 0),
subjects: ((json["subjects"] as Map?) ?? {}).map(
(key, value) => MapEntry(
Subject(id: key, name: "", category: Category.fromJson({})),
value,
),
),
);
}
Map toMap() {
return {
"last_updated": lastUpdated.millisecondsSinceEpoch,
"subjects": subjects.map((key, value) => MapEntry(key.id, value)),
};
}
}

View File

@@ -7,7 +7,7 @@ class Supporter {
factory Supporter.fromJson(Map json) {
return Supporter(
json["name"] ?? "",
(json["name"] ?? "").trim(),
json["amount"] ?? "",
json["platform"] ?? "",
);

View File

@@ -3,6 +3,8 @@ import 'package:filcnaplo_kreta_api/client/api.dart';
import 'package:filcnaplo_kreta_api/models/student.dart';
import 'package:uuid/uuid.dart';
enum Role { student, parent }
class User {
late String id;
String username;
@@ -10,6 +12,10 @@ class User {
String instituteCode;
String name;
Student student;
Role role;
String nickname;
String get displayName => nickname != '' ? nickname : name;
User({
String? id,
@@ -18,11 +24,13 @@ class User {
required this.password,
required this.instituteCode,
required this.student,
required this.role,
this.nickname = "",
}) {
if (id != null) {
this.id = id;
} else {
this.id = Uuid().v4();
this.id = const Uuid().v4();
}
}
@@ -32,8 +40,10 @@ class User {
instituteCode: map["institute_code"],
username: map["username"],
password: map["password"],
name: map["name"],
name: map["name"].trim(),
student: Student.fromJson(jsonDecode(map["student"])),
role: Role.values[map["role"] ?? 0],
nickname: map["nickname"] ?? "",
);
}
@@ -45,9 +55,14 @@ class User {
"institute_code": instituteCode,
"name": name,
"student": jsonEncode(student.json),
"role": role.index,
"nickname": nickname,
};
}
@override
String toString() => jsonEncode(toMap());
static Map<String, Object?> loginBody({
required String username,
required String password,
@@ -58,7 +73,20 @@ class User {
"password": password,
"institute_code": instituteCode,
"grant_type": "password",
"client_id": KretaAPI.CLIENT_ID,
"client_id": KretaAPI.clientId,
};
}
static Map<String, Object?> refreshBody({
required String refreshToken,
required String instituteCode,
}) {
return {
"refresh_token": refreshToken,
"institute_code": instituteCode,
"client_id": KretaAPI.clientId,
"grant_type": "refresh_token",
"refresh_user_data": "false",
};
}
}

View File

@@ -1,145 +0,0 @@
import 'package:filcnaplo/models/settings.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class AppTheme {
// Dev note: All of these could be constant variables, but this is better for
// development (you don't have to hot-restart)
static const String _fontFamily = "Montserrat";
// Light Theme
static ThemeData lightTheme(BuildContext context) {
var lightColors = LightAppColors();
Color accent = accentColorMap[Provider.of<SettingsProvider>(context, listen: false).accentColor] ?? Color(0);
return ThemeData(
brightness: Brightness.light,
fontFamily: _fontFamily,
scaffoldBackgroundColor: lightColors.background,
backgroundColor: lightColors.highlight,
primaryColor: lightColors.filc,
dividerColor: Color(0),
colorScheme: ColorScheme.fromSwatch(
accentColor: accent,
backgroundColor: lightColors.background,
brightness: Brightness.light,
cardColor: lightColors.highlight,
errorColor: lightColors.red,
primaryColorDark: lightColors.filc,
primarySwatch: Colors.teal,
),
shadowColor: lightColors.shadow,
appBarTheme: AppBarTheme(backgroundColor: lightColors.background),
indicatorColor: accent,
iconTheme: IconThemeData(color: lightColors.text.withOpacity(.75)));
}
// Dark Theme
static ThemeData darkTheme(BuildContext context) {
var darkColors = DarkAppColors();
Color accent = accentColorMap[Provider.of<SettingsProvider>(context, listen: false).accentColor] ?? Color(0);
return ThemeData(
brightness: Brightness.dark,
fontFamily: _fontFamily,
scaffoldBackgroundColor: darkColors.background,
backgroundColor: darkColors.highlight,
primaryColor: darkColors.filc,
dividerColor: Color(0),
colorScheme: ColorScheme.fromSwatch(
accentColor: accent,
backgroundColor: darkColors.background,
brightness: Brightness.dark,
cardColor: darkColors.highlight,
errorColor: darkColors.red,
primaryColorDark: darkColors.filc,
primarySwatch: Colors.teal,
),
shadowColor: darkColors.shadow,
appBarTheme: AppBarTheme(backgroundColor: darkColors.background),
indicatorColor: accent,
iconTheme: IconThemeData(color: darkColors.text.withOpacity(.75)));
}
}
class AppColors {
static ThemeAppColors of(BuildContext context) {
return Theme.of(context).brightness == Brightness.light ? LightAppColors() : DarkAppColors();
}
}
enum AccentColor { filc, blue, green, lime, yellow, orange, red, pink, purple }
Map<AccentColor, Color> accentColorMap = {
AccentColor.filc: Color(0xff20AC9B),
AccentColor.blue: Colors.blue.shade300,
AccentColor.green: Colors.green.shade300,
AccentColor.lime: Colors.lime.shade300,
AccentColor.yellow: Colors.yellow.shade300,
AccentColor.orange: Colors.deepOrange.shade300,
AccentColor.red: Colors.red.shade300,
AccentColor.pink: Colors.pink.shade300,
AccentColor.purple: Colors.purple.shade300,
};
abstract class ThemeAppColors {
final Color shadow = Color(0);
final Color text = Color(0);
final Color background = Color(0);
final Color highlight = Color(0);
final Color red = Color(0);
final Color orange = Color(0);
final Color yellow = Color(0);
final Color green = Color(0);
final Color filc = Color(0);
final Color teal = Color(0);
final Color blue = Color(0);
final Color indigo = Color(0);
final Color purple = Color(0);
final Color pink = Color(0);
}
class LightAppColors implements ThemeAppColors {
final shadow = Color(0xffE8E8E8);
final text = Colors.black;
final background = Color(0xffF4F9FF);
final highlight = Color(0xffFFFFFF);
final red = Color(0xffFF3B30);
final orange = Color(0xffFF9500);
final yellow = Color(0xffFFCC00);
final green = Color(0xff34C759);
final filc = Color(0xff247665);
final teal = Color(0xff5AC8FA);
final blue = Color(0xff007AFF);
final indigo = Color(0xff5856D6);
final purple = Color(0xffAF52DE);
final pink = Color(0xffFF2D55);
}
class DarkAppColors implements ThemeAppColors {
final shadow = Color(0);
final text = Colors.white;
final background = Color(0xff000000);
final highlight = Color(0xff141516);
final red = Color(0xffFF453A);
final orange = Color(0xffFF9F0A);
final yellow = Color(0xffFFD60A);
final green = Color(0xff32D74B);
final filc = Color(0xff29826F);
final teal = Color(0xff64D2FF);
final blue = Color(0xff0A84FF);
final indigo = Color(0xff5E5CE6);
final purple = Color(0xffBF5AF2);
final pink = Color(0xffFF375F);
}
class ThemeModeObserver extends ChangeNotifier {
ThemeMode _themeMode;
ThemeMode get themeMode => _themeMode;
ThemeModeObserver({ThemeMode initialTheme = ThemeMode.system}) : _themeMode = initialTheme;
void changeTheme(ThemeMode mode) {
_themeMode = mode;
notifyListeners();
}
}

View File

@@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
enum AccentColor { filc, blue, green, lime, yellow, orange, red, pink, purple, adaptive, custom }
Map<AccentColor, Color> accentColorMap = {
AccentColor.filc: const Color(0xff20AC9B),
AccentColor.blue: Colors.blue.shade300,
AccentColor.green: Colors.green.shade400,
AccentColor.lime: Colors.lightGreen.shade400,
AccentColor.yellow: Colors.orange.shade300,
AccentColor.orange: Colors.deepOrange.shade300,
AccentColor.red: Colors.red.shade300,
AccentColor.pink: Colors.pink.shade300,
AccentColor.purple: Colors.purple.shade300,
AccentColor.adaptive: const Color(0xff20AC9B),
AccentColor.custom: const Color(0xff20AC9B),
};

View File

@@ -0,0 +1,46 @@
import 'dart:io';
import 'package:filcnaplo/theme/colors/dark_desktop.dart';
import 'package:filcnaplo/theme/colors/dark_mobile.dart';
import 'package:filcnaplo/theme/colors/light_desktop.dart';
import 'package:filcnaplo/theme/colors/light_mobile.dart';
import 'package:flutter/material.dart';
class AppColors {
static ThemeAppColors of(BuildContext context) => fromBrightness(Theme.of(context).brightness);
static fromBrightness(Brightness brightness) {
if (Platform.isAndroid || Platform.isIOS) {
switch (brightness) {
case Brightness.light:
return LightMobileAppColors();
case Brightness.dark:
return DarkMobileAppColors();
}
} else {
switch (brightness) {
case Brightness.light:
return LightDesktopAppColors();
case Brightness.dark:
return DarkDesktopAppColors();
}
}
}
}
abstract class ThemeAppColors {
final Color shadow = const Color(0x00000000);
final Color text = const Color(0x00000000);
final Color background = const Color(0x00000000);
final Color highlight = const Color(0x00000000);
final Color red = const Color(0x00000000);
final Color orange = const Color(0x00000000);
final Color yellow = const Color(0x00000000);
final Color green = const Color(0x00000000);
final Color filc = const Color(0x00000000);
final Color teal = const Color(0x00000000);
final Color blue = const Color(0x00000000);
final Color indigo = const Color(0x00000000);
final Color purple = const Color(0x00000000);
final Color pink = const Color(0x00000000);
}

View File

@@ -0,0 +1,33 @@
import 'package:filcnaplo/theme/colors/colors.dart';
import 'package:flutter/material.dart';
class DarkDesktopAppColors implements ThemeAppColors {
@override
final shadow = const Color(0x00000000);
@override
final text = Colors.white;
@override
final background = const Color.fromARGB(255, 42, 42, 42);
@override
final highlight = const Color.fromARGB(255, 46, 48, 50);
@override
final red = const Color(0xffFF453A);
@override
final orange = const Color(0xffFF9F0A);
@override
final yellow = const Color(0xffFFD60A);
@override
final green = const Color(0xff32D74B);
@override
final filc = const Color(0xff29826F);
@override
final teal = const Color(0xff64D2FF);
@override
final blue = const Color(0xff0A84FF);
@override
final indigo = const Color(0xff5E5CE6);
@override
final purple = const Color(0xffBF5AF2);
@override
final pink = const Color(0xffFF375F);
}

View File

@@ -0,0 +1,33 @@
import 'package:filcnaplo/theme/colors/colors.dart';
import 'package:flutter/material.dart';
class DarkMobileAppColors implements ThemeAppColors {
@override
final shadow = const Color(0x00000000);
@override
final text = Colors.white;
@override
final background = const Color(0xff000000);
@override
final highlight = const Color(0xff141516);
@override
final red = const Color(0xffFF453A);
@override
final orange = const Color(0xffFF9F0A);
@override
final yellow = const Color(0xffFFD60A);
@override
final green = const Color(0xff32D74B);
@override
final filc = const Color(0xff29826F);
@override
final teal = const Color(0xff64D2FF);
@override
final blue = const Color(0xff0A84FF);
@override
final indigo = const Color(0xff5E5CE6);
@override
final purple = const Color(0xffBF5AF2);
@override
final pink = const Color(0xffFF375F);
}

View File

@@ -0,0 +1,33 @@
import 'package:filcnaplo/theme/colors/colors.dart';
import 'package:flutter/material.dart';
class LightDesktopAppColors implements ThemeAppColors {
@override
final shadow = const Color(0xffE8E8E8);
@override
final text = Colors.black;
@override
final background = const Color(0xffF4F9FF);
@override
final highlight = const Color(0xffFFFFFF);
@override
final red = const Color(0xffFF3B30);
@override
final orange = const Color(0xffFF9500);
@override
final yellow = const Color(0xffFFCC00);
@override
final green = const Color(0xff34C759);
@override
final filc = const Color(0xff247665);
@override
final teal = const Color(0xff5AC8FA);
@override
final blue = const Color(0xff007AFF);
@override
final indigo = const Color(0xff5856D6);
@override
final purple = const Color(0xffAF52DE);
@override
final pink = const Color(0xffFF2D55);
}

View File

@@ -0,0 +1,33 @@
import 'package:filcnaplo/theme/colors/colors.dart';
import 'package:flutter/material.dart';
class LightMobileAppColors implements ThemeAppColors {
@override
final shadow = const Color(0xffE8E8E8);
@override
final text = Colors.black;
@override
final background = const Color(0xffF4F9FF);
@override
final highlight = const Color(0xffFFFFFF);
@override
final red = const Color(0xffFF3B30);
@override
final orange = const Color(0xffFF9500);
@override
final yellow = const Color(0xffFFCC00);
@override
final green = const Color(0xff34C759);
@override
final filc = const Color(0xff247665);
@override
final teal = const Color(0xff5AC8FA);
@override
final blue = const Color(0xff007AFF);
@override
final indigo = const Color(0xff5856D6);
@override
final purple = const Color(0xffAF52DE);
@override
final pink = const Color(0xffFF2D55);
}

View File

@@ -0,0 +1,13 @@
import 'package:flutter/material.dart';
class ThemeModeObserver extends ChangeNotifier {
ThemeMode _themeMode;
ThemeMode get themeMode => _themeMode;
ThemeModeObserver({ThemeMode initialTheme = ThemeMode.system}) : _themeMode = initialTheme;
void changeTheme(ThemeMode mode) {
_themeMode = mode;
notifyListeners();
}
}

View File

@@ -0,0 +1,138 @@
import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo/theme/colors/accent.dart';
import 'package:filcnaplo/theme/colors/colors.dart';
import 'package:flutter/material.dart';
import 'package:material_color_utilities/material_color_utilities.dart';
import 'package:provider/provider.dart';
class AppTheme {
// Dev note: All of these could be constant variables, but this is better for
// development (you don't have to hot-restart)
static const String _fontFamily = "Montserrat";
static Color? _paletteAccentLight(CorePalette? palette) => palette != null ? Color(palette.primary.get(70)) : null;
static Color? _paletteHighlightLight(CorePalette? palette) => palette != null ? Color(palette.neutral.get(100)) : null;
static Color? _paletteBackgroundLight(CorePalette? palette) => palette != null ? Color(palette.neutral.get(95)) : null;
static Color? _paletteAccentDark(CorePalette? palette) => palette != null ? Color(palette.primary.get(80)) : null;
static Color? _paletteBackgroundDark(CorePalette? palette) => palette != null ? Color(palette.neutralVariant.get(10)) : null;
static Color? _paletteHighlightDark(CorePalette? palette) => palette != null ? Color(palette.neutralVariant.get(20)) : null;
// Light Theme
static ThemeData lightTheme(BuildContext context, {CorePalette? palette}) {
var lightColors = AppColors.fromBrightness(Brightness.light);
final settings = Provider.of<SettingsProvider>(context, listen: false);
AccentColor accentColor = settings.accentColor;
final customAccentColor = accentColor == AccentColor.custom ? settings.customAccentColor : null;
Color accent = customAccentColor ?? accentColorMap[accentColor] ?? const Color(0x00000000);
if (accentColor == AccentColor.adaptive) {
if (palette != null) accent = _paletteAccentLight(palette)!;
} else {
palette = null;
}
Color backgroundColor =
accentColor == AccentColor.custom ? settings.customBackgroundColor : _paletteBackgroundLight(palette) ?? lightColors.background;
Color highlighColor =
accentColor == AccentColor.custom ? settings.customHighlightColor : _paletteHighlightLight(palette) ?? lightColors.highlight;
return ThemeData(
brightness: Brightness.light,
useMaterial3: false,
fontFamily: _fontFamily,
scaffoldBackgroundColor: backgroundColor,
backgroundColor: highlighColor,
primaryColor: lightColors.filc,
dividerColor: const Color(0x00000000),
colorScheme: ColorScheme.fromSwatch(
accentColor: accent,
backgroundColor: backgroundColor,
brightness: Brightness.light,
cardColor: highlighColor,
errorColor: lightColors.red,
primaryColorDark: lightColors.filc,
),
shadowColor: highlighColor.withOpacity(.5), //lightColors.shadow,
appBarTheme: AppBarTheme(backgroundColor: backgroundColor),
indicatorColor: accent,
iconTheme: IconThemeData(color: lightColors.text.withOpacity(.75)),
navigationBarTheme: NavigationBarThemeData(
indicatorColor: accent.withOpacity(accentColor == AccentColor.adaptive ? 0.4 : 0.8),
iconTheme: MaterialStateProperty.all(IconThemeData(color: lightColors.text)),
backgroundColor: highlighColor,
labelTextStyle: MaterialStateProperty.all(TextStyle(
fontSize: 13.0,
fontWeight: FontWeight.w500,
color: lightColors.text.withOpacity(0.8),
)),
labelBehavior: NavigationDestinationLabelBehavior.alwaysShow,
height: 76.0,
),
sliderTheme: SliderThemeData(
inactiveTrackColor: accent.withOpacity(.3),
),
progressIndicatorTheme: ProgressIndicatorThemeData(color: accent),
expansionTileTheme: ExpansionTileThemeData(iconColor: accent),
);
}
// Dark Theme
static ThemeData darkTheme(BuildContext context, {CorePalette? palette}) {
var darkColors = AppColors.fromBrightness(Brightness.dark);
final settings = Provider.of<SettingsProvider>(context, listen: false);
AccentColor accentColor = settings.accentColor;
final customAccentColor = accentColor == AccentColor.custom ? settings.customAccentColor : null;
Color accent = customAccentColor ?? accentColorMap[accentColor] ?? const Color(0x00000000);
if (accentColor == AccentColor.adaptive) {
if (palette != null) accent = _paletteAccentDark(palette)!;
} else {
palette = null;
}
Color backgroundColor =
accentColor == AccentColor.custom ? settings.customBackgroundColor : _paletteBackgroundDark(palette) ?? darkColors.background;
Color highlightColor = accentColor == AccentColor.custom ? settings.customHighlightColor : _paletteHighlightDark(palette) ?? darkColors.highlight;
return ThemeData(
brightness: Brightness.dark,
useMaterial3: false,
fontFamily: _fontFamily,
scaffoldBackgroundColor: backgroundColor,
backgroundColor: highlightColor,
primaryColor: darkColors.filc,
dividerColor: const Color(0x00000000),
colorScheme: ColorScheme.fromSwatch(
accentColor: accent,
backgroundColor: backgroundColor,
brightness: Brightness.dark,
cardColor: highlightColor,
errorColor: darkColors.red,
primaryColorDark: darkColors.filc,
),
shadowColor: highlightColor.withOpacity(.5), //darkColors.shadow,
appBarTheme: AppBarTheme(backgroundColor: backgroundColor),
indicatorColor: accent,
iconTheme: IconThemeData(color: darkColors.text.withOpacity(.75)),
navigationBarTheme: NavigationBarThemeData(
indicatorColor: accent.withOpacity(accentColor == AccentColor.adaptive ? 0.4 : 0.8),
iconTheme: MaterialStateProperty.all(IconThemeData(color: darkColors.text)),
backgroundColor: highlightColor,
labelTextStyle: MaterialStateProperty.all(TextStyle(
fontSize: 13.0,
fontWeight: FontWeight.w500,
color: darkColors.text.withOpacity(0.8),
)),
labelBehavior: NavigationDestinationLabelBehavior.alwaysShow,
height: 76.0,
),
sliderTheme: SliderThemeData(
inactiveTrackColor: accent.withOpacity(.3),
),
progressIndicatorTheme: ProgressIndicatorThemeData(color: accent),
expansionTileTheme: ExpansionTileThemeData(iconColor: accent),
);
}
}

Some files were not shown because too many files have changed in this diff Show More