Compare commits

...

308 Commits

Author SHA1 Message Date
c05771607c switch to seepine/hash-files for faster hashing
Some checks failed
Generate APK / build (pull_request) Has been cancelled
2024-05-13 10:39:52 +02:00
7b728d910e fix go-hashfiles action URL
All checks were successful
Generate APK / build (pull_request) Successful in 55m42s
2024-05-13 10:28:36 +02:00
5886ed2bce update Go
Some checks failed
Generate APK / build (pull_request) Failing after 36s
2024-05-13 10:24:11 +02:00
3fdd92ea35 workaround for unsupported hashFiles function 2024-05-13 10:22:26 +02:00
708e25c622 improvement: trigger workflow only when closing pull request 2024-05-13 10:13:53 +02:00
36d9e02de7 adapt README
Some checks failed
Generate APK / build (push) Has been cancelled
2024-05-13 10:06:04 +02:00
ebe6bf1cc0 remove release due to signing issues 2024-05-13 10:04:52 +02:00
4bd95237c6 final fix for workflows
Some checks failed
Generate debug APK / build (push) Has been cancelled
2024-05-13 09:50:08 +02:00
cb553b1a33 merge: v2.6.5
All checks were successful
Generate debug APK / build (push) Successful in 47m47s
2024-05-12 20:59:38 +02:00
f5a95421a1 refresh & normalize READMEs 2024-05-12 19:47:36 +02:00
f2857bdece add Gradle caching + rename artifacts
Some checks failed
Generate debug APK / build (push) Has been cancelled
2024-05-12 19:19:14 +02:00
3c1497394c fix: artifacts (second try) 2024-05-12 19:08:38 +02:00
26596e8254 Merge branch 'release/2.6.5' into develop 2024-05-12 17:51:07 +02:00
6e472d6c5c Version 2.6.5 2024-05-12 17:50:56 +02:00
9f6ae0e6b5 switch to unofficial upload-artifact due to GHES compatibility issue
Some checks failed
Generate debug APK / build (push) Has been cancelled
2024-05-12 16:38:42 +02:00
6f4826249c Add remote mapping for Wulkanowy SDK (#2550)
Co-authored-by: Mikołaj Pich <m.pich@outlook.com>
2024-05-12 16:21:28 +02:00
01fa007c89 fix indentation
Some checks failed
Generate debug APK / build (push) Failing after 32m0s
2024-05-12 13:41:36 +02:00
3f7db44ea7 fix: release workflow & update README
Some checks are pending
Generate debug APK / build (push) Waiting to run
2024-05-12 13:39:55 +02:00
c426701b27 add: missing .sk translation
Some checks failed
Generate debug APK / build (push) Has been cancelled
2024-05-12 13:08:43 +02:00
2d8db2b071 fix: normalize case in README 2024-05-12 13:07:13 +02:00
01f6f402b4 update: README 2024-05-12 13:06:06 +02:00
e058134740 fix: release workflow 2024-05-12 13:03:43 +02:00
0aefab9463 upload debug apks only to artifacts 2024-05-12 13:01:11 +02:00
748e9f62f3 rename: build_android -> build_android_debug 2024-05-12 12:50:21 +02:00
c40e371843 patch: disable release signing 2024-05-12 12:49:57 +02:00
484996120e add cache support to build workflow
Some checks failed
Generate APK / build (push) Has been cancelled
2024-05-11 15:44:25 +02:00
2de3362e0b fix: add Android SDK build step
All checks were successful
Generate APK / build (push) Successful in 37m45s
2024-05-11 15:11:22 +02:00
470827fc42 fix: workflow syntax
Some checks failed
Generate APK / build (push) Failing after 14m13s
2024-05-11 14:48:14 +02:00
6a524deca0 add: build workflow
Some checks failed
Generate APK / build (push) Failing after 0s
2024-05-11 14:45:15 +02:00
f9fcfe6d1a sync with origin 2024-05-11 08:43:29 +02:00
02f8b45ad5 remove workflows 2024-05-11 08:42:45 +02:00
6ccb2e9b5c merge: v2.6.4
Some checks failed
Generate APK / build (push) Failing after 1h51m59s
2024-05-10 20:23:11 +02:00
cbf405fb16 apply mod changes to original repository 2024-05-10 20:22:58 +02:00
2f3e1b6aae Merge branch 'release/2.6.4' into develop 2024-05-10 12:35:39 +02:00
567d868f76 Version 2.6.4 2024-05-10 12:30:30 +02:00
12030efee2 Version 2.6.3 2024-05-09 22:59:20 +02:00
04c382643d Fix a typo in settings - apperance & behaviour - menu configuration (#2547) 2024-05-09 18:17:10 +02:00
58f7c84cd5 fix: invalid order of actions 2024-05-09 14:36:06 +02:00
a2e641523a fix: adapt actions to gitea 2024-05-09 14:33:52 +02:00
3b2eed487a fix: adapt actions to gitea (first try) 2024-05-09 14:30:07 +02:00
bc999f1b9c omit VULCAN block, sync with latest SDK 2024-05-09 12:46:53 +02:00
fc140ad9c1 Merge branch 'release/2.6.2' into develop 2024-05-08 01:55:42 +02:00
78a2cc89e9 Version 2.6.2 2024-05-08 01:55:09 +02:00
dbfe5c8918 Bump com.android.tools.build:gradle from 8.3.2 to 8.4.0 (#2539) 2024-05-05 08:27:41 +00:00
c697ca7ad1 Bump androidx.fragment:fragment-ktx from 1.6.2 to 1.7.0 (#2538) 2024-05-05 08:27:28 +00:00
1b00f4e518 Bump about_libraries from 11.1.3 to 11.1.4 (#2540) 2024-05-05 08:27:10 +00:00
fb77bf882f Bump androidx.viewpager2:viewpager2 from 1.1.0-beta02 to 1.1.0-rc01 (#2542) 2024-05-05 08:26:55 +00:00
466ebbef3a Bump com.google.firebase:firebase-bom from 32.8.1 to 33.0.0 (#2544) 2024-05-05 08:26:29 +00:00
458a4c8164 Bump androidx.core:core-ktx from 1.13.0 to 1.13.1 (#2545) 2024-05-05 08:26:14 +00:00
0363c0854f Merge branch 'release/2.6.1' into develop 2024-05-02 15:20:56 +02:00
233ddc955b Version 2.6.1 2024-05-02 15:20:45 +02:00
065c711f91 Add missing release client_info definition to conform gms plugin in hms release (#2537) 2024-05-02 14:10:33 +02:00
49655c11c9 Revert "Bump com.google.android.gms:play-services-ads from 22.6.0 to 23.0.0 (…" (#2536)
This reverts commit a7c2009e49.
2024-05-02 12:43:03 +02:00
38fd4eda22 Merge branch 'release/2.6.0' into develop 2024-05-01 22:30:53 +02:00
b71630246a Version 2.6.0 2024-05-01 22:30:41 +02:00
fd2eac1f08 New Crowdin updates (#2535) 2024-05-01 19:28:49 +02:00
1545ff65d3 CS and SK listing update (#2534) 2024-05-01 19:07:41 +02:00
ff32c82851 New Crowdin updates (#2532)
Co-authored-by: Mikołaj Pich <m.pich@outlook.com>
2024-05-01 18:59:57 +02:00
d531a94594 Update screenshots (#2533) 2024-05-01 18:50:57 +02:00
71ab9586ac Add admin message to LoginStudentSelect and LoginSymbol (#2531)
Co-authored-by: Mikołaj Pich <m.pich@outlook.com>
2024-05-01 16:57:31 +02:00
e1a19be06c New Crowdin updates (#2530) 2024-05-01 12:29:41 +02:00
6f2168d641 Additional lessons in timetable view (#2491)
Co-authored-by: Mikołaj Pich <m.pich@outlook.com>
2024-04-26 23:33:45 +02:00
cde2121b60 New Crowdin updates (#2527) 2024-04-26 22:05:46 +02:00
d7fec4806e update git artifacts 2024-04-26 16:00:49 +02:00
4fba76d327 merge: v2.8.5 2024-04-25 20:48:55 +02:00
a0bc37e826 Merge branch 'bugfix/2.5.8' into develop 2024-04-25 12:45:26 +02:00
f983a23b1a Version 2.5.8 2024-04-25 12:45:09 +02:00
6a1851da13 Bump sdk to 2.5.8-SNAPSHOT 2024-04-25 09:26:47 +02:00
ad5381ce34 Fix race condition of showing empty view in timetable (#2486)
Co-authored-by: Mikołaj Pich <m.pich@outlook.com>
2024-04-24 22:46:55 +02:00
dbc7587741 Add settings button to attendance calculator (#2492) 2024-04-24 22:44:59 +02:00
bc3aa7b8dc New Crowdin updates (#2526) 2024-04-24 22:28:16 +02:00
6bf6a9da11 New Crowdin updates (#2480) 2024-04-24 19:27:11 +02:00
ab175bdd9a Merge branch 'bugfix/2.5.7' into develop 2024-04-22 22:42:22 +02:00
8dbbea2138 Merge branch 'bugfix/2.5.7' 2024-04-22 22:36:13 +02:00
f6226e6b53 Version 2.5.7 2024-04-22 22:36:05 +02:00
43d13db07c Update translations 2024-04-22 22:17:03 +02:00
82210c37e3 Display separate annual average and semester average if available (#2524)
* Add displaying year average on grade details screen

* Add displaying year average on grade summary screen

* Add displaying year average on grade summary header

* Fix tests

* Hide semester average if it is not available in grade summary item

* Add full names of summary averages labels
2024-04-22 22:03:42 +02:00
2816d7217a Version 2.5.6 2024-04-22 00:39:44 +02:00
2fa868173b Show graduated students on top of student select items list 2024-04-22 00:39:44 +02:00
622c75bb42 Don't display brackets in login student select items when schoolShortName is blank 2024-04-22 00:39:44 +02:00
2121125283 Migrate away from userLoginId to studentId due to vulcan last changes 2024-04-22 00:39:44 +02:00
c72a117e34 Bump sdk to 2.5.6-SNAPSHOT 2024-04-22 00:39:44 +02:00
b5cc32d59f Merge branch 'bugfix/2.5.6' 2024-04-22 00:39:19 +02:00
d943d03266 Version 2.5.6 2024-04-22 00:39:04 +02:00
6eca8c42f5 Show graduated students on top of student select items list 2024-04-22 00:11:29 +02:00
af989ba9f6 Don't display brackets in login student select items when schoolShortName is blank 2024-04-21 23:59:29 +02:00
4a65a5b192 Migrate away from userLoginId to studentId due to vulcan last changes 2024-04-21 23:51:34 +02:00
bbbafdfe70 Bump sdk to 2.5.6-SNAPSHOT 2024-04-21 20:37:31 +02:00
860095e862 Bump androidx.activity:activity-ktx from 1.8.2 to 1.9.0 (#2522) 2024-04-21 02:31:28 +00:00
ff9be43291 Bump org.apache.commons:commons-text from 1.11.0 to 1.12.0 (#2523) 2024-04-21 02:25:16 +00:00
a487378daf Bump androidx.core:core-ktx from 1.12.0 to 1.13.0 (#2521) 2024-04-21 02:24:04 +00:00
895f5cbb76 Bump com.android.tools.build:gradle from 8.2.2 to 8.3.2 (#2517) 2024-04-14 14:24:38 +00:00
8b9b1460ab Update AuthDialog text (#2506) 2024-04-13 22:03:23 +02:00
7edd3df074 Bump about_libraries from 11.1.1 to 11.1.3 (#2518) 2024-04-13 19:56:16 +00:00
16c51f7b07 Bump com.google.firebase:firebase-bom from 32.8.0 to 32.8.1 (#2519) 2024-04-13 19:55:50 +00:00
7a3a97447f Add isAdded check in AdditionalLessonAddDialog (#2515) 2024-04-08 20:51:05 +02:00
b500d8e204 Bump org.sonarsource.scanner.gradle:sonarqube-gradle-plugin (#2511) 2024-04-08 18:09:38 +00:00
c34a369286 Bump org.robolectric:robolectric from 4.11.1 to 4.12.1 (#2514) 2024-04-08 18:08:20 +00:00
b9f3ab2e56 Bump com.squareup.retrofit2:retrofit from 2.10.0 to 2.11.0 (#2513) 2024-04-08 18:08:01 +00:00
6b59973624 Bump hilt_version from 2.51 to 2.51.1 (#2510) 2024-04-08 17:41:28 +00:00
d18485293d Merge branch 'bugfix/2.5.5' into develop 2024-03-26 20:38:07 +01:00
a82e11d694 Merge branch 'bugfix/2.5.5' 2024-03-26 20:38:01 +01:00
4dc5fc65ac Version 2.5.5 2024-03-26 20:37:51 +01:00
7463cf6253 Replace function in DAO to 'OR' in SQL query 2024-03-26 20:29:35 +01:00
d799ec7ac9 Add skipping migration when previous attempt failed (#2508) 2024-03-26 19:19:44 +01:00
254719f22f Remove classId from semester query when eduOne (#2509) 2024-03-26 19:02:35 +01:00
596e8df4fc Merge branch 'bugfix/2.5.4' into develop 2024-03-26 13:29:54 +01:00
e1e276e1ea Merge branch 'bugfix/2.5.4' 2024-03-26 13:29:26 +01:00
8cdd4311a9 Version 2.5.4 2024-03-26 13:29:22 +01:00
b7f7b16aef Add logging error from units and symbols during registration (#2507) 2024-03-26 12:54:07 +01:00
f13ce6e2b4 Bump com.squareup.retrofit2:retrofit from 2.9.0 to 2.10.0 (#2502) 2024-03-25 12:32:28 +00:00
8c10606b61 Bump about_libraries from 11.1.0 to 11.1.1 (#2503) 2024-03-25 12:32:07 +00:00
7fda4276d6 Bump com.google.firebase:firebase-bom from 32.7.4 to 32.8.0 (#2504) 2024-03-25 12:31:46 +00:00
7993366bfc Merge branch 'bugfix/2.5.3' into develop 2024-03-25 00:05:11 +01:00
2e71c50894 Merge branch 'bugfix/2.5.3' 2024-03-24 23:43:51 +01:00
b3faac01a5 Version 2.5.3 2024-03-24 23:43:45 +01:00
3881678208 Fix issues related to not authenticated eduOne students (#2501) 2024-03-24 23:22:07 +01:00
76d038eefa Hide grade statistics when is eduOne, add isEduOne flag migration (#2496)
Co-authored-by: Mikołaj Pich <m.pich@outlook.com>
2024-03-24 19:42:36 +01:00
3a55c3c760 Add ignoring FeatureUnavailableException in SyncWorker (#2500) 2024-03-24 18:55:23 +01:00
a0818de7d1 Add isAdded condition to HomeworkAddDialog (#2499) 2024-03-24 14:55:05 +01:00
ecae5d96c4 pr: merge #11
add developer mode
2024-03-22 17:07:45 +01:00
ddd49575d3 bump version 2024-03-22 17:05:06 +01:00
5b0748a254 translation updates 2024-03-22 17:04:30 +01:00
47ff549d4b impr: code improvements 2024-03-22 17:00:41 +01:00
a1203b360e add: translations (2/2) 2024-03-22 16:46:54 +01:00
c80c041dd0 add: developer mode + translations (1/2) 2024-03-22 16:45:34 +01:00
667b1b14d2 update: new release.yml file 2024-03-22 08:21:18 +01:00
3f2a02f242 add: missing play resource 2024-03-22 06:27:12 +01:00
29196c62fa fix: missing play resource 2024-03-22 06:26:16 +01:00
e5412086d5 add: new settings view
merge #9
2024-03-21 23:41:09 +01:00
ce114e1824 version bump 2024-03-21 23:39:45 +01:00
b0d221473c update: README 2024-03-21 23:38:58 +01:00
ee2803b0d3 fix: typo 2024-03-21 23:31:10 +01:00
47817c47f8 add: language translations 2024-03-21 23:27:40 +01:00
92bad9d112 add: hiding grades, notes and attendance entries 2024-03-21 23:21:06 +01:00
b280316b07 Bump sdk to 2.5.3-SNAPSHOT 2024-03-21 23:06:18 +01:00
0554aa91fd Add WulkanowySdkFactory (#2479) 2024-03-21 22:11:03 +01:00
5a77d1e940 Hide lesson number when is eduOne (#2498) 2024-03-21 21:19:49 +01:00
d17614fa64 add: new settings fragment + basic functionality 2024-03-21 20:17:05 +01:00
c9a42a6cf6 Add try catch to initialize MobileAds SDK (#2497) 2024-03-21 11:03:08 +01:00
d068371fb1 fix: invalid build directory + auto generated release notes 2024-03-20 21:55:04 +01:00
af71023bfb fix 2024-03-20 21:10:52 +01:00
b5adc27efa final fix 2024-03-20 21:08:16 +01:00
de0b9cf221 remove unneccessary if statement 2024-03-20 21:03:30 +01:00
425d28d563 switch to new release action 2024-03-20 20:53:10 +01:00
31744e7e77 remove deprecated code 2024-03-20 20:07:52 +01:00
925ad9616d fix: github actions (attempt 4) 2024-03-20 20:05:59 +01:00
878e7b4247 fix: github actions (attempt 3) 2024-03-20 20:03:00 +01:00
e347045ac8 github actions fix (second try) 2024-03-20 19:48:40 +01:00
8eb65196cb separate locales (avoid future conflicts) + rewrite github actions 2024-03-20 19:36:07 +01:00
f99df4ad46 sync fork with v2.5.2 + fix eduOne 2024-03-20 18:15:23 +01:00
27eb0588d7 Merge branch 'bugfix/2.5.2' into develop 2024-03-20 02:24:26 +01:00
e17129efea Merge branch 'bugfix/2.5.2' 2024-03-20 02:18:07 +01:00
6047af9ff0 Version 2.5.2 2024-03-20 02:18:00 +01:00
d789aa718e Change AuthDialog condition to isAuth flag (#2495)
Co-authored-by: Mikołaj Pich <m.pich@outlook.com>
2024-03-20 01:49:55 +01:00
8623b53357 Fix lateness color in attendance (#2481) 2024-03-19 22:13:32 +01:00
78e28ad791 Remove savedInstance in MessagePreviewFragment (#2477) 2024-03-19 22:13:09 +01:00
377c288e9e Add missing onDetachView in AutDialog (#2476) 2024-03-19 22:12:54 +01:00
b31c7e1720 Fix task description color crash (#2475) 2024-03-19 22:12:39 +01:00
d01fe9c370 Bump sdk to 2.5.2-SNAPSHOT 2024-03-19 22:11:02 +01:00
34d34a050a Add widget updating on data sync (#2487)
---------

Co-authored-by: Faierbel <RafalBO99@outlook.com>
2024-03-17 21:06:40 +01:00
961bc24f27 Add docs to Resource, changing networkBoundResource generics naming (#2483) 2024-03-13 19:13:56 +01:00
8a90b61b97 Refactor networkBoundResource (#2482)
---------

Co-authored-by: Faierbel <RafalBO99@outlook.com>
2024-03-13 13:01:00 +01:00
6a8f6f9496 Add WulkanowySdkFactory (#2479) 2024-03-11 23:38:39 +01:00
afb5ae741c Fix lateness color in attendance (#2481) 2024-03-11 23:38:17 +01:00
95e41b5570 Handle subjects with no attendances in attendance calculator better (#2478)
---------

Co-authored-by: Faierbel <RafalBO99@outlook.com>
2024-03-11 19:19:24 +00:00
eb6fdd900e New Crowdin updates (#2470) 2024-03-11 11:47:13 +01:00
88def5eff8 Remove savedInstance in MessagePreviewFragment (#2477) 2024-03-11 11:45:28 +01:00
0e99c81eb8 Add missing onDetachView in AutDialog (#2476) 2024-03-11 11:45:15 +01:00
38c00ddab5 Fix task description color crash (#2475) 2024-03-11 11:44:59 +01:00
c72cc39920 Separate strings from array to avoid duplications (#2473) 2024-03-09 21:01:58 +01:00
4ef9fb1f28 Update preferences strings (#2472) 2024-03-09 10:05:12 +01:00
5dd5697f65 Remove firebase disable flag (#2471) 2024-03-09 10:03:36 +01:00
a7c2009e49 Bump com.google.android.gms:play-services-ads from 22.6.0 to 23.0.0 (#2469) 2024-03-08 20:16:03 +00:00
a71ef4a4b2 Bump com.google.firebase:firebase-bom from 32.7.3 to 32.7.4 (#2467) 2024-03-08 20:14:33 +00:00
30413086fc Bump kotlin_version from 1.9.22 to 1.9.23 (#2466) 2024-03-08 20:13:58 +00:00
98ddf97855 Update gradle wrapper to 8.6 (#2468) 2024-03-08 20:57:26 +01:00
8f5a210ec7 Add attendance calculator (#1597)
---------

Co-authored-by: Mikołaj Pich <m.pich@outlook.com>
Co-authored-by: Faierbel <RafalBO99@outlook.com>
2024-03-08 20:36:43 +01:00
ce09b07cfd Merge branch 'release/2.5.1' into develop 2024-03-03 11:19:48 +01:00
5ed19cb21a Merge branch 'release/2.5.1' 2024-03-03 11:19:42 +01:00
0a1f7270b4 Version 2.5.1 2024-03-03 11:15:11 +01:00
47d8513a77 New Crowdin updates (#2464) 2024-03-03 10:35:17 +01:00
00432ab911 Merge branch 'release/2.5.0' into develop 2024-03-02 21:18:14 +01:00
7b2c839775 Merge branch 'release/2.5.0' 2024-03-02 21:18:10 +01:00
f455064b9d Version 2.5.0 2024-03-02 21:18:02 +01:00
2bbc157d03 Add some new symbols to symbol autocomplete field (#2461) 2024-03-02 20:45:23 +01:00
a0a0b8dea6 New Crowdin updates (#2460) 2024-03-02 20:37:36 +01:00
3bab883a56 Add a message explaining the reason for the captcha to the captcha dialog (#2459) 2024-03-02 19:49:08 +01:00
b319bb03cd New Crowdin updates (#2458) 2024-03-02 17:31:44 +01:00
333306e7ba Wrap delete and save operations in database transactions (#2450) 2024-03-02 16:25:27 +00:00
fb240938ed Bump about_libraries from 10.10.0 to 11.1.0 (#2454) 2024-03-02 16:21:55 +00:00
dc9af29a44 Bump hilt_version from 2.50 to 2.51 (#2456) 2024-03-02 16:11:13 +00:00
e9d64de0cb Improve invalid password message (#2451) 2024-03-02 17:10:38 +01:00
05bda598fc Bump mockk from 1.13.9 to 1.13.10 (#2455) 2024-03-02 16:10:19 +00:00
3564366a8f Bump com.google.firebase:firebase-bom from 32.7.2 to 32.7.3 (#2453) 2024-03-02 16:08:32 +00:00
f2d26453ed Fix calculating average with optional arithmetic average on and no grade with average in second semester (#2448) 2024-03-02 17:01:12 +01:00
ccba31f2e8 Add last announcements to school announcements (#2452) 2024-03-02 16:55:54 +01:00
a7238e3f23 New Crowdin updates (#2447) 2024-03-01 22:16:56 +01:00
ea28fc783c Add message from trash restoring (#2438) 2024-03-01 21:14:43 +01:00
c04752ed39 Fix timetable items layout (#2446) 2024-03-01 10:32:55 +01:00
0264682d0d setup APK build action 2024-03-01 09:18:08 +01:00
c395abb7e4 sync with original repository 2024-03-01 09:13:18 +01:00
dfd5e10fbf Delete Icon 2024-03-01 09:08:57 +01:00
c198e6a2f7 New Crowdin updates (#2445) 2024-03-01 00:06:54 +01:00
2c1337bb51 New Crowdin updates (#2439) 2024-02-29 21:36:51 +01:00
7a4032dda4 Add mute message senders (#2415)
Co-authored-by: Mikołaj Pich <m.pich@outlook.com>
2024-02-29 21:30:02 +01:00
130f6164c4 add: gradle auto-build 2024-02-29 15:57:28 +01:00
b2191ab21b update: README 2024-02-29 10:41:24 +01:00
eb08a2cddb add: allow selecting grades to hide 2024-02-29 10:40:33 +01:00
eb63e5eb04 remove: attendance always 100% [SK] 2024-02-29 09:46:04 +01:00
7a8ab6fb52 remove: attendance always 100% [CZ] 2024-02-29 09:45:51 +01:00
0eadf36222 remove: attendance always 100% [DE] 2024-02-29 09:45:40 +01:00
85e3bb4d17 remove: attendance always 100% 2024-02-29 09:45:24 +01:00
84969e6f9b Merge branch 'develop' of https://github.com/sadorowo/wulkanowy-mod into develop 2024-02-29 09:42:29 +01:00
d39998000b fix: remove WIP code 2024-02-29 09:42:24 +01:00
b6cbd0523b Merge branch 'wulkanowy:develop' into develop 2024-02-29 09:40:58 +01:00
4d74c252c8 add: custom attendance percentage 2024-02-29 09:40:29 +01:00
1ab300d74f Bump android_hilt from 1.1.0 to 1.2.0 (#2443) 2024-02-27 08:53:00 +00:00
1b8c389984 Bump io.coil-kt:coil from 2.5.0 to 2.6.0 (#2441) 2024-02-27 08:52:47 +00:00
74a20b2f65 Add Github Sponsor (#2444) 2024-02-27 09:42:44 +01:00
c1687f5856 sync fork with v2.4.2 2024-02-25 20:14:04 +01:00
d5c17285c1 Fix error handling in login (#2437) 2024-02-25 16:37:28 +01:00
e378b4c70a Fix loading timetable and attendance when should be refreshed returns true (#2436) 2024-02-25 16:36:50 +01:00
31854fc4b8 Fix text cut off across the app when text size is set to 200% (#2435) 2024-02-25 16:35:56 +01:00
f52fe8306f Merge branch 'release/2.4.2' into develop 2024-02-22 16:15:36 +01:00
3eae3a7667 Merge branch 'release/2.4.2' 2024-02-22 16:15:30 +01:00
b613b84469 Version 2.4.2 2024-02-22 16:15:24 +01:00
2776d019b9 Revert "Bump com.google.android.ump:user-messaging-platform from 2.1.0 to 2.2…" (#2434)
This reverts commit fc91936884.
2024-02-22 15:52:40 +01:00
729e72cddb Fix text pasting into date field in additional lesson add dialog (#2433) 2024-02-21 21:36:20 +01:00
ec101c1f52 Fix error handling in widgets (#2430) 2024-02-19 11:30:05 +01:00
cfec79405f Bump org.jetbrains.kotlinx:kotlinx-serialization-json (#2429) 2024-02-18 14:21:59 +00:00
7d8be1b9fc Bump coroutines from 1.7.3 to 1.8.0 (#2428) 2024-02-18 14:21:38 +00:00
c781159e75 Merge branch 'release/2.4.1' into develop 2024-02-17 13:10:39 +01:00
90a5b9e20f Merge branch 'release/2.4.1' 2024-02-17 13:10:34 +01:00
3cf6c295b0 Version 2.4.1 2024-02-17 13:07:45 +01:00
e757585bd3 New Crowdin updates (#2426) 2024-02-17 12:43:52 +01:00
736d16a7ab Make WebkitCookieManagerProxy no-op if webview is not available on the device (#2427) 2024-02-17 12:31:14 +01:00
6f4a8d5534 Add missing symbol and custom domain suffix validation (#2425) 2024-02-17 11:41:32 +01:00
b5e17c4ff7 Bump com.google.firebase:firebase-bom from 32.7.1 to 32.7.2 (#2424) 2024-02-16 18:50:07 +00:00
cc01525f16 Bump com.google.gms:google-services from 4.4.0 to 4.4.1 (#2423) 2024-02-16 18:49:46 +00:00
c2ec05662b Merge branch 'release/2.4.0' into develop 2024-02-09 19:45:05 +01:00
b99ba48d2c Merge branch 'release/2.4.0' 2024-02-09 19:45:01 +01:00
2d4a1bff83 Version 2.4.0 2024-02-09 19:44:55 +01:00
cd853e4d57 New Crowdin updates (#2417) 2024-02-09 18:34:04 +00:00
8183d7d5a0 Change default symbol for standard register variant (#2421) 2024-02-09 16:35:18 +01:00
3f199cb610 Replace fakelog.cf to wulkanowy.net.pl (#2419) 2024-02-09 12:40:01 +00:00
22f72981cb Add descriptive grades (#2411) 2024-02-08 09:16:09 +01:00
bce92b7347 Fix displaying lessons for tomorrow if there is no more lessons for today (#2416) 2024-02-08 07:49:17 +01:00
ed5166333a Add the ability to dynamically expand the application window (#2413) 2024-02-06 19:48:31 +01:00
a05f1f70f7 Add colors to attendance and ! to unexcused lateness (#2412) 2024-02-06 18:21:56 +01:00
e58a60410c Fix android tests (#2410) 2024-02-04 10:48:59 +01:00
fc91936884 Bump com.google.android.ump:user-messaging-platform from 2.1.0 to 2.2.0 (#2408) 2024-01-26 10:30:36 +00:00
88043569ac Bump com.huawei.hms:hianalytics from 6.12.0.300 to 6.12.0.301 (#2407) 2024-01-26 10:23:01 +00:00
10add8a70e Bump com.android.tools.build:gradle from 8.2.1 to 8.2.2 (#2406) 2024-01-26 10:21:30 +00:00
098af9884a Bump com.google.firebase:firebase-bom from 32.7.0 to 32.7.1 (#2404) 2024-01-23 19:17:29 +00:00
554c1b1261 Merge branch 'release/2.3.5' into develop 2024-01-21 21:04:35 +01:00
496695162d Merge branch 'release/2.3.5' 2024-01-21 21:04:29 +01:00
dc59f4ffa3 Version 2.3.5 2024-01-21 21:04:23 +01:00
e0f4cad7fb Add missing unitId to sdk switchSemester call (#2402) 2024-01-21 20:01:00 +01:00
a51a54dc7a Normalize synchronization date ranges to fix weird notification issues (#2403) 2024-01-21 18:59:54 +01:00
7cdac6ede1 Add admin message to error view in dashboard (#2400) 2024-01-21 12:39:23 +01:00
9dfb282e88 Add X to close admin message (#2401) 2024-01-21 11:39:55 +01:00
725668f855 Bump androidx.lifecycle:lifecycle-livedata-ktx from 2.6.2 to 2.7.0 (#2398) 2024-01-19 09:22:34 +00:00
e58c155961 New Crowdin updates (#2396) 2024-01-14 18:02:15 +01:00
05a5047a70 Merge branch 'release/2.3.4' into develop 2024-01-14 17:33:30 +01:00
1fe464a289 Merge branch 'release/2.3.4' 2024-01-14 17:33:14 +01:00
497acf9d68 Version 2.3.4 2024-01-14 17:32:41 +01:00
976eb5a772 Fix cancelling dashboard jobs (#2395) 2024-01-14 16:45:30 +01:00
9ececeb4e9 New Crowdin updates (#2394) 2024-01-14 16:41:57 +01:00
096fe359e7 Make some improvements in captcha dialog (#2393)
* Add improvements retrying after captcha solved

* Add showAuthDialog from BaseActivity instead of displaying this dialog manually

* Add getCookieStore() with removeAll impl in WebkitCookieManagerProxy

* Add debounce to captcha dialog showing logic

* Add refresh button to captcha dialog

* Destroy webview along with captcha dialog

* Add clear webkit cookies button to debug menu

* Add captcha error message

* Update captcha verified message
2024-01-14 13:09:04 +00:00
a98e8398fd Add webview to obtain cloudflare captcha cookies for okhttp (#2392) 2024-01-12 18:34:43 +01:00
d8c4926a97 Merge branch 'release/2.3.3' into develop 2024-01-09 21:46:10 +01:00
17c139b559 Merge branch 'release/2.3.3' 2024-01-09 21:46:04 +01:00
ddbcc7a04c Version 2.3.3 2024-01-09 21:45:59 +01:00
9f9eb60280 Merge branch 'release/2.3.2' into develop 2024-01-09 19:31:29 +01:00
f893170dec Merge branch 'release/2.3.2' 2024-01-09 19:31:22 +01:00
cff08d6322 Version 2.3.2 2024-01-09 19:27:03 +01:00
9dee7f01f6 Avoid deleting luckynumber when SDK returns null (#2391) 2024-01-09 19:07:46 +01:00
8324a9cac3 Use emptyCookieJarInterceptor in SDK configuration (#2390) 2024-01-09 19:00:37 +01:00
5316e3e1bf Bump mockk from 1.13.8 to 1.13.9 (#2389) 2024-01-08 15:32:30 +00:00
81e80181f2 New Crowdin updates (#2388) 2024-01-08 16:32:06 +01:00
6ee38e9259 Add clearing all data and key entry when decryption failed (#2386) 2024-01-06 00:01:33 +01:00
40df80371c Use forked slf4j-timber to fix logging problems with slf4j v2 (#2387) 2024-01-05 16:03:50 +01:00
f7b25139c0 update: README 2024-01-04 09:41:22 +01:00
a3596c35b8 Update AGP and Gradle (#2385) 2024-01-04 09:33:51 +01:00
6e751c83f6 fix: bad attendance always visible 2024-01-03 20:23:12 +01:00
43a01e4e04 fix: always empty notes 2024-01-03 19:41:05 +01:00
ee906d02ae Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	app/src/main/java/io/github/wulkanowy/ui/modules/dashboard/DashboardPresenter.kt
2024-01-03 19:25:38 +01:00
83cf39a28c fix: double attendance + add: fake 100% attendance 2024-01-03 19:24:53 +01:00
66b7ea4cb4 Merge branch 'release/2.3.1' into develop 2024-01-03 16:02:39 +01:00
770749e158 Merge branch 'release/2.3.1' 2024-01-03 16:02:32 +01:00
0aa83b020e Bump sdk to 2.3.3 2024-01-03 16:01:30 +01:00
4d1218d1d3 Version 2.3.1 2024-01-03 14:53:16 +01:00
3c5c50f84d Merge branch 'develop' into develop 2024-01-02 22:01:41 +01:00
d0d3b11662 add: hidden settings. almost full customization. 2024-01-02 20:31:36 +01:00
0ea6cbc8ed Merge branch 'release/2.3.0' into develop 2024-01-02 01:51:58 +01:00
eb31f9578f Merge branch 'release/2.3.0' 2024-01-02 01:51:52 +01:00
f69d50d2c1 Version 2.3.0 2024-01-02 01:51:09 +01:00
8a424ee6a4 New Crowdin updates (#2381) 2024-01-02 01:30:31 +01:00
7dfa48bbe3 Add User Messaging Platform SDK for ads agreements (#2375) 2024-01-01 21:19:00 +01:00
d811cdb919 Bump com.huawei.agconnect:agcp from 1.9.1.302 to 1.9.1.303 (#2377) 2024-01-01 17:39:08 +00:00
e2f2e21081 Bump com.huawei.agconnect:agconnect-crash from 1.9.1.302 to 1.9.1.303 (#2379) 2024-01-01 17:38:29 +00:00
c812310497 Bump about_libraries from 10.9.2 to 10.10.0 (#2380) 2024-01-01 18:36:02 +01:00
d3ff6682ca update build tools 2023-12-30 20:27:38 +01:00
7f6475cf35 Merge branch 'release/2.2.7' into develop 2023-12-27 22:21:18 +01:00
a571dabb71 Merge branch 'wulkanowy:develop' into develop 2023-12-14 18:17:32 +01:00
5c27edbe3c Merge branch 'wulkanowy:develop' into develop 2023-12-01 13:38:57 +01:00
598edaadb7 completely hide notes & exclude META-INF/* 2023-11-06 21:05:31 +01:00
374 changed files with 26211 additions and 3332 deletions

View File

@ -162,7 +162,7 @@ jobs:
openssl aes-256-cbc -d -in ./app/upload-key-encrypted.jks -k $ENCRYPT_KEY >> ./app/upload-key.jks openssl aes-256-cbc -d -in ./app/upload-key-encrypted.jks -k $ENCRYPT_KEY >> ./app/upload-key.jks
- run: - run:
name: Publish release name: Publish release
command: ./gradlew publishPlayRelease --no-daemon --stacktrace --console=plain -PenableCrashlytics -PdisablePreDex command: ./gradlew publishPlayRelease --no-daemon --stacktrace --console=plain -PdisablePreDex
workflows: workflows:
version: 2 version: 2

View File

@ -1,7 +1,7 @@
[*] [*]
charset=utf-8 charset=utf-8
end_of_line=lf end_of_line=lf
insert_final_newline=true insert_final_newline=Advanced
indent_style=space indent_style=space
indent_size=4 indent_size=4

4
.gitea/FUNDING.yml Normal file
View File

@ -0,0 +1,4 @@
# These are supported funding model platforms
github: wulkanowy
custom: https://www.paypal.com/paypalme/wulkanowy

18
.gitea/release.yml Normal file
View File

@ -0,0 +1,18 @@
changelog:
exclude:
labels:
- "release ignore"
categories:
- title: breaking changes
labels:
- major
- title: new features
labels:
- minor
- fr:approved
- title: translation updates
labels:
- translation
- title: features
labels:
- "*"

View File

@ -0,0 +1,80 @@
name: Generate APK
env:
main_project_module: app
on:
pull_request:
types:
- closed
jobs:
build:
runs-on: ubuntu-latest
env:
RUNNER_TOOL_CACHE: /toolcache
steps:
- name: Checkout the repository
uses: actions/checkout@v3
- name: Set current date as env variable
run: echo "date_today=$(date +'%Y-%m-%d')" >> $GITHUB_ENV
- name: Set repository name as env variable
run: echo "repository_name=$(echo '${{ gitea.repository }}' | awk -F '/' '{print $2}')" >> $GITHUB_ENV
- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: 'zulu' # See 'Supported distributions' for available options
java-version: '17'
cache: 'gradle'
- name: Get hash of Gradle files
uses: seepine/hash-files@v1
id: get-hash
with:
patterns: |-
**/*.gradle*
- name: Cache Gradle
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ steps.get-hash.outputs.hash }}
- name: Get app version
id: get_version
run: echo "VERSION_NAME=$(grep -m1 "versionName" app/build.gradle | awk '{print $2}' | tr -d \''"\')" >> $GITHUB_ENV
- name: Change wrapper permissions
run: chmod +x ./gradlew
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Build debug APK
run: ./gradlew assembleDebug
- name: Upload artifacts
uses: actions/upload-artifact@v3 # not v4 because of GHES
with:
name: wulkanowy_mod_debug_builds
path: |
app/build/outputs/**/*-debug.apk
- name: Create release
uses: akkuman/gitea-release-action@v1
env:
NODE_OPTIONS: '--experimental-fetch'
with:
files: |
app/build/outputs/**/*-debug.apk
name: Release ${{ env.VERSION_NAME }} (${{ env.date_today }})
tag_name: v${{ env.VERSION_NAME }}

View File

@ -1,79 +0,0 @@
name: Deploy release
on:
release:
types: [ created ]
jobs:
deploy-google-play:
name: Google Play
runs-on: ubuntu-latest
timeout-minutes: 10
environment: google-play
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: 17
- uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}
- name: Decrypt keys
env:
ENCRYPT_KEY: ${{ secrets.ENCRYPT_KEY }}
SERVICES_ENCRYPT_KEY: ${{ secrets.SERVICES_ENCRYPT_KEY }}
run: |
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/google-services.json.gpg
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg
- name: Upload apk to google play
env:
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
ANDROID_PUBLISHER_CREDENTIALS: ${{ secrets.ANDROID_PUBLISHER_CREDENTIALS }}
ADMOB_PROJECT_ID: ${{ secrets.ADMOB_PROJECT_ID }}
SINGLE_SUPPORT_AD_ID: ${{ secrets.SINGLE_SUPPORT_AD_ID }}
DASHBOARD_TILE_AD_ID: ${{ secrets.DASHBOARD_TILE_AD_ID }}
SET_BUILD_TIMESTAMP: ${{ secrets.SET_BUILD_TIMESTAMP }}
run: ./gradlew publishPlayReleaseApps -PenableFirebase --stacktrace;
deploy-app-gallery:
name: AppGallery
runs-on: ubuntu-latest
timeout-minutes: 10
environment: app-gallery
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: 17
- uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}
- name: Decrypt keys
env:
ENCRYPT_KEY: ${{ secrets.ENCRYPT_KEY }}
SERVICES_ENCRYPT_KEY: ${{ secrets.SERVICES_ENCRYPT_KEY }}
run: |
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/agconnect-services.json.gpg
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg
- name: Prepare credentials
env:
AGC_CREDENTIALS: ${{ secrets.AGC_CREDENTIALS }}
run: echo $AGC_CREDENTIALS > ./app/src/release/agconnect-credentials.json
- name: Build and publish HMS version
env:
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
SET_BUILD_TIMESTAMP: ${{ secrets.SET_BUILD_TIMESTAMP }}
run: ./gradlew bundleHmsRelease --stacktrace && ./gradlew publishHuaweiAppGalleryHmsRelease --stacktrace

View File

@ -1,146 +0,0 @@
name: Deploy DEV
on:
push:
# branches: [ develop ]
branches: [ '!*' ]
pull_request_target:
# branches: [ develop ]
branches: [ '!*' ]
workflow_dispatch:
jobs:
deploy-appcenter:
name: App Center
runs-on: ubuntu-latest
timeout-minutes: 10
environment: app-center
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: 17
- uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}
- name: Set run number with offset
env:
BUILD_NUMBER_OFFSET: ${{ secrets.BUILD_NUMBER_OFFSET }}
run: echo "RUN_NUMBER=$((GITHUB_RUN_NUMBER+BUILD_NUMBER_OFFSET))" >> $GITHUB_ENV
- name: Prepare build configuration
run: |
sed -i -e "s#applicationIdSuffix \".dev\"#applicationIdSuffix \".${GITHUB_HEAD_REF//[-.\/]/_}\"#" app/build.gradle
sed -i -e "s#.dev\"#.${GITHUB_HEAD_REF//[-.\/]/_}\"#" app/src/debug/google-services.json
sed -i -e "s#.dev\"#.${GITHUB_HEAD_REF//[-.\/]/_}\"#" app/src/debug/agconnect-services.json
sed -i -e '/versionNameSuffix/d' app/build.gradle
- name: Add signing config
run: |
cat >> app/build.gradle <<EOF
android.signingConfigs.debug {
storeFile file("bitrise.jks")
storePassword System.getenv("BITRISE_KEYSTORE_PASSWORD")
keyAlias System.getenv("BITRISE_KEY_ALIAS")
keyPassword System.getenv("BITRISE_KEY_PASSWORD")
}
EOF
- name: Decrypt keys
env:
BITRISE_ENCRYPT_KEY: ${{ secrets.BITRISE_ENCRYPT_KEY }}
run: |
gpg --yes --batch --passphrase=$BITRISE_ENCRYPT_KEY ./app/bitrise.jks.gpg
- name: Bump version
uses: chkfung/android-version-actions@v1.1
with:
gradlePath: app/build.gradle
versionCode: ${{ env.RUN_NUMBER }}
versionName: ${{ env.RUN_NUMBER }}-${{ github.head_ref }}
- name: Build apk
env:
BITRISE_KEYSTORE_PASSWORD: ${{ secrets.BITRISE_KEYSTORE_PASSWORD }}
BITRISE_KEY_ALIAS: ${{ secrets.BITRISE_KEY_ALIAS }}
BITRISE_KEY_PASSWORD: ${{ secrets.BITRISE_KEY_PASSWORD }}
run: ./gradlew assembleFdroidDebug --stacktrace
- name: Upload apk to github artifacts
uses: actions/upload-artifact@v3
with:
name: wulkanowyDEV-${{ env.RUN_NUMBER }}.apk
path: app/build/outputs/apk/fdroid/debug/app-fdroid-debug.apk
- name: Deploy to app center
uses: wzieba/AppCenter-Github-Action@v1
with:
appName: wulkanowy/wulkanowy
token: ${{ secrets.APP_CENTER_TOKEN }}
group: Testers
file: app/build/outputs/apk/fdroid/debug/app-fdroid-debug.apk
notifyTesters: true
debug: true
deploy-app-distribution:
name: App Distribution
runs-on: ubuntu-latest
timeout-minutes: 10
environment: app-distribution
if: github.event_name != 'pull_request_target'
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: 17
- uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}
- name: Set run number with offset
env:
BUILD_NUMBER_OFFSET: ${{ secrets.BUILD_NUMBER_OFFSET }}
run: echo "RUN_NUMBER=$((GITHUB_RUN_NUMBER+BUILD_NUMBER_OFFSET))" >> $GITHUB_ENV
- name: Add signing config
run: |
cat >> app/build.gradle <<EOF
android.signingConfigs.debug {
storeFile file("bitrise.jks")
storePassword System.getenv("BITRISE_KEYSTORE_PASSWORD")
keyAlias System.getenv("BITRISE_KEY_ALIAS")
keyPassword System.getenv("BITRISE_KEY_PASSWORD")
}
EOF
- name: Decrypt keys
env:
BITRISE_ENCRYPT_KEY: ${{ secrets.BITRISE_ENCRYPT_KEY }}
BITRISE_SERVICES_ENCRYPT_KEY: ${{ secrets.BITRISE_SERVICES_ENCRYPT_KEY }}
run: |
gpg --yes --batch --passphrase=$BITRISE_SERVICES_ENCRYPT_KEY ./app/src/debug/google-services.json.gpg
gpg --yes --batch --passphrase=$BITRISE_ENCRYPT_KEY ./app/bitrise.jks.gpg
- name: Bump version
uses: chkfung/android-version-actions@v1.1
with:
gradlePath: app/build.gradle
versionCode: ${{ env.RUN_NUMBER }}
versionName: ${{ env.RUN_NUMBER }}
- name: Build apk
env:
BITRISE_KEYSTORE_PASSWORD: ${{ secrets.BITRISE_KEYSTORE_PASSWORD }}
BITRISE_KEY_ALIAS: ${{ secrets.BITRISE_KEY_ALIAS }}
BITRISE_KEY_PASSWORD: ${{ secrets.BITRISE_KEY_PASSWORD }}
run: ./gradlew assemblePlayDebug -PenableFirebase --stacktrace
- name: Upload apk to github artifacts
uses: actions/upload-artifact@v3
with:
name: wulkanowyDEV-${{ env.RUN_NUMBER }}-dev.apk
path: app/build/outputs/apk/play/debug/app-play-debug.apk
- name: Deploy to app distribution
uses: wzieba/Firebase-Distribution-Github-Action@v1
with:
appId: ${{ secrets.FIREBASE_APP_ID }}
token: ${{ secrets.FIREBASE_TOKEN }}
groups: discord
file: app/build/outputs/apk/play/debug/app-play-debug.apk

View File

@ -1,90 +0,0 @@
name: Tests
on:
push:
branches:
- master
- develop
- 'hotfix/**'
tags: [ '*' ]
pull_request:
jobs:
tests-fdroid:
name: F-Droid
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: fkirc/skip-duplicate-actions@master
- uses: actions/checkout@v3
- uses: gradle/wrapper-validation-action@v1
- uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: 17
- uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}
- name: Unit tests
run: |
./gradlew testFdroidDebugUnitTest --stacktrace
./gradlew jacocoTestReport --stacktrace
- uses: codecov/codecov-action@v3
with:
flags: unit
tests-play:
name: Play
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: fkirc/skip-duplicate-actions@master
- uses: actions/checkout@v3
- uses: gradle/wrapper-validation-action@v1
- uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: 17
- uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}
- name: Unit tests
run: |
./gradlew testPlayDebugUnitTest --stacktrace
./gradlew jacocoTestReport --stacktrace
- uses: codecov/codecov-action@v3
with:
flags: unit
tests-hms:
name: HMS
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: fkirc/skip-duplicate-actions@master
- uses: actions/checkout@v3
- uses: gradle/wrapper-validation-action@v1
- uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: 17
- uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}
- name: Unit tests
run: |
./gradlew testHmsDebugUnitTest --stacktrace
./gradlew jacocoTestReport --stacktrace
- uses: codecov/codecov-action@v3
with:
flags: unit

17
.gitignore vendored
View File

@ -65,6 +65,12 @@ captures/
.idea/uiDesigner.xml .idea/uiDesigner.xml
.idea/runConfigurations.xml .idea/runConfigurations.xml
.idea/discord.xml .idea/discord.xml
.idea/migrations.xml
.idea/androidTestResultsUserPreferences.xml
.idea/copilot
.idea/deploymentTargetDropDown.xml
.idea/deploymentTargetSelector.xml
.idea/kotlinc.xml
# Keystore files # Keystore files
*.jks *.jks
@ -111,12 +117,13 @@ Thumbs.db
*.ear *.ear
### AndroidStudio Patch ### ### AndroidStudio Patch ###
!/gradle/wrapper/gradle-wrapper.jar !/gradle/wrapper/gradle-wrapper.jar
.idea/jarRepositories.xml .idea/jarRepositories.xml
### Services config files
agconnect-services.json
agconnect-credentials.json
google-services.json
!app/google-services.json
app/src/release/agconnect-services.json
app/src/release/agconnect-credentials.json
.idea/deploymentTargetDropDown.xml
.idea/kotlinc.xml

10
.idea/migrations.xml generated
View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

View File

@ -61,7 +61,7 @@ script:
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/agconnect-services.json.gpg; gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/agconnect-services.json.gpg;
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg; gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg;
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg; gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg;
./gradlew publishPlayRelease -PenableFirebase --stacktrace; ./gradlew publishPlayRelease --stacktrace;
fi fi
after_success: after_success:

View File

@ -1,73 +1,33 @@
Česká verze / [Deutsche Version](README.de.md) / [English version](README.en.md) / [Polska wersja](README.md) / [Slovenská verzia](README.sk.md) Česká verze / [Deutsche Version](README.de.md) / [English version](README.en.md) / [Polska wersja](README.md) / [Slovenská verzia](README.sk.md)
# Wulkanowy # Wulkanowy MOD
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/wulkanowy/wulkanowy/test.yml?branch=develop&style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions) ## Funkce:
[![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy) * skrýt známky
[![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) * Skrýt jednotlivé záznamy o docházce.
[![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/) * Skrýt komentáře.
[![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases) * falešná docházka %
[![Crowdin](https://badges.crowdin.net/wulkanowy2/localized.svg)](https://translate.wulkanowy.net.pl)
Neoficiální klient deníku VULCAN UONET+ pro žáka a rodiče Chcete-li se dostat na skrytý panel:
1. Přejděte na kartu „Další“.
2. Přejděte na panel „Nastavení“.
3. Přejděte na panel „O aplikaci“.
4. Klikněte 5x na logo aplikace
5. Přejděte na domovskou obrazovku
6. Přejděte do nastavení
7. Zadejte „tajná nastavení“
## Funkce # Instalace
* přihlášení pomocí emailu a hesla | Název souboru | Přizpůsobeno |
* funkce z webové stránky deníku: | ---------------- | ----------------- |
* známky | `*-fdroid-*.apk` | F-Droid |
* statistiky známek | `*-hms-*.apk` | Huawei AppGallery |
* frekvence | `*-play-*.apk` | Play Store |
* procento frekvence
* zkoušky
* plán lekce
* dokončené lekce
* zprávy
* domácí úkoly
* poznámky
* šťastné číslo
* další lekce
* školní setkání
* informace o žáku a škole
* výpočet průměru nezávisle na preferencích školy
* upozornění, např. o nových známkách
* podpora více účtů s možností přejmenování žáků
* tmavý a černý (AMOLED) motiv
* offline režim
* volitelné reklamy na podporu projektu
## Stáhnout Stáhněte si vybranou verzi z [releases](https://git.sador.me/sadorowo/wulkanowy-mod/releases).
Doporučujeme stáhnout nejnovější dostupnou verzi.
Aktuální verzi si můžete stáhnout z Google Play, F-Droid nebo Huawei AppGallery # O projektu Wulkanowy
[<img src="https://play.google.com/intl/cs-CZ/badges/images/generic/cs_badge_web_generic.png" Chcete si přečíst více o projektu Wulkanowy? [Klikněte sem](https://github.com/wulkanowy/wulkanowy)
alt="Nyní na Google Play"
height="80">](https://play.google.com/store/apps/details?id=io.github.wulkanowy)
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Stáhnout s F-Droid"
height="80">](https://f-droid.org/packages/io.github.wulkanowy/)
[<img src="https://i.imgur.com/baTGiDP.png"
alt="Objevuj v AppGallery"
height="80">](https://appgallery.cloud.huawei.com/ag/n/app/C101440411?channelId=Badge&id=1b3f7fbb700849a9be0dba6b520b2282&s=EB1D3BF9ED9D1564D869B7B94B18016D3CABFCA5AEFB8E29F675FA04E0DC131D&detailType=0&v=)
Můžete si také stáhnout [vývojovou verzi](https://wulkanowy.github.io/#download), která zahrnuje nové funkce připravované pro příští vydání
## Postaveno s pomocí
* [Wulkanowy SDK](https://github.com/wulkanowy/sdk)
* [Kotlin Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html)
* [Hilt](https://dagger.dev/hilt/)
* [Room](https://developer.android.com/topic/libraries/architecture/room)
* [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager)
## Spolupráce
Přispějte do projektu vytvořením PR nebo odesláním issue na GitHub.
Pro zájemce o překlad aplikace do různých jazyků poskytujeme Crowdin:
https://crowdin.com/project/wulkanowy2
## Licence
Tento projekt je licencován pod licencí Apache License 2.0 - podrobnosti v souboru [LICENSE](LICENSE)

View File

@ -1,73 +1,33 @@
[Česká verze](README.cs.md) / Deutsche Version / [English version](README.en.md) / [Polska wersja](README.md) / [Slovenská verzia](README.sk.md) [Česká verze](README.cs.md) / Deutsche Version / [English version](README.en.md) / [Polska wersja](README.md) / [Slovenská verzia](README.sk.md)
# Wulkanowy # Wulkanowy MOD
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/wulkanowy/wulkanowy/test.yml?branch=develop&style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions) ## Funktionen:
[![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy) * Noten ausblenden
[![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) * Individuelle Anwesenheitslisten ausblenden.
[![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/) * Kommentare ausblenden.
[![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases) * Anwesenheit fälschen %
[![Crowdin](https://badges.crowdin.net/wulkanowy2/localized.svg)](https://translate.wulkanowy.net.pl)
Inoffizieller Android VULCAN UONET+ Registrierungsclient für Schüler und ihre Eltern So gelangen Sie zum ausgeblendeten Bereich:
1. Gehen Sie zur Registerkarte „Mehr“.
2. Gehen Sie zum Bereich „Einstellungen“.
3. Gehen Sie zum Bereich „Über die Anwendung“.
4. Klicken Sie fünfmal auf das Anwendungslogo
5. Gehen Sie zum Startbildschirm
6. Gehen Sie zu den Einstellungen
7. Geben Sie „Geheime Einstellungen“ ein
## Merkmale # Installation
* Einloggen mit E-Mail und Passwort | Dateiname | Angepasst an |
* Funktionen von der Registerwebsite: | ---------------- | ----------------- |
* Noten | `*-fdroid-*.apk` | F-Droid |
* Notenstatistik | `*-hms-*.apk` | Huawei AppGallery |
* Anwesenheit | `*-play-*.apk` | Play Store |
* Prozentsatz der Anwesenheit
* Prüfungen
* Stundenplan
* abgeschlossene Unterrichtsstunden
* Nachrichten
* Hausaufgaben
* Anmerkungen
* Glückszahl
* Zusätzliche Lektionen
* Schulkonferenzen
* Schüler- und Schulinformationen
* Berechnung des Durchschnitts unabhängig von den Präferenzen der Schule
* Benachrichtigungen, z. B. über eine neue Note
* Unterstützung für mehrere Konten mit der Möglichkeit, den Namen des Schülers zu ändern
* dunkles und schwarzes (AMOLED) Thema
* Offline-Modus
* optionale Werbungen, die es uns ermöglichen das Projekt zu unterstützen
## Herunterladen Laden Sie die ausgewählte Version von [hier](https://git.sador.me/sadorowo/wulkanowy-mod/releases) herunter.
Wir empfehlen, die neueste verfügbare Version herunterzuladen.
Die aktuelle Version können Sie von der Google Play, F-Droid oder Huawei AppGallery store herunterladen # Über das Wulkanowy-Projekt
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" Möchten Sie mehr über das Wulkanowy-Projekt lesen? [Hier klicken](https://github.com/wulkanowy/wulkanowy)
alt="Get it on Google Play"
height="80">](https://play.google.com/store/apps/details?id=io.github.wulkanowy)
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Get it on F-Droid"
height="80">](https://f-droid.org/packages/io.github.wulkanowy/)
[<img src="appgallery_badge.png"
alt="Explore it on AppGallery"
height="80">](https://appgallery.cloud.huawei.com/ag/n/app/C101440411?channelId=Badge&id=1b3f7fbb700849a9be0dba6b520b2282&s=EB1D3BF9ED9D1564D869B7B94B18016D3CABFCA5AEFB8E29F675FA04E0DC131D&detailType=0&v=)
Sie können auch eine [Entwicklungsversion herunterladen](https://wulkanowy.github.io/#download) die beinhaltet neue Funktionen, die für die nächste Version vorbereitet werden
## Gebaut mit
* [Wulkanowy SDK](https://github.com/wulkanowy/sdk)
* [Kotlin Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html)
* [Hilt](https://dagger.dev/hilt/)
* [Room](https://developer.android.com/topic/libraries/architecture/room)
* [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager)
## Beitragen
Bitte tragen Sie zum Projekt bei, indem Sie entweder eine PR erstellen oder ein Issue auf GitHub einreichen.
Für Personen, die daran interessiert sind, die Anwendung in verschiedene Sprachen zu übersetzen, bieten wir Crowdin
https://crowdin.com/project/wulkanowy2
## Lizenz
Dieses Projekt ist unter der Apache License 2.0 lizenziert - siehe die [LIZENZ](LICENSE) Datei für Details

View File

@ -1,73 +1,33 @@
[Česká verze](README.cs.md) / [Deutsche Version](README.de.md) / English version / [Polska wersja](README.md) / [Slovenská verzia](README.sk.md) [Česká verze](README.cs.md) / [Deutsche Version](README.de.md) / English version / [Polska wersja](README.md) / [Slovenská verzia](README.sk.md)
# Wulkanowy # Wulkanowy MOD
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/wulkanowy/wulkanowy/test.yml?branch=develop&style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions) ## Functions:
[![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy) * hide grades
[![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) * hide individual attendance entries
[![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/) * hide comments
[![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases) * fake attendance %.
[![Crowdin](https://badges.crowdin.net/wulkanowy2/localized.svg)](https://translate.wulkanowy.net.pl)
Unofficial android VULCAN UONET+ register client for both students and their parents To get to the hidden panel:
1. Go to the "More" tab
2. Go to the "Settings" panel
3. Go to the "About application" panel
4. Click on the application logo 5 times
5. Go to the home screen
6. Go to settings
7. Enter "secret settings"
## Features # Installation
* logging in using the email and password | File name | Adapted to |
* functions from the register website: | ---------------- | ----------------- |
* grades | `*-fdroid-*.apk` | F-Droid |
* grade statistics | `*-hms-*.apk` | Huawei AppGallery |
* attendance | `*-play-*.apk` | Play Store |
* percentage of attendance
* exams
* timetable
* completed lessons
* messages
* homework
* notes
* lucky number
* additional lessons
* school conferences
* student and school information
* calculation of the average independently of school's preferences
* notifications, e.g. about a new grade
* support for multiple accounts with the ability to rename students
* dark and black (AMOLED) theme
* offline mode
* optional ads which allow to support the project
## Download Download application from [releases](https://git.sador.me/sadorowo/wulkanowy-mod/releases).
We recommend downloading the latest available version.
You can download the current version from the Google Play, F-Droid or Huawei AppGallery store # About the Wulkanowy project
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" Want to read more about the Wulkanowy project? [Click here](https://github.com/wulkanowy/wulkanowy)
alt="Get it on Google Play"
height="80">](https://play.google.com/store/apps/details?id=io.github.wulkanowy)
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Get it on F-Droid"
height="80">](https://f-droid.org/packages/io.github.wulkanowy/)
[<img src="appgallery_badge.png"
alt="Explore it on AppGallery"
height="80">](https://appgallery.cloud.huawei.com/ag/n/app/C101440411?channelId=Badge&id=1b3f7fbb700849a9be0dba6b520b2282&s=EB1D3BF9ED9D1564D869B7B94B18016D3CABFCA5AEFB8E29F675FA04E0DC131D&detailType=0&v=)
You can also download a [development version](https://wulkanowy.github.io/#download) that includes new features being prepared for the next release
## Built With
* [Wulkanowy SDK](https://github.com/wulkanowy/sdk)
* [Kotlin Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html)
* [Hilt](https://dagger.dev/hilt/)
* [Room](https://developer.android.com/topic/libraries/architecture/room)
* [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager)
## Contributing
Please contribute to the project either by creating a PR or submitting an issue on GitHub.
For people interested in translating the application into different languages, we provide Crowdin
https://crowdin.com/project/wulkanowy2
## License
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details

View File

@ -1,74 +1,33 @@
[Česká verze](README.cs.md) / [Deutsche Version](README.de.md) / [English version](README.en.md) / Polska wersja / [Slovenská verzia](README.sk.md) [Česká verze](README.cs.md) / [Deutsche Version](README.de.md) / [English version](README.en.md) / Polska wersja / [Slovenská verzia](README.sk.md)
# Wulkanowy # Wulkanowy MOD
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/wulkanowy/wulkanowy/test.yml?branch=develop&style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions) ## Funkcje:
[![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy) * ukryj oceny
[![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) * ukryj poszczególne wpisy frekwencji
[![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/) * ukryj uwagi
[![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases) * sfałszuj % frekwencji
[![Crowdin](https://badges.crowdin.net/wulkanowy2/localized.svg)](https://translate.wulkanowy.net.pl)
Nieoficjalny klient dziennika VULCAN UONET+ dla ucznia i rodzica Aby dostać się do ukrytego panelu:
1. Przejdź do karty "Więcej"
2. Przejdź do panelu "Ustawienia"
3. Przejdź do panelu "O aplikacji"
4. Kliknij 5 razy w logo aplikacji
5. Przejdź na ekran główny
6. Wejdź w ustawienia
7. Wejdź w "sekretne ustawienia"
## Funkcje # Instalacja
* logowanie za pomocą e-maila i hasła | Nazwa pliku | Przystosowana do |
* funkcje ze strony internetowej dziennika: | ---------------- | ----------------- |
* oceny | `*-fdroid-*.apk` | F-Droid |
* statystyki ocen | `*-hms-*.apk` | Huawei AppGallery |
* frekwencja | `*-play-*.apk` | Sklep Play |
* procent frekwencji
* sprawdziany
* plan lekcji
* lekcje zrealizowane
* wiadomości
* zadania domowe
* uwagi
* szczęśliwy numerek
* dodatkowe lekcje
* zebrania w szkole
* informacje o uczniu i szkole
* obliczanie średniej niezależnie od preferencji szkoły
* powiadomienia np. o nowej ocenie
* obsługa wielu kont wraz z możliwością zmiany nazwy ucznia
* ciemny i czarny (AMOLED) motyw
* tryb offline
* opcjonalne reklamy umożliwiające wsparcie projektu
## Pobierz Pobierz wybraną wersję z [wydań](https://git.sador.me/sadorowo/wulkanowy-mod/releases).
Zalecamy pobranie najnowszej dostępnej wersji.
Aktualną wersję możesz pobrać ze sklepu Google Play, F-Droid lub Huawei AppGallery # O projekcie Wulkanowy
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" Chcesz poczytać więcej o projekcie Wulkanowy? [Kliknij tutaj](https://github.com/wulkanowy/wulkanowy)
alt="Pobierz z Google Play"
height="80">](https://play.google.com/store/apps/details?id=io.github.wulkanowy)
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Pobierz z F-Droid"
height="80">](https://f-droid.org/packages/io.github.wulkanowy/)
[<img src="appgallery_badge.png"
alt="Odkrywaj w AppGallery"
height="80">](https://appgallery.cloud.huawei.com/ag/n/app/C101440411?channelId=Badge&id=1b3f7fbb700849a9be0dba6b520b2282&s=EB1D3BF9ED9D1564D869B7B94B18016D3CABFCA5AEFB8E29F675FA04E0DC131D&detailType=0&v=)
Możesz także pobrać [wersję rozwojową](https://wulkanowy.github.io/#download), która zawiera nowe funkcje przygotowywane do następnego wydania
## Zbudowana za pomocą
* [Wulkanowy SDK](https://github.com/wulkanowy/sdk)
* [Kotlin Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html)
* [Hilt](https://dagger.dev/hilt/)
* [Room](https://developer.android.com/topic/libraries/architecture/room)
* [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager)
## Współpraca
Wnieś swój wkład w projekt, tworząc PR lub wysyłając issue na GitHub.
Dla osób zainteresowanych tłumaczeniem aplikacji na różne języki udostępniamy Crowdina
https://crowdin.com/project/wulkanowy2
## Licencja
Ten projekt udostępniany jest na licencji Apache License 2.0 - szczegóły w pliku [LICENSE](LICENSE)

View File

@ -1,73 +1,33 @@
[Česká verze](README.cs.md) / [Deutsche Version](README.de.md) / [English version](README.en.md) / [Polska wersja](README.md) / Slovenská verzia [Česká verze](README.cs.md) / [Deutsche Version](README.de.md) / [English version](README.en.md) / [Polska wersja](README.md) / Slovenská verzia
# Wulkanowy # Wulkanowy MOD
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/wulkanowy/wulkanowy/test.yml?branch=develop&style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions) ## Funkcie:
[![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy) * skryť známky
[![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) * Skryť individuálne záznamy o dochádzke.
[![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/) * Skryť komentáre.
[![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases) * falošná dochádzka %
[![Crowdin](https://badges.crowdin.net/wulkanowy2/localized.svg)](https://translate.wulkanowy.net.pl)
Neoficiálny klient denníka VULCAN UONET+ pre žiaka a rodičov Ak chcete prejsť na skrytý panel:
1. Prejdite na kartu „Viac“.
2. Prejdite na panel „Nastavenia“.
3. Prejdite na panel „O aplikácii“.
4. Kliknite 5-krát na logo aplikácie
5. Prejdite na domovskú obrazovku
6. Prejdite do nastavení
7. Zadajte „tajné nastavenia“
## Funkcie # Inštalácia
* prihlásenie pomocou emailu a hesla | Názov súboru | Prispôsobené |
* funkcie z webovej stránky denníka: | ---------------- | ----------------- |
* známky | `*-fdroid-*.apk` | F-Droid |
* štatistiky známok | `*-hms-*.apk` | Huawei AppGallery |
* frekvencia | `*-play-*.apk` | Play Store |
* percento frekvencie
* skúšky
* plán lekcie
* dokončené lekcie
* správy
* domáce úlohy
* poznámky
* šťastné číslo
* ďalšie lekcie
* školské stretnutie
* informácie o žiakovi a škole
* výpočet priemeru nezávisle od preferencií školy
* upozornenia, napr. o nových známkach
* podpora viacerých účtov s možnosťou premenovania žiakov
* tmavý a čierny (AMOLED) motív
* offline režim
* voliteľné reklamy na podporu projektu
## Stiahn Stiahnite si vybranú verziu z [releases](https://git.sador.me/sadorowo/wulkanowy-mod/releases).
Odporúčame stiahnuť najnovšiu dostupnú verziu.
Aktuálnu verziu si môžete stiahnuť z Google Play, F-Droid alebo Huawei AppGallery # O projekte Wulkanowy
[<img src="https://play.google.com/intl/sk/badges/images/generic/sk_badge_web_generic.png" Chcete si prečítať viac o projekte Wulkanowy? [Kliknite sem](https://github.com/wulkanowy/wulkanowy)
alt="Nyní na Google Play"
height="80">](https://play.google.com/store/apps/details?id=io.github.wulkanowy)
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Stiahnuť s F-Droid"
height="80">](https://f-droid.org/packages/io.github.wulkanowy/)
[<img src="https://i.imgur.com/sX8UyAw.png"
alt="Objavíte v AppGallery"
height="80">](https://appgallery.cloud.huawei.com/ag/n/app/C101440411?channelId=Badge&id=1b3f7fbb700849a9be0dba6b520b2282&s=EB1D3BF9ED9D1564D869B7B94B18016D3CABFCA5AEFB8E29F675FA04E0DC131D&detailType=0&v=)
Môžete si tiež stiahnuť [vývojovú verziu](https://wulkanowy.github.io/#download), ktorá zahrňuje nové funkcie pripravované pre budúce vydanie
## Postavené s pomocou
* [Wulkanowy SDK](https://github.com/wulkanowy/sdk)
* [Kotlin Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html)
* [Hilt](https://dagger.dev/hilt/)
* [Room](https://developer.android.com/topic/libraries/architecture/room)
* [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager)
## Spolupráca
Prispejte do projektu vytvorením PR alebo odoslaním issue na GitHub.
Pre záujemcov o preklad aplikácie do rôznych jazykov poskytujeme Crowdin:
https://crowdin.com/project/wulkanowy2
## Licencia
Tento projekt je licencovaný pod licenciou Apache License 2.0 - podrobnosti v súbore [LICENSE](LICENSE)

View File

@ -27,15 +27,12 @@ android {
testApplicationId "io.github.tests.wulkanowy" testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 34 targetSdkVersion 34
versionCode 139 versionCode 165
versionName "2.2.7" versionName "2.6.5"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "app_name", "Wulkanowy" resValue "string", "app_name", "Wulkanowy"
manifestPlaceholders = [ manifestPlaceholders = [admob_project_id: ""]
firebase_enabled: project.hasProperty("enableFirebase"),
admob_project_id: ""
]
buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "null" buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "null"
buildConfigField "String", "DASHBOARD_TILE_AD_ID", "null" buildConfigField "String", "DASHBOARD_TILE_AD_ID", "null"
@ -65,8 +62,8 @@ android {
release { release {
minifyEnabled true minifyEnabled true
shrinkResources true shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' // proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release // signingConfig signingConfigs.release
buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\"" buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
buildConfigField "String", "SCHOOLS_BASE_URL", '"https://schools.wulkanowy.net.pl"' buildConfigField "String", "SCHOOLS_BASE_URL", '"https://schools.wulkanowy.net.pl"'
} }
@ -76,7 +73,6 @@ android {
resValue "string", "app_name", "Wulkanowy DEV" resValue "string", "app_name", "Wulkanowy DEV"
applicationIdSuffix ".dev" applicationIdSuffix ".dev"
versionNameSuffix "-dev" versionNameSuffix "-dev"
ext.enableCrashlytics = project.hasProperty("enableFirebase")
buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\"" buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
buildConfigField "String", "SCHOOLS_BASE_URL", '"https://schools.wulkanowy.net.pl"' buildConfigField "String", "SCHOOLS_BASE_URL", '"https://schools.wulkanowy.net.pl"'
} }
@ -142,7 +138,9 @@ android {
packagingOptions { packagingOptions {
resources { resources {
excludes += ['META-INF/library_release.kotlin_module', excludes += ['META-INF/library_release.kotlin_module',
'META-INF/library-core_release.kotlin_module'] 'META-INF/library-core_release.kotlin_module',
'META-INF/LICENSE.md',
'META-INF/LICENSE-notice.md']
} }
} }
@ -163,7 +161,7 @@ play {
track = 'production' track = 'production'
releaseStatus = ReleaseStatus.IN_PROGRESS releaseStatus = ReleaseStatus.IN_PROGRESS
userFraction = 0.99d userFraction = 0.99d
updatePriority = 5 updatePriority = 3
enabled.set(false) enabled.set(false)
} }
@ -185,31 +183,31 @@ huaweiPublish {
ext { ext {
work_manager = "2.9.0" work_manager = "2.9.0"
android_hilt = "1.1.0" android_hilt = "1.2.0"
room = "2.6.1" room = "2.6.1"
chucker = "4.0.0" chucker = "4.0.0"
mockk = "1.13.8" mockk = "1.13.10"
coroutines = "1.7.3" coroutines = "1.8.0"
} }
dependencies { dependencies {
implementation 'io.github.wulkanowy:sdk:2.2.7' implementation 'io.github.wulkanowy:sdk:2.6.4'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2" implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
implementation 'androidx.core:core-ktx:1.12.0' implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.core:core-splashscreen:1.0.1' implementation 'androidx.core:core-splashscreen:1.0.1'
implementation "androidx.activity:activity-ktx:1.8.2" implementation "androidx.activity:activity-ktx:1.9.0"
implementation "androidx.appcompat:appcompat:1.6.1" implementation "androidx.appcompat:appcompat:1.6.1"
implementation "androidx.fragment:fragment-ktx:1.6.2" implementation "androidx.fragment:fragment-ktx:1.7.0"
implementation "androidx.annotation:annotation:1.7.1" implementation "androidx.annotation:annotation:1.7.1"
implementation "androidx.preference:preference-ktx:1.2.1" implementation "androidx.preference:preference-ktx:1.2.1"
implementation "androidx.recyclerview:recyclerview:1.3.2" implementation "androidx.recyclerview:recyclerview:1.3.2"
implementation "androidx.viewpager2:viewpager2:1.1.0-beta02" implementation "androidx.viewpager2:viewpager2:1.1.0-rc01"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "androidx.constraintlayout:constraintlayout:2.1.4" implementation "androidx.constraintlayout:constraintlayout:2.1.4"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0" implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
@ -221,7 +219,7 @@ dependencies {
implementation "androidx.work:work-runtime:$work_manager" implementation "androidx.work:work-runtime:$work_manager"
playImplementation "androidx.work:work-gcm:$work_manager" playImplementation "androidx.work:work-gcm:$work_manager"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.2" implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.7.0"
implementation "androidx.room:room-runtime:$room" implementation "androidx.room:room-runtime:$room"
implementation "androidx.room:room-ktx:$room" implementation "androidx.room:room-ktx:$room"
@ -235,32 +233,35 @@ dependencies {
implementation 'com.github.ncapdevi:FragNav:3.3.0' implementation 'com.github.ncapdevi:FragNav:3.3.0'
implementation "com.github.YarikSOffice:lingver:1.3.0" implementation "com.github.YarikSOffice:lingver:1.3.0"
implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:retrofit:2.11.0'
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0" implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0"
implementation "com.squareup.okhttp3:logging-interceptor:4.12.0" implementation "com.squareup.okhttp3:logging-interceptor:4.12.0"
implementation "com.squareup.okhttp3:okhttp-urlconnection:4.12.0"
implementation "com.jakewharton.timber:timber:5.0.1" implementation "com.jakewharton.timber:timber:5.0.1"
implementation "at.favre.lib:slf4j-timber:1.0.1" implementation 'com.github.Faierbel:slf4j-timber:2.0'
implementation 'com.github.bastienpaulfr:Treessence:1.1.2' implementation 'com.github.bastienpaulfr:Treessence:1.1.2'
implementation "com.mikepenz:aboutlibraries-core:$about_libraries" implementation "com.mikepenz:aboutlibraries-core:$about_libraries"
implementation 'io.coil-kt:coil:2.5.0' implementation 'io.coil-kt:coil:2.6.0'
implementation "io.github.wulkanowy:AppKillerManager:3.0.1" implementation "io.github.wulkanowy:AppKillerManager:3.0.1"
implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'me.xdrop:fuzzywuzzy:1.4.0'
implementation 'com.fredporciuncula:flow-preferences:1.9.1' implementation 'com.fredporciuncula:flow-preferences:1.9.1'
implementation 'org.apache.commons:commons-text:1.11.0' implementation 'org.apache.commons:commons-text:1.12.0'
playImplementation platform('com.google.firebase:firebase-bom:32.7.0') playImplementation platform('com.google.firebase:firebase-bom:33.0.0')
playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-analytics'
playImplementation 'com.google.firebase:firebase-messaging' playImplementation 'com.google.firebase:firebase-messaging'
playImplementation 'com.google.firebase:firebase-crashlytics:' playImplementation 'com.google.firebase:firebase-crashlytics:'
playImplementation 'com.google.firebase:firebase-config-ktx' playImplementation 'com.google.firebase:firebase-config'
playImplementation 'com.google.android.gms:play-services-ads:22.6.0' playImplementation 'com.google.android.gms:play-services-ads:22.6.0'
playImplementation "com.google.android.play:integrity:1.3.0" playImplementation "com.google.android.play:integrity:1.3.0"
playImplementation 'com.google.android.play:app-update-ktx:2.1.0' playImplementation 'com.google.android.play:app-update-ktx:2.1.0'
playImplementation 'com.google.android.play:review-ktx:2.0.1' playImplementation 'com.google.android.play:review-ktx:2.0.1'
playImplementation "com.google.android.ump:user-messaging-platform:2.1.0"
hmsImplementation 'com.huawei.hms:hianalytics:6.12.0.300' hmsImplementation 'com.huawei.hms:hianalytics:6.12.0.301'
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.302' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.303'
releaseImplementation "com.github.chuckerteam.chucker:library-no-op:$chucker" releaseImplementation "com.github.chuckerteam.chucker:library-no-op:$chucker"
@ -273,7 +274,7 @@ dependencies {
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines"
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
testImplementation 'org.robolectric:robolectric:4.11.1' testImplementation 'org.robolectric:robolectric:4.12.1'
testImplementation "androidx.test:runner:1.5.2" testImplementation "androidx.test:runner:1.5.2"
testImplementation "androidx.test.ext:junit:1.1.5" testImplementation "androidx.test.ext:junit:1.1.5"
testImplementation "androidx.test:core:1.5.0" testImplementation "androidx.test:core:1.5.0"

View File

@ -36,6 +36,37 @@
"status": 2 "status": 2
} }
} }
},
{
"client_info": {
"mobilesdk_app_id": "1:1091101852179:android:b558a25f65d088b1",
"android_client_info": {
"package_name": "io.github.wulkanowy"
}
},
"oauth_client": [
{
"client_id": "",
"client_type": 3
}
],
"api_key": [
{
"current_key": ""
}
],
"services": {
"analytics_service": {
"status": 1
},
"appinvite_service": {
"status": 1,
"other_platform_oauth_client": []
},
"ads_service": {
"status": 2
}
}
} }
], ],
"configuration_version": "1" "configuration_version": "1"

View File

@ -1,7 +1,8 @@
#!/bin/bash - #!/bin/bash -
content=$(cat < "app/src/main/play/release-notes/pl-PL/default.txt") || exit content=$(cat < "app/src/main/play/release-notes/pl-PL/default.txt") || exit
if [[ "${#content}" -gt 500 ]]; then content2=echo "$content" | dos2unix
if [[ "${#content2}" -gt 500 ]]; then
echo >&2 "Release notes content has reached the limit of 500 characters" echo >&2 "Release notes content has reached the limit of 500 characters"
exit 1 exit 1
fi fi

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -14,34 +14,37 @@ import kotlin.test.assertFailsWith
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class ScramblerTest { class ScramblerTest {
private val scrambler = Scrambler(ApplicationProvider.getApplicationContext())
@Test @Test
fun encryptDecryptTest() { fun encryptDecryptTest() {
assertEquals("TEST", decrypt(encrypt("TEST", assertEquals(
ApplicationProvider.getApplicationContext()))) "TEST", scrambler.decrypt(scrambler.encrypt("TEST"))
)
} }
@Test @Test
fun emptyTextEncryptTest() { fun emptyTextEncryptTest() {
assertFailsWith<ScramblerException> { assertFailsWith<ScramblerException> {
decrypt("") scrambler.decrypt("")
} }
assertFailsWith<ScramblerException> { assertFailsWith<ScramblerException> {
encrypt("", ApplicationProvider.getApplicationContext()) scrambler.encrypt("")
} }
} }
@Test @Test
@SdkSuppress(minSdkVersion = 18) @SdkSuppress(minSdkVersion = 18)
fun emptyKeyStoreTest() { fun emptyKeyStoreTest() {
val text = encrypt("test", ApplicationProvider.getApplicationContext()) val text = scrambler.encrypt("test")
val keyStore = KeyStore.getInstance("AndroidKeyStore") val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null) keyStore.load(null)
keyStore.deleteEntry("wulkanowy_password") keyStore.deleteEntry("wulkanowy_password")
assertFailsWith<ScramblerException> { assertFailsWith<ScramblerException> {
decrypt(text) scrambler.decrypt(text)
} }
} }
} }

View File

@ -1,92 +0,0 @@
{
"agcgw": {
"backurl": "connect-dre.hispace.hicloud.com",
"url": "connect-dre.dbankcloud.cn",
"websocketbackurl": "connect-ws-dre.hispace.dbankcloud.com",
"websocketurl": "connect-ws-dre.hispace.dbankcloud.cn"
},
"agcgw_all": {
"CN": "connect-drcn.dbankcloud.cn",
"CN_back": "connect-drcn.hispace.hicloud.com",
"DE": "connect-dre.dbankcloud.cn",
"DE_back": "connect-dre.hispace.hicloud.com",
"RU": "connect-drru.hispace.dbankcloud.ru",
"RU_back": "connect-drru.hispace.dbankcloud.cn",
"SG": "connect-dra.dbankcloud.cn",
"SG_back": "connect-dra.hispace.hicloud.com"
},
"websocketgw_all": {
"CN": "connect-ws-drcn.hispace.dbankcloud.cn",
"CN_back": "connect-ws-drcn.hispace.dbankcloud.com",
"DE": "connect-ws-dre.hispace.dbankcloud.cn",
"DE_back": "connect-ws-dre.hispace.dbankcloud.com",
"RU": "connect-ws-drru.hispace.dbankcloud.ru",
"RU_back": "connect-ws-drru.hispace.dbankcloud.cn",
"SG": "connect-ws-dra.hispace.dbankcloud.cn",
"SG_back": "connect-ws-dra.hispace.dbankcloud.com"
},
"client": {
"cp_id": "890048000024105546",
"product_id": "736430079244736562",
"client_id": "514530959291319360",
"client_secret": "C42522DBF17D3D4BBE9D9C1783A54484B7E6844B388B7A67502D36A633A4186B",
"project_id": "736430079244736562",
"app_id": "106552551",
"api_key": "CgB6e3x9BUNiq+r8ebCHNojjjYsMT4pJSjjNDOkm9owtBb6rVI6LjnASoZBRxbjjhObcrV5gANo99fI/eKZDTbWS",
"package_name": "io.github.wulkanowy.dev"
},
"oauth_client": {
"client_id": "106552551",
"client_type": 1
},
"app_info": {
"app_id": "106552551",
"package_name": "io.github.wulkanowy.dev"
},
"service": {
"analytics": {
"collector_url": "datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn",
"collector_url_ru": "datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com",
"collector_url_sg": "datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn",
"collector_url_de": "datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn",
"collector_url_cn": "datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn",
"resource_id": "p1",
"channel_id": ""
},
"search":{
"url":"https://search-dre.cloud.huawei.com"
},
"cloudstorage": {
"storage_url_sg_back": "https://agc-storage-dra.cloud.huawei.asia",
"storage_url_ru_back": "https://agc-storage-drru.cloud.huawei.ru",
"storage_url_ru": "https://agc-storage-drru.cloud.huawei.ru",
"storage_url_de_back": "https://agc-storage-dre.cloud.huawei.eu",
"storage_url_de": "https://ops-dre.agcstorage.link",
"storage_url": "https://agc-storage-drcn.platform.dbankcloud.cn",
"storage_url_sg": "https://ops-dra.agcstorage.link",
"storage_url_cn_back": "https://agc-storage-drcn.cloud.huawei.com.cn",
"storage_url_cn": "https://agc-storage-drcn.platform.dbankcloud.cn"
},
"ml": {
"mlservice_url": "ml-api-dre.ai.dbankcloud.com,ml-api-dre.ai.dbankcloud.cn"
}
},
"region": "DE",
"configuration_version": "3.0",
"appInfos": [
{
"package_name": "io.github.wulkanowy.dev",
"client": {
"app_id": "106552551"
},
"app_info": {
"package_name": "io.github.wulkanowy.dev",
"app_id": "106552551"
},
"oauth_client": {
"client_type": 1,
"client_id": "106552551"
}
}
]
}

View File

@ -5,6 +5,7 @@ import android.view.View
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
import kotlinx.coroutines.flow.MutableStateFlow
import javax.inject.Inject import javax.inject.Inject
@Suppress("unused") @Suppress("unused")
@ -13,9 +14,11 @@ class AdsHelper @Inject constructor(
private val preferencesRepository: PreferencesRepository private val preferencesRepository: PreferencesRepository
) { ) {
val isMobileAdsSdkInitialized = MutableStateFlow(false)
val canShowAd = false
fun initialize() { fun initialize() {
preferencesRepository.isAdsEnabled = false preferencesRepository.isAdsEnabled = false
preferencesRepository.isAgreeToProcessData = false
preferencesRepository.selectedDashboardTiles -= DashboardItem.Tile.ADS preferencesRepository.selectedDashboardTiles -= DashboardItem.Tile.ADS
} }

View File

@ -5,6 +5,7 @@ import android.view.View
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
import kotlinx.coroutines.flow.MutableStateFlow
import javax.inject.Inject import javax.inject.Inject
@Suppress("unused") @Suppress("unused")
@ -12,10 +13,11 @@ class AdsHelper @Inject constructor(
@ApplicationContext private val context: Context, @ApplicationContext private val context: Context,
private val preferencesRepository: PreferencesRepository private val preferencesRepository: PreferencesRepository
) { ) {
val isMobileAdsSdkInitialized = MutableStateFlow(false)
val canShowAd = false
fun initialize() { fun initialize() {
preferencesRepository.isAdsEnabled = false preferencesRepository.isAdsEnabled = false
preferencesRepository.isAgreeToProcessData = false
preferencesRepository.selectedDashboardTiles -= DashboardItem.Tile.ADS preferencesRepository.selectedDashboardTiles -= DashboardItem.Tile.ADS
} }

View File

@ -44,13 +44,14 @@
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
android:supportsRtl="false" android:supportsRtl="false"
android:theme="@style/WulkanowyTheme" android:theme="@style/WulkanowyTheme"
android:resizeableActivity="true"
tools:ignore="DataExtractionRules,UnusedAttribute"> tools:ignore="DataExtractionRules,UnusedAttribute">
<activity <activity
android:name=".ui.modules.splash.SplashActivity" android:name=".ui.modules.splash.SplashActivity"
android:exported="true" android:exported="true"
android:screenOrientation="portrait" android:screenOrientation="portrait"
android:theme="@style/WulkanowyTheme.SplashScreen" android:theme="@style/WulkanowyTheme.SplashScreen"
tools:ignore="LockedOrientationActivity"> tools:ignore="DiscouragedApi,LockedOrientationActivity">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
@ -154,33 +155,9 @@
android:resource="@xml/provider_paths" /> android:resource="@xml/provider_paths" />
</provider> </provider>
<!-- workaround for https://github.com/firebase/firebase-android-sdk/issues/473 enabled:false -->
<!-- https://firebase.googleblog.com/2017/03/take-control-of-your-firebase-init-on.html -->
<provider
android:name="com.google.firebase.provider.FirebaseInitProvider"
android:authorities="${applicationId}.firebaseinitprovider"
android:enabled="${firebase_enabled}"
android:exported="false"
tools:ignore="MissingClass" />
<meta-data <meta-data
android:name="install_channel" android:name="install_channel"
android:value="${install_channel}" /> android:value="${install_channel}" />
<meta-data
android:name="firebase_analytics_collection_enabled"
android:value="${firebase_enabled}" />
<meta-data
android:name="google_analytics_adid_collection_enabled"
android:value="${firebase_enabled}" />
<meta-data
android:name="firebase_crashlytics_collection_enabled"
android:value="${firebase_enabled}" />
<meta-data
android:name="firebase_messaging_auto_init_enabled"
android:value="${firebase_enabled}" />
<meta-data
android:name="firebase_inapp_messaging_auto_data_collection_enabled"
android:value="${firebase_enabled}" />
<meta-data <meta-data
android:name="com.google.firebase.messaging.default_notification_icon" android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_stat_all" /> android:resource="@drawable/ic_stat_all" />

View File

@ -54,5 +54,9 @@
{ {
"displayName": "Antoni Paduch", "displayName": "Antoni Paduch",
"githubUsername": "janAte1" "githubUsername": "janAte1"
},
{
"displayName": "Kamil Wąsik",
"githubUsername": "JestemKamil"
} }
] ]

View File

@ -12,7 +12,6 @@ import fr.bipi.treessence.file.FileLoggerTree
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.ui.base.ThemeManager import io.github.wulkanowy.ui.base.ThemeManager
import io.github.wulkanowy.utils.ActivityLifecycleLogger import io.github.wulkanowy.utils.ActivityLifecycleLogger
import io.github.wulkanowy.utils.AdsHelper
import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.CrashLogExceptionTree import io.github.wulkanowy.utils.CrashLogExceptionTree
@ -37,9 +36,6 @@ class WulkanowyApp : Application(), Configuration.Provider {
@Inject @Inject
lateinit var analyticsHelper: AnalyticsHelper lateinit var analyticsHelper: AnalyticsHelper
@Inject
lateinit var adsHelper: AdsHelper
@Inject @Inject
lateinit var remoteConfigHelper: RemoteConfigHelper lateinit var remoteConfigHelper: RemoteConfigHelper
@ -56,7 +52,6 @@ class WulkanowyApp : Application(), Configuration.Provider {
super.onCreate() super.onCreate()
initializeAppLanguage() initializeAppLanguage()
themeManager.applyDefaultTheme() themeManager.applyDefaultTheme()
adsHelper.initialize()
remoteConfigHelper.initialize() remoteConfigHelper.initialize()
initLogging() initLogging()
} }

View File

@ -13,21 +13,18 @@ import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import io.github.wulkanowy.data.api.AdminMessageService import io.github.wulkanowy.data.api.services.SchoolsService
import io.github.wulkanowy.data.api.SchoolsService import io.github.wulkanowy.data.api.services.WulkanowyService
import io.github.wulkanowy.data.db.AppDatabase import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.RemoteConfigHelper
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.create import retrofit2.create
import timber.log.Timber
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Singleton import javax.inject.Singleton
@ -35,19 +32,6 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
internal class DataModule { internal class DataModule {
@Singleton
@Provides
fun provideSdk(chuckerInterceptor: ChuckerInterceptor, remoteConfig: RemoteConfigHelper) =
Sdk().apply {
androidVersion = android.os.Build.VERSION.RELEASE
buildTag = android.os.Build.MODEL
userAgentTemplate = remoteConfig.userAgentTemplate
setSimpleHttpLogger { Timber.d(it) }
// for debug only
addInterceptor(chuckerInterceptor, network = true)
}
@Singleton @Singleton
@Provides @Provides
fun provideChuckerCollector( fun provideChuckerCollector(
@ -87,7 +71,7 @@ internal class DataModule {
okHttpClient: OkHttpClient, okHttpClient: OkHttpClient,
json: Json, json: Json,
appInfo: AppInfo appInfo: AppInfo
): AdminMessageService = Retrofit.Builder() ): WulkanowyService = Retrofit.Builder()
.baseUrl(appInfo.messagesBaseUrl) .baseUrl(appInfo.messagesBaseUrl)
.client(okHttpClient) .client(okHttpClient)
.addConverterFactory(json.asConverterFactory("application/json".toMediaType())) .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
@ -251,4 +235,12 @@ internal class DataModule {
@Singleton @Singleton
@Provides @Provides
fun provideAdminMessageDao(database: AppDatabase) = database.adminMessagesDao fun provideAdminMessageDao(database: AppDatabase) = database.adminMessagesDao
@Singleton
@Provides
fun provideMutesDao(database: AppDatabase) = database.mutedMessageSendersDao
@Singleton
@Provides
fun provideGradeDescriptiveDao(database: AppDatabase) = database.gradeDescriptiveDao
} }

View File

@ -1,27 +1,73 @@
package io.github.wulkanowy.data package io.github.wulkanowy.data
import kotlinx.coroutines.flow.* import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import timber.log.Timber import timber.log.Timber
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
sealed class Resource<T> { sealed interface Resource<out T> {
/**
open class Loading<T> : Resource<T>() * The initial value of a resource flow. Indicates no data that is currently available to be shown,
* however with the expectation that the state will transition to another one soon.
*/
open class Loading<T> : Resource<T>
/**
* A semi-loading state with some data available to be displayed (usually cached data loaded from
* the database). Still not the target state and it's expected to transition into another one soon.
*/
data class Intermediate<T>(val data: T) : Loading<T>() data class Intermediate<T>(val data: T) : Loading<T>()
data class Success<T>(val data: T) : Resource<T>() /**
* The happy-path target state. Data can either be:
* - loaded from the database - while it may seem like this case is already handled by the
* Intermediate state, the difference here is semantic. Cached data is returned as Intermediate
* when there's a API request in progress (or soon expected to be), however when there is no
* intention of immediately querying the API, the cached data is returned as a Success.
* - fetched from the API.
*/
data class Success<T>(val data: T) : Resource<T>
data class Error<T>(val error: Throwable) : Resource<T>() /**
* Something bad happened and we were unable to get the requested data. This can be caused by
* a database error, a network error, or really just any other error. Upon receiving this state
* the UI can either: display a full screen error, or, when it has received any data previously,
* display a snack bar informing of the problem.
*/
data class Error<T>(val error: Throwable) : Resource<T>
} }
val <T> Resource<T>.dataOrNull: T? val <T> Resource<T>.dataOrNull: T?
get() = when (this) { get() = when (this) {
is Resource.Success -> this.data is Resource.Success -> this.data
is Resource.Intermediate -> this.data is Resource.Intermediate -> this.data
is Resource.Loading -> null else -> null
is Resource.Error -> null }
val <T> Resource<T>.dataOrThrow: T
get() = when (this) {
is Resource.Success -> this.data
is Resource.Intermediate -> this.data
is Resource.Loading -> throw IllegalStateException("Resource is in loading state")
is Resource.Error -> throw this.error
} }
val <T> Resource<T>.errorOrNull: Throwable? val <T> Resource<T>.errorOrNull: Throwable?
@ -47,6 +93,22 @@ fun <T, U> Resource<T>.mapData(block: (T) -> U) = when (this) {
is Resource.Error -> Resource.Error(this.error) is Resource.Error -> Resource.Error(this.error)
} }
/**
* Injects another flow into this flow's resource data.
*/
inline fun <T1, T2, R> Flow<Resource<T1>>.combineWithResourceData(
flow: Flow<T2>,
crossinline block: suspend (T1, T2) -> R
): Flow<Resource<R>> =
combine(flow) { resource, inject ->
when (resource) {
is Resource.Success -> Resource.Success(block(resource.data, inject))
is Resource.Intermediate -> Resource.Intermediate(block(resource.data, inject))
is Resource.Loading -> Resource.Loading()
is Resource.Error -> Resource.Error(resource.error)
}
}
fun <T> Flow<Resource<T>>.logResourceStatus(name: String, showData: Boolean = false) = onEach { fun <T> Flow<Resource<T>>.logResourceStatus(name: String, showData: Boolean = false) = onEach {
val description = when (it) { val description = when (it) {
is Resource.Intermediate -> "intermediate data received" + if (showData) " (data: `${it.data}`)" else "" is Resource.Intermediate -> "intermediate data received" + if (showData) " (data: `${it.data}`)" else ""
@ -57,8 +119,29 @@ fun <T> Flow<Resource<T>>.logResourceStatus(name: String, showData: Boolean = fa
Timber.i("$name: $description") Timber.i("$name: $description")
} }
fun <T, U> Flow<Resource<T>>.mapResourceData(block: (T) -> U) = map { inline fun <T, U> Flow<Resource<T>>.mapResourceData(crossinline block: suspend (T) -> U) = map {
it.mapData(block) when (it) {
is Resource.Success -> Resource.Success(block(it.data))
is Resource.Intermediate -> Resource.Intermediate(block(it.data))
is Resource.Loading -> Resource.Loading()
is Resource.Error -> Resource.Error(it.error)
}
}
@OptIn(ExperimentalCoroutinesApi::class)
fun <T, U> Flow<Resource<T>>.flatMapResourceData(
inheritIntermediate: Boolean = true, block: suspend (T) -> Flow<Resource<U>>
) = flatMapLatest {
when (it) {
is Resource.Success -> block(it.data)
is Resource.Intermediate -> block(it.data).map { newRes ->
if (inheritIntermediate && newRes is Resource.Success) Resource.Intermediate(newRes.data)
else newRes
}
is Resource.Loading -> flowOf(Resource.Loading())
is Resource.Error -> flowOf(Resource.Error(it.error))
}
} }
fun <T> Flow<Resource<T>>.onResourceData(block: suspend (T) -> Unit) = onEach { fun <T> Flow<Resource<T>>.onResourceData(block: suspend (T) -> Unit) = onEach {
@ -88,13 +171,13 @@ fun <T> Flow<Resource<T>>.onResourceSuccess(block: suspend (T) -> Unit) = onEach
} }
} }
fun <T> Flow<Resource<T>>.onResourceError(block: (Throwable) -> Unit) = onEach { fun <T> Flow<Resource<T>>.onResourceError(block: suspend (Throwable) -> Unit) = onEach {
if (it is Resource.Error) { if (it is Resource.Error) {
block(it.error) block(it.error)
} }
} }
fun <T> Flow<Resource<T>>.onResourceNotLoading(block: () -> Unit) = onEach { fun <T> Flow<Resource<T>>.onResourceNotLoading(block: suspend () -> Unit) = onEach {
if (it !is Resource.Loading) { if (it !is Resource.Loading) {
block() block()
} }
@ -104,70 +187,99 @@ suspend fun <T> Flow<Resource<T>>.toFirstResult() = filter { it !is Resource.Loa
suspend fun <T> Flow<Resource<T>>.waitForResult() = takeWhile { it is Resource.Loading }.collect() suspend fun <T> Flow<Resource<T>>.waitForResult() = takeWhile { it is Resource.Loading }.collect()
inline fun <ResultType, RequestType> networkBoundResource( // Can cause excessive amounts of `Resource.Intermediate` to be emitted. Unless that is desired,
mutex: Mutex = Mutex(), // use `debounceIntermediates` to alleviate this behavior.
showSavedOnLoading: Boolean = true, inline fun <reified T> combineResourceFlows(flows: Iterable<Flow<Resource<T>>>): Flow<Resource<List<T>>> =
crossinline isResultEmpty: (ResultType) -> Boolean, combine(flows) { items ->
crossinline query: () -> Flow<ResultType>, var isIntermediate = false
crossinline fetch: suspend (ResultType) -> RequestType, val data = mutableListOf<T>()
crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit, for (item in items) {
crossinline onFetchFailed: (Throwable) -> Unit = { }, when (item) {
crossinline shouldFetch: (ResultType) -> Boolean = { true }, is Resource.Success -> data.add(item.data)
crossinline filterResult: (ResultType) -> ResultType = { it } is Resource.Intermediate -> {
) = flow { isIntermediate = true
emit(Resource.Loading()) data.add(item.data)
val data = query().first()
emitAll(if (shouldFetch(data)) {
val filteredResult = filterResult(data)
if (showSavedOnLoading && !isResultEmpty(filteredResult)) {
emit(Resource.Intermediate(filteredResult))
} }
try { is Resource.Loading -> return@combine Resource.Loading()
val newData = fetch(data) is Resource.Error -> continue
mutex.withLock { saveFetchResult(query().first(), newData) } }
query().map { Resource.Success(filterResult(it)) } }
} catch (throwable: Throwable) { if (data.isEmpty()) {
onFetchFailed(throwable) // All items have to be errors for this to happen, so just return the first one.
query().map { Resource.Error(throwable) } // mapData is functionally useless and exists only to satisfy the type checker
items.first().mapData { listOf(it) }
} else if (isIntermediate) {
Resource.Intermediate(data)
} else {
Resource.Success(data)
}
}
@OptIn(FlowPreview::class)
fun <T> Flow<Resource<T>>.debounceIntermediates(timeout: Duration = 5.seconds) = flow {
var wasIntermediate = false
emitAll(this@debounceIntermediates.debounce {
if (it is Resource.Intermediate) {
if (!wasIntermediate) {
wasIntermediate = true
Duration.ZERO
} else {
timeout
} }
} else { } else {
query().map { Resource.Success(filterResult(it)) } wasIntermediate = false
Duration.ZERO
}
}) })
} }
inline fun <OutputType, ApiType> networkBoundResource(
mutex: Mutex = Mutex(),
crossinline isResultEmpty: (OutputType) -> Boolean,
crossinline query: () -> Flow<OutputType>,
crossinline fetch: suspend () -> ApiType,
crossinline saveFetchResult: suspend (old: OutputType, new: ApiType) -> Unit,
crossinline shouldFetch: (OutputType) -> Boolean = { true },
crossinline filterResult: (OutputType) -> OutputType = { it }
) = networkBoundResource(
mutex = mutex,
isResultEmpty = isResultEmpty,
query = query,
fetch = fetch,
saveFetchResult = saveFetchResult,
shouldFetch = shouldFetch,
mapResult = filterResult
)
@JvmName("networkBoundResourceWithMap") @JvmName("networkBoundResourceWithMap")
inline fun <ResultType, RequestType, T> networkBoundResource( inline fun <DatabaseType, ApiType, OutputType> networkBoundResource(
mutex: Mutex = Mutex(), mutex: Mutex = Mutex(),
showSavedOnLoading: Boolean = true, crossinline isResultEmpty: (OutputType) -> Boolean,
crossinline isResultEmpty: (T) -> Boolean, crossinline query: () -> Flow<DatabaseType>,
crossinline query: () -> Flow<ResultType>, crossinline fetch: suspend () -> ApiType,
crossinline fetch: suspend (ResultType) -> RequestType, crossinline saveFetchResult: suspend (old: DatabaseType, new: ApiType) -> Unit,
crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit, crossinline shouldFetch: (DatabaseType) -> Boolean = { true },
crossinline onFetchFailed: (Throwable) -> Unit = { }, crossinline mapResult: (DatabaseType) -> OutputType,
crossinline shouldFetch: (ResultType) -> Boolean = { true },
crossinline mapResult: (ResultType) -> T,
) = flow { ) = flow {
emit(Resource.Loading()) emit(Resource.Loading())
val data = query().first() val data = query().first()
emitAll(if (shouldFetch(data)) { if (shouldFetch(data)) {
val mappedResult = mapResult(data) emit(Resource.Intermediate(data))
if (showSavedOnLoading && !isResultEmpty(mappedResult)) {
emit(Resource.Intermediate(mappedResult))
}
try { try {
val newData = fetch(data) val newData = fetch()
mutex.withLock { saveFetchResult(query().first(), newData) } mutex.withLock { saveFetchResult(query().first(), newData) }
query().map { Resource.Success(mapResult(it)) }
} catch (throwable: Throwable) { } catch (throwable: Throwable) {
onFetchFailed(throwable) emit(Resource.Error(throwable))
query().map { Resource.Error(throwable) } return@flow
} }
} else { }
query().map { Resource.Success(mapResult(it)) }
}) emitAll(query().map { Resource.Success(it) })
} }
.mapResourceData { mapResult(it) }
.filterNot { it is Resource.Intermediate && isResultEmpty(it.data) }

View File

@ -0,0 +1,142 @@
package io.github.wulkanowy.data
import com.chuckerteam.chucker.api.ChuckerInterceptor
import io.github.wulkanowy.data.db.dao.StudentDao
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentIsEduOne
import io.github.wulkanowy.data.repositories.WulkanowyRepository
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.RemoteConfigHelper
import io.github.wulkanowy.utils.WebkitCookieManagerProxy
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class WulkanowySdkFactory @Inject constructor(
private val chuckerInterceptor: ChuckerInterceptor,
private val remoteConfig: RemoteConfigHelper,
private val webkitCookieManagerProxy: WebkitCookieManagerProxy,
private val studentDb: StudentDao,
private val wulkanowyRepository: WulkanowyRepository,
) {
private val eduOneMutex = Mutex()
private val migrationFailedStudentIds = mutableSetOf<Long>()
private val sdk = Sdk().apply {
androidVersion = android.os.Build.VERSION.RELEASE
buildTag = android.os.Build.MODEL
userAgentTemplate = remoteConfig.userAgentTemplate
setSimpleHttpLogger { Timber.d(it) }
setAdditionalCookieManager(webkitCookieManagerProxy)
// for debug only
addInterceptor(chuckerInterceptor, network = true)
}
fun createBase() = sdk
suspend fun create(): Sdk {
val mapping = wulkanowyRepository.getMapping()
return createBase().apply {
if (mapping != null) {
endpointsMapping = mapping.endpoints
vTokenMapping = mapping.vTokens
}
}
}
suspend fun create(student: Student, semester: Semester? = null): Sdk {
val overrideIsEduOne = checkEduOneAndMigrateIfNecessary(student)
return buildSdk(student, semester, overrideIsEduOne)
}
private suspend fun buildSdk(
student: Student,
semester: Semester?,
isStudentEduOne: Boolean
): Sdk {
return create().apply {
email = student.email
password = student.password
symbol = student.symbol
schoolSymbol = student.schoolSymbol
studentId = student.studentId
classId = student.classId
emptyCookieJarInterceptor = true
isEduOne = isStudentEduOne
if (Sdk.Mode.valueOf(student.loginMode) == Sdk.Mode.HEBE) {
mobileBaseUrl = student.mobileBaseUrl
} else {
scrapperBaseUrl = student.scrapperBaseUrl
domainSuffix = student.scrapperDomainSuffix
loginType = Sdk.ScrapperLoginType.valueOf(student.loginType)
}
mode = Sdk.Mode.valueOf(student.loginMode)
mobileBaseUrl = student.mobileBaseUrl
keyId = student.certificateKey
privatePem = student.privateKey
if (semester != null) {
diaryId = semester.diaryId
kindergartenDiaryId = semester.kindergartenDiaryId
schoolYear = semester.schoolYear
unitId = semester.unitId
}
}
}
private suspend fun checkEduOneAndMigrateIfNecessary(student: Student): Boolean {
if (student.isEduOne != null) return student.isEduOne
if (student.id in migrationFailedStudentIds) {
Timber.i("Migration eduOne: skipping because of previous failure")
return false
}
eduOneMutex.withLock {
if (student.id in migrationFailedStudentIds) {
Timber.i("Migration eduOne: skipping because of previous failure")
return false
}
val studentFromDatabase = studentDb.loadById(student.id)
if (studentFromDatabase?.isEduOne != null) {
Timber.i("Migration eduOne: already done")
return studentFromDatabase.isEduOne
}
Timber.i("Migration eduOne: flag missing. Running migration...")
val initializedSdk = buildSdk(
student = student,
semester = null,
isStudentEduOne = false, // doesn't matter
)
val newCurrentStudent = runCatching { initializedSdk.getCurrentStudent() }
.onFailure { Timber.e(it, "Migration eduOne: can't get current student") }
.getOrNull()
if (newCurrentStudent == null) {
Timber.i("Migration eduOne: failed, so skipping")
migrationFailedStudentIds.add(student.id)
return false
}
Timber.i("Migration eduOne: success. New isEduOne flag: ${newCurrentStudent.isEduOne}")
val studentIsEduOne = StudentIsEduOne(
id = student.id,
isEduOne = newCurrentStudent.isEduOne
)
studentDb.update(studentIsEduOne)
return newCurrentStudent.isEduOne
}
}
}

View File

@ -0,0 +1,14 @@
package io.github.wulkanowy.data.api.models
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class Mapping(
@SerialName("endpoints")
val endpoints: Map<String, Map<String, Map<String, String>>>,
@SerialName("vTokens")
val vTokens: Map<String, Map<String, Map<String, String>>>,
)

View File

@ -1,4 +1,4 @@
package io.github.wulkanowy.data.api package io.github.wulkanowy.data.api.services
import io.github.wulkanowy.data.pojos.IntegrityRequest import io.github.wulkanowy.data.pojos.IntegrityRequest
import io.github.wulkanowy.data.pojos.LoginEvent import io.github.wulkanowy.data.pojos.LoginEvent

View File

@ -1,12 +1,16 @@
package io.github.wulkanowy.data.api package io.github.wulkanowy.data.api.services
import io.github.wulkanowy.data.api.models.Mapping
import io.github.wulkanowy.data.db.entities.AdminMessage import io.github.wulkanowy.data.db.entities.AdminMessage
import retrofit2.http.GET import retrofit2.http.GET
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
interface AdminMessageService { interface WulkanowyService {
@GET("/v1.json") @GET("/v1.json")
suspend fun getAdminMessages(): List<AdminMessage> suspend fun getAdminMessages(): List<AdminMessage>
@GET("/mapping1.json")
suspend fun getMapping(): Mapping
} }

View File

@ -1,11 +1,129 @@
package io.github.wulkanowy.data.db package io.github.wulkanowy.data.db
import android.content.Context import android.content.Context
import androidx.room.* import androidx.room.AutoMigration
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.RoomDatabase.JournalMode.TRUNCATE import androidx.room.RoomDatabase.JournalMode.TRUNCATE
import io.github.wulkanowy.data.db.dao.* import androidx.room.TypeConverters
import io.github.wulkanowy.data.db.entities.* import io.github.wulkanowy.data.db.dao.AdminMessageDao
import io.github.wulkanowy.data.db.migrations.* import io.github.wulkanowy.data.db.dao.AttendanceDao
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
import io.github.wulkanowy.data.db.dao.ConferenceDao
import io.github.wulkanowy.data.db.dao.ExamDao
import io.github.wulkanowy.data.db.dao.GradeDao
import io.github.wulkanowy.data.db.dao.GradeDescriptiveDao
import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao
import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao
import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao
import io.github.wulkanowy.data.db.dao.GradeSummaryDao
import io.github.wulkanowy.data.db.dao.HomeworkDao
import io.github.wulkanowy.data.db.dao.LuckyNumberDao
import io.github.wulkanowy.data.db.dao.MailboxDao
import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
import io.github.wulkanowy.data.db.dao.MessagesDao
import io.github.wulkanowy.data.db.dao.MobileDeviceDao
import io.github.wulkanowy.data.db.dao.MutedMessageSendersDao
import io.github.wulkanowy.data.db.dao.NoteDao
import io.github.wulkanowy.data.db.dao.NotificationDao
import io.github.wulkanowy.data.db.dao.RecipientDao
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
import io.github.wulkanowy.data.db.dao.SchoolDao
import io.github.wulkanowy.data.db.dao.SemesterDao
import io.github.wulkanowy.data.db.dao.StudentDao
import io.github.wulkanowy.data.db.dao.StudentInfoDao
import io.github.wulkanowy.data.db.dao.SubjectDao
import io.github.wulkanowy.data.db.dao.TeacherDao
import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao
import io.github.wulkanowy.data.db.dao.TimetableDao
import io.github.wulkanowy.data.db.dao.TimetableHeaderDao
import io.github.wulkanowy.data.db.entities.AdminMessage
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.AttendanceSummary
import io.github.wulkanowy.data.db.entities.CompletedLesson
import io.github.wulkanowy.data.db.entities.Conference
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradeDescriptive
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
import io.github.wulkanowy.data.db.entities.GradePointsStatistics
import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.MessageAttachment
import io.github.wulkanowy.data.db.entities.MobileDevice
import io.github.wulkanowy.data.db.entities.MutedMessageSender
import io.github.wulkanowy.data.db.entities.Note
import io.github.wulkanowy.data.db.entities.Notification
import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.School
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentInfo
import io.github.wulkanowy.data.db.entities.Subject
import io.github.wulkanowy.data.db.entities.Teacher
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.db.entities.TimetableAdditional
import io.github.wulkanowy.data.db.entities.TimetableHeader
import io.github.wulkanowy.data.db.migrations.Migration10
import io.github.wulkanowy.data.db.migrations.Migration11
import io.github.wulkanowy.data.db.migrations.Migration12
import io.github.wulkanowy.data.db.migrations.Migration13
import io.github.wulkanowy.data.db.migrations.Migration14
import io.github.wulkanowy.data.db.migrations.Migration15
import io.github.wulkanowy.data.db.migrations.Migration16
import io.github.wulkanowy.data.db.migrations.Migration17
import io.github.wulkanowy.data.db.migrations.Migration18
import io.github.wulkanowy.data.db.migrations.Migration19
import io.github.wulkanowy.data.db.migrations.Migration2
import io.github.wulkanowy.data.db.migrations.Migration20
import io.github.wulkanowy.data.db.migrations.Migration21
import io.github.wulkanowy.data.db.migrations.Migration22
import io.github.wulkanowy.data.db.migrations.Migration23
import io.github.wulkanowy.data.db.migrations.Migration24
import io.github.wulkanowy.data.db.migrations.Migration25
import io.github.wulkanowy.data.db.migrations.Migration26
import io.github.wulkanowy.data.db.migrations.Migration27
import io.github.wulkanowy.data.db.migrations.Migration28
import io.github.wulkanowy.data.db.migrations.Migration29
import io.github.wulkanowy.data.db.migrations.Migration3
import io.github.wulkanowy.data.db.migrations.Migration30
import io.github.wulkanowy.data.db.migrations.Migration31
import io.github.wulkanowy.data.db.migrations.Migration32
import io.github.wulkanowy.data.db.migrations.Migration33
import io.github.wulkanowy.data.db.migrations.Migration34
import io.github.wulkanowy.data.db.migrations.Migration35
import io.github.wulkanowy.data.db.migrations.Migration36
import io.github.wulkanowy.data.db.migrations.Migration37
import io.github.wulkanowy.data.db.migrations.Migration38
import io.github.wulkanowy.data.db.migrations.Migration39
import io.github.wulkanowy.data.db.migrations.Migration4
import io.github.wulkanowy.data.db.migrations.Migration40
import io.github.wulkanowy.data.db.migrations.Migration41
import io.github.wulkanowy.data.db.migrations.Migration42
import io.github.wulkanowy.data.db.migrations.Migration43
import io.github.wulkanowy.data.db.migrations.Migration44
import io.github.wulkanowy.data.db.migrations.Migration46
import io.github.wulkanowy.data.db.migrations.Migration49
import io.github.wulkanowy.data.db.migrations.Migration5
import io.github.wulkanowy.data.db.migrations.Migration50
import io.github.wulkanowy.data.db.migrations.Migration51
import io.github.wulkanowy.data.db.migrations.Migration53
import io.github.wulkanowy.data.db.migrations.Migration54
import io.github.wulkanowy.data.db.migrations.Migration55
import io.github.wulkanowy.data.db.migrations.Migration57
import io.github.wulkanowy.data.db.migrations.Migration58
import io.github.wulkanowy.data.db.migrations.Migration6
import io.github.wulkanowy.data.db.migrations.Migration63
import io.github.wulkanowy.data.db.migrations.Migration7
import io.github.wulkanowy.data.db.migrations.Migration8
import io.github.wulkanowy.data.db.migrations.Migration9
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import javax.inject.Singleton import javax.inject.Singleton
@ -41,7 +159,9 @@ import javax.inject.Singleton
TimetableHeader::class, TimetableHeader::class,
SchoolAnnouncement::class, SchoolAnnouncement::class,
Notification::class, Notification::class,
AdminMessage::class AdminMessage::class,
MutedMessageSender::class,
GradeDescriptive::class,
], ],
autoMigrations = [ autoMigrations = [
AutoMigration(from = 44, to = 45), AutoMigration(from = 44, to = 45),
@ -51,6 +171,13 @@ import javax.inject.Singleton
AutoMigration(from = 54, to = 55, spec = Migration55::class), AutoMigration(from = 54, to = 55, spec = Migration55::class),
AutoMigration(from = 55, to = 56), AutoMigration(from = 55, to = 56),
AutoMigration(from = 56, to = 57, spec = Migration57::class), AutoMigration(from = 56, to = 57, spec = Migration57::class),
AutoMigration(from = 57, to = 58, spec = Migration58::class),
AutoMigration(from = 58, to = 59),
AutoMigration(from = 59, to = 60),
AutoMigration(from = 60, to = 61),
AutoMigration(from = 61, to = 62),
AutoMigration(from = 62, to = 63, spec = Migration63::class),
AutoMigration(from = 63, to = 64),
], ],
version = AppDatabase.VERSION_SCHEMA, version = AppDatabase.VERSION_SCHEMA,
exportSchema = true exportSchema = true
@ -59,7 +186,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
companion object { companion object {
const val VERSION_SCHEMA = 57 const val VERSION_SCHEMA = 64
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
Migration2(), Migration2(),
@ -184,4 +311,8 @@ abstract class AppDatabase : RoomDatabase() {
abstract val notificationDao: NotificationDao abstract val notificationDao: NotificationDao
abstract val adminMessagesDao: AdminMessageDao abstract val adminMessagesDao: AdminMessageDao
abstract val mutedMessageSendersDao: MutedMessageSendersDao
abstract val gradeDescriptiveDao: GradeDescriptiveDao
} }

View File

@ -2,24 +2,14 @@ package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Query import androidx.room.Query
import androidx.room.Transaction
import io.github.wulkanowy.data.db.entities.AdminMessage import io.github.wulkanowy.data.db.entities.AdminMessage
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
@Dao @Dao
abstract class AdminMessageDao : BaseDao<AdminMessage> { interface AdminMessageDao : BaseDao<AdminMessage> {
@Query("SELECT * FROM AdminMessages") @Query("SELECT * FROM AdminMessages")
abstract fun loadAll(): Flow<List<AdminMessage>> fun loadAll(): Flow<List<AdminMessage>>
@Transaction
open suspend fun removeOldAndSaveNew(
oldMessages: List<AdminMessage>,
newMessages: List<AdminMessage>
) {
deleteAll(oldMessages)
insertAll(newMessages)
}
} }

View File

@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Query import androidx.room.Query
import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import java.time.LocalDate import java.time.LocalDate
import javax.inject.Singleton import javax.inject.Singleton

View File

@ -3,6 +3,7 @@ package io.github.wulkanowy.data.db.dao
import androidx.room.Delete import androidx.room.Delete
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Transaction
import androidx.room.Update import androidx.room.Update
interface BaseDao<T> { interface BaseDao<T> {
@ -15,4 +16,10 @@ interface BaseDao<T> {
@Delete @Delete
suspend fun deleteAll(items: List<T>) suspend fun deleteAll(items: List<T>)
@Transaction
suspend fun removeOldAndSaveNew(oldItems: List<T>, newItems: List<T>) {
deleteAll(oldItems)
insertAll(newItems)
}
} }

View File

@ -12,4 +12,8 @@ interface GradeDao : BaseDao<Grade> {
@Query("SELECT * FROM Grades WHERE semester_id = :semesterId AND student_id = :studentId") @Query("SELECT * FROM Grades WHERE semester_id = :semesterId AND student_id = :studentId")
fun loadAll(semesterId: Int, studentId: Int): Flow<List<Grade>> fun loadAll(semesterId: Int, studentId: Int): Flow<List<Grade>>
@Query("SELECT * FROM Grades WHERE semester_id = :semesterId AND student_id = :studentId " +
"AND entry NOT IN(:censoredEntries)")
fun loadAllCensored(semesterId: Int, studentId: Int, censoredEntries: Array<String>): Flow<List<Grade>>
} }

View File

@ -0,0 +1,15 @@
package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Query
import io.github.wulkanowy.data.db.entities.GradeDescriptive
import kotlinx.coroutines.flow.Flow
import javax.inject.Singleton
@Singleton
@Dao
interface GradeDescriptiveDao : BaseDao<GradeDescriptive> {
@Query("SELECT * FROM GradesDescriptive WHERE semester_id = :semesterId AND student_id = :studentId")
fun loadAll(semesterId: Int, studentId: Int): Flow<List<GradeDescriptive>>
}

View File

@ -5,15 +5,23 @@ import androidx.room.Query
import androidx.room.Transaction import androidx.room.Transaction
import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.MessageWithAttachment import io.github.wulkanowy.data.db.entities.MessageWithAttachment
import io.github.wulkanowy.data.db.entities.MessageWithMutedAuthor
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface MessagesDao : BaseDao<Message> { interface MessagesDao : BaseDao<Message> {
@Transaction @Transaction
@Query("SELECT * FROM Messages WHERE message_global_key = :messageGlobalKey") @Query("SELECT * FROM Messages WHERE message_global_key = :messageGlobalKey")
fun loadMessageWithAttachment(messageGlobalKey: String): Flow<MessageWithAttachment?> fun loadMessageWithAttachment(messageGlobalKey: String): Flow<MessageWithAttachment?>
@Transaction
@Query("SELECT * FROM Messages WHERE mailbox_key = :mailboxKey AND folder_id = :folder ORDER BY date DESC")
fun loadMessagesWithMutedAuthor(mailboxKey: String, folder: Int): Flow<List<MessageWithMutedAuthor>>
@Transaction
@Query("SELECT * FROM Messages WHERE email = :email AND folder_id = :folder ORDER BY date DESC")
fun loadMessagesWithMutedAuthor(folder: Int, email: String): Flow<List<MessageWithMutedAuthor>>
@Query("SELECT * FROM Messages WHERE mailbox_key = :mailboxKey AND folder_id = :folder ORDER BY date DESC") @Query("SELECT * FROM Messages WHERE mailbox_key = :mailboxKey AND folder_id = :folder ORDER BY date DESC")
fun loadAll(mailboxKey: String, folder: Int): Flow<List<Message>> fun loadAll(mailboxKey: String, folder: Int): Flow<List<Message>>

View File

@ -8,6 +8,6 @@ import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface MobileDeviceDao : BaseDao<MobileDevice> { interface MobileDeviceDao : BaseDao<MobileDevice> {
@Query("SELECT * FROM MobileDevices WHERE user_login_id = :userLoginId ORDER BY date DESC") @Query("SELECT * FROM MobileDevices WHERE user_login_id = :studentId ORDER BY date DESC")
fun loadAll(userLoginId: Int): Flow<List<MobileDevice>> fun loadAll(studentId: Int): Flow<List<MobileDevice>>
} }

View File

@ -0,0 +1,20 @@
package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import io.github.wulkanowy.data.db.entities.MutedMessageSender
@Dao
interface MutedMessageSendersDao : BaseDao<MutedMessageSender> {
@Query("SELECT COUNT(*) FROM MutedMessageSenders WHERE author = :author")
suspend fun checkMute(author: String): Boolean
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertMute(mute: MutedMessageSender): Long
@Query("DELETE FROM MutedMessageSenders WHERE author = :author")
suspend fun deleteMute(author: String)
}

View File

@ -9,7 +9,6 @@ import javax.inject.Singleton
@Singleton @Singleton
@Dao @Dao
interface NoteDao : BaseDao<Note> { interface NoteDao : BaseDao<Note> {
@Query("SELECT * FROM Notes WHERE student_id = :studentId") @Query("SELECT * FROM Notes WHERE student_id = :studentId")
fun loadAll(studentId: Int): Flow<List<Note>> fun loadAll(studentId: Int): Flow<List<Note>>
} }

View File

@ -10,6 +10,6 @@ import javax.inject.Singleton
@Singleton @Singleton
interface SchoolAnnouncementDao : BaseDao<SchoolAnnouncement> { interface SchoolAnnouncementDao : BaseDao<SchoolAnnouncement> {
@Query("SELECT * FROM SchoolAnnouncements WHERE user_login_id = :userLoginId ORDER BY date DESC") @Query("SELECT * FROM SchoolAnnouncements WHERE user_login_id = :studentId ORDER BY date DESC")
fun loadAll(userLoginId: Int): Flow<List<SchoolAnnouncement>> fun loadAll(studentId: Int): Flow<List<SchoolAnnouncement>>
} }

View File

@ -14,6 +14,6 @@ interface SemesterDao : BaseDao<Semester> {
@Insert(onConflict = OnConflictStrategy.IGNORE) @Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertSemesters(items: List<Semester>): List<Long> suspend fun insertSemesters(items: List<Semester>): List<Long>
@Query("SELECT * FROM Semesters WHERE student_id = :studentId AND class_id = :classId") @Query("SELECT * FROM Semesters WHERE (student_id = :studentId AND class_id = :classId) OR (student_id = :studentId AND class_id = 0)")
suspend fun loadAll(studentId: Int, classId: Int): List<Semester> suspend fun loadAll(studentId: Int, classId: Int): List<Semester>
} }

View File

@ -1,11 +1,18 @@
package io.github.wulkanowy.data.db.dao package io.github.wulkanowy.data.db.dao
import androidx.room.* import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentIsAuthorized
import io.github.wulkanowy.data.db.entities.StudentIsEduOne
import io.github.wulkanowy.data.db.entities.StudentName import io.github.wulkanowy.data.db.entities.StudentName
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
@ -18,6 +25,12 @@ abstract class StudentDao {
@Delete @Delete
abstract suspend fun delete(student: Student) abstract suspend fun delete(student: Student)
@Update(entity = Student::class)
abstract suspend fun update(studentIsAuthorized: StudentIsAuthorized)
@Update(entity = Student::class)
abstract suspend fun update(studentIsEduOne: StudentIsEduOne)
@Update(entity = Student::class) @Update(entity = Student::class)
abstract suspend fun update(studentNickAndAvatar: StudentNickAndAvatar) abstract suspend fun update(studentNickAndAvatar: StudentNickAndAvatar)
@ -34,11 +47,11 @@ abstract class StudentDao {
abstract suspend fun loadAll(): List<Student> abstract suspend fun loadAll(): List<Student>
@Transaction @Transaction
@Query("SELECT * FROM Students JOIN Semesters ON Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id") @Query("SELECT * FROM Students JOIN Semesters ON (Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id) OR (Students.student_id = Semesters.student_id AND Semesters.class_id = 0)")
abstract suspend fun loadStudentsWithSemesters(): Map<Student, List<Semester>> abstract suspend fun loadStudentsWithSemesters(): Map<Student, List<Semester>>
@Transaction @Transaction
@Query("SELECT * FROM Students JOIN Semesters ON Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id WHERE Students.id = :id") @Query("SELECT * FROM Students JOIN Semesters ON (Students.student_id = Semesters.student_id AND Students.class_id = Semesters.class_id) OR (Students.student_id = Semesters.student_id AND Semesters.class_id = 0) WHERE Students.id = :id")
abstract suspend fun loadStudentWithSemestersById(id: Long): Map<Student, List<Semester>> abstract suspend fun loadStudentWithSemestersById(id: Long): Map<Student, List<Semester>>
@Query("UPDATE Students SET is_current = 1 WHERE id = :id") @Query("UPDATE Students SET is_current = 1 WHERE id = :id")
@ -47,6 +60,9 @@ abstract class StudentDao {
@Query("UPDATE Students SET is_current = 0") @Query("UPDATE Students SET is_current = 0")
abstract suspend fun resetCurrent() abstract suspend fun resetCurrent()
@Query("DELETE FROM Students WHERE email = :email AND user_name = :userName")
abstract suspend fun deleteByEmailAndUserName(email: String, userName: String)
@Transaction @Transaction
open suspend fun switchCurrent(id: Long) { open suspend fun switchCurrent(id: Long) {
resetCurrent() resetCurrent()

View File

@ -15,5 +15,5 @@ interface TimetableDao : BaseDao<Timetable> {
fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow<List<Timetable>> fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow<List<Timetable>>
@Query("SELECT * FROM Timetable WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") @Query("SELECT * FROM Timetable WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
fun load(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): List<Timetable> suspend fun load(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): List<Timetable>
} }

View File

@ -4,6 +4,8 @@ import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import io.github.wulkanowy.data.enums.MessageType import io.github.wulkanowy.data.enums.MessageType
import io.github.wulkanowy.data.serializers.SafeMessageTypeEnumListSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable @Serializable
@ -34,9 +36,14 @@ data class AdminMessage(
val priority: String, val priority: String,
@SerialName("messageTypes")
@Serializable(with = SafeMessageTypeEnumListSerializer::class)
@ColumnInfo(name = "types", defaultValue = "[]") @ColumnInfo(name = "types", defaultValue = "[]")
val types: List<MessageType> = emptyList(), val types: List<MessageType> = emptyList(),
@ColumnInfo(name = "is_dismissible") @ColumnInfo(name = "is_ok_visible", defaultValue = "0")
val isDismissible: Boolean = false val isOkVisible: Boolean = false,
@ColumnInfo(name = "is_x_visible", defaultValue = "0")
val isXVisible: Boolean = false
) )

View File

@ -0,0 +1,27 @@
package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable
@Entity(tableName = "GradesDescriptive")
data class GradeDescriptive(
@ColumnInfo(name = "semester_id")
val semesterId: Int,
@ColumnInfo(name = "student_id")
val studentId: Int,
val subject: String,
val description: String,
) : Serializable {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
@ColumnInfo(name = "is_notified")
var isNotified: Boolean = true
}

View File

@ -33,7 +33,13 @@ data class GradeSummary(
@ColumnInfo(name = "points_sum") @ColumnInfo(name = "points_sum")
val pointsSum: String, val pointsSum: String,
val average: Double @ColumnInfo(name = "points_sum_all_year")
val pointsSumAllYear: String?,
val average: Double,
@ColumnInfo(name = "average_all_year")
val averageAllYear: Double? = null,
) { ) {
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
var id: Long = 0 var id: Long = 0

View File

@ -2,11 +2,15 @@ package io.github.wulkanowy.data.db.entities
import androidx.room.Embedded import androidx.room.Embedded
import androidx.room.Relation import androidx.room.Relation
import java.io.Serializable
data class MessageWithAttachment( data class MessageWithAttachment(
@Embedded @Embedded
val message: Message, val message: Message,
@Relation(parentColumn = "message_global_key", entityColumn = "message_global_key") @Relation(parentColumn = "message_global_key", entityColumn = "message_global_key")
val attachments: List<MessageAttachment> val attachments: List<MessageAttachment>,
)
@Relation(parentColumn = "correspondents", entityColumn = "author")
val mutedMessageSender: MutedMessageSender?,
) : Serializable

View File

@ -0,0 +1,12 @@
package io.github.wulkanowy.data.db.entities
import androidx.room.Embedded
import androidx.room.Relation
data class MessageWithMutedAuthor(
@Embedded
val message: Message,
@Relation(parentColumn = "correspondents", entityColumn = "author")
val mutedMessageSender: MutedMessageSender?,
)

View File

@ -9,8 +9,8 @@ import java.time.Instant
@Entity(tableName = "MobileDevices") @Entity(tableName = "MobileDevices")
data class MobileDevice( data class MobileDevice(
@ColumnInfo(name = "user_login_id") @ColumnInfo(name = "user_login_id") // todo: change column name
val userLoginId: Int, val studentId: Int,
@ColumnInfo(name = "device_id") @ColumnInfo(name = "device_id")
val deviceId: Int, val deviceId: Int,

View File

@ -0,0 +1,15 @@
package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable
@Entity(tableName = "MutedMessageSenders")
data class MutedMessageSender(
@ColumnInfo(name = "author")
val author: String,
) : Serializable {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}

View File

@ -9,14 +9,16 @@ import java.time.LocalDate
@Entity(tableName = "SchoolAnnouncements") @Entity(tableName = "SchoolAnnouncements")
data class SchoolAnnouncement( data class SchoolAnnouncement(
@ColumnInfo(name = "user_login_id") @ColumnInfo(name = "user_login_id") // todo: change column name
val userLoginId: Int, val studentId: Int,
val date: LocalDate, val date: LocalDate,
val subject: String, val subject: String,
val content: String val content: String,
val author: String? = null,
) : Serializable { ) : Serializable {
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)

View File

@ -49,6 +49,7 @@ data class Student(
@ColumnInfo(name = "student_id") @ColumnInfo(name = "student_id")
val studentId: Int, val studentId: Int,
@Deprecated("not available in VULCAN anymore")
@ColumnInfo(name = "user_login_id") @ColumnInfo(name = "user_login_id")
val userLoginId: Int, val userLoginId: Int,
@ -78,6 +79,13 @@ data class Student(
@ColumnInfo(name = "registration_date") @ColumnInfo(name = "registration_date")
val registrationDate: Instant, val registrationDate: Instant,
@ColumnInfo(name = "is_authorized", defaultValue = "0")
val isAuthorized: Boolean,
@ColumnInfo(name = "is_edu_one", defaultValue = "NULL")
val isEduOne: Boolean?,
) : Serializable { ) : Serializable {
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
@ -88,3 +96,22 @@ data class Student(
@ColumnInfo(name = "avatar_color") @ColumnInfo(name = "avatar_color")
var avatarColor = 0L var avatarColor = 0L
} }
@Entity
data class StudentIsAuthorized(
@PrimaryKey
var id: Long,
@ColumnInfo(name = "is_authorized", defaultValue = "NULL")
val isAuthorized: Boolean?,
) : Serializable
@Entity
data class StudentIsEduOne(
@PrimaryKey
var id: Long,
@ColumnInfo(name = "is_edu_one", defaultValue = "NULL")
val isEduOne: Boolean?,
) : Serializable

View File

@ -0,0 +1,10 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.DeleteColumn
import androidx.room.migration.AutoMigrationSpec
@DeleteColumn(
tableName = "AdminMessages",
columnName = "is_dismissible",
)
class Migration58 : AutoMigrationSpec

View File

@ -0,0 +1,11 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.AutoMigrationSpec
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration63 : AutoMigrationSpec {
override fun onPostMigrate(db: SupportSQLiteDatabase) {
db.execSQL("UPDATE Students SET is_edu_one = NULL WHERE is_edu_one = 0")
}
}

View File

@ -7,6 +7,6 @@ enum class AppTheme(val value: String) {
BLACK("black"); BLACK("black");
companion object { companion object {
fun getByValue(value: String) = values().find { it.value == value } ?: LIGHT fun getByValue(value: String) = entries.find { it.value == value } ?: LIGHT
} }
} }

View File

@ -0,0 +1,13 @@
package io.github.wulkanowy.data.enums
enum class AttendanceCalculatorSortingMode(private val value: String) {
ALPHABETIC("alphabetic"),
ATTENDANCE("attendance_percentage"),
LESSON_BALANCE("lesson_balance");
companion object {
fun getByValue(value: String) =
AttendanceCalculatorSortingMode.values()
.find { it.value == value } ?: ALPHABETIC
}
}

View File

@ -8,6 +8,6 @@ enum class GradeColorTheme(val value: String) : Serializable {
GRADE_COLOR("grade_color"); GRADE_COLOR("grade_color");
companion object { companion object {
fun getByValue(value: String) = values().find { it.value == value } ?: VULCAN fun getByValue(value: String) = entries.find { it.value == value } ?: VULCAN
} }
} }

View File

@ -6,6 +6,6 @@ enum class GradeExpandMode(val value: String) {
ALWAYS_EXPANDED("always"); ALWAYS_EXPANDED("always");
companion object { companion object {
fun getByValue(value: String) = values().find { it.value == value } ?: ONE fun getByValue(value: String) = entries.find { it.value == value } ?: ONE
} }
} }

View File

@ -6,6 +6,6 @@ enum class GradeSortingMode(val value: String) {
AVERAGE("average"); AVERAGE("average");
companion object { companion object {
fun getByValue(value: String) = values().find { it.value == value } ?: ALPHABETIC fun getByValue(value: String) = entries.find { it.value == value } ?: ALPHABETIC
} }
} }

View File

@ -3,5 +3,10 @@ package io.github.wulkanowy.data.enums
enum class MessageFolder(val id: Int = 1) { enum class MessageFolder(val id: Int = 1) {
RECEIVED(1), RECEIVED(1),
SENT(2), SENT(2),
TRASHED(3) TRASHED(3),
;
companion object {
fun byId(id: Int) = entries.first { it.id == id }
}
} }

View File

@ -4,6 +4,8 @@ enum class MessageType {
GENERAL_MESSAGE, GENERAL_MESSAGE,
DASHBOARD_MESSAGE, DASHBOARD_MESSAGE,
LOGIN_MESSAGE, LOGIN_MESSAGE,
LOGIN_STUDENT_SELECT_MESSAGE,
LOGIN_SYMBOL_MESSAGE,
PASS_RESET_MESSAGE, PASS_RESET_MESSAGE,
ERROR_OVERRIDE, ERROR_OVERRIDE,
} }

View File

@ -0,0 +1,11 @@
package io.github.wulkanowy.data.enums
enum class ShowAdditionalLessonsMode(val value: String) {
NONE("none"),
INLINE("inline"),
BELOW("below");
companion object {
fun getByValue(value: String) = entries.find { it.value == value } ?: INLINE
}
}

View File

@ -6,6 +6,6 @@ enum class TimetableMode(val value: String) {
SMALL_OTHER_GROUP("small"); SMALL_OTHER_GROUP("small");
companion object { companion object {
fun getByValue(value: String) = values().find { it.value == value } ?: ONLY_CURRENT_GROUP fun getByValue(value: String) = entries.find { it.value == value } ?: ONLY_CURRENT_GROUP
} }
} }

View File

@ -3,12 +3,26 @@ package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.sdk.pojo.DirectorInformation as SdkDirectorInformation import io.github.wulkanowy.sdk.pojo.DirectorInformation as SdkDirectorInformation
import io.github.wulkanowy.sdk.pojo.LastAnnouncement as SdkLastAnnouncement
@JvmName("mapDirectorInformationToEntities")
fun List<SdkDirectorInformation>.mapToEntities(student: Student) = map { fun List<SdkDirectorInformation>.mapToEntities(student: Student) = map {
SchoolAnnouncement( SchoolAnnouncement(
userLoginId = student.userLoginId, studentId = student.studentId,
date = it.date, date = it.date,
subject = it.subject, subject = it.subject,
content = it.content, content = it.content,
author = null,
)
}
@JvmName("mapLastAnnouncementsToEntities")
fun List<SdkLastAnnouncement>.mapToEntities(student: Student) = map {
SchoolAnnouncement(
studentId = student.studentId,
date = it.date,
subject = it.subject,
content = it.content,
author = it.author,
) )
} }

View File

@ -1,10 +1,12 @@
package io.github.wulkanowy.data.mappers package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradeDescriptive
import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.sdk.pojo.GradeSummary as SdkGradeSummary
import io.github.wulkanowy.sdk.pojo.Grade as SdkGrade import io.github.wulkanowy.sdk.pojo.Grade as SdkGrade
import io.github.wulkanowy.sdk.pojo.GradeDescriptive as SdkGradeDescriptive
import io.github.wulkanowy.sdk.pojo.GradeSummary as SdkGradeSummary
fun List<SdkGrade>.mapToEntities(semester: Semester) = map { fun List<SdkGrade>.mapToEntities(semester: Semester) = map {
Grade( Grade(
@ -35,8 +37,22 @@ fun List<SdkGradeSummary>.mapToEntities(semester: Semester) = map {
predictedGrade = it.predicted, predictedGrade = it.predicted,
finalGrade = it.final, finalGrade = it.final,
pointsSum = it.pointsSum, pointsSum = it.pointsSum,
pointsSumAllYear = it.pointsSumAllYear,
proposedPoints = it.proposedPoints, proposedPoints = it.proposedPoints,
finalPoints = it.finalPoints, finalPoints = it.finalPoints,
average = it.average average = it.average,
averageAllYear = it.averageAllYear,
) )
} }
@JvmName("mapGradeDescriptiveToEntities")
fun List<SdkGradeDescriptive>.mapToEntities(semester: Semester) = map {
GradeDescriptive(
semesterId = semester.semesterId,
studentId = semester.studentId,
subject = it.subject,
description = it.description
)
}

View File

@ -8,7 +8,7 @@ import io.github.wulkanowy.sdk.pojo.Token as SdkToken
fun List<SdkDevice>.mapToEntities(student: Student) = map { fun List<SdkDevice>.mapToEntities(student: Student) = map {
MobileDevice( MobileDevice(
userLoginId = student.userLoginId, studentId = student.studentId,
date = it.createDate.toInstant(), date = it.createDate.toInstant(),
deviceId = it.id, deviceId = it.id,
name = it.name name = it.name

View File

@ -34,17 +34,19 @@ fun SdkRegisterUser.mapToPojo(password: String?) = RegisterUser(
error = it.error, error = it.error,
students = it.subjects students = it.subjects
.filterIsInstance<SdkRegisterStudent>() .filterIsInstance<SdkRegisterStudent>()
.map { registerSubject -> .map { registerStudent ->
RegisterStudent( RegisterStudent(
studentId = registerSubject.studentId, studentId = registerStudent.studentId,
studentName = registerSubject.studentName, studentName = registerStudent.studentName,
studentSecondName = registerSubject.studentSecondName, studentSecondName = registerStudent.studentSecondName,
studentSurname = registerSubject.studentSurname, studentSurname = registerStudent.studentSurname,
className = registerSubject.className, className = registerStudent.className,
classId = registerSubject.classId, classId = registerStudent.classId,
isParent = registerSubject.isParent, isParent = registerStudent.isParent,
semesters = registerSubject.semesters isAuthorized = registerStudent.isAuthorized,
.mapToEntities(registerSubject.studentId), isEduOne = registerStudent.isEduOne,
semesters = registerStudent.semesters
.mapToEntities(registerStudent.studentId),
) )
}, },
) )
@ -84,6 +86,8 @@ fun RegisterStudent.mapToStudentWithSemesters(
password = user.password.orEmpty(), password = user.password.orEmpty(),
isCurrent = false, isCurrent = false,
registrationDate = Instant.now(), registrationDate = Instant.now(),
isAuthorized = this.isAuthorized,
isEduOne = this.isEduOne,
).apply { ).apply {
avatarColor = colors.random() avatarColor = colors.random()
}, },

View File

@ -0,0 +1,14 @@
package io.github.wulkanowy.data.pojos
data class AttendanceData(
val subjectName: String,
val lessonBalance: Int,
val presences: Int,
val absences: Int,
) {
val total: Int
get() = presences + absences
val presencePercentage: Double
get() = if (total == 0) 0.0 else (presences.toDouble() / total) * 100
}

View File

@ -45,4 +45,6 @@ data class RegisterStudent(
val classId: Int, val classId: Int,
val isParent: Boolean, val isParent: Boolean,
val semesters: List<Semester>, val semesters: List<Semester>,
val isAuthorized: Boolean,
val isEduOne: Boolean
) : java.io.Serializable ) : java.io.Serializable

View File

@ -1,33 +0,0 @@
package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.Resource
import io.github.wulkanowy.data.api.AdminMessageService
import io.github.wulkanowy.data.db.dao.AdminMessageDao
import io.github.wulkanowy.data.db.entities.AdminMessage
import io.github.wulkanowy.data.networkBoundResource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class AdminMessageRepository @Inject constructor(
private val adminMessageService: AdminMessageService,
private val adminMessageDao: AdminMessageDao,
) {
private val saveFetchResultMutex = Mutex()
fun getAdminMessages(): Flow<Resource<List<AdminMessage>>> =
networkBoundResource(
mutex = saveFetchResultMutex,
isResultEmpty = { false },
query = { adminMessageDao.loadAll() },
fetch = { adminMessageService.getAdminMessages() },
shouldFetch = { true },
saveFetchResult = { oldItems, newItems ->
adminMessageDao.removeOldAndSaveNew(oldItems, newItems)
},
showSavedOnLoading = false,
)
}

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.AttendanceDao import io.github.wulkanowy.data.db.dao.AttendanceDao
import io.github.wulkanowy.data.db.dao.TimetableDao import io.github.wulkanowy.data.db.dao.TimetableDao
import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Attendance
@ -7,13 +8,17 @@ import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.pojo.Absent import io.github.wulkanowy.sdk.pojo.Absent
import io.github.wulkanowy.utils.* import io.github.wulkanowy.sdk.pojo.Attendance as SdkAttendance
import kotlinx.coroutines.Dispatchers import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.sunday
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.withContext
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.LocalTime import java.time.LocalTime
@ -24,14 +29,73 @@ import javax.inject.Singleton
class AttendanceRepository @Inject constructor( class AttendanceRepository @Inject constructor(
private val attendanceDb: AttendanceDao, private val attendanceDb: AttendanceDao,
private val timetableDb: TimetableDao, private val timetableDb: TimetableDao,
private val sdk: Sdk, private val wulkanowySdkFactory: WulkanowySdkFactory,
private val refreshHelper: AutoRefreshHelper, private val refreshHelper: AutoRefreshHelper,
private val preferencesRepository: PreferencesRepository
) { ) {
private val saveFetchResultMutex = Mutex() private val saveFetchResultMutex = Mutex()
private val cacheKey = "attendance" private val cacheKey = "attendance"
private fun filterAttendance(
hiddenAttendanceTiles: List<DashboardItem.HiddenAttendanceTile>,
attendanceItem: Attendance
): Boolean {
return when {
attendanceItem.absence && attendanceItem.excused && hiddenAttendanceTiles.contains(
DashboardItem.HiddenAttendanceTile.EXCUSED_ABSENCE
) -> false
attendanceItem.absence && !attendanceItem.excused && hiddenAttendanceTiles.contains(
DashboardItem.HiddenAttendanceTile.UNEXCUSED_ABSENCE
) -> false
attendanceItem.lateness && attendanceItem.excused && hiddenAttendanceTiles.contains(
DashboardItem.HiddenAttendanceTile.EXCUSED_LATENESS
) -> false
attendanceItem.lateness && !attendanceItem.excused && hiddenAttendanceTiles.contains(
DashboardItem.HiddenAttendanceTile.UNEXCUSED_LATENESS
) -> false
attendanceItem.exemption && hiddenAttendanceTiles.contains(DashboardItem.HiddenAttendanceTile.EXEMPTION) -> false
attendanceItem.deleted && hiddenAttendanceTiles.contains(DashboardItem.HiddenAttendanceTile.DELETED) -> false
attendanceItem.presence && hiddenAttendanceTiles.contains(DashboardItem.HiddenAttendanceTile.PRESENT) -> false
else -> !hiddenAttendanceTiles.contains(DashboardItem.HiddenAttendanceTile.UNKNOWN)
}
}
private fun filterAttendance(
hiddenAttendanceTiles: List<DashboardItem.HiddenAttendanceTile>,
attendanceItem: SdkAttendance
): Boolean {
return when {
attendanceItem.absence && attendanceItem.excused && hiddenAttendanceTiles.contains(
DashboardItem.HiddenAttendanceTile.EXCUSED_ABSENCE
) -> false
attendanceItem.absence && !attendanceItem.excused && hiddenAttendanceTiles.contains(
DashboardItem.HiddenAttendanceTile.UNEXCUSED_ABSENCE
) -> false
attendanceItem.lateness && attendanceItem.excused && hiddenAttendanceTiles.contains(
DashboardItem.HiddenAttendanceTile.EXCUSED_LATENESS
) -> false
attendanceItem.lateness && !attendanceItem.excused && hiddenAttendanceTiles.contains(
DashboardItem.HiddenAttendanceTile.UNEXCUSED_LATENESS
) -> false
attendanceItem.exemption && hiddenAttendanceTiles.contains(DashboardItem.HiddenAttendanceTile.EXEMPTION) -> false
attendanceItem.deleted && hiddenAttendanceTiles.contains(DashboardItem.HiddenAttendanceTile.DELETED) -> false
attendanceItem.presence && hiddenAttendanceTiles.contains(DashboardItem.HiddenAttendanceTile.PRESENT) -> false
else -> !hiddenAttendanceTiles.contains(DashboardItem.HiddenAttendanceTile.UNKNOWN)
}
}
fun getAttendance( fun getAttendance(
student: Student, student: Student,
semester: Semester, semester: Semester,
@ -49,26 +113,34 @@ class AttendanceRepository @Inject constructor(
it.isEmpty() || forceRefresh || isExpired it.isEmpty() || forceRefresh || isExpired
}, },
query = { query = {
attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday) val hiddenAttendanceItems = preferencesRepository.hiddenAttendanceItems
attendanceDb
.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday)
.map {
it.filter { item -> filterAttendance(hiddenAttendanceItems, item) }
}
}, },
fetch = { fetch = {
val lessons = withContext(Dispatchers.IO) { val hiddenAttendanceItems = preferencesRepository.hiddenAttendanceItems
timetableDb.load(
val lessons = timetableDb.load(
semester.diaryId, semester.studentId, start.monday, end.sunday semester.diaryId, semester.studentId, start.monday, end.sunday
) )
}
sdk.init(student) wulkanowySdkFactory.create(student, semester)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getAttendance(start.monday, end.sunday) .getAttendance(start.monday, end.sunday)
.filter { item -> filterAttendance(hiddenAttendanceItems, item) }
.mapToEntities(semester, lessons) .mapToEntities(semester, lessons)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
attendanceDb.deleteAll(old uniqueSubtract new)
val attendanceToAdd = (new uniqueSubtract old).map { newAttendance -> val attendanceToAdd = (new uniqueSubtract old).map { newAttendance ->
newAttendance.apply { if (notify) isNotified = false } newAttendance.apply { if (notify) isNotified = false }
} }
attendanceDb.insertAll(attendanceToAdd) attendanceDb.removeOldAndSaveNew(
oldItems = old uniqueSubtract new,
newItems = attendanceToAdd,
)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
}, },
filterResult = { it.filter { item -> item.date in start..end } } filterResult = { it.filter { item -> item.date in start..end } }
@ -79,7 +151,13 @@ class AttendanceRepository @Inject constructor(
start: LocalDate, start: LocalDate,
end: LocalDate end: LocalDate
): Flow<List<Attendance>> { ): Flow<List<Attendance>> {
return attendanceDb.loadAll(semester.diaryId, semester.studentId, start, end) val hiddenAttendanceItems = preferencesRepository.hiddenAttendanceItems
return attendanceDb
.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday)
.map {
it.filter { item -> filterAttendance(hiddenAttendanceItems, item) }
}
} }
suspend fun updateTimetable(timetable: List<Attendance>) { suspend fun updateTimetable(timetable: List<Attendance>) {
@ -87,8 +165,10 @@ class AttendanceRepository @Inject constructor(
} }
suspend fun excuseForAbsence( suspend fun excuseForAbsence(
student: Student, semester: Semester, student: Student,
absenceList: List<Attendance>, reason: String? = null semester: Semester,
absenceList: List<Attendance>,
reason: String? = null
) { ) {
val items = absenceList.map { attendance -> val items = absenceList.map { attendance ->
Absent( Absent(
@ -96,8 +176,7 @@ class AttendanceRepository @Inject constructor(
timeId = attendance.timeId timeId = attendance.timeId
) )
} }
sdk.init(student) wulkanowySdkFactory.create(student, semester)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.excuseForAbsence(items, reason) .excuseForAbsence(items, reason)
} }
} }

View File

@ -1,14 +1,15 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import androidx.room.withTransaction
import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject import javax.inject.Inject
@ -17,8 +18,9 @@ import javax.inject.Singleton
@Singleton @Singleton
class AttendanceSummaryRepository @Inject constructor( class AttendanceSummaryRepository @Inject constructor(
private val attendanceDb: AttendanceSummaryDao, private val attendanceDb: AttendanceSummaryDao,
private val sdk: Sdk,
private val refreshHelper: AutoRefreshHelper, private val refreshHelper: AutoRefreshHelper,
private val appDatabase: AppDatabase,
private val wulkanowySdkFactory: WulkanowySdkFactory,
) { ) {
private val saveFetchResultMutex = Mutex() private val saveFetchResultMutex = Mutex()
@ -39,14 +41,15 @@ class AttendanceSummaryRepository @Inject constructor(
}, },
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) }, query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) },
fetch = { fetch = {
sdk.init(student) wulkanowySdkFactory.create(student, semester)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getAttendanceSummary(subjectId) .getAttendanceSummary(subjectId)
.mapToEntities(semester, subjectId) .mapToEntities(semester, subjectId)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
appDatabase.withTransaction {
attendanceDb.deleteAll(old uniqueSubtract new) attendanceDb.deleteAll(old uniqueSubtract new)
attendanceDb.insertAll(new uniqueSubtract old) attendanceDb.insertAll(new uniqueSubtract old)
}
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
} }
) )

View File

@ -1,12 +1,16 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.sunday
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import java.time.LocalDate import java.time.LocalDate
import javax.inject.Inject import javax.inject.Inject
@ -15,7 +19,7 @@ import javax.inject.Singleton
@Singleton @Singleton
class CompletedLessonsRepository @Inject constructor( class CompletedLessonsRepository @Inject constructor(
private val completedLessonsDb: CompletedLessonsDao, private val completedLessonsDb: CompletedLessonsDao,
private val sdk: Sdk, private val wulkanowySdkFactory: WulkanowySdkFactory,
private val refreshHelper: AutoRefreshHelper, private val refreshHelper: AutoRefreshHelper,
) { ) {
@ -47,14 +51,15 @@ class CompletedLessonsRepository @Inject constructor(
) )
}, },
fetch = { fetch = {
sdk.init(student) wulkanowySdkFactory.create(student, semester)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getCompletedLessons(start.monday, end.sunday) .getCompletedLessons(start.monday, end.sunday)
.mapToEntities(semester) .mapToEntities(semester)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
completedLessonsDb.deleteAll(old uniqueSubtract new) completedLessonsDb.removeOldAndSaveNew(
completedLessonsDb.insertAll(new uniqueSubtract old) oldItems = old uniqueSubtract new,
newItems = new uniqueSubtract old,
)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
}, },
filterResult = { it.filter { item -> item.date in start..end } } filterResult = { it.filter { item -> item.date in start..end } }

View File

@ -1,15 +1,14 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.ConferenceDao import io.github.wulkanowy.data.db.dao.ConferenceDao
import io.github.wulkanowy.data.db.entities.Conference import io.github.wulkanowy.data.db.entities.Conference
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
@ -20,7 +19,7 @@ import javax.inject.Singleton
@Singleton @Singleton
class ConferenceRepository @Inject constructor( class ConferenceRepository @Inject constructor(
private val conferenceDb: ConferenceDao, private val conferenceDb: ConferenceDao,
private val sdk: Sdk, private val wulkanowySdkFactory: WulkanowySdkFactory,
private val refreshHelper: AutoRefreshHelper, private val refreshHelper: AutoRefreshHelper,
) { ) {
@ -45,19 +44,18 @@ class ConferenceRepository @Inject constructor(
conferenceDb.loadAll(semester.diaryId, student.studentId, startDate) conferenceDb.loadAll(semester.diaryId, student.studentId, startDate)
}, },
fetch = { fetch = {
sdk.init(student) wulkanowySdkFactory.create(student, semester)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getConferences() .getConferences()
.mapToEntities(semester) .mapToEntities(semester)
.filter { it.date >= startDate } .filter { it.date >= startDate }
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
val conferencesToSave = (new uniqueSubtract old).onEach { conferenceDb.removeOldAndSaveNew(
oldItems = old uniqueSubtract new,
newItems = (new uniqueSubtract old).onEach {
if (notify) it.isNotified = false if (notify) it.isNotified = false
} },
)
conferenceDb.deleteAll(old uniqueSubtract new)
conferenceDb.insertAll(conferencesToSave)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
} }
) )

View File

@ -1,13 +1,17 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.ExamDao import io.github.wulkanowy.data.db.dao.ExamDao
import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.endExamsDay
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.startExamsDay
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import java.time.LocalDate import java.time.LocalDate
@ -17,7 +21,7 @@ import javax.inject.Singleton
@Singleton @Singleton
class ExamRepository @Inject constructor( class ExamRepository @Inject constructor(
private val examDb: ExamDao, private val examDb: ExamDao,
private val sdk: Sdk, private val wulkanowySdkFactory: WulkanowySdkFactory,
private val refreshHelper: AutoRefreshHelper, private val refreshHelper: AutoRefreshHelper,
) { ) {
@ -50,31 +54,32 @@ class ExamRepository @Inject constructor(
) )
}, },
fetch = { fetch = {
sdk.init(student) wulkanowySdkFactory.create(student, semester)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getExams(start.startExamsDay, start.endExamsDay) .getExams(start.startExamsDay, start.endExamsDay)
.mapToEntities(semester) .mapToEntities(semester)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
val examsToSave = (new uniqueSubtract old).onEach { examDb.removeOldAndSaveNew(
oldItems = old uniqueSubtract new,
newItems = (new uniqueSubtract old).onEach {
if (notify) it.isNotified = false if (notify) it.isNotified = false
} },
)
examDb.deleteAll(old uniqueSubtract new)
examDb.insertAll(examsToSave)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
}, },
filterResult = { it.filter { item -> item.date in start..end } } filterResult = { it.filter { item -> item.date in start..end } }
) )
fun getExamsFromDatabase(semester: Semester, start: LocalDate): Flow<List<Exam>> { fun getExamsFromDatabase(
return examDb.loadAll( semester: Semester,
start: LocalDate,
end: LocalDate
): Flow<List<Exam>> = examDb.loadAll(
diaryId = semester.diaryId, diaryId = semester.diaryId,
studentId = semester.studentId, studentId = semester.studentId,
from = start.startExamsDay, from = start,
end = start.endExamsDay end = end,
) )
}
suspend fun updateExam(exam: List<Exam>) = examDb.updateAll(exam) suspend fun updateExam(exam: List<Exam>) = examDb.updateAll(exam)
} }

View File

@ -1,19 +1,25 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.GradeDao import io.github.wulkanowy.data.db.dao.GradeDao
import io.github.wulkanowy.data.db.dao.GradeDescriptiveDao
import io.github.wulkanowy.data.db.dao.GradeSummaryDao import io.github.wulkanowy.data.db.dao.GradeSummaryDao
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradeDescriptive
import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.toLocalDate
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import timber.log.Timber
import java.time.Instant import java.time.Instant
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -22,13 +28,20 @@ import javax.inject.Singleton
class GradeRepository @Inject constructor( class GradeRepository @Inject constructor(
private val gradeDb: GradeDao, private val gradeDb: GradeDao,
private val gradeSummaryDb: GradeSummaryDao, private val gradeSummaryDb: GradeSummaryDao,
private val sdk: Sdk, private val gradeDescriptiveDb: GradeDescriptiveDao,
private val wulkanowySdkFactory: WulkanowySdkFactory,
private val refreshHelper: AutoRefreshHelper, private val refreshHelper: AutoRefreshHelper,
private val preferencesRepository: PreferencesRepository
) { ) {
private val saveFetchResultMutex = Mutex() private val saveFetchResultMutex = Mutex()
private val cacheKey = "grade" private fun loadGrades(semesterId: Int, studentId: Int): Flow<List<Grade>> {
val hiddenGrades = preferencesRepository.hiddenGrades
Timber.i("Load grades for semester $semesterId student $studentId")
return gradeDb.loadAllCensored(semesterId, studentId, hiddenGrades.toTypedArray())
}
fun getGrades( fun getGrades(
student: Student, student: Student,
@ -41,30 +54,57 @@ class GradeRepository @Inject constructor(
//When details is empty and summary is not, app will not use summary cache - edge case //When details is empty and summary is not, app will not use summary cache - edge case
it.first.isEmpty() it.first.isEmpty()
}, },
shouldFetch = { (details, summaries) -> shouldFetch = { (details, summaries, descriptive) ->
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) val isExpired =
details.isEmpty() || summaries.isEmpty() || forceRefresh || isExpired refreshHelper.shouldBeRefreshed(getRefreshKey(GRADE_CACHE_KEY, semester))
details.isEmpty() || (summaries.isEmpty() && descriptive.isEmpty()) || forceRefresh || isExpired
}, },
query = { query = {
val detailsFlow = gradeDb.loadAll(semester.semesterId, semester.studentId) val detailsFlow = gradeDb.loadAll(semester.semesterId, semester.studentId)
val summaryFlow = gradeSummaryDb.loadAll(semester.semesterId, semester.studentId) val summaryFlow = gradeSummaryDb.loadAll(semester.semesterId, semester.studentId)
detailsFlow.combine(summaryFlow) { details, summaries -> details to summaries } val descriptiveFlow =
gradeDescriptiveDb.loadAll(semester.semesterId, semester.studentId)
combine(detailsFlow, summaryFlow, descriptiveFlow) { details, summaries, descriptive ->
Triple(details, summaries, descriptive)
}
}, },
fetch = { fetch = {
val (details, summary) = sdk.init(student) val hiddenGrades = preferencesRepository.hiddenGrades
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
val (details, summary, descriptive) = wulkanowySdkFactory.create(student, semester)
.getGrades(semester.semesterId) .getGrades(semester.semesterId)
details.mapToEntities(semester) to summary.mapToEntities(semester) val censoredDetails = details.filterNot { it.entry in hiddenGrades }
Triple(
censoredDetails.mapToEntities(semester),
summary.mapToEntities(semester),
descriptive.mapToEntities(semester)
)
}, },
saveFetchResult = { (oldDetails, oldSummary), (newDetails, newSummary) -> saveFetchResult = { (oldDetails, oldSummary, oldDescriptive), (newDetails, newSummary, newDescriptive) ->
refreshGradeDetails(student, oldDetails, newDetails, notify) refreshGradeDetails(student, oldDetails, newDetails, notify)
refreshGradeSummaries(oldSummary, newSummary, notify) refreshGradeSummaries(oldSummary, newSummary, notify)
refreshGradeDescriptions(oldDescriptive, newDescriptive, notify)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(GRADE_CACHE_KEY, semester))
} }
) )
private suspend fun refreshGradeDescriptions(
old: List<GradeDescriptive>,
new: List<GradeDescriptive>,
notify: Boolean
) {
gradeDescriptiveDb.removeOldAndSaveNew(
oldItems = old uniqueSubtract new,
newItems = (new uniqueSubtract old).onEach {
if (notify) it.isNotified = false
},
)
}
private suspend fun refreshGradeDetails( private suspend fun refreshGradeDetails(
student: Student, student: Student,
oldGrades: List<Grade>, oldGrades: List<Grade>,
@ -73,13 +113,16 @@ class GradeRepository @Inject constructor(
) { ) {
val notifyBreakDate = oldGrades.maxByOrNull { it.date }?.date val notifyBreakDate = oldGrades.maxByOrNull { it.date }?.date
?: student.registrationDate.toLocalDate() ?: student.registrationDate.toLocalDate()
gradeDb.deleteAll(oldGrades uniqueSubtract newDetails)
gradeDb.insertAll((newDetails uniqueSubtract oldGrades).onEach { gradeDb.removeOldAndSaveNew(
oldItems = oldGrades uniqueSubtract newDetails,
newItems = (newDetails uniqueSubtract oldGrades).onEach {
if (it.date >= notifyBreakDate) it.apply { if (it.date >= notifyBreakDate) it.apply {
isRead = false isRead = false
if (notify) isNotified = false if (notify) isNotified = false
} }
}) },
)
} }
private suspend fun refreshGradeSummaries( private suspend fun refreshGradeSummaries(
@ -87,9 +130,23 @@ class GradeRepository @Inject constructor(
newSummary: List<GradeSummary>, newSummary: List<GradeSummary>,
notify: Boolean notify: Boolean
) { ) {
gradeSummaryDb.deleteAll(oldSummaries uniqueSubtract newSummary) gradeSummaryDb.removeOldAndSaveNew(
gradeSummaryDb.insertAll((newSummary uniqueSubtract oldSummaries).onEach { summary -> oldItems = oldSummaries uniqueSubtract newSummary,
val oldSummary = oldSummaries.find { old -> old.subject == summary.subject } newItems = (newSummary uniqueSubtract oldSummaries).onEach { summary ->
getGradeSummaryWithUpdatedNotificationState(
summary = summary,
oldSummary = oldSummaries.find { it.subject == summary.subject },
notify = notify,
)
},
)
}
private fun getGradeSummaryWithUpdatedNotificationState(
summary: GradeSummary,
oldSummary: GradeSummary?,
notify: Boolean,
) {
summary.isPredictedGradeNotified = when { summary.isPredictedGradeNotified = when {
summary.predictedGrade.isEmpty() -> true summary.predictedGrade.isEmpty() -> true
notify && oldSummary?.predictedGrade != summary.predictedGrade -> false notify && oldSummary?.predictedGrade != summary.predictedGrade -> false
@ -100,7 +157,6 @@ class GradeRepository @Inject constructor(
notify && oldSummary?.finalGrade != summary.finalGrade -> false notify && oldSummary?.finalGrade != summary.finalGrade -> false
else -> true else -> true
} }
summary.predictedGradeLastChange = when { summary.predictedGradeLastChange = when {
oldSummary == null -> Instant.now() oldSummary == null -> Instant.now()
summary.predictedGrade != oldSummary.predictedGrade -> Instant.now() summary.predictedGrade != oldSummary.predictedGrade -> Instant.now()
@ -111,17 +167,16 @@ class GradeRepository @Inject constructor(
summary.finalGrade != oldSummary.finalGrade -> Instant.now() summary.finalGrade != oldSummary.finalGrade -> Instant.now()
else -> oldSummary.finalGradeLastChange else -> oldSummary.finalGradeLastChange
} }
})
} }
fun getUnreadGrades(semester: Semester): Flow<List<Grade>> { fun getUnreadGrades(semester: Semester): Flow<List<Grade>> {
return gradeDb.loadAll(semester.semesterId, semester.studentId).map { return loadGrades(semester.semesterId, semester.studentId).map {
it.filter { grade -> !grade.isRead } it.filter { grade -> !grade.isRead }
} }
} }
fun getGradesFromDatabase(semester: Semester): Flow<List<Grade>> { fun getGradesFromDatabase(semester: Semester): Flow<List<Grade>> {
return gradeDb.loadAll(semester.semesterId, semester.studentId) return loadGrades(semester.semesterId, semester.studentId)
} }
fun getGradesPredictedFromDatabase(semester: Semester): Flow<List<GradeSummary>> { fun getGradesPredictedFromDatabase(semester: Semester): Flow<List<GradeSummary>> {
@ -132,6 +187,10 @@ class GradeRepository @Inject constructor(
return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId) return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId)
} }
fun getGradesDescriptiveFromDatabase(semester: Semester): Flow<List<GradeDescriptive>> {
return gradeDescriptiveDb.loadAll(semester.semesterId, semester.studentId)
}
suspend fun updateGrade(grade: Grade) { suspend fun updateGrade(grade: Grade) {
return gradeDb.updateAll(listOf(grade)) return gradeDb.updateAll(listOf(grade))
} }
@ -143,4 +202,13 @@ class GradeRepository @Inject constructor(
suspend fun updateGradesSummary(gradesSummary: List<GradeSummary>) { suspend fun updateGradesSummary(gradesSummary: List<GradeSummary>) {
return gradeSummaryDb.updateAll(gradesSummary) return gradeSummaryDb.updateAll(gradesSummary)
} }
suspend fun updateGradesDescriptive(gradesDescriptive: List<GradeDescriptive>) {
return gradeDescriptiveDb.updateAll(gradesDescriptive)
}
private companion object {
private const val GRADE_CACHE_KEY = "grade"
}
} }

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao
import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao
import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao
@ -12,13 +13,11 @@ import io.github.wulkanowy.data.mappers.mapPointsToStatisticsItems
import io.github.wulkanowy.data.mappers.mapSemesterToStatisticItems import io.github.wulkanowy.data.mappers.mapSemesterToStatisticItems
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import java.util.* import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -27,7 +26,7 @@ class GradeStatisticsRepository @Inject constructor(
private val gradePartialStatisticsDb: GradePartialStatisticsDao, private val gradePartialStatisticsDb: GradePartialStatisticsDao,
private val gradePointsStatisticsDb: GradePointsStatisticsDao, private val gradePointsStatisticsDb: GradePointsStatisticsDao,
private val gradeSemesterStatisticsDb: GradeSemesterStatisticsDao, private val gradeSemesterStatisticsDb: GradeSemesterStatisticsDao,
private val sdk: Sdk, private val wulkanowySdkFactory: WulkanowySdkFactory,
private val refreshHelper: AutoRefreshHelper, private val refreshHelper: AutoRefreshHelper,
) { ) {
@ -55,14 +54,15 @@ class GradeStatisticsRepository @Inject constructor(
}, },
query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) }, query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
fetch = { fetch = {
sdk.init(student) wulkanowySdkFactory.create(student, semester)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getGradesPartialStatistics(semester.semesterId) .getGradesPartialStatistics(semester.semesterId)
.mapToEntities(semester) .mapToEntities(semester)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
gradePartialStatisticsDb.deleteAll(old uniqueSubtract new) gradePartialStatisticsDb.removeOldAndSaveNew(
gradePartialStatisticsDb.insertAll(new uniqueSubtract old) oldItems = old uniqueSubtract new,
newItems = new uniqueSubtract old,
)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(partialCacheKey, semester)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(partialCacheKey, semester))
}, },
mapResult = { items -> mapResult = { items ->
@ -79,6 +79,7 @@ class GradeStatisticsRepository @Inject constructor(
) )
listOf(summaryItem) + items listOf(summaryItem) + items
} }
else -> items.filter { it.subject == subjectName } else -> items.filter { it.subject == subjectName }
}.mapPartialToStatisticItems() }.mapPartialToStatisticItems()
} }
@ -100,14 +101,15 @@ class GradeStatisticsRepository @Inject constructor(
}, },
query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) }, query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
fetch = { fetch = {
sdk.init(student) wulkanowySdkFactory.create(student, semester)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getGradesSemesterStatistics(semester.semesterId) .getGradesSemesterStatistics(semester.semesterId)
.mapToEntities(semester) .mapToEntities(semester)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
gradeSemesterStatisticsDb.deleteAll(old uniqueSubtract new) gradeSemesterStatisticsDb.removeOldAndSaveNew(
gradeSemesterStatisticsDb.insertAll(new uniqueSubtract old) oldItems = old uniqueSubtract new,
newItems = new uniqueSubtract old,
)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(semesterCacheKey, semester)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(semesterCacheKey, semester))
}, },
mapResult = { items -> mapResult = { items ->
@ -137,6 +139,7 @@ class GradeStatisticsRepository @Inject constructor(
} }
listOf(summaryItem) + itemsWithAverage listOf(summaryItem) + itemsWithAverage
} }
else -> itemsWithAverage.filter { it.subject == subjectName } else -> itemsWithAverage.filter { it.subject == subjectName }
}.mapSemesterToStatisticItems() }.mapSemesterToStatisticItems()
} }
@ -156,14 +159,15 @@ class GradeStatisticsRepository @Inject constructor(
}, },
query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) }, query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
fetch = { fetch = {
sdk.init(student) wulkanowySdkFactory.create(student, semester)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getGradesPointsStatistics(semester.semesterId) .getGradesPointsStatistics(semester.semesterId)
.mapToEntities(semester) .mapToEntities(semester)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
gradePointsStatisticsDb.deleteAll(old uniqueSubtract new) gradePointsStatisticsDb.removeOldAndSaveNew(
gradePointsStatisticsDb.insertAll(new uniqueSubtract old) oldItems = old uniqueSubtract new,
newItems = new uniqueSubtract old,
)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(pointsCacheKey, semester)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(pointsCacheKey, semester))
}, },
mapResult = { items -> mapResult = { items ->

View File

@ -1,13 +1,17 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.HomeworkDao import io.github.wulkanowy.data.db.dao.HomeworkDao
import io.github.wulkanowy.data.db.entities.Homework import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.sunday
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import java.time.LocalDate import java.time.LocalDate
import javax.inject.Inject import javax.inject.Inject
@ -16,7 +20,7 @@ import javax.inject.Singleton
@Singleton @Singleton
class HomeworkRepository @Inject constructor( class HomeworkRepository @Inject constructor(
private val homeworkDb: HomeworkDao, private val homeworkDb: HomeworkDao,
private val sdk: Sdk, private val wulkanowySdkFactory: WulkanowySdkFactory,
private val refreshHelper: AutoRefreshHelper, private val refreshHelper: AutoRefreshHelper,
) { ) {
@ -49,20 +53,19 @@ class HomeworkRepository @Inject constructor(
) )
}, },
fetch = { fetch = {
sdk.init(student) wulkanowySdkFactory.create(student, semester)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getHomework(start.monday, end.sunday) .getHomework(start.monday, end.sunday)
.mapToEntities(semester) .mapToEntities(semester)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
val homeWorkToSave = (new uniqueSubtract old).onEach {
if (notify) it.isNotified = false
}
val filteredOld = old.filterNot { it.isAddedByUser } val filteredOld = old.filterNot { it.isAddedByUser }
homeworkDb.deleteAll(filteredOld uniqueSubtract new) homeworkDb.removeOldAndSaveNew(
homeworkDb.insertAll(homeWorkToSave) oldItems = filteredOld uniqueSubtract new,
newItems = (new uniqueSubtract old).onEach {
if (notify) it.isNotified = false
},
)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
} }
) )

View File

@ -1,12 +1,13 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.LuckyNumberDao import io.github.wulkanowy.data.db.dao.LuckyNumberDao
import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntity import io.github.wulkanowy.data.mappers.mapToEntity
import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.ui.modules.luckynumberwidget.LuckyNumberWidgetProvider
import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.AppWidgetUpdater
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
@ -18,7 +19,8 @@ import javax.inject.Singleton
@Singleton @Singleton
class LuckyNumberRepository @Inject constructor( class LuckyNumberRepository @Inject constructor(
private val luckyNumberDb: LuckyNumberDao, private val luckyNumberDb: LuckyNumberDao,
private val sdk: Sdk private val wulkanowySdkFactory: WulkanowySdkFactory,
private val appWidgetUpdater: AppWidgetUpdater,
) { ) {
private val saveFetchResultMutex = Mutex() private val saveFetchResultMutex = Mutex()
@ -27,20 +29,28 @@ class LuckyNumberRepository @Inject constructor(
student: Student, student: Student,
forceRefresh: Boolean, forceRefresh: Boolean,
notify: Boolean = false, notify: Boolean = false,
isFromAppWidget: Boolean = false
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it == null }, isResultEmpty = { it == null },
shouldFetch = { it == null || forceRefresh }, shouldFetch = { it == null || forceRefresh },
query = { luckyNumberDb.load(student.studentId, now()) }, query = { luckyNumberDb.load(student.studentId, now()) },
fetch = { fetch = {
sdk.init(student).getLuckyNumber(student.schoolShortName)?.mapToEntity(student) wulkanowySdkFactory.create(student)
.getLuckyNumber(student.schoolShortName)
?.mapToEntity(student)
}, },
saveFetchResult = { old, new -> saveFetchResult = { oldLuckyNumber, newLuckyNumber ->
if (new != old) { newLuckyNumber ?: return@networkBoundResource
old?.let { luckyNumberDb.deleteAll(listOfNotNull(it)) }
luckyNumberDb.insertAll(listOfNotNull((new?.apply { if (newLuckyNumber != oldLuckyNumber) {
if (notify) isNotified = false luckyNumberDb.removeOldAndSaveNew(
}))) oldItems = listOfNotNull(oldLuckyNumber),
newItems = listOf(newLuckyNumber.apply { if (notify) isNotified = false }),
)
if (!isFromAppWidget) {
appWidgetUpdater.updateAllAppWidgetsByProvider(LuckyNumberWidgetProvider::class)
}
} }
} }
) )

View File

@ -4,17 +4,22 @@ import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.Resource
import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.db.dao.MailboxDao import io.github.wulkanowy.data.db.dao.MailboxDao
import io.github.wulkanowy.data.db.dao.MessageAttachmentDao import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
import io.github.wulkanowy.data.db.dao.MessagesDao import io.github.wulkanowy.data.db.dao.MessagesDao
import io.github.wulkanowy.data.db.dao.MutedMessageSendersDao
import io.github.wulkanowy.data.db.entities.Mailbox import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.MessageWithAttachment import io.github.wulkanowy.data.db.entities.MessageWithAttachment
import io.github.wulkanowy.data.db.entities.MessageWithMutedAuthor
import io.github.wulkanowy.data.db.entities.MutedMessageSender
import io.github.wulkanowy.data.db.entities.Recipient import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.enums.MessageFolder import io.github.wulkanowy.data.enums.MessageFolder
import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
import io.github.wulkanowy.data.enums.MessageFolder.SENT
import io.github.wulkanowy.data.enums.MessageFolder.TRASHED import io.github.wulkanowy.data.enums.MessageFolder.TRASHED
import io.github.wulkanowy.data.mappers.mapFromEntities import io.github.wulkanowy.data.mappers.mapFromEntities
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
@ -22,16 +27,14 @@ import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.data.onResourceError import io.github.wulkanowy.data.onResourceError
import io.github.wulkanowy.data.onResourceSuccess import io.github.wulkanowy.data.onResourceSuccess
import io.github.wulkanowy.data.pojos.MessageDraft import io.github.wulkanowy.data.pojos.MessageDraft
import io.github.wulkanowy.data.toFirstResult
import io.github.wulkanowy.data.waitForResult import io.github.wulkanowy.data.waitForResult
import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.pojo.Folder import io.github.wulkanowy.sdk.pojo.Folder
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
@ -42,8 +45,9 @@ import javax.inject.Singleton
@Singleton @Singleton
class MessageRepository @Inject constructor( class MessageRepository @Inject constructor(
private val messagesDb: MessagesDao, private val messagesDb: MessagesDao,
private val mutedMessageSendersDao: MutedMessageSendersDao,
private val messageAttachmentDao: MessageAttachmentDao, private val messageAttachmentDao: MessageAttachmentDao,
private val sdk: Sdk, private val wulkanowySdkFactory: WulkanowySdkFactory,
@ApplicationContext private val context: Context, @ApplicationContext private val context: Context,
private val refreshHelper: AutoRefreshHelper, private val refreshHelper: AutoRefreshHelper,
private val sharedPrefProvider: SharedPrefProvider, private val sharedPrefProvider: SharedPrefProvider,
@ -51,7 +55,6 @@ class MessageRepository @Inject constructor(
private val mailboxDao: MailboxDao, private val mailboxDao: MailboxDao,
private val getMailboxByStudentUseCase: GetMailboxByStudentUseCase, private val getMailboxByStudentUseCase: GetMailboxByStudentUseCase,
) { ) {
private val saveFetchResultMutex = Mutex() private val saveFetchResultMutex = Mutex()
private val messagesCacheKey = "message" private val messagesCacheKey = "message"
@ -63,7 +66,7 @@ class MessageRepository @Inject constructor(
folder: MessageFolder, folder: MessageFolder,
forceRefresh: Boolean, forceRefresh: Boolean,
notify: Boolean = false, notify: Boolean = false,
): Flow<Resource<List<Message>>> = networkBoundResource( ): Flow<Resource<List<MessageWithMutedAuthor>>> = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() }, isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
@ -74,21 +77,30 @@ class MessageRepository @Inject constructor(
}, },
query = { query = {
if (mailbox == null) { if (mailbox == null) {
messagesDb.loadAll(folder.id, student.email) messagesDb.loadMessagesWithMutedAuthor(folder.id, student.email)
} else messagesDb.loadAll(mailbox.globalKey, folder.id) } else messagesDb.loadMessagesWithMutedAuthor(mailbox.globalKey, folder.id)
}, },
fetch = { fetch = {
sdk.init(student).getMessages( wulkanowySdkFactory.create(student)
.getMessages(
folder = Folder.valueOf(folder.name), folder = Folder.valueOf(folder.name),
mailboxKey = mailbox?.globalKey, mailboxKey = mailbox?.globalKey,
).mapToEntities(student, mailbox, mailboxDao.loadAll(student.email)) )
.mapToEntities(
student = student,
mailbox = mailbox,
allMailboxes = mailboxDao.loadAll(student.email)
)
}, },
saveFetchResult = { old, new -> saveFetchResult = { oldWithAuthors, new ->
messagesDb.deleteAll(old uniqueSubtract new) val old = oldWithAuthors.map { it.message }
messagesDb.insertAll((new uniqueSubtract old).onEach { messagesDb.removeOldAndSaveNew(
it.isNotified = !notify oldItems = old uniqueSubtract new,
}) newItems = (new uniqueSubtract old).onEach {
val muted = isMuted(it.correspondents)
it.isNotified = !notify || muted
},
)
refreshHelper.updateLastRefreshTimestamp( refreshHelper.updateLastRefreshTimestamp(
getRefreshKey(messagesCacheKey, mailbox, folder) getRefreshKey(messagesCacheKey, mailbox, folder)
) )
@ -106,12 +118,11 @@ class MessageRepository @Inject constructor(
Timber.d("Message content in db empty: ${it.message.content.isBlank()}") Timber.d("Message content in db empty: ${it.message.content.isBlank()}")
(it.message.unread && markAsRead) || it.message.content.isBlank() (it.message.unread && markAsRead) || it.message.content.isBlank()
}, },
query = { query = { messagesDb.loadMessageWithAttachment(message.messageGlobalKey) },
messagesDb.loadMessageWithAttachment(message.messageGlobalKey)
},
fetch = { fetch = {
sdk.init(student).getMessageDetails( wulkanowySdkFactory.create(student)
messageKey = it!!.message.messageGlobalKey, .getMessageDetails(
messageKey = message.messageGlobalKey,
markAsRead = message.unread && markAsRead, markAsRead = message.unread && markAsRead,
) )
}, },
@ -152,19 +163,33 @@ class MessageRepository @Inject constructor(
subject: String, subject: String,
content: String, content: String,
recipients: List<Recipient>, recipients: List<Recipient>,
mailboxId: String, mailbox: Mailbox,
) { ) {
sdk.init(student).sendMessage( wulkanowySdkFactory.create(student)
.sendMessage(
subject = subject, subject = subject,
content = content, content = content,
recipients = recipients.mapFromEntities(), recipients = recipients.mapFromEntities(),
mailboxId = mailboxId, mailboxId = mailbox.globalKey,
) )
refreshFolders(student, mailbox, listOf(SENT))
} }
suspend fun deleteMessages(student: Student, mailbox: Mailbox?, messages: List<Message>) { suspend fun restoreMessages(student: Student, mailbox: Mailbox?, messages: List<Message>) {
wulkanowySdkFactory.create(student)
.restoreMessages(messages = messages.map { it.messageGlobalKey })
refreshFolders(student, mailbox)
}
suspend fun deleteMessage(student: Student, message: Message) {
deleteMessages(student, listOf(message))
}
suspend fun deleteMessages(student: Student, messages: List<Message>) {
val firstMessage = messages.first() val firstMessage = messages.first()
sdk.init(student).deleteMessages( wulkanowySdkFactory.create(student)
.deleteMessages(
messages = messages.map { it.messageGlobalKey }, messages = messages.map { it.messageGlobalKey },
removeForever = firstMessage.folderId == TRASHED.id, removeForever = firstMessage.folderId == TRASHED.id,
) )
@ -181,18 +206,24 @@ class MessageRepository @Inject constructor(
} }
messagesDb.updateAll(deletedMessages) messagesDb.updateAll(deletedMessages)
} else messagesDb.deleteAll(messages) } else {
messagesDb.deleteAll(messages)
}
}
private suspend fun refreshFolders(
student: Student,
mailbox: Mailbox?,
folders: List<MessageFolder> = MessageFolder.entries
) {
folders.forEach {
getMessages( getMessages(
student = student, student = student,
mailbox = mailbox, mailbox = mailbox,
folder = TRASHED, folder = it,
forceRefresh = true, forceRefresh = true,
).first() ).toFirstResult()
} }
suspend fun deleteMessage(student: Student, mailbox: Mailbox?, message: Message) {
deleteMessages(student, mailbox, listOf(message))
} }
suspend fun getMailboxes(student: Student, forceRefresh: Boolean) = networkBoundResource( suspend fun getMailboxes(student: Student, forceRefresh: Boolean) = networkBoundResource(
@ -206,7 +237,9 @@ class MessageRepository @Inject constructor(
}, },
query = { mailboxDao.loadAll(student.email, student.symbol, student.schoolSymbol) }, query = { mailboxDao.loadAll(student.email, student.symbol, student.schoolSymbol) },
fetch = { fetch = {
sdk.init(student).getMailboxes().mapToEntities(student) wulkanowySdkFactory.create(student)
.getMailboxes()
.mapToEntities(student)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
mailboxDao.deleteAll(old uniqueSubtract new) mailboxDao.deleteAll(old uniqueSubtract new)
@ -236,4 +269,18 @@ class MessageRepository @Inject constructor(
context.getString(R.string.pref_key_message_draft), context.getString(R.string.pref_key_message_draft),
value?.let { json.encodeToString(it) } value?.let { json.encodeToString(it) }
) )
private suspend fun isMuted(author: String): Boolean {
return mutedMessageSendersDao.checkMute(author)
}
suspend fun muteMessage(author: String) {
if (isMuted(author)) return
mutedMessageSendersDao.insertMute(MutedMessageSender(author))
}
suspend fun unmuteMessage(author: String) {
if (!isMuted(author)) return
mutedMessageSendersDao.deleteMute(author)
}
} }

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.MobileDeviceDao import io.github.wulkanowy.data.db.dao.MobileDeviceDao
import io.github.wulkanowy.data.db.entities.MobileDevice import io.github.wulkanowy.data.db.entities.MobileDevice
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
@ -8,10 +9,8 @@ import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.mappers.mapToMobileDeviceToken import io.github.wulkanowy.data.mappers.mapToMobileDeviceToken
import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.data.pojos.MobileDeviceToken import io.github.wulkanowy.data.pojos.MobileDeviceToken
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject import javax.inject.Inject
@ -20,7 +19,7 @@ import javax.inject.Singleton
@Singleton @Singleton
class MobileDeviceRepository @Inject constructor( class MobileDeviceRepository @Inject constructor(
private val mobileDb: MobileDeviceDao, private val mobileDb: MobileDeviceDao,
private val sdk: Sdk, private val wulkanowySdkFactory: WulkanowySdkFactory,
private val refreshHelper: AutoRefreshHelper, private val refreshHelper: AutoRefreshHelper,
) { ) {
@ -39,32 +38,30 @@ class MobileDeviceRepository @Inject constructor(
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
it.isEmpty() || forceRefresh || isExpired it.isEmpty() || forceRefresh || isExpired
}, },
query = { mobileDb.loadAll(student.userLoginId) }, query = { mobileDb.loadAll(student.studentId) },
fetch = { fetch = {
sdk.init(student) wulkanowySdkFactory.create(student, semester)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getRegisteredDevices() .getRegisteredDevices()
.mapToEntities(student) .mapToEntities(student)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
mobileDb.deleteAll(old uniqueSubtract new) mobileDb.removeOldAndSaveNew(
mobileDb.insertAll(new uniqueSubtract old) oldItems = old uniqueSubtract new,
newItems = new uniqueSubtract old,
)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
} }
) )
suspend fun unregisterDevice(student: Student, semester: Semester, device: MobileDevice) { suspend fun unregisterDevice(student: Student, semester: Semester, device: MobileDevice) {
sdk.init(student) wulkanowySdkFactory.create(student, semester)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.unregisterDevice(device.deviceId) .unregisterDevice(device.deviceId)
mobileDb.deleteAll(listOf(device)) mobileDb.deleteAll(listOf(device))
} }
suspend fun getToken(student: Student, semester: Semester): MobileDeviceToken { suspend fun getToken(student: Student, semester: Semester): MobileDeviceToken {
return sdk.init(student) return wulkanowySdkFactory.create(student, semester)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getToken() .getToken()
.mapToMobileDeviceToken() .mapToMobileDeviceToken()
} }

View File

@ -1,13 +1,16 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.NoteDao import io.github.wulkanowy.data.db.dao.NoteDao
import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.data.db.entities.Note
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.toLocalDate
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject import javax.inject.Inject
@ -16,8 +19,9 @@ import javax.inject.Singleton
@Singleton @Singleton
class NoteRepository @Inject constructor( class NoteRepository @Inject constructor(
private val noteDb: NoteDao, private val noteDb: NoteDao,
private val sdk: Sdk, private val wulkanowySdkFactory: WulkanowySdkFactory,
private val refreshHelper: AutoRefreshHelper, private val refreshHelper: AutoRefreshHelper,
private val preferencesRepository: PreferencesRepository
) { ) {
private val saveFetchResultMutex = Mutex() private val saveFetchResultMutex = Mutex()
@ -36,24 +40,29 @@ class NoteRepository @Inject constructor(
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
getRefreshKey(cacheKey, semester) getRefreshKey(cacheKey, semester)
) )
it.isEmpty() || forceRefresh || isExpired it.isEmpty() || forceRefresh || isExpired
}, },
query = { noteDb.loadAll(student.studentId) }, query = { noteDb.loadAll(student.studentId) },
fetch = { fetch = {
sdk.init(student) val showNotes = preferencesRepository.showNotes
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
wulkanowySdkFactory.create(student, semester)
.getNotes() .getNotes()
.filter { showNotes }
.mapToEntities(semester) .mapToEntities(semester)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
noteDb.deleteAll(old uniqueSubtract new) val notesToAdd = (new uniqueSubtract old).onEach {
noteDb.insertAll((new uniqueSubtract old).onEach {
if (it.date >= student.registrationDate.toLocalDate()) it.apply { if (it.date >= student.registrationDate.toLocalDate()) it.apply {
isRead = false isRead = false
if (notify) isNotified = false if (notify) isNotified = false
} }
}) }
noteDb.removeOldAndSaveNew(
oldItems = old uniqueSubtract new,
newItems = notesToAdd,
)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
} }
) )

View File

@ -7,9 +7,18 @@ import androidx.core.content.edit
import com.fredporciuncula.flow.preferences.FlowSharedPreferences import com.fredporciuncula.flow.preferences.FlowSharedPreferences
import com.fredporciuncula.flow.preferences.Preference import com.fredporciuncula.flow.preferences.Preference
import com.fredporciuncula.flow.preferences.Serializer import com.fredporciuncula.flow.preferences.Serializer
import com.fredporciuncula.flow.preferences.map
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.enums.* import io.github.wulkanowy.data.api.models.Mapping
import io.github.wulkanowy.data.enums.AppTheme
import io.github.wulkanowy.data.enums.AttendanceCalculatorSortingMode
import io.github.wulkanowy.data.enums.GradeColorTheme
import io.github.wulkanowy.data.enums.GradeExpandMode
import io.github.wulkanowy.data.enums.GradeSortingMode
import io.github.wulkanowy.data.enums.ShowAdditionalLessonsMode
import io.github.wulkanowy.data.enums.TimetableGapsMode
import io.github.wulkanowy.data.enums.TimetableMode
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
import io.github.wulkanowy.ui.modules.settings.appearance.menuorder.AppMenuItem import io.github.wulkanowy.ui.modules.settings.appearance.menuorder.AppMenuItem
@ -17,8 +26,9 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import timber.log.Timber
import java.time.Instant import java.time.Instant
import java.util.* import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -29,6 +39,7 @@ class PreferencesRepository @Inject constructor(
private val flowSharedPref: FlowSharedPreferences, private val flowSharedPref: FlowSharedPreferences,
private val json: Json, private val json: Json,
) { ) {
private val NO_ATTENDANCE_VALUE = -1.0
val isShowPresent: Boolean val isShowPresent: Boolean
get() = getBoolean( get() = getBoolean(
@ -36,6 +47,27 @@ class PreferencesRepository @Inject constructor(
R.bool.pref_default_attendance_present R.bool.pref_default_attendance_present
) )
val targetAttendanceFlow: Flow<Int>
get() = flowSharedPref.getInt(
context.getString(R.string.pref_key_attendance_target),
context.resources.getInteger(R.integer.pref_default_attendance_target)
).asFlow()
val attendanceCalculatorSortingModeFlow: Flow<AttendanceCalculatorSortingMode>
get() = flowSharedPref.getString(
context.getString(R.string.pref_key_attendance_calculator_sorting_mode),
context.resources.getString(R.string.pref_default_attendance_calculator_sorting_mode)
).asFlow().map(AttendanceCalculatorSortingMode::getByValue)
/**
* Subjects are empty when they don't have any attendances (total = 0, attendances = 0, absences = 0).
*/
val attendanceCalculatorShowEmptySubjects: Flow<Boolean>
get() = flowSharedPref.getBoolean(
context.getString(R.string.pref_key_attendance_calculator_show_empty_subjects),
context.resources.getBoolean(R.bool.pref_default_attendance_calculator_show_empty_subjects)
).asFlow()
private val gradeAverageModePref: Preference<GradeAverageMode> private val gradeAverageModePref: Preference<GradeAverageMode>
get() = getObjectFlow( get() = getObjectFlow(
R.string.pref_key_grade_average_mode, R.string.pref_key_grade_average_mode,
@ -186,6 +218,12 @@ class PreferencesRepository @Inject constructor(
) )
) )
val showAdditionalLessonsInPlan: ShowAdditionalLessonsMode
get() = getString(
R.string.pref_key_timetable_show_additional_lessons,
R.string.pref_default_timetable_show_additional_lessons
).let { ShowAdditionalLessonsMode.getByValue(it) }
val gradeSortingMode: GradeSortingMode val gradeSortingMode: GradeSortingMode
get() = GradeSortingMode.getByValue( get() = GradeSortingMode.getByValue(
getString( getString(
@ -267,6 +305,60 @@ class PreferencesRepository @Inject constructor(
selectedDashboardTilesPreference.set(filteredValue) selectedDashboardTilesPreference.set(filteredValue)
} }
var attendancePercentage: Double?
get() = attendancePercentagePreference.get().takeIf { it != NO_ATTENDANCE_VALUE }
set(value) = attendancePercentagePreference.set(value ?: NO_ATTENDANCE_VALUE)
var hiddenAttendanceItems: List<DashboardItem.HiddenAttendanceTile>
get() = hiddenAttendanceItemsPreference.get().toList()
set(value) = hiddenAttendanceItemsPreference.set(value.toSet())
var hiddenGrades: List<String>
get() = hiddenGradesPreference.get().toList()
set(value) = hiddenGradesPreference.set(value.toSet())
var showNotes: Boolean
get() = showNotesPreference.get()
set(value) = showNotesPreference.set(value)
var developerMode: Boolean
get() = developerModePreference.get()
set(value) = developerModePreference.set(value)
private val developerModePreference: Preference<Boolean>
get() = flowSharedPref.getBoolean(
context.getString(R.string.pref_key_developer_mode),
context.resources.getBoolean(R.bool.pref_default_developer_mode)
)
private val hiddenGradesPreference: Preference<Set<String>>
get() {
val defaultSet = context.resources.getStringArray(R.array.pref_default_hidden_grades).toSet()
val prefKey = "hidden_grades"
return flowSharedPref.getStringSet(prefKey, defaultSet)
}
private val showNotesPreference: Preference<Boolean>
get() = flowSharedPref.getBoolean(
context.getString(R.string.pref_key_show_notes),
context.resources.getBoolean(R.bool.pref_default_show_notes)
)
private val hiddenAttendanceItemsPreference: Preference<Set<DashboardItem.HiddenAttendanceTile>>
get() {
val defaultSet =
context.resources.getStringArray(R.array.pref_default_hidden_attendance_items).toSet()
val prefKey = "attendance_items"
return flowSharedPref
.getStringSet(prefKey, defaultSet)
.map(
mapper = { it -> it.map { DashboardItem.HiddenAttendanceTile.valueOf(it) }.toSet() },
reverse = { it -> it.map { it.name }.toSet() }
)
}
private val selectedDashboardTilesPreference: Preference<Set<String>> private val selectedDashboardTilesPreference: Preference<Set<String>>
get() { get() {
val defaultSet = val defaultSet =
@ -276,6 +368,19 @@ class PreferencesRepository @Inject constructor(
return flowSharedPref.getStringSet(prefKey, defaultSet) return flowSharedPref.getStringSet(prefKey, defaultSet)
} }
private val attendancePercentagePreference: Preference<Double>
get() {
val prefKey = context.getString(R.string.pref_key_attendance_percentage)
val defaultValue = context.resources.getString(R.string.pref_default_attendance_percentage)
return flowSharedPref
.getString(prefKey, defaultValue)
.map(
mapper = { it.toDoubleOrNull() ?: NO_ATTENDANCE_VALUE },
reverse = { it.toString() }
)
}
var dismissedAdminMessageIds: List<Int> var dismissedAdminMessageIds: List<Int>
get() = sharedPref.getStringSet(PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS, emptySet()) get() = sharedPref.getStringSet(PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS, emptySet())
.orEmpty() .orEmpty()
@ -303,19 +408,6 @@ class PreferencesRepository @Inject constructor(
get() = sharedPref.getBoolean(PREF_KEY_APP_SUPPORT_SHOWN, false) get() = sharedPref.getBoolean(PREF_KEY_APP_SUPPORT_SHOWN, false)
set(value) = sharedPref.edit { putBoolean(PREF_KEY_APP_SUPPORT_SHOWN, value) } set(value) = sharedPref.edit { putBoolean(PREF_KEY_APP_SUPPORT_SHOWN, value) }
var isAgreeToProcessData: Boolean
get() = getBoolean(
R.string.pref_key_ads_consent_data_processing,
R.bool.pref_default_ads_consent_data_processing
)
set(value) = sharedPref.edit {
putBoolean(context.getString(R.string.pref_key_ads_consent_data_processing), value)
}
var isPersonalizedAdsEnabled: Boolean
get() = sharedPref.getBoolean(PREF_KEY_PERSONALIZED_ADS_ENABLED, false)
set(value) = sharedPref.edit { putBoolean(PREF_KEY_PERSONALIZED_ADS_ENABLED, value) }
val isAdsEnabledFlow = flowSharedPref.getBoolean( val isAdsEnabledFlow = flowSharedPref.getBoolean(
context.getString(R.string.pref_key_ads_enabled), context.getString(R.string.pref_key_ads_enabled),
context.resources.getBoolean(R.bool.pref_default_ads_enabled) context.resources.getBoolean(R.bool.pref_default_ads_enabled)
@ -354,6 +446,15 @@ class PreferencesRepository @Inject constructor(
get() = sharedPref.getString(PREF_KEY_INSTALLATION_ID, null).orEmpty() get() = sharedPref.getString(PREF_KEY_INSTALLATION_ID, null).orEmpty()
private set(value) = sharedPref.edit { putString(PREF_KEY_INSTALLATION_ID, value) } private set(value) = sharedPref.edit { putString(PREF_KEY_INSTALLATION_ID, value) }
var mapping: Mapping?
get() {
val value = sharedPref.getString("mapping", null)
return value?.let { json.decodeFromString(it) }
}
set(value) = sharedPref.edit(commit = true) {
putString("mapping", value?.let { json.encodeToString(it) })
}
init { init {
if (installationId.isEmpty()) { if (installationId.isEmpty()) {
installationId = UUID.randomUUID().toString() installationId = UUID.randomUUID().toString()
@ -398,7 +499,6 @@ class PreferencesRepository @Inject constructor(
private const val PREF_KEY_IN_APP_REVIEW_DATE = "in_app_review_date" private const val PREF_KEY_IN_APP_REVIEW_DATE = "in_app_review_date"
private const val PREF_KEY_IN_APP_REVIEW_DONE = "in_app_review_done" private const val PREF_KEY_IN_APP_REVIEW_DONE = "in_app_review_done"
private const val PREF_KEY_APP_SUPPORT_SHOWN = "app_support_shown" private const val PREF_KEY_APP_SUPPORT_SHOWN = "app_support_shown"
private const val PREF_KEY_PERSONALIZED_ADS_ENABLED = "personalized_ads_enabled"
private const val PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS = "admin_message_dismissed_ids" private const val PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS = "admin_message_dismissed_ids"
} }
} }

View File

@ -1,12 +1,15 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.RecipientDao import io.github.wulkanowy.data.db.dao.RecipientDao
import io.github.wulkanowy.data.db.entities.* import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.MailboxType
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -14,19 +17,22 @@ import javax.inject.Singleton
@Singleton @Singleton
class RecipientRepository @Inject constructor( class RecipientRepository @Inject constructor(
private val recipientDb: RecipientDao, private val recipientDb: RecipientDao,
private val sdk: Sdk, private val wulkanowySdkFactory: WulkanowySdkFactory,
private val refreshHelper: AutoRefreshHelper, private val refreshHelper: AutoRefreshHelper,
) { ) {
private val cacheKey = "recipient" private val cacheKey = "recipient"
suspend fun refreshRecipients(student: Student, mailbox: Mailbox, type: MailboxType) { suspend fun refreshRecipients(student: Student, mailbox: Mailbox, type: MailboxType) {
val new = sdk.init(student).getRecipients(mailbox.globalKey) val new = wulkanowySdkFactory.create(student)
.getRecipients(mailbox.globalKey)
.mapToEntities(mailbox.globalKey) .mapToEntities(mailbox.globalKey)
val old = recipientDb.loadAll(type, mailbox.globalKey) val old = recipientDb.loadAll(type, mailbox.globalKey)
recipientDb.deleteAll(old uniqueSubtract new) recipientDb.removeOldAndSaveNew(
recipientDb.insertAll(new uniqueSubtract old) oldItems = old uniqueSubtract new,
newItems = new uniqueSubtract old,
)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
} }
@ -54,7 +60,7 @@ class RecipientRepository @Inject constructor(
): List<Recipient> { ): List<Recipient> {
mailbox ?: return emptyList() mailbox ?: return emptyList()
return sdk.init(student) return wulkanowySdkFactory.create(student)
.getMessageReplayDetails(message.messageGlobalKey) .getMessageReplayDetails(message.messageGlobalKey)
.sender .sender
.let(::listOf) .let(::listOf)

View File

@ -1,17 +1,23 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.data.WulkanowySdkFactory
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class RecoverRepository @Inject constructor(private val sdk: Sdk) { class RecoverRepository @Inject constructor(
private val wulkanowySdkFactory: WulkanowySdkFactory
) {
suspend fun getReCaptchaSiteKey(host: String, symbol: String): Pair<String, String> { suspend fun getReCaptchaSiteKey(host: String, symbol: String): Pair<String, String> =
return sdk.getPasswordResetCaptchaCode(host, symbol) wulkanowySdkFactory.create()
} .getPasswordResetCaptchaCode(host, symbol)
suspend fun sendRecoverRequest( suspend fun sendRecoverRequest(
url: String, symbol: String, email: String, reCaptchaResponse: String url: String,
): String = sdk.sendPasswordResetRequest(url, symbol, email, reCaptchaResponse) symbol: String,
email: String,
reCaptchaResponse: String
): String = wulkanowySdkFactory.create()
.sendPasswordResetRequest(url, symbol, email, reCaptchaResponse)
} }

View File

@ -1,14 +1,13 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
@ -18,7 +17,7 @@ import javax.inject.Singleton
@Singleton @Singleton
class SchoolAnnouncementRepository @Inject constructor( class SchoolAnnouncementRepository @Inject constructor(
private val schoolAnnouncementDb: SchoolAnnouncementDao, private val schoolAnnouncementDb: SchoolAnnouncementDao,
private val sdk: Sdk, private val wulkanowySdkFactory: WulkanowySdkFactory,
private val refreshHelper: AutoRefreshHelper, private val refreshHelper: AutoRefreshHelper,
) { ) {
@ -38,26 +37,27 @@ class SchoolAnnouncementRepository @Inject constructor(
it.isEmpty() || forceRefresh || isExpired it.isEmpty() || forceRefresh || isExpired
}, },
query = { query = {
schoolAnnouncementDb.loadAll(student.userLoginId) schoolAnnouncementDb.loadAll(student.studentId)
}, },
fetch = { fetch = {
sdk.init(student) val sdk = wulkanowySdkFactory.create(student)
.getDirectorInformation() val lastAnnouncements = sdk.getLastAnnouncements().mapToEntities(student)
.mapToEntities(student) val directorInformation = sdk.getDirectorInformation().mapToEntities(student)
lastAnnouncements + directorInformation
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
val schoolAnnouncementsToSave = (new uniqueSubtract old).onEach { schoolAnnouncementDb.removeOldAndSaveNew(
oldItems = old uniqueSubtract new,
newItems = (new uniqueSubtract old).onEach {
if (notify) it.isNotified = false if (notify) it.isNotified = false
} },
)
schoolAnnouncementDb.deleteAll(old uniqueSubtract new)
schoolAnnouncementDb.insertAll(schoolAnnouncementsToSave)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
} }
) )
fun getSchoolAnnouncementFromDatabase(student: Student): Flow<List<SchoolAnnouncement>> { fun getSchoolAnnouncementFromDatabase(student: Student): Flow<List<SchoolAnnouncement>> {
return schoolAnnouncementDb.loadAll(student.userLoginId) return schoolAnnouncementDb.loadAll(student.studentId)
} }
suspend fun updateSchoolAnnouncement(schoolAnnouncement: List<SchoolAnnouncement>) = suspend fun updateSchoolAnnouncement(schoolAnnouncement: List<SchoolAnnouncement>) =

View File

@ -1,14 +1,13 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.WulkanowySdkFactory
import io.github.wulkanowy.data.db.dao.SchoolDao import io.github.wulkanowy.data.db.dao.SchoolDao
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntity import io.github.wulkanowy.data.mappers.mapToEntity
import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -16,7 +15,7 @@ import javax.inject.Singleton
@Singleton @Singleton
class SchoolRepository @Inject constructor( class SchoolRepository @Inject constructor(
private val schoolDb: SchoolDao, private val schoolDb: SchoolDao,
private val sdk: Sdk, private val wulkanowySdkFactory: WulkanowySdkFactory,
private val refreshHelper: AutoRefreshHelper, private val refreshHelper: AutoRefreshHelper,
) { ) {
@ -39,17 +38,16 @@ class SchoolRepository @Inject constructor(
}, },
query = { schoolDb.load(semester.studentId, semester.classId) }, query = { schoolDb.load(semester.studentId, semester.classId) },
fetch = { fetch = {
sdk.init(student) wulkanowySdkFactory.create(student, semester)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getSchool() .getSchool()
.mapToEntity(semester) .mapToEntity(semester)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
if (old != null && new != old) { if (old != null && new != old) {
with(schoolDb) { schoolDb.removeOldAndSaveNew(
deleteAll(listOf(old)) oldItems = listOf(old),
insertAll(listOf(new)) newItems = listOf(new)
} )
} else if (old == null) { } else if (old == null) {
schoolDb.insertAll(listOf(new)) schoolDb.insertAll(listOf(new))
} }

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