1
0

Compare commits

..

201 Commits
1.1.4 ... 1.2.2

Author SHA1 Message Date
6a00e75816 Merge branch 'release/1.2.2' 2021-09-13 14:53:32 +02:00
957adaf6ee Version 1.2.2 2021-09-13 14:53:27 +02:00
827fb33eeb Fix login process after was interrupted (#1505) 2021-09-13 14:36:31 +02:00
19c96ee83f Unlock sunday in navigation datepicker (#1506) 2021-09-13 14:19:46 +02:00
5a7f52c773 Update help email pre-filled content (#1507) 2021-09-13 14:19:24 +02:00
dddeff802f Fix date picker crash after saved state (#1502) 2021-09-12 17:29:46 +02:00
91f6310892 Restore lucky number in more view (#1504) 2021-09-11 19:43:05 +02:00
0389642543 Fix empty list on excuse submit (#1501) 2021-09-11 19:40:09 +02:00
8528e0beff Fix crash in school info when dialer is unavailable (#1500) 2021-09-10 09:49:22 +00:00
e665a8f18b Fix error view in attendance summary (#1492) 2021-09-10 00:48:29 +02:00
6d5acbad2c Fix overlapping error view (#1493) 2021-09-10 00:36:44 +02:00
7217d0f753 Fix NPE in timetable dashboard tile (#1498) 2021-09-10 00:27:48 +02:00
16a5d88dfb Fix overlapping shadow in dashboard (#1494) 2021-09-10 00:25:23 +02:00
646a46727f Update material chips input (#1495) 2021-09-08 09:13:52 +02:00
f5e9197f98 Bump work_manager from 2.5.0 to 2.6.0 (#1478) 2021-09-06 23:38:10 +00:00
b47f26684b Change AppGallery deploy format to aab (#1483) 2021-09-06 03:27:54 +02:00
3a03b5f1c6 Merge branch 'release/1.2.1' into develop 2021-09-05 23:29:30 +02:00
3d0dcead50 Merge branch 'release/1.2.1' 2021-09-05 23:29:23 +02:00
b64b41c11c Version 1.2.1 2021-09-05 23:29:15 +02:00
77c5330f91 Dashboard fixes (#1463) 2021-09-05 23:24:03 +02:00
2b55ec02ff New translations strings.xml (Polish) (#1474) 2021-09-05 23:06:44 +02:00
49ebae6e63 Fix overlaping empty and error view in grade statistics (#1475)
Co-authored-by: Mikołaj Pich <m.pich@outlook.com>
2021-09-05 19:59:03 +00:00
44a9db48a6 Bump hianalytics from 6.2.0.300 to 6.2.0.301 (#1476) 2021-09-05 18:17:18 +00:00
0008a72be1 Bump kotlinx-coroutines-test from 1.5.1 to 1.5.2 (#1477) 2021-09-05 18:10:22 +00:00
a43acaaa07 Bump kotlinx-coroutines-android from 1.5.1 to 1.5.2 (#1479) 2021-09-05 18:10:05 +00:00
e6c9abb4e5 Bump core from 1.10.0 to 1.10.1 (#1480) 2021-09-05 18:09:42 +00:00
3b9451184c Fix preview of second student guardian when first guardian is null (#1473) 2021-09-05 03:15:40 +02:00
45d1727dbe Add missing school announcement dialog (#1470) 2021-09-04 15:54:37 +02:00
8d7b611c44 Fix showing error view in timetable (#1472) 2021-09-04 15:54:05 +02:00
c3adb9b6d6 Bump agp to 7.0.2 (#1469) 2021-09-03 22:54:29 +02:00
d87283eb31 Fix opening twitter link from about on android 11 (#1460) 2021-08-30 00:20:13 +02:00
d139c22782 Bump hianalytics from 6.1.1.300 to 6.2.0.300 (#1457) 2021-08-29 19:37:18 +00:00
e557021ad9 Bump huawei-publish-gradle-plugin from 1.2.4 to 1.3.0 (#1458) 2021-08-29 19:37:01 +00:00
37af5de25c Merge branch 'release/1.2.0' into develop 2021-08-29 21:08:23 +02:00
db6c84775b Merge branch 'release/1.2.0' 2021-08-29 21:08:18 +02:00
72d8b4aa84 Version 1.2.0 2021-08-29 21:08:08 +02:00
170b7c4379 New Crowdin updates (#1459) 2021-08-29 21:06:33 +02:00
79e9e1a780 New Crowdin updates (#1321) 2021-08-29 20:01:36 +02:00
98dcc62bb7 Add excuse function to "not excusable" account (#1429) 2021-08-29 19:47:14 +02:00
ea0fb00bde Fix crash on opening date pickers during holidays (#1456) 2021-08-29 19:31:28 +02:00
4aa6b0b995 Hide keyboard on opening login host dropdown (#1455) 2021-08-29 19:00:30 +02:00
57d11e825b Update readBy and unreadBy fields during message list fetch (#1452) 2021-08-29 15:40:28 +02:00
2f43b6e552 Change display name for MRmlik12 (#1451) 2021-08-29 14:08:48 +02:00
765f8a2d1f Add in app review (#1435) 2021-08-29 00:43:58 +02:00
04c727a0c8 Exams and homework notification fixes (#1292) 2021-08-29 00:41:58 +02:00
55518cb044 Add missing dashboard item in default view settings (#1450) 2021-08-28 21:43:10 +02:00
cebd1aa75d Remove lithuanian lang (#1449) 2021-08-28 12:14:01 +02:00
4a38a0be70 Add change password snackbar (#1336) 2021-08-26 17:35:41 +00:00
b4b9d91ea6 Update dependencies (#1448) 2021-08-25 20:49:44 +02:00
a6a2bcff3b Remove Zachowanie from all count of subjects (#1447) 2021-08-24 19:51:08 +02:00
2979d8b62a Show information when the recipient has read the message (#1430) 2021-08-23 16:16:41 +00:00
aba2068a84 New timetable widget design (#1384) 2021-08-23 16:02:21 +00:00
076948a680 Bump gradle from 7.0.0 to 7.0.1 (#1445) 2021-08-23 13:53:04 +00:00
1cfabe43a5 Add captions for averages from how many items have been counted (#1437) 2021-08-23 15:48:48 +02:00
02b87c8c6a Bump firebase-bom from 28.3.1 to 28.4.0 (#1446) 2021-08-23 13:42:20 +00:00
eb94e06d54 Fix buggy timers in timetable (#1428)
Co-authored-by: Rafał Borcz <RafalBO99@outlook.com>
2021-08-22 16:33:12 +02:00
d3b3939d26 Bump timber from 4.7.1 to 5.0.1 (#1440) 2021-08-17 13:44:06 +00:00
9c5d2fbf84 Bump google-services from 4.3.9 to 4.3.10 (#1439) 2021-08-17 13:32:48 +00:00
428e40d7fe Bump hianalytics from 6.1.0.300 to 6.1.1.300 (#1441) 2021-08-17 13:32:34 +00:00
9c819835ca Add last sync date in sync settings (#1436) 2021-08-15 13:59:32 +00:00
626169de11 Add drag and drop to dashboard tiles (#1415) 2021-08-10 11:55:51 +02:00
72ef5f428e Bump firebase-bom from 28.3.0 to 28.3.1 (#1431) 2021-08-09 13:55:03 +00:00
4ae3f7b016 Bump google-services from 4.3.8 to 4.3.9 (#1432) 2021-08-09 13:54:48 +00:00
7c94837af0 Bump coil from 1.3.1 to 1.3.2 (#1433) 2021-08-09 13:54:31 +00:00
2a91346155 Bump activity-ktx from 1.3.0 to 1.3.1 (#1434) 2021-08-09 13:53:54 +00:00
ec6d18968f Fix filter search bug (#1427)
* Fix filter search bug

* refractor
2021-08-07 10:27:51 +02:00
b61e63249c Add messages sorting (#1262) 2021-08-04 15:16:54 +02:00
d73aa605f9 Change dashboard no data strings (#1426) 2021-08-03 18:55:20 +02:00
14f4808434 Add student nick-or-name to notification summary (#1425) 2021-08-03 15:48:11 +02:00
2bc6d7ad0d Bump gradle from 4.2.2 to 7.0.0 (#1419) 2021-08-02 16:28:12 +00:00
888052cd9c Bump agcp from 1.5.3.200 to 1.6.0.300 (#1424) 2021-08-02 15:18:29 +00:00
bdc2281fdc Bump agconnect-crash from 1.5.3.200 to 1.6.0.300 (#1423) 2021-08-02 15:18:18 +00:00
d01edc2312 Bump activity-ktx from 1.2.4 to 1.3.0 (#1422) 2021-08-02 15:17:05 +00:00
484a3aa731 Bump chucker from 3.5.1 to 3.5.2 (#1417) 2021-08-02 13:26:33 +00:00
51be23470f Bump hilt_version from 2.38 to 2.38.1 (#1418) 2021-08-02 13:25:19 +00:00
19495ffce9 Bump constraintlayout from 2.1.0-beta02 to 2.1.0 (#1420) 2021-08-02 13:24:31 +00:00
bd766d33db Bump coil from 1.3.0 to 1.3.1 (#1421) 2021-08-02 13:23:25 +00:00
ff8b3f8837 Send mutltiple notifications instead of summary notification (#1365) 2021-08-01 13:19:46 +02:00
e678e6d7f9 Fix margin and attedance destination (#1414) 2021-07-31 20:19:05 +02:00
f6f3447f1d Fix sorting notes in loading state (#1413) 2021-07-31 18:08:35 +02:00
e1c1f305c4 Add draft message (#1306) 2021-07-31 18:00:22 +02:00
c8c9001277 Add twitter page link to about fragment (#1411) 2021-07-30 19:13:06 +02:00
3422951e47 Add dashboard (#1267) 2021-07-30 18:49:19 +02:00
3278c11cce Bump hilt_version from 2.37 to 2.38 (#1405) 2021-07-27 08:39:37 +00:00
e00dea51f1 Bump appcompat from 1.3.0 to 1.3.1 (#1409) 2021-07-26 19:55:08 +00:00
5bf411039d Bump fragment-ktx from 1.3.5 to 1.3.6 (#1404) 2021-07-26 19:54:50 +00:00
62ca394c9a Bump appcompat-resources from 1.3.0 to 1.3.1 (#1403) 2021-07-26 19:48:14 +00:00
255b89bbb3 Bump about_libraries from 8.9.0 to 8.9.1 (#1406) 2021-07-26 19:42:25 +00:00
f7987f4b29 Bump chucker from 3.5.0 to 3.5.1 (#1407) 2021-07-26 19:42:11 +00:00
0b583439dd Bump firebase-bom from 28.2.1 to 28.3.0 (#1408) 2021-07-26 19:41:55 +00:00
ca481dc6f5 Bump activity-ktx from 1.2.3 to 1.2.4 (#1410) 2021-07-26 19:41:28 +00:00
f6a92a4cc3 Bump hianalytics from 6.0.0.301 to 6.1.0.300 (#1402) 2021-07-21 07:21:49 +00:00
55a6219a42 Bump kotlin_version from 1.5.20 to 1.5.21 (#1401) 2021-07-21 07:21:25 +00:00
ad653f10df Bump firebase-bom from 28.2.0 to 28.2.1 (#1400) 2021-07-12 22:44:06 +02:00
7a780486f6 Bump kotlinx-coroutines-android from 1.5.0 to 1.5.1-native-mt (#1395) 2021-07-12 19:54:35 +00:00
c01b0eff9d Bump agconnect-crash from 1.5.2.300 to 1.5.3.200 (#1399) 2021-07-12 19:53:03 +00:00
3a4614e2b7 Bump robolectric from 4.5.1 to 4.6.1 (#1392) 2021-07-12 19:52:39 +00:00
44ee8859b1 Bump junit from 1.1.2 to 1.1.3 (#1389) 2021-07-12 19:52:25 +00:00
3dd7878ae5 Bump mockk from 1.11.0 to 1.12.0 (#1387) 2021-07-12 19:47:16 +00:00
0305a005ab Bump agcp from 1.5.2.300 to 1.5.3.200 (#1398) 2021-07-12 19:47:04 +00:00
2229f0e3e9 Bump coil from 1.2.2 to 1.3.0 (#1397) 2021-07-12 19:46:13 +00:00
d9aab7afa2 Bump runner from 1.3.0 to 1.4.0 (#1386) 2021-07-12 19:45:36 +00:00
0558f30646 Bump material from 1.3.0 to 1.4.0 (#1390) 2021-07-12 19:44:04 +00:00
238f257c6c Bump hianalytics from 5.3.1.300 to 6.0.0.301 (#1394) 2021-07-12 19:43:46 +00:00
40372e7cea Bump core-ktx from 1.5.0 to 1.6.0 (#1388) 2021-07-12 19:40:32 +00:00
b65a060fca Bump chucker from 3.4.0 to 3.5.0 (#1385) 2021-07-12 19:40:15 +00:00
c1ed748188 Bump gradle from 4.2.1 to 4.2.2 (#1393) 2021-07-12 19:39:19 +00:00
b911521ccc Bump core from 1.3.0 to 1.4.0 (#1391) 2021-07-12 19:39:02 +00:00
b046679542 Bump kotlin_version from 1.5.10 to 1.5.20 (#1381) 2021-06-27 20:09:46 +00:00
3decc95a20 Bump firebase-crashlytics-gradle from 2.7.0 to 2.7.1 (#1382) 2021-06-27 19:37:24 +00:00
c301198006 Bump firebase-bom from 28.1.0 to 28.2.0 (#1383) 2021-06-27 19:33:51 +00:00
190a5f2067 Bump fragment-ktx from 1.3.4 to 1.3.5 (#1380) 2021-06-22 20:37:21 +00:00
27e1a07eec Add notifications debug screen (#1370) 2021-06-21 10:29:04 +02:00
64feae9f1c Bump hilt_version from 2.36 to 2.37 (#1376) 2021-06-12 12:31:59 +00:00
085158721e Bump sonarqube-gradle-plugin from 3.2.0 to 3.3 (#1378) 2021-06-11 11:45:25 +00:00
44888b048d Add "I forgot my password" button in upper bar (#1375) 2021-06-11 09:53:11 +02:00
b9a12e46bf Upgrade gradle to 6.9 (#1371) 2021-06-09 09:46:16 +02:00
2fe5e62e72 Bump recyclerview from 1.2.0 to 1.2.1 (#1366) 2021-06-08 10:02:09 +00:00
6305e1a908 Bump firebase-bom from 28.0.1 to 28.1.0 (#1367) 2021-06-08 10:01:12 +00:00
6bf7a2e26c Bump firebase-crashlytics-gradle from 2.6.1 to 2.7.0 (#1368) 2021-06-08 10:00:27 +00:00
34487175d8 Bump coil from 1.2.1 to 1.2.2 (#1369) 2021-06-08 10:00:10 +00:00
f02db914bf Fix treessence upgrade (#1361) 2021-06-02 12:45:16 +02:00
18e0a59e2b Bump hilt_version from 2.35.1 to 2.36 (#1362) 2021-06-02 09:12:01 +00:00
e4371af284 Bump about_libraries from 8.8.5 to 8.9.0 (#1363) 2021-06-02 08:21:34 +00:00
44d5f69de1 Remove jcenter repository and unused code from gradle config (#1360) 2021-05-28 10:57:25 +02:00
d13e0adb00 Bump appcompat from 1.2.0 to 1.3.0 (#1357) 2021-05-28 07:09:47 +00:00
e60e573ac0 Bump appcompat-resources from 1.2.0 to 1.3.0 (#1359) 2021-05-28 07:01:57 +00:00
91dbc9e3d7 Bump core-ktx from 1.3.2 to 1.5.0 (#1356) 2021-05-28 06:15:18 +00:00
8ccbea2c21 Bump fragment-ktx from 1.3.3 to 1.3.4 (#1358) 2021-05-25 11:10:52 +00:00
211cb5e4f2 Merge branch 'hotfix/messages-send' into develop 2021-05-21 14:09:45 +02:00
b60c59216d Merge branch 'hotfix/messages-send' 2021-05-21 14:08:00 +02:00
d20f3180cf Version 1.1.6 2021-05-21 14:03:13 +02:00
17761af9d6 Bump sdk 2021-05-21 12:35:57 +02:00
067817bace Bump huawei-publish-gradle-plugin from 1.2.2 to 1.2.4 (#1314)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Mikołaj Pich <m.pich@outlook.com>
(cherry picked from commit 022a4d1ea2)
2021-05-21 12:30:36 +02:00
51800d91b2 Bump gradle from 4.1.3 to 4.2.1 (#1339) 2021-05-19 22:40:21 +00:00
3640c4f249 Update dependabot config (#1353) 2021-05-18 09:40:08 +00:00
05aa38b591 Add arrows to student family list items (#1338) 2021-05-17 15:35:36 +02:00
983dcd8656 Add conferences and announcements notifications (#1330) 2021-05-17 15:19:39 +02:00
59cf4fb222 Bump kotlinx-coroutines-android from 1.5.0-RC to 1.5.0 (#1348) 2021-05-17 11:33:27 +00:00
d6ebcc97e3 Bump firebase-crashlytics-gradle from 2.5.2 to 2.6.1 (#1342) 2021-05-17 11:32:03 +00:00
047579c394 Bump agcp from 1.5.2.201 to 1.5.2.300 (#1349) 2021-05-17 11:24:31 +00:00
a90fd4b776 Bump agconnect-crash from 1.5.2.201 to 1.5.2.300 (#1350) 2021-05-17 11:21:48 +00:00
03d3a5db11 Bump firebase-bom from 27.1.0 to 28.0.1 (#1341) 2021-05-17 11:21:31 +00:00
bf5e61490d Bump google-services from 4.3.5 to 4.3.8 (#1344) 2021-05-17 11:18:08 +00:00
d87fa589a8 Bump hianalytics from 5.3.0.300 to 5.3.1.300 (#1332) 2021-05-10 11:50:20 +00:00
1fff1c2b14 Bump kotlin_version from 1.4.32 to 1.5.0 (#1310) 2021-05-06 11:02:08 +00:00
58d66b6e70 Bump work_hilt from 1.0.0-beta01 to 1.0.0 (#1329) 2021-05-05 22:23:31 +00:00
202d13d509 Bump activity-ktx from 1.2.2 to 1.2.3 (#1328) 2021-05-05 22:21:48 +00:00
db808de06c Fix no info text position in school announcements (#1327) 2021-05-05 20:18:16 +02:00
d8dae09f39 Add notification icons (#1276)
Co-authored-by: Mikołaj Pich <m.pich@outlook.com>
2021-05-03 18:24:54 +02:00
32640e0796 Change mobile devices item order (#1324)
* Change more items order

* Resolve conflicts
2021-05-03 18:23:09 +02:00
4e80441167 Add school announcements (#1323) 2021-05-03 17:24:01 +02:00
075cfb20b1 Fix missing snackbar in sync settings fragment (#1325) 2021-05-03 16:42:59 +02:00
56e4e9be5e Fix homework dialog attachments margin (#1322) 2021-05-02 21:56:41 +02:00
53c798ebdb Change titles in Student Info (#1121) 2021-05-02 14:09:42 +00:00
022a4d1ea2 Bump huawei-publish-gradle-plugin from 1.2.2 to 1.2.4 (#1314)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Mikołaj Pich <m.pich@outlook.com>
2021-05-02 13:49:28 +02:00
dfa25d8445 New Crowdin updates (#1268) 2021-05-02 12:53:53 +02:00
b2efe0d981 Probably fix list divider color (#1270)
Co-authored-by: Rafał Borcz <RafalBO99@outlook.com>
2021-05-02 12:33:27 +02:00
2a7f846d3f Bump hilt_version from 2.35 to 2.35.1 (#1313) 2021-04-30 14:05:08 +00:00
da5817d08a Bump sonarqube-gradle-plugin from 3.1.1 to 3.2.0 (#1315) 2021-04-30 13:59:46 +00:00
cacf2f651a Bump agcp from 1.5.1.300 to 1.5.2.201 (#1311) 2021-04-30 13:57:51 +00:00
605c816a32 Bump coil from 1.2.0 to 1.2.1 (#1312) 2021-04-30 13:57:35 +00:00
1587be2fa8 Bump hianalytics from 5.2.0.301 to 5.3.0.300 (#1319) 2021-04-30 13:53:54 +00:00
00b23ca20c Bump agconnect-crash from 1.5.1.300 to 1.5.2.201 (#1316) 2021-04-30 13:46:30 +00:00
34db20ab0c Upgrade to GitHub-native Dependabot (#1309) 2021-04-30 13:23:57 +00:00
0d271d925a Bump room from 2.3.0-rc01 to 2.3.0 (#1302) 2021-04-25 01:31:48 +02:00
b7da43a52a Bump hilt_version from 2.34.1-beta to 2.35 (#1303) 2021-04-24 12:57:55 +00:00
44af5d59fb Bump fragment-ktx from 1.3.2 to 1.3.3 (#1304) 2021-04-24 12:57:41 +00:00
bc776993a9 Bump firebase-bom from 27.0.0 to 27.1.0 (#1305) 2021-04-24 12:57:21 +00:00
9a19ce9ca4 Fix Homework dialog buttons layout (#1297) 2021-04-19 16:09:14 +00:00
6855296de4 Merge branch 'hotfix/all-year-average' into develop 2021-04-18 20:43:29 +02:00
800a31f160 Merge branch 'hotfix/all-year-average' 2021-04-18 20:40:02 +02:00
8b83b37b09 Version 1.1.5 2021-04-18 20:38:18 +02:00
43e95cfdc6 Fix all year average 2021-04-18 19:36:02 +02:00
ae2a697e01 Merge pull request #1295 from wulkanowy/rbo/update-workflow
Update github action workflow
2021-04-18 19:34:32 +02:00
b695c7f600 Bump hilt_version from 2.34-beta to 2.34.1-beta (#1293) 2021-04-17 20:45:38 +00:00
3a1a383383 Bump recyclerview from 1.1.0 to 1.2.0 (#1287) 2021-04-14 18:59:12 +00:00
97810d02ab Bump moshi from 1.11.0 to 1.12.0 (#1284) 2021-04-14 18:58:54 +00:00
30b337a364 Bump about_libraries from 8.8.4 to 8.8.5 (#1281) 2021-04-13 11:55:39 +00:00
8f6c847562 Bump agconnect-crash from 1.5.1.200 to 1.5.1.300 (#1283) 2021-04-13 11:44:01 +00:00
e5d54c95f3 Bump firebase-bom from 26.8.0 to 27.0.0 (#1285) 2021-04-13 11:43:09 +00:00
eb5ad81ec1 Bump agcp from 1.5.1.200 to 1.5.1.300 (#1286) 2021-04-13 11:42:48 +00:00
3aa9f0ca2f Bump coil from 1.1.1 to 1.2.0 (#1282) 2021-04-13 11:42:21 +00:00
8e587358aa Bump hilt_version from 2.33-beta to 2.34-beta (#1280) 2021-04-13 11:39:14 +00:00
4492f4a864 Dialogs adjustments to meet MD rules (#1227) 2021-04-12 20:06:28 +00:00
b453225941 Student info and Teachers screens unification (#1279) 2021-04-12 19:58:58 +00:00
13ccfda009 Migrate material date picker (#1277) 2021-04-12 21:43:52 +02:00
95ffb0a687 Ignore all throwable from WebView (#1275) 2021-04-08 10:41:49 +02:00
f131edf857 Add system settings shortcut (#1271) 2021-04-07 11:56:33 +00:00
bd2d26418a Cleanup github workflows (#1272) 2021-04-07 13:25:48 +02:00
aeb3b2a030 Display day header from website in timetable (#1269) 2021-04-05 15:07:29 +02:00
7bc5219d81 Add new notifications (#1243) 2021-04-04 14:15:07 +00:00
6cb4ea4b0f Drop support for android 4.x (#1232) 2021-04-03 09:56:07 +00:00
0bdd33ef4a Migrate to material components bottom navigation (#1244) 2021-04-03 11:46:36 +02:00
792de4cd3d Merge branch 'hotfix/fix-recovery-visibility' into develop 2021-04-01 22:40:33 +02:00
3071e19584 Implement a toggleable setting to count an arithmetic average of grades when all weights are equal to zero (#1186) 2021-03-30 13:59:36 +02:00
f2130998ec Bump firebase-crashlytics-gradle from 2.5.1 to 2.5.2 (#1264) 2021-03-30 10:49:40 +00:00
8a5ca8c91f Bump firebase-bom from 26.7.0 to 26.8.0 (#1263) 2021-03-30 10:48:12 +00:00
fada13e2d3 Update issue templates (#1257) 2021-03-29 18:39:08 +02:00
6e19eb943d Add deploy to AppGallery github actions config (#1259) 2021-03-29 17:37:26 +02:00
c70fe3430c Merge branch 'release/1.1.3' into develop 2021-03-28 20:13:43 +02:00
395 changed files with 20696 additions and 3063 deletions

View File

@ -1,3 +1,12 @@
---
name: Bug report
about: Utwórz raport błędu, aby pomóc nam ulepszyć Wulkanowego
title: ''
labels: ''
assignees: ''
---
## Co powinno się dziać ## Co powinno się dziać

View File

@ -0,0 +1,20 @@
---
name: Feature request
about: Zaproponuj nowy pomysł dla Wulkanowego
title: ''
labels: ''
assignees: ''
---
** Czy Twoja prośba o funkcję jest związana z problemem? Proszę opisz.**
Jasny i zwięzły opis problemu. Np. Zawsze jestem sfrustrowany, gdy [...]
** Opisz żądane rozwiązanie **
Jasny i zwięzły opis tego, co chcesz, aby się wydarzyło.
** Opisz alternatywy, które rozważałeś **
Jasny i zwięzły opis wszelkich rozważanych alternatywnych rozwiązań lub funkcji.
** Dodatkowy kontekst **
Dodaj inny kontekst lub zrzuty ekranu dotyczące żądania funkcji tutaj.

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

@ -0,0 +1,12 @@
version: 2
updates:
- package-ecosystem: gradle
directory: "/"
schedule:
interval: weekly
open-pull-requests-limit: 10
target-branch: develop
ignore:
- dependency-name: io.github.wulkanowy:sdk
reviewers:
- Faierbel

74
.github/workflows/deploy-store.yml vendored Normal file
View File

@ -0,0 +1,74 @@
name: Deploy to app stores
on:
release:
types: [ created ]
jobs:
deploy-google-play:
name: Deploy to google play
runs-on: ubuntu-latest
timeout-minutes: 10
environment: google-play
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: 11
- uses: actions/cache@v2
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/key.p12.gpg
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg
- name: Upload apk to google play
env:
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
PLAY_SERVICE_ACCOUNT_EMAIL: ${{ secrets.PLAY_SERVICE_ACCOUNT_EMAIL }}
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
run: ./gradlew publishPlayRelease -PenableFirebase --stacktrace;
deploy-app-gallery:
name: Deploy to AppGallery
runs-on: ubuntu-latest
timeout-minutes: 10
environment: app-gallery
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: 11
- uses: actions/cache@v2
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/key.p12.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_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
run: ./gradlew bundleHmsRelease --stacktrace && ./gradlew publishHuaweiAppGalleryHmsRelease --stacktrace

144
.github/workflows/deploy-test.yml vendored Normal file
View File

@ -0,0 +1,144 @@
name: Deploy to app tests
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@v2
- uses: actions/setup-java@v1
with:
java-version: 11
- uses: actions/cache@v2
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@v2
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@v2
- uses: actions/setup-java@v1
with:
java-version: 11
- uses: actions/cache@v2
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@v2
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,13 +1,11 @@
name: Test and deploy name: Tests
on: on:
push: push:
branches: [ develop ] branches: [ master, develop ]
tags: [ '*' ] tags: [ '*' ]
pull_request: pull_request:
branches: [ develop ] branches: [ master, develop ]
workflow_dispatch:
jobs: jobs:
unit-tests: unit-tests:
@ -29,174 +27,8 @@ jobs:
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }} key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}
- name: Unit tests - name: Unit tests
run: | run: |
./gradlew --build-cache -Pcoverage testFdroidDebugUnitTest --stacktrace ./gradlew testFdroidDebugUnitTest --stacktrace
./gradlew --build-cache -Pcoverage jacocoTestReport --stacktrace ./gradlew jacocoTestReport --stacktrace
- uses: codecov/codecov-action@v1 - uses: codecov/codecov-action@v1
with: with:
flags: unit flags: unit
deploy-google-play:
name: Deploy to google play
runs-on: ubuntu-latest
timeout-minutes: 10
environment: google-play
needs: [ unit-tests ]
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: 11
- uses: actions/cache@v2
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/key.p12.gpg
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg
- name: Upload apk to google play
env:
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
PLAY_SERVICE_ACCOUNT_EMAIL: ${{ secrets.PLAY_SERVICE_ACCOUNT_EMAIL }}
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
run: ./gradlew publishPlayRelease -PenableFirebase --stacktrace;
deploy-appcenter:
name: Deploy to App Center
runs-on: ubuntu-latest
timeout-minutes: 10
environment: app-center
if: github.ref != 'refs/heads/develop'
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: 11
- uses: actions/cache@v2
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@v2
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: Deploy to AppDistribution
runs-on: ubuntu-latest
timeout-minutes: 10
environment: app-distribution
if: github.ref == 'refs/heads/develop'
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: 11
- uses: actions/cache@v2
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@v2
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

2
.gitignore vendored
View File

@ -117,3 +117,5 @@ Thumbs.db
app/src/release/agconnect-services.json app/src/release/agconnect-services.json
app/src/release/agconnect-credentials.json
.idea/deploymentTargetDropDown.xml

View File

@ -7,15 +7,6 @@
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" /> <package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
</value> </value>
</option> </option>
<option name="PACKAGES_IMPORT_LAYOUT">
<value>
<package name="" alias="false" withSubpackages="true" />
<package name="java" alias="false" withSubpackages="true" />
<package name="javax" alias="false" withSubpackages="true" />
<package name="kotlin" alias="false" withSubpackages="true" />
<package name="" alias="true" withSubpackages="true" />
</value>
</option>
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" /> <option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" /> <option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
<option name="WRAP_ELVIS_EXPRESSIONS" value="0" /> <option name="WRAP_ELVIS_EXPRESSIONS" value="0" />

View File

@ -2,7 +2,7 @@
# Wulkanowy # Wulkanowy
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/wulkanowy/wulkanowy/Test%20and%20deploy/develop?style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions) [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/wulkanowy/wulkanowy/Tests/develop?style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions)
[![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy) [![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy)
[![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) [![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr)
[![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/) [![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/)

View File

@ -2,7 +2,7 @@
# Wulkanowy # Wulkanowy
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/wulkanowy/wulkanowy/Test%20and%20deploy/develop?style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions) [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/wulkanowy/wulkanowy/Tests/develop?style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions)
[![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy) [![Codecov](https://img.shields.io/codecov/c/github/wulkanowy/wulkanowy/master.svg?style=flat-square)](https://codecov.io/gh/wulkanowy/wulkanowy)
[![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) [![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr)
[![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/) [![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/)

Binary file not shown.

View File

@ -1,11 +1,13 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-parcelize'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin' apply plugin: 'dagger.hilt.android.plugin'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics' apply plugin: 'com.google.firebase.crashlytics'
apply plugin: 'com.github.triplet.play' apply plugin: 'com.github.triplet.play'
apply plugin: 'ru.cian.huawei-publish'
apply plugin: 'com.mikepenz.aboutlibraries.plugin' apply plugin: 'com.mikepenz.aboutlibraries.plugin'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.huawei.agconnect' apply plugin: 'com.huawei.agconnect'
apply from: 'jacoco.gradle' apply from: 'jacoco.gradle'
apply from: 'sonarqube.gradle' apply from: 'sonarqube.gradle'
@ -13,16 +15,14 @@ apply from: 'hooks.gradle'
android { android {
compileSdkVersion 30 compileSdkVersion 30
buildToolsVersion '30.0.3'
defaultConfig { defaultConfig {
applicationId "io.github.wulkanowy" applicationId "io.github.wulkanowy"
testApplicationId "io.github.tests.wulkanowy" testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 17 minSdkVersion 21
targetSdkVersion 30 targetSdkVersion 30
versionCode 90 versionCode 95
versionName "1.1.4" versionName "1.2.2"
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
@ -67,7 +67,6 @@ android {
resValue "string", "app_name", "Wulkanowy DEV " + defaultConfig.versionCode resValue "string", "app_name", "Wulkanowy DEV " + defaultConfig.versionCode
applicationIdSuffix ".dev" applicationIdSuffix ".dev"
versionNameSuffix "-dev" versionNameSuffix "-dev"
testCoverageEnabled = project.hasProperty('coverage')
ext.enableCrashlytics = project.hasProperty("enableFirebase") ext.enableCrashlytics = project.hasProperty("enableFirebase")
} }
} }
@ -77,7 +76,6 @@ android {
productFlavors { productFlavors {
hms { hms {
dimension "platform" dimension "platform"
minSdkVersion 19
manifestPlaceholders = [ manifestPlaceholders = [
install_channel: "AppGallery" install_channel: "AppGallery"
] ]
@ -99,11 +97,7 @@ android {
} }
buildFeatures { buildFeatures {
viewBinding = true viewBinding true
}
lintOptions {
disable 'HardwareIds'
} }
testOptions.unitTests { testOptions.unitTests {
@ -112,13 +106,12 @@ android {
compileOptions { compileOptions {
coreLibraryDesugaringEnabled true coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_11
} }
kotlinOptions { kotlinOptions {
useIR = true jvmTarget = "11"
jvmTarget = "1.8"
freeCompilerArgs += ["-Xopt-in=kotlin.RequiresOptIn", "-Xjvm-default=all"] freeCompilerArgs += ["-Xopt-in=kotlin.RequiresOptIn", "-Xjvm-default=all"]
} }
@ -132,6 +125,10 @@ android {
} }
} }
kapt {
correctErrorTypes true
}
play { play {
serviceAccountEmail = System.getenv("PLAY_SERVICE_ACCOUNT_EMAIL") ?: "jan@fakelog.cf" serviceAccountEmail = System.getenv("PLAY_SERVICE_ACCOUNT_EMAIL") ?: "jan@fakelog.cf"
serviceAccountCredentials = file('key.p12') serviceAccountCredentials = file('key.p12')
@ -140,41 +137,49 @@ play {
updatePriority = 3 updatePriority = 3
} }
huaweiPublish {
instances {
hmsRelease {
credentialsPath = "$rootDir/app/src/release/agconnect-credentials.json"
buildFormat = "aab"
deployType = "draft"
}
}
}
ext { ext {
work_manager = "2.5.0" work_manager = "2.6.0"
work_hilt = "1.0.0-beta01" android_hilt = "1.0.0"
room = "2.3.0-rc01" room = "2.3.0"
chucker = "3.4.0" chucker = "3.5.2"
mockk = "1.11.0" mockk = "1.12.0"
moshi = "1.11.0" moshi = "1.12.0"
} }
dependencies { dependencies {
implementation "io.github.wulkanowy:sdk:1.1.3" implementation "io.github.wulkanowy:sdk:1.2.2"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3'
implementation "androidx.core:core-ktx:1.3.2" implementation "androidx.core:core-ktx:1.6.0"
implementation "androidx.activity:activity-ktx:1.2.2" implementation "androidx.activity:activity-ktx:1.3.1"
implementation "androidx.appcompat:appcompat:1.2.0" implementation "androidx.appcompat:appcompat:1.3.1"
implementation "androidx.appcompat:appcompat-resources:1.2.0" implementation "androidx.appcompat:appcompat-resources:1.3.1"
implementation "androidx.fragment:fragment-ktx:1.3.2" implementation "androidx.fragment:fragment-ktx:1.3.6"
implementation "androidx.annotation:annotation:1.2.0" implementation "androidx.annotation:annotation:1.2.0"
implementation "androidx.multidex:multidex:2.0.1"
implementation "androidx.preference:preference-ktx:1.1.1" implementation "androidx.preference:preference-ktx:1.1.1"
implementation "androidx.recyclerview:recyclerview:1.1.0" implementation "androidx.recyclerview:recyclerview:1.2.1"
implementation "androidx.viewpager:viewpager:1.0.0" implementation "androidx.viewpager:viewpager:1.0.0"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "androidx.constraintlayout:constraintlayout:2.0.4" implementation "androidx.constraintlayout:constraintlayout:2.1.0"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0" implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
implementation "com.google.android.material:material:1.3.0" implementation "com.google.android.material:material:1.4.0"
implementation "com.github.wulkanowy:material-chips-input:2.2.0" implementation "com.github.wulkanowy:material-chips-input:2.3.1"
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0" implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
implementation 'com.mikhaellopez:circularimageview:4.2.0' implementation 'com.github.lopspower:CircularImageView:4.2.0'
implementation "androidx.work:work-runtime-ktx:$work_manager" implementation "androidx.work:work-runtime-ktx:$work_manager"
playImplementation "androidx.work:work-gcm:$work_manager" playImplementation "androidx.work:work-gcm:$work_manager"
@ -187,55 +192,56 @@ dependencies {
implementation "com.google.dagger:hilt-android:$hilt_version" implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version" kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
implementation "androidx.hilt:hilt-work:$work_hilt" kapt "androidx.hilt:hilt-compiler:$android_hilt"
kapt "androidx.hilt:hilt-compiler:$work_hilt" implementation "androidx.hilt:hilt-work:$android_hilt"
implementation "com.aurelhubert:ahbottomnavigation:2.3.4" implementation 'com.github.ncapdevi:FragNav:3.3.0'
implementation "com.ncapdevi:frag-nav:3.3.0"
implementation "com.github.YarikSOffice:lingver:1.3.0" implementation "com.github.YarikSOffice:lingver:1.3.0"
implementation "com.squareup.moshi:moshi:$moshi" implementation "com.squareup.moshi:moshi:$moshi"
implementation "com.squareup.moshi:moshi-adapters:$moshi" implementation "com.squareup.moshi:moshi-adapters:$moshi"
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi" kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi"
implementation "com.jakewharton.timber:timber:4.7.1"
implementation "com.jakewharton.timber:timber:5.0.1"
implementation "at.favre.lib:slf4j-timber:1.0.1" implementation "at.favre.lib:slf4j-timber:1.0.1"
implementation "fr.bipi.treessence:treessence:0.3.2" implementation 'com.github.bastienpaulfr:Treessence:1.0.4'
implementation "com.mikepenz:aboutlibraries-core:$about_libraries" implementation "com.mikepenz:aboutlibraries-core:$about_libraries"
implementation 'com.wdullaer:materialdatetimepicker:4.2.3' implementation "io.coil-kt:coil:1.3.2"
implementation "io.coil-kt:coil:1.1.1"
implementation "io.github.wulkanowy:AppKillerManager:3.0.0" implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
implementation 'me.xdrop:fuzzywuzzy:1.3.1' implementation 'me.xdrop:fuzzywuzzy:1.3.1'
implementation 'com.fredporciuncula:flow-preferences:1.5.0'
playImplementation platform('com.google.firebase:firebase-bom:26.7.0') playImplementation platform('com.google.firebase:firebase-bom:28.4.0')
playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-analytics-ktx'
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.android.play:core:1.10.1'
playImplementation 'com.google.android.play:core-ktx:1.8.1' playImplementation 'com.google.android.play:core-ktx:1.8.1'
hmsImplementation 'com.huawei.hms:hianalytics:5.2.0.301' hmsImplementation 'com.huawei.hms:hianalytics:6.2.0.301'
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.5.1.200' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.0.300'
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
debugImplementation "com.github.ChuckerTeam.Chucker:library:$chucker" debugImplementation "com.github.ChuckerTeam.Chucker:library:$chucker"
debugImplementation "com.amitshekhar.android:debug-db:1.0.6" debugImplementation 'com.github.amitshekhariitbhu.Android-Debug-Database:debug-db:v1.0.6'
testImplementation "junit:junit:4.13.2" testImplementation "junit:junit:4.13.2"
testImplementation "io.mockk:mockk:$mockk" testImplementation "io.mockk:mockk:$mockk"
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.2' testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.2'
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
testImplementation 'org.robolectric:robolectric:4.5.1' testImplementation 'org.robolectric:robolectric:4.6.1'
testImplementation "androidx.test:runner:1.3.0" testImplementation "androidx.test:runner:1.4.0"
testImplementation "androidx.test.ext:junit:1.1.2" testImplementation "androidx.test.ext:junit:1.1.3"
testImplementation "androidx.test:core:1.3.0" testImplementation "androidx.test:core:1.4.0"
testImplementation "androidx.room:room-testing:$room" testImplementation "androidx.room:room-testing:$room"
testImplementation "com.google.dagger:hilt-android-testing:$hilt_version" testImplementation "com.google.dagger:hilt-android-testing:$hilt_version"
kaptTest "com.google.dagger:hilt-android-compiler:$hilt_version" kaptTest "com.google.dagger:hilt-android-compiler:$hilt_version"
androidTestImplementation "androidx.test:core:1.3.0" androidTestImplementation "androidx.test:core:1.4.0"
androidTestImplementation "androidx.test:runner:1.3.0" androidTestImplementation "androidx.test:runner:1.4.0"
androidTestImplementation "androidx.test.ext:junit:1.1.2" androidTestImplementation "androidx.test.ext:junit:1.1.3"
androidTestImplementation "io.mockk:mockk-android:$mockk" androidTestImplementation "io.mockk:mockk-android:$mockk"
androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" androidTestImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
} }

View File

@ -1,8 +1,8 @@
apply plugin: "jacoco" apply plugin: "jacoco"
jacoco { jacoco {
toolVersion "0.8.5" toolVersion "0.8.7"
reportsDir = file("$buildDir/reports") reportsDirectory.set(file("$buildDir/reports"))
} }
tasks.withType(Test) { tasks.withType(Test) {
@ -16,8 +16,8 @@ task jacocoTestReport(type: JacocoReport) {
description = "Generate Jacoco coverage reports" description = "Generate Jacoco coverage reports"
reports { reports {
xml.enabled = true xml.required.set(true)
html.enabled = true html.required.set(true)
} }
def excludes = ['**/R.class', def excludes = ['**/R.class',

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 652 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 460 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 426 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 451 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 297 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 588 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 897 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 519 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 692 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 805 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 700 B

View File

@ -0,0 +1,17 @@
package io.github.wulkanowy.utils
import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.ui.modules.main.MainActivity
import javax.inject.Singleton
import javax.inject.Inject
@Singleton
class InAppReviewHelper @Inject constructor(
@ApplicationContext private val context: Context
) {
fun showInAppReview(activity: MainActivity) {
// do nothing
}
}

View File

@ -3,11 +3,6 @@ package io.github.wulkanowy.utils
import android.util.Log import android.util.Log
import com.huawei.agconnect.crash.AGConnectCrash import com.huawei.agconnect.crash.AGConnectCrash
import fr.bipi.tressence.base.FormatterPriorityTree import fr.bipi.tressence.base.FormatterPriorityTree
import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException
import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException
import java.io.InterruptedIOException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
class CrashLogTree : FormatterPriorityTree(Log.VERBOSE) { class CrashLogTree : FormatterPriorityTree(Log.VERBOSE) {
@ -20,21 +15,10 @@ class CrashLogTree : FormatterPriorityTree(Log.VERBOSE) {
} }
} }
class CrashLogExceptionTree : FormatterPriorityTree(Log.ERROR) { class CrashLogExceptionTree : FormatterPriorityTree(Log.ERROR, ExceptionFilter) {
private val connectCrash by lazy { AGConnectCrash.getInstance() } private val connectCrash by lazy { AGConnectCrash.getInstance() }
override fun skipLog(priority: Int, tag: String?, message: String, t: Throwable?): Boolean {
return when (t) {
is FeatureDisabledException,
is FeatureNotAvailableException,
is UnknownHostException,
is SocketTimeoutException,
is InterruptedIOException -> true
else -> super.skipLog(priority, tag, message, t)
}
}
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
if (skipLog(priority, tag, message, t)) return if (skipLog(priority, tag, message, t)) return

View File

@ -0,0 +1,17 @@
package io.github.wulkanowy.utils
import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.ui.modules.main.MainActivity
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class InAppReviewHelper @Inject constructor(
@ApplicationContext private val context: Context
) {
fun showInAppReview(activity: MainActivity) {
// do nothing
}
}

View File

@ -6,6 +6,7 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
@ -118,11 +119,9 @@
<receiver android:name=".services.alarm.TimetableNotificationReceiver" /> <receiver android:name=".services.alarm.TimetableNotificationReceiver" />
<provider <provider
android:name="androidx.work.impl.WorkManagerInitializer" android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.workmanager-init" android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="remove" /> tools:node="remove" />
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider" android:authorities="${applicationId}.fileprovider"
@ -167,7 +166,7 @@
<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_push" /> android:resource="@drawable/ic_stat_all" />
<meta-data <meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id" android:name="com.google.firebase.messaging.default_notification_channel_id"

View File

@ -36,7 +36,7 @@
"githubUsername": "Luncenok" "githubUsername": "Luncenok"
}, },
{ {
"displayName": "MRmlik12", "displayName": "Daniel Olczyk",
"githubUsername": "MRmlik12" "githubUsername": "MRmlik12"
}, },
{ {
@ -46,5 +46,9 @@
{ {
"displayName": "Kamil Studziński", "displayName": "Kamil Studziński",
"githubUsername": "studzinskik" "githubUsername": "studzinskik"
},
{
"displayName": "Tomasz F.",
"githubUsername": "Pengwius"
} }
] ]

View File

@ -2,14 +2,12 @@ package io.github.wulkanowy
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Application import android.app.Application
import android.content.Context
import android.util.Log.DEBUG import android.util.Log.DEBUG
import android.util.Log.INFO import android.util.Log.INFO
import android.util.Log.VERBOSE import android.util.Log.VERBOSE
import android.webkit.WebView import android.webkit.WebView
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.hilt.work.HiltWorkerFactory import androidx.hilt.work.HiltWorkerFactory
import androidx.multidex.MultiDex
import androidx.work.Configuration import androidx.work.Configuration
import com.yariksoffice.lingver.Lingver import com.yariksoffice.lingver.Lingver
import dagger.hilt.android.HiltAndroidApp import dagger.hilt.android.HiltAndroidApp
@ -43,11 +41,6 @@ class WulkanowyApp : Application(), Configuration.Provider {
@Inject @Inject
lateinit var analyticsHelper: AnalyticsHelper lateinit var analyticsHelper: AnalyticsHelper
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
MultiDex.install(this)
}
@SuppressLint("UnsafeOptInUsageWarning") @SuppressLint("UnsafeOptInUsageWarning")
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
@ -91,7 +84,7 @@ class WulkanowyApp : Application(), Configuration.Provider {
//https://stackoverflow.com/questions/40398528/android-webview-language-changes-abruptly-on-android-7-0-and-above //https://stackoverflow.com/questions/40398528/android-webview-language-changes-abruptly-on-android-7-0-and-above
try { try {
WebView(this).destroy() WebView(this).destroy()
} catch (e: Exception) { } catch (e: Throwable) {
//Ignore exceptions //Ignore exceptions
} }
} }

View File

@ -8,6 +8,8 @@ import androidx.preference.PreferenceManager
import com.chuckerteam.chucker.api.ChuckerCollector import com.chuckerteam.chucker.api.ChuckerCollector
import com.chuckerteam.chucker.api.ChuckerInterceptor import com.chuckerteam.chucker.api.ChuckerInterceptor
import com.chuckerteam.chucker.api.RetentionManager import com.chuckerteam.chucker.api.RetentionManager
import com.squareup.moshi.Moshi
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
@ -18,6 +20,7 @@ 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.sdk.Sdk
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import kotlinx.coroutines.ExperimentalCoroutinesApi
import timber.log.Timber import timber.log.Timber
import javax.inject.Singleton import javax.inject.Singleton
@ -77,6 +80,16 @@ internal class RepositoryModule {
fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences = fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(context) PreferenceManager.getDefaultSharedPreferences(context)
@OptIn(ExperimentalCoroutinesApi::class)
@Singleton
@Provides
fun provideFlowSharedPref(sharedPreferences: SharedPreferences) =
FlowSharedPreferences(sharedPreferences)
@Singleton
@Provides
fun provideMoshi() = Moshi.Builder().build()
@Singleton @Singleton
@Provides @Provides
fun provideStudentDao(database: AppDatabase) = database.studentDao fun provideStudentDao(database: AppDatabase) = database.studentDao
@ -181,4 +194,12 @@ internal class RepositoryModule {
@Singleton @Singleton
@Provides @Provides
fun provideStudentInfoDao(database: AppDatabase) = database.studentInfoDao fun provideStudentInfoDao(database: AppDatabase) = database.studentInfoDao
@Singleton
@Provides
fun provideTimetableHeaderDao(database: AppDatabase) = database.timetableHeaderDao
@Singleton
@Provides
fun provideSchoolAnnouncementDao(database: AppDatabase) = database.schoolAnnouncementDao
} }

View File

@ -10,6 +10,7 @@ import io.github.wulkanowy.data.db.dao.AttendanceDao
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
import io.github.wulkanowy.data.db.dao.ConferenceDao import io.github.wulkanowy.data.db.dao.ConferenceDao
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
import io.github.wulkanowy.data.db.dao.ExamDao import io.github.wulkanowy.data.db.dao.ExamDao
import io.github.wulkanowy.data.db.dao.GradeDao import io.github.wulkanowy.data.db.dao.GradeDao
import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao
@ -32,10 +33,12 @@ import io.github.wulkanowy.data.db.dao.SubjectDao
import io.github.wulkanowy.data.db.dao.TeacherDao import io.github.wulkanowy.data.db.dao.TeacherDao
import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao
import io.github.wulkanowy.data.db.dao.TimetableDao import io.github.wulkanowy.data.db.dao.TimetableDao
import io.github.wulkanowy.data.db.dao.TimetableHeaderDao
import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.AttendanceSummary import io.github.wulkanowy.data.db.entities.AttendanceSummary
import io.github.wulkanowy.data.db.entities.CompletedLesson import io.github.wulkanowy.data.db.entities.CompletedLesson
import io.github.wulkanowy.data.db.entities.Conference import io.github.wulkanowy.data.db.entities.Conference
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradePartialStatistics import io.github.wulkanowy.data.db.entities.GradePartialStatistics
@ -58,6 +61,7 @@ import io.github.wulkanowy.data.db.entities.Subject
import io.github.wulkanowy.data.db.entities.Teacher import io.github.wulkanowy.data.db.entities.Teacher
import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.db.entities.TimetableAdditional 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.Migration10
import io.github.wulkanowy.data.db.migrations.Migration11 import io.github.wulkanowy.data.db.migrations.Migration11
import io.github.wulkanowy.data.db.migrations.Migration12 import io.github.wulkanowy.data.db.migrations.Migration12
@ -86,6 +90,10 @@ import io.github.wulkanowy.data.db.migrations.Migration32
import io.github.wulkanowy.data.db.migrations.Migration33 import io.github.wulkanowy.data.db.migrations.Migration33
import io.github.wulkanowy.data.db.migrations.Migration34 import io.github.wulkanowy.data.db.migrations.Migration34
import io.github.wulkanowy.data.db.migrations.Migration35 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.Migration4
import io.github.wulkanowy.data.db.migrations.Migration5 import io.github.wulkanowy.data.db.migrations.Migration5
import io.github.wulkanowy.data.db.migrations.Migration6 import io.github.wulkanowy.data.db.migrations.Migration6
@ -124,6 +132,8 @@ import javax.inject.Singleton
Conference::class, Conference::class,
TimetableAdditional::class, TimetableAdditional::class,
StudentInfo::class, StudentInfo::class,
TimetableHeader::class,
SchoolAnnouncement::class,
], ],
version = AppDatabase.VERSION_SCHEMA, version = AppDatabase.VERSION_SCHEMA,
exportSchema = true exportSchema = true
@ -132,7 +142,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
companion object { companion object {
const val VERSION_SCHEMA = 35 const val VERSION_SCHEMA = 39
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
Migration2(), Migration2(),
@ -168,7 +178,11 @@ abstract class AppDatabase : RoomDatabase() {
Migration32(), Migration32(),
Migration33(), Migration33(),
Migration34(), Migration34(),
Migration35(appInfo) Migration35(appInfo),
Migration36(),
Migration37(),
Migration38(),
Migration39(),
) )
fun newInstance( fun newInstance(
@ -234,4 +248,8 @@ abstract class AppDatabase : RoomDatabase() {
abstract val timetableAdditionalDao: TimetableAdditionalDao abstract val timetableAdditionalDao: TimetableAdditionalDao
abstract val studentInfoDao: StudentInfoDao abstract val studentInfoDao: StudentInfoDao
abstract val timetableHeaderDao: TimetableHeaderDao
abstract val schoolAnnouncementDao: SchoolAnnouncementDao
} }

View File

@ -20,9 +20,15 @@ class SharedPrefProvider @Inject constructor(
fun getLong(key: String, defaultValue: Long) = sharedPref.getLong(key, defaultValue) fun getLong(key: String, defaultValue: Long) = sharedPref.getLong(key, defaultValue)
fun getString(key: String) = sharedPref.getString(key, null)
fun getString(key: String, defaultValue: String): String = sharedPref.getString(key, defaultValue) ?: defaultValue fun getString(key: String, defaultValue: String): String = sharedPref.getString(key, defaultValue) ?: defaultValue
fun putString(key: String, value: String, sync: Boolean = false) { fun getBoolean(key: String, defaultValue: Boolean): Boolean = sharedPref.getBoolean(key, defaultValue)
fun putBoolean(key: String, value: Boolean, sync: Boolean = false) = sharedPref.edit(sync) { putBoolean(key, value) }
fun putString(key: String, value: String?, sync: Boolean = false) {
sharedPref.edit(sync) { putString(key, value) } sharedPref.edit(sync) { putString(key, value) }
} }

View File

@ -4,12 +4,13 @@ import androidx.room.Dao
import androidx.room.Query import androidx.room.Query
import io.github.wulkanowy.data.db.entities.Conference import io.github.wulkanowy.data.db.entities.Conference
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import java.time.LocalDateTime
import javax.inject.Singleton import javax.inject.Singleton
@Dao @Dao
@Singleton @Singleton
interface ConferenceDao : BaseDao<Conference> { interface ConferenceDao : BaseDao<Conference> {
@Query("SELECT * FROM Conferences WHERE diary_id = :diaryId AND student_id = :studentId") @Query("SELECT * FROM Conferences WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :startDate")
fun loadAll(diaryId: Int, studentId: Int): Flow<List<Conference>> fun loadAll(diaryId: Int, studentId: Int, startDate: LocalDateTime): Flow<List<Conference>>
} }

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.SchoolAnnouncement
import kotlinx.coroutines.flow.Flow
import javax.inject.Singleton
@Dao
@Singleton
interface SchoolAnnouncementDao : BaseDao<SchoolAnnouncement> {
@Query("SELECT * FROM SchoolAnnouncements WHERE student_id = :studentId")
fun loadAll(studentId: Int): Flow<List<SchoolAnnouncement>>
}

View File

@ -14,33 +14,39 @@ import javax.inject.Singleton
@Singleton @Singleton
@Dao @Dao
interface StudentDao { abstract class StudentDao {
@Insert(onConflict = ABORT) @Insert(onConflict = ABORT)
suspend fun insertAll(student: List<Student>): List<Long> abstract suspend fun insertAll(student: List<Student>): List<Long>
@Delete @Delete
suspend fun delete(student: Student) abstract suspend fun delete(student: Student)
@Update(entity = Student::class) @Update(entity = Student::class)
suspend fun update(studentNickAndAvatar: StudentNickAndAvatar) abstract suspend fun update(studentNickAndAvatar: StudentNickAndAvatar)
@Query("SELECT * FROM Students WHERE is_current = 1") @Query("SELECT * FROM Students WHERE is_current = 1")
suspend fun loadCurrent(): Student? abstract suspend fun loadCurrent(): Student?
@Query("SELECT * FROM Students WHERE id = :id") @Query("SELECT * FROM Students WHERE id = :id")
suspend fun loadById(id: Long): Student? abstract suspend fun loadById(id: Long): Student?
@Query("SELECT * FROM Students") @Query("SELECT * FROM Students")
suspend fun loadAll(): List<Student> abstract suspend fun loadAll(): List<Student>
@Transaction @Transaction
@Query("SELECT * FROM Students") @Query("SELECT * FROM Students")
suspend fun loadStudentsWithSemesters(): List<StudentWithSemesters> abstract suspend fun loadStudentsWithSemesters(): List<StudentWithSemesters>
@Query("UPDATE Students SET is_current = 1 WHERE id = :id") @Query("UPDATE Students SET is_current = 1 WHERE id = :id")
suspend fun updateCurrent(id: Long) abstract suspend fun updateCurrent(id: Long)
@Query("UPDATE Students SET is_current = 0") @Query("UPDATE Students SET is_current = 0")
suspend fun resetCurrent() abstract suspend fun resetCurrent()
@Transaction
open suspend fun switchCurrent(id: Long) {
resetCurrent()
updateCurrent(id)
}
} }

View File

@ -0,0 +1,16 @@
package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Query
import io.github.wulkanowy.data.db.entities.TimetableHeader
import kotlinx.coroutines.flow.Flow
import java.time.LocalDate
import javax.inject.Singleton
@Dao
@Singleton
interface TimetableHeaderDao : BaseDao<TimetableHeader> {
@Query("SELECT * FROM TimetableHeaders WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow<List<TimetableHeader>>
}

View File

@ -32,4 +32,7 @@ data class Conference(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
var id: Long = 0 var id: Long = 0
@ColumnInfo(name = "is_notified")
var isNotified: Boolean = true
} }

View File

@ -36,4 +36,7 @@ data class Exam(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
var id: Long = 0 var id: Long = 0
@ColumnInfo(name = "is_notified")
var isNotified: Boolean = true
} }

View File

@ -37,4 +37,7 @@ data class Homework(
@ColumnInfo(name = "is_done") @ColumnInfo(name = "is_done")
var isDone: Boolean = false var isDone: Boolean = false
@ColumnInfo(name = "is_notified")
var isNotified: Boolean = true
} }

View File

@ -3,8 +3,10 @@ package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.squareup.moshi.JsonClass
import java.io.Serializable import java.io.Serializable
@JsonClass(generateAdapter = true)
@Entity(tableName = "Recipients") @Entity(tableName = "Recipients")
data class Recipient( data class Recipient(

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
import java.time.LocalDate
@Entity(tableName = "SchoolAnnouncements")
data class SchoolAnnouncement(
@ColumnInfo(name = "student_id")
val studentId: Int,
val date: LocalDate,
val subject: String,
val content: String
) : Serializable {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
@ColumnInfo(name = "is_notified")
var isNotified: Boolean = true
}

View File

@ -0,0 +1,25 @@
package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable
import java.time.LocalDate
@Entity(tableName = "TimetableHeaders")
data class TimetableHeader(
@ColumnInfo(name = "student_id")
val studentId: Int,
@ColumnInfo(name = "diary_id")
val diaryId: Int,
val date: LocalDate,
val content: String,
) : Serializable {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}

View File

@ -0,0 +1,12 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration36 : Migration(35, 36) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Exams ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1")
database.execSQL("ALTER TABLE Homework ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1")
}
}

View File

@ -0,0 +1,21 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration37 : Migration(36, 37) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS TimetableHeaders (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
student_id INTEGER NOT NULL,
diary_id INTEGER NOT NULL,
date INTEGER NOT NULL,
content TEXT NOT NULL
)
"""
)
}
}

View File

@ -0,0 +1,19 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration38 : Migration(37, 38) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("""
CREATE TABLE IF NOT EXISTS `SchoolAnnouncements` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`student_id` INTEGER NOT NULL,
`date` INTEGER NOT NULL,
`subject` TEXT NOT NULL,
`content` TEXT NOT NULL
)
""")
}
}

View File

@ -0,0 +1,12 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration39 : Migration(38, 39) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Conferences ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1")
database.execSQL("ALTER TABLE SchoolAnnouncements ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1")
}
}

View File

@ -0,0 +1,14 @@
package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.sdk.pojo.DirectorInformation as SdkDirectorInformation
fun List<SdkDirectorInformation>.mapToEntities(student: Student) = map {
SchoolAnnouncement(
studentId = student.studentId,
date = it.date,
subject = it.subject,
content = it.content,
)
}

View File

@ -3,9 +3,19 @@ package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.db.entities.TimetableAdditional import io.github.wulkanowy.data.db.entities.TimetableAdditional
import io.github.wulkanowy.data.db.entities.TimetableHeader
import io.github.wulkanowy.data.pojos.TimetableFull
import io.github.wulkanowy.sdk.pojo.TimetableFull as SdkTimetableFull
import io.github.wulkanowy.sdk.pojo.TimetableDayHeader as SdkTimetableHeader
import io.github.wulkanowy.sdk.pojo.Timetable as SdkTimetable import io.github.wulkanowy.sdk.pojo.Timetable as SdkTimetable
import io.github.wulkanowy.sdk.pojo.TimetableAdditional as SdkTimetableAdditional import io.github.wulkanowy.sdk.pojo.TimetableAdditional as SdkTimetableAdditional
fun SdkTimetableFull.mapToEntities(semester: Semester) = TimetableFull(
lessons = lessons.mapToEntities(semester),
additional = additional.mapToEntities(semester),
headers = headers.mapToEntities(semester)
)
fun List<SdkTimetable>.mapToEntities(semester: Semester) = map { fun List<SdkTimetable>.mapToEntities(semester: Semester) = map {
Timetable( Timetable(
studentId = semester.studentId, studentId = semester.studentId,
@ -39,3 +49,13 @@ fun List<SdkTimetableAdditional>.mapToEntities(semester: Semester) = map {
end = it.end end = it.end
) )
} }
@JvmName("mapToEntitiesTimetableHeaders")
fun List<SdkTimetableHeader>.mapToEntities(semester: Semester) = map {
TimetableHeader(
studentId = semester.studentId,
diaryId = semester.diaryId,
date = it.date,
content = it.content
)
}

View File

@ -0,0 +1,11 @@
package io.github.wulkanowy.data.pojos
import com.squareup.moshi.JsonClass
import io.github.wulkanowy.ui.modules.message.send.RecipientChipItem
@JsonClass(generateAdapter = true)
data class MessageDraft(
val recipients: List<RecipientChipItem>,
val subject: String,
val content: String,
)

View File

@ -0,0 +1,36 @@
package io.github.wulkanowy.data.pojos
import androidx.annotation.DrawableRes
import androidx.annotation.PluralsRes
import androidx.annotation.StringRes
import io.github.wulkanowy.services.sync.notifications.NotificationType
import io.github.wulkanowy.ui.modules.main.MainView
sealed interface Notification {
val type: NotificationType
val startMenu: MainView.Section
val icon: Int
val titleStringRes: Int
val contentStringRes: Int
}
data class MultipleNotifications(
override val type: NotificationType,
override val startMenu: MainView.Section,
@DrawableRes override val icon: Int,
@PluralsRes override val titleStringRes: Int,
@PluralsRes override val contentStringRes: Int,
@PluralsRes val summaryStringRes: Int,
val lines: List<String>,
) : Notification
data class OneNotification(
override val type: NotificationType,
override val startMenu: MainView.Section,
@DrawableRes override val icon: Int,
@StringRes override val titleStringRes: Int,
@StringRes override val contentStringRes: Int,
val contentValues: List<String>,
) : Notification

View File

@ -0,0 +1,11 @@
package io.github.wulkanowy.data.pojos
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.db.entities.TimetableAdditional
import io.github.wulkanowy.data.db.entities.TimetableHeader
data class TimetableFull(
val lessons: List<Timetable>,
val additional: List<TimetableAdditional>,
val headers: List<TimetableHeader>,
)

View File

@ -15,6 +15,7 @@ class AppCreatorRepository @Inject constructor(
private val dispatchers: DispatchersProvider private val dispatchers: DispatchersProvider
) { ) {
@Suppress("BlockingMethodInNonBlockingContext")
suspend fun getAppCreators() = withContext(dispatchers.backgroundThread) { suspend fun getAppCreators() = withContext(dispatchers.backgroundThread) {
val moshi = Moshi.Builder().build() val moshi = Moshi.Builder().build()
val type = Types.newParameterizedType(List::class.java, Contributor::class.java) val type = Types.newParameterizedType(List::class.java, Contributor::class.java)

View File

@ -25,9 +25,17 @@ class AttendanceSummaryRepository @Inject constructor(
private val cacheKey = "attendance_summary" private val cacheKey = "attendance_summary"
fun getAttendanceSummary(student: Student, semester: Semester, subjectId: Int, forceRefresh: Boolean) = networkBoundResource( fun getAttendanceSummary(
student: Student,
semester: Semester,
subjectId: Int,
forceRefresh: Boolean
) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester)) }, shouldFetch = {
it.isEmpty() || forceRefresh
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
},
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) }, query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) },
fetch = { fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)

View File

@ -1,6 +1,7 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
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.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
@ -10,7 +11,11 @@ import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneOffset
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -25,19 +30,44 @@ class ConferenceRepository @Inject constructor(
private val cacheKey = "conference" private val cacheKey = "conference"
fun getConferences(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource( fun getConferences(
student: Student,
semester: Semester,
forceRefresh: Boolean,
notify: Boolean = false,
startDate: LocalDateTime = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC)
) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester)) }, shouldFetch = {
query = { conferenceDb.loadAll(semester.diaryId, student.studentId) }, it.isEmpty() || forceRefresh
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
},
query = {
conferenceDb.loadAll(semester.diaryId, student.studentId, startDate)
},
fetch = { fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
.getConferences() .getConferences()
.mapToEntities(semester) .mapToEntities(semester)
.filter { it.date >= startDate }
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
val conferencesToSave = (new uniqueSubtract old).onEach {
if (notify) it.isNotified = false
}
conferenceDb.deleteAll(old uniqueSubtract new) conferenceDb.deleteAll(old uniqueSubtract new)
conferenceDb.insertAll(new uniqueSubtract old) conferenceDb.insertAll(conferencesToSave)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
} }
) )
fun getConferenceFromDatabase(semester: Semester): Flow<List<Conference>> =
conferenceDb.loadAll(
diaryId = semester.diaryId,
studentId = semester.studentId,
startDate = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC)
)
suspend fun updateConference(conference: List<Conference>) = conferenceDb.updateAll(conference)
} }

View File

@ -1,6 +1,7 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
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.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
@ -12,6 +13,7 @@ import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.startExamsDay import io.github.wulkanowy.utils.startExamsDay
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow
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
@ -28,20 +30,54 @@ class ExamRepository @Inject constructor(
private val cacheKey = "exam" private val cacheKey = "exam"
fun getExams(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource( fun getExams(
student: Student,
semester: Semester,
start: LocalDate,
end: LocalDate,
forceRefresh: Boolean,
notify: Boolean = false
) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) }, shouldFetch = {
query = { examDb.loadAll(semester.diaryId, semester.studentId, start.startExamsDay, start.endExamsDay) }, val isShouldBeRefreshed = refreshHelper.isShouldBeRefreshed(
key = getRefreshKey(cacheKey, semester, start, end)
)
it.isEmpty() || forceRefresh || isShouldBeRefreshed
},
query = {
examDb.loadAll(
diaryId = semester.diaryId,
studentId = semester.studentId,
from = start.startExamsDay,
end = start.endExamsDay
)
},
fetch = { fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
.getExams(start.startExamsDay, start.endExamsDay, semester.semesterId) .getExams(start.startExamsDay, start.endExamsDay, semester.semesterId)
.mapToEntities(semester) .mapToEntities(semester)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
val examsToSave = (new uniqueSubtract old).onEach {
if (notify) it.isNotified = false
}
examDb.deleteAll(old uniqueSubtract new) examDb.deleteAll(old uniqueSubtract new)
examDb.insertAll(new uniqueSubtract old) 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>> {
return examDb.loadAll(
diaryId = semester.diaryId,
studentId = semester.studentId,
from = start.startExamsDay,
end = start.endExamsDay
)
}
suspend fun updateExam(exam: List<Exam>) = examDb.updateAll(exam)
} }

View File

@ -33,10 +33,16 @@ class GradeRepository @Inject constructor(
private val cacheKey = "grade" private val cacheKey = "grade"
fun getGrades(student: Student, semester: Semester, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource( fun getGrades(
student: Student,
semester: Semester,
forceRefresh: Boolean,
notify: Boolean = false
) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
shouldFetch = { (details, summaries) -> shouldFetch = { (details, summaries) ->
val isShouldBeRefreshed = refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester)) val isShouldBeRefreshed =
refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
details.isEmpty() || summaries.isEmpty() || forceRefresh || isShouldBeRefreshed details.isEmpty() || summaries.isEmpty() || forceRefresh || isShouldBeRefreshed
}, },
query = { query = {
@ -59,8 +65,14 @@ class GradeRepository @Inject constructor(
} }
) )
private suspend fun refreshGradeDetails(student: Student, oldGrades: List<Grade>, newDetails: List<Grade>, notify: Boolean) { private suspend fun refreshGradeDetails(
val notifyBreakDate = oldGrades.maxByOrNull { it.date }?.date ?: student.registrationDate.toLocalDate() student: Student,
oldGrades: List<Grade>,
newDetails: List<Grade>,
notify: Boolean
) {
val notifyBreakDate =
oldGrades.maxByOrNull { it.date }?.date ?: student.registrationDate.toLocalDate()
gradeDb.deleteAll(oldGrades uniqueSubtract newDetails) gradeDb.deleteAll(oldGrades uniqueSubtract newDetails)
gradeDb.insertAll((newDetails uniqueSubtract oldGrades).onEach { gradeDb.insertAll((newDetails uniqueSubtract oldGrades).onEach {
if (it.date >= notifyBreakDate) it.apply { if (it.date >= notifyBreakDate) it.apply {
@ -70,10 +82,15 @@ class GradeRepository @Inject constructor(
}) })
} }
private suspend fun refreshGradeSummaries(oldSummaries: List<GradeSummary>, newSummary: List<GradeSummary>, notify: Boolean) { private suspend fun refreshGradeSummaries(
oldSummaries: List<GradeSummary>,
newSummary: List<GradeSummary>,
notify: Boolean
) {
gradeSummaryDb.deleteAll(oldSummaries uniqueSubtract newSummary) gradeSummaryDb.deleteAll(oldSummaries uniqueSubtract newSummary)
gradeSummaryDb.insertAll((newSummary uniqueSubtract oldSummaries).onEach { summary -> gradeSummaryDb.insertAll((newSummary uniqueSubtract oldSummaries).onEach { summary ->
val oldSummary = oldSummaries.find { oldSummary -> oldSummary.subject == summary.subject } val oldSummary =
oldSummaries.find { oldSummary -> oldSummary.subject == summary.subject }
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
@ -104,22 +121,16 @@ class GradeRepository @Inject constructor(
} }
} }
fun getNotNotifiedGrades(semester: Semester): Flow<List<Grade>> { fun getGradesFromDatabase(semester: Semester): Flow<List<Grade>> {
return gradeDb.loadAll(semester.semesterId, semester.studentId).map { return gradeDb.loadAll(semester.semesterId, semester.studentId)
it.filter { grade -> !grade.isNotified }
}
} }
fun getNotNotifiedPredictedGrades(semester: Semester): Flow<List<GradeSummary>> { fun getGradesPredictedFromDatabase(semester: Semester): Flow<List<GradeSummary>> {
return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId).map { return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId)
it.filter { gradeSummary -> !gradeSummary.isPredictedGradeNotified }
}
} }
fun getNotNotifiedFinalGrades(semester: Semester): Flow<List<GradeSummary>> { fun getGradesFinalFromDatabase(semester: Semester): Flow<List<GradeSummary>> {
return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId).map { return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId)
it.filter { gradeSummary -> !gradeSummary.isFinalGradeNotified }
}
} }
suspend fun updateGrade(grade: Grade) { suspend fun updateGrade(grade: Grade) {

View File

@ -29,18 +29,38 @@ class HomeworkRepository @Inject constructor(
private val cacheKey = "homework" private val cacheKey = "homework"
fun getHomework(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource( fun getHomework(
student: Student, semester: Semester,
start: LocalDate, end: LocalDate,
forceRefresh: Boolean, notify: Boolean = false
) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) }, shouldFetch = {
query = { homeworkDb.loadAll(semester.semesterId, semester.studentId, start.monday, end.sunday) }, val isShouldBeRefreshed = refreshHelper.isShouldBeRefreshed(
key = getRefreshKey(cacheKey, semester, start, end)
)
it.isEmpty() || forceRefresh || isShouldBeRefreshed
},
query = {
homeworkDb.loadAll(
semesterId = semester.semesterId,
studentId = semester.studentId,
from = start.monday,
end = end.sunday
)
},
fetch = { fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) sdk.init(student).switchDiary(semester.diaryId, 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
}
homeworkDb.deleteAll(old uniqueSubtract new) homeworkDb.deleteAll(old uniqueSubtract new)
homeworkDb.insertAll(new uniqueSubtract old) homeworkDb.insertAll(homeWorkToSave)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
} }
@ -51,4 +71,9 @@ class HomeworkRepository @Inject constructor(
isDone = !isDone isDone = !isDone
})) }))
} }
fun getHomeworkFromDatabase(semester: Semester, start: LocalDate, end: LocalDate) =
homeworkDb.loadAll(semester.semesterId, semester.studentId, start.monday, end.sunday)
suspend fun updateHomework(homework: List<Homework>) = homeworkDb.updateAll(homework)
} }

View File

@ -1,8 +1,15 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import android.content.Context
import com.squareup.moshi.Moshi
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import io.github.wulkanowy.data.Resource
import io.github.wulkanowy.data.db.SharedPrefProvider
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.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.Recipient import io.github.wulkanowy.data.db.entities.Recipient
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
@ -10,6 +17,8 @@ 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.mappers.mapFromEntities import io.github.wulkanowy.data.mappers.mapFromEntities
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.pojos.MessageDraft
import io.github.wulkanowy.data.pojos.MessageDraftJsonAdapter
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.pojo.Folder import io.github.wulkanowy.sdk.pojo.Folder
import io.github.wulkanowy.sdk.pojo.SentMessage import io.github.wulkanowy.sdk.pojo.SentMessage
@ -19,7 +28,6 @@ import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.networkBoundResource
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.map
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import timber.log.Timber import timber.log.Timber
import java.time.LocalDateTime.now import java.time.LocalDateTime.now
@ -31,7 +39,10 @@ class MessageRepository @Inject constructor(
private val messagesDb: MessagesDao, private val messagesDb: MessagesDao,
private val messageAttachmentDao: MessageAttachmentDao, private val messageAttachmentDao: MessageAttachmentDao,
private val sdk: Sdk, private val sdk: Sdk,
@ApplicationContext private val context: Context,
private val refreshHelper: AutoRefreshHelper, private val refreshHelper: AutoRefreshHelper,
private val sharedPrefProvider: SharedPrefProvider,
private val moshi: Moshi,
) { ) {
private val saveFetchResultMutex = Mutex() private val saveFetchResultMutex = Mutex()
@ -39,22 +50,54 @@ class MessageRepository @Inject constructor(
private val cacheKey = "message" private val cacheKey = "message"
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
fun getMessages(student: Student, semester: Semester, folder: MessageFolder, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource( fun getMessages(
student: Student, semester: Semester,
folder: MessageFolder, forceRefresh: Boolean, notify: Boolean = false
): Flow<Resource<List<Message>>> = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, student, folder)) }, shouldFetch = {
it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(
getRefreshKey(cacheKey, student, folder)
)
},
query = { messagesDb.loadAll(student.id.toInt(), folder.id) }, query = { messagesDb.loadAll(student.id.toInt(), folder.id) },
fetch = { sdk.init(student).getMessages(Folder.valueOf(folder.name), now().minusMonths(3), now()).mapToEntities(student) }, fetch = {
sdk.init(student).getMessages(Folder.valueOf(folder.name), now().minusMonths(3), now())
.mapToEntities(student)
},
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
messagesDb.deleteAll(old uniqueSubtract new) messagesDb.deleteAll(old uniqueSubtract new)
messagesDb.insertAll((new uniqueSubtract old).onEach { messagesDb.insertAll((new uniqueSubtract old).onEach {
it.isNotified = !notify it.isNotified = !notify
}) })
messagesDb.updateAll(getMessagesWithReadByChange(old, new, !notify))
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student, folder)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student, folder))
} }
) )
fun getMessage(student: Student, message: Message, markAsRead: Boolean = false) = networkBoundResource( private fun getMessagesWithReadByChange(
old: List<Message>, new: List<Message>,
setNotified: Boolean
): List<Message> {
val oldMeta = old.map { Triple(it, it.readBy, it.unreadBy) }
val newMeta = new.map { Triple(it, it.readBy, it.unreadBy) }
val updatedItems = newMeta uniqueSubtract oldMeta
return updatedItems.map {
val oldItem = old.find { item -> item.messageId == it.first.messageId }
it.first.apply {
id = oldItem?.id ?: 0
isNotified = oldItem?.isNotified ?: setNotified
content = oldItem?.content.orEmpty()
}
}
}
fun getMessage(
student: Student, message: Message, markAsRead: Boolean = false
): Flow<Resource<MessageWithAttachment?>> = networkBoundResource(
shouldFetch = { shouldFetch = {
checkNotNull(it, { "This message no longer exist!" }) checkNotNull(it, { "This message no longer exist!" })
Timber.d("Message content in db empty: ${it.message.content.isEmpty()}") Timber.d("Message content in db empty: ${it.message.content.isEmpty()}")
@ -62,14 +105,20 @@ class MessageRepository @Inject constructor(
}, },
query = { messagesDb.loadMessageWithAttachment(student.id.toInt(), message.messageId) }, query = { messagesDb.loadMessageWithAttachment(student.id.toInt(), message.messageId) },
fetch = { fetch = {
sdk.init(student).getMessageDetails(it!!.message.messageId, message.folderId, markAsRead, message.realId).let { details -> sdk.init(student).getMessageDetails(
messageId = it!!.message.messageId,
folderId = message.folderId,
read = markAsRead,
id = message.realId
).let { details ->
details.content to details.attachments.mapToEntities() details.content to details.attachments.mapToEntities()
} }
}, },
saveFetchResult = { old, (downloadedMessage, attachments) -> saveFetchResult = { old, (downloadedMessage, attachments) ->
checkNotNull(old, { "Fetched message no longer exist!" }) checkNotNull(old, { "Fetched message no longer exist!" })
messagesDb.updateAll(listOf(old.message.copy(unread = !markAsRead).apply { messagesDb.updateAll(listOf(old.message.apply {
id = old.message.id id = old.message.id
unread = !markAsRead
content = content.ifBlank { downloadedMessage } content = content.ifBlank { downloadedMessage }
})) }))
messageAttachmentDao.insertAttachments(attachments) messageAttachmentDao.insertAttachments(attachments)
@ -77,30 +126,42 @@ class MessageRepository @Inject constructor(
} }
) )
fun getNotNotifiedMessages(student: Student): Flow<List<Message>> { fun getMessagesFromDatabase(student: Student): Flow<List<Message>> {
return messagesDb.loadAll(student.id.toInt(), RECEIVED.id).map { it.filter { message -> !message.isNotified && message.unread } } return messagesDb.loadAll(student.id.toInt(), RECEIVED.id)
} }
suspend fun updateMessages(messages: List<Message>) { suspend fun updateMessages(messages: List<Message>) {
return messagesDb.updateAll(messages) return messagesDb.updateAll(messages)
} }
suspend fun sendMessage(student: Student, subject: String, content: String, recipients: List<Recipient>): SentMessage { suspend fun sendMessage(
return sdk.init(student).sendMessage( student: Student, subject: String, content: String,
subject = subject, recipients: List<Recipient>
content = content, ): SentMessage = sdk.init(student).sendMessage(
recipients = recipients.mapFromEntities() subject = subject,
) content = content,
} recipients = recipients.mapFromEntities()
)
suspend fun deleteMessage(student: Student, message: Message) { suspend fun deleteMessage(student: Student, message: Message) {
val isDeleted = sdk.init(student).deleteMessages(listOf(message.messageId), message.folderId) val isDeleted = sdk.init(student).deleteMessages(
messages = listOf(message.messageId), message.folderId
)
if (message.folderId != MessageFolder.TRASHED.id) { if (message.folderId != MessageFolder.TRASHED.id && isDeleted) {
if (isDeleted) messagesDb.updateAll(listOf(message.copy(folderId = MessageFolder.TRASHED.id).apply { val deletedMessage = message.copy(folderId = MessageFolder.TRASHED.id).apply {
id = message.id id = message.id
content = message.content content = message.content
})) }
messagesDb.updateAll(listOf(deletedMessage))
} else messagesDb.deleteAll(listOf(message)) } else messagesDb.deleteAll(listOf(message))
} }
var draftMessage: MessageDraft?
get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_send_draft))
?.let { MessageDraftJsonAdapter(moshi).fromJson(it) }
set(value) = sharedPrefProvider.putString(
context.getString(R.string.pref_key_message_send_draft),
value?.let { MessageDraftJsonAdapter(moshi).toJson(it) }
)
} }

View File

@ -50,8 +50,8 @@ class NoteRepository @Inject constructor(
} }
) )
fun getNotNotifiedNotes(student: Student): Flow<List<Note>> { fun getNotesFromDatabase(student: Student): Flow<List<Note>> {
return noteDb.loadAll(student.studentId).map { it.filter { note -> !note.isNotified } } return noteDb.loadAll(student.studentId)
} }
suspend fun updateNote(note: Note) { suspend fun updateNote(note: Note) {

View File

@ -3,18 +3,41 @@ package io.github.wulkanowy.data.repositories
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.core.content.edit import androidx.core.content.edit
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
import com.fredporciuncula.flow.preferences.Preference
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.adapter
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.sdk.toLocalDate
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.grade.GradeSortingMode import io.github.wulkanowy.ui.modules.grade.GradeSortingMode
import io.github.wulkanowy.utils.toTimestamp
import io.github.wulkanowy.utils.toLocalDateTime
import io.github.wulkanowy.utils.toTimestamp
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import java.time.LocalDate
import java.time.LocalDateTime
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@OptIn(ExperimentalCoroutinesApi::class)
@Singleton @Singleton
class PreferencesRepository @Inject constructor( class PreferencesRepository @Inject constructor(
private val sharedPref: SharedPreferences, private val sharedPref: SharedPreferences,
@ApplicationContext val context: Context private val flowSharedPref: FlowSharedPreferences,
@ApplicationContext val context: Context,
moshi: Moshi
) { ) {
@OptIn(ExperimentalStdlibApi::class)
private val dashboardItemsPositionAdapter: JsonAdapter<Map<DashboardItem.Type, Int>> =
moshi.adapter()
val startMenuIndex: Int val startMenuIndex: Int
get() = getString(R.string.pref_key_start_menu, R.string.pref_default_startup).toInt() get() = getString(R.string.pref_key_start_menu, R.string.pref_default_startup).toInt()
@ -146,9 +169,78 @@ class PreferencesRepository @Inject constructor(
R.bool.pref_default_subjects_without_grades R.bool.pref_default_subjects_without_grades
) )
var isKitkatDialogDisabled: Boolean val isOptionalArithmeticAverage: Boolean
get() = sharedPref.getBoolean("kitkat_dialog_disabled", false) get() = getBoolean(
set(value) = sharedPref.edit { putBoolean("kitkat_dialog_disabled", value) } R.string.pref_key_optional_arithmetic_average,
R.bool.pref_default_optional_arithmetic_average
)
var lasSyncDate: LocalDateTime
get() = getLong(
R.string.pref_key_last_sync_date,
R.string.pref_default_last_sync_date
).toLocalDateTime()
set(value) = sharedPref.edit().putLong("last_sync_date", value.toTimestamp()).apply()
var dashboardItemsPosition: Map<DashboardItem.Type, Int>?
get() {
val json = sharedPref.getString(PREF_KEY_DASHBOARD_ITEMS_POSITION, null) ?: return null
return dashboardItemsPositionAdapter.fromJson(json)
}
set(value) = sharedPref.edit {
putString(
PREF_KEY_DASHBOARD_ITEMS_POSITION,
dashboardItemsPositionAdapter.toJson(value)
)
}
val selectedDashboardTilesFlow: Flow<Set<DashboardItem.Tile>>
get() = selectedDashboardTilesPreference.asFlow()
.map { set ->
set.map { DashboardItem.Tile.valueOf(it) }
.plus(DashboardItem.Tile.ACCOUNT)
.toSet()
}
var selectedDashboardTiles: Set<DashboardItem.Tile>
get() = selectedDashboardTilesPreference.get()
.map { DashboardItem.Tile.valueOf(it) }
.plus(DashboardItem.Tile.ACCOUNT)
.toSet()
set(value) {
val filteredValue = value.filterNot { it == DashboardItem.Tile.ACCOUNT }
.map { it.name }
.toSet()
selectedDashboardTilesPreference.set(filteredValue)
}
private val selectedDashboardTilesPreference: Preference<Set<String>>
get() {
val defaultSet =
context.resources.getStringArray(R.array.pref_default_dashboard_tiles).toSet()
val prefKey = context.getString(R.string.pref_key_dashboard_tiles)
return flowSharedPref.getStringSet(prefKey, defaultSet)
}
var inAppReviewCount: Int
get() = sharedPref.getInt(PREF_KEY_IN_APP_REVIEW_COUNT, 0)
set(value) = sharedPref.edit().putInt(PREF_KEY_IN_APP_REVIEW_COUNT, value).apply()
var inAppReviewDate: LocalDate?
get() = sharedPref.getLong(PREF_KEY_IN_APP_REVIEW_DATE, 0).takeIf { it != 0L }?.toLocalDate()
set(value) = sharedPref.edit().putLong(PREF_KEY_IN_APP_REVIEW_DATE, value!!.toTimestamp()).apply()
var isAppReviewDone: Boolean
get() = sharedPref.getBoolean(PREF_KEY_IN_APP_REVIEW_DONE, false)
set(value) = sharedPref.edit().putBoolean(PREF_KEY_IN_APP_REVIEW_DONE, value).apply()
private fun getLong(id: Int, default: Int) = getLong(context.getString(id), default)
private fun getLong(id: String, default: Int) =
sharedPref.getLong(id, context.resources.getString(default).toLong())
private fun getString(id: Int, default: Int) = getString(context.getString(id), default) private fun getString(id: Int, default: Int) = getString(context.getString(id), default)
@ -159,4 +251,15 @@ class PreferencesRepository @Inject constructor(
private fun getBoolean(id: String, default: Int) = private fun getBoolean(id: String, default: Int) =
sharedPref.getBoolean(id, context.resources.getBoolean(default)) sharedPref.getBoolean(id, context.resources.getBoolean(default))
private companion object {
private const val PREF_KEY_DASHBOARD_ITEMS_POSITION = "dashboard_items_position"
private const val PREF_KEY_IN_APP_REVIEW_COUNT = "in_app_review_count"
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"
}
} }

View File

@ -0,0 +1,65 @@
package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
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.mappers.mapToEntities
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class SchoolAnnouncementRepository @Inject constructor(
private val schoolAnnouncementDb: SchoolAnnouncementDao,
private val sdk: Sdk,
private val refreshHelper: AutoRefreshHelper,
) {
private val saveFetchResultMutex = Mutex()
private val cacheKey = "school_announcement"
fun getSchoolAnnouncements(
student: Student,
forceRefresh: Boolean,
notify: Boolean = false
) = networkBoundResource(
mutex = saveFetchResultMutex,
shouldFetch = {
it.isEmpty() || forceRefresh
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, student))
},
query = {
schoolAnnouncementDb.loadAll(
student.studentId)
},
fetch = {
sdk.init(student)
.getDirectorInformation()
.mapToEntities(student)
},
saveFetchResult = { old, new ->
val schoolAnnouncementsToSave = (new uniqueSubtract old).onEach {
if (notify) it.isNotified = false
}
schoolAnnouncementDb.deleteAll(old uniqueSubtract new)
schoolAnnouncementDb.insertAll(schoolAnnouncementsToSave)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
}
)
fun getSchoolAnnouncementFromDatabase(student: Student): Flow<List<SchoolAnnouncement>> {
return schoolAnnouncementDb.loadAll(student.studentId)
}
suspend fun updateSchoolAnnouncement(schoolAnnouncement: List<SchoolAnnouncement>) = schoolAnnouncementDb.updateAll(schoolAnnouncement)
}

View File

@ -22,7 +22,11 @@ class SemesterRepository @Inject constructor(
private val dispatchers: DispatchersProvider private val dispatchers: DispatchersProvider
) { ) {
suspend fun getSemesters(student: Student, forceRefresh: Boolean = false, refreshOnNoCurrent: Boolean = false) = withContext(dispatchers.backgroundThread) { suspend fun getSemesters(
student: Student,
forceRefresh: Boolean = false,
refreshOnNoCurrent: Boolean = false
) = withContext(dispatchers.backgroundThread) {
val semesters = semesterDb.loadAll(student.studentId, student.classId) val semesters = semesterDb.loadAll(student.studentId, student.classId)
if (isShouldFetch(student, semesters, forceRefresh, refreshOnNoCurrent)) { if (isShouldFetch(student, semesters, forceRefresh, refreshOnNoCurrent)) {
@ -31,14 +35,21 @@ class SemesterRepository @Inject constructor(
} else semesters } else semesters
} }
private fun isShouldFetch(student: Student, semesters: List<Semester>, forceRefresh: Boolean, refreshOnNoCurrent: Boolean): Boolean { private fun isShouldFetch(
student: Student,
semesters: List<Semester>,
forceRefresh: Boolean,
refreshOnNoCurrent: Boolean
): Boolean {
val isNoSemesters = semesters.isEmpty() val isNoSemesters = semesters.isEmpty()
val isRefreshOnModeChangeRequired = if (Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) { val isRefreshOnModeChangeRequired =
semesters.firstOrNull { it.isCurrent }?.diaryId == 0 if (Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
} else false semesters.firstOrNull { it.isCurrent }?.diaryId == 0
} else false
val isRefreshOnNoCurrentAppropriate = refreshOnNoCurrent && !semesters.any { semester -> semester.isCurrent } val isRefreshOnNoCurrentAppropriate =
refreshOnNoCurrent && !semesters.any { semester -> semester.isCurrent }
return forceRefresh || isNoSemesters || isRefreshOnModeChangeRequired || isRefreshOnNoCurrentAppropriate return forceRefresh || isNoSemesters || isRefreshOnModeChangeRequired || isRefreshOnNoCurrentAppropriate
} }
@ -52,7 +63,8 @@ class SemesterRepository @Inject constructor(
semesterDb.insertSemesters(new.uniqueSubtract(old)) semesterDb.insertSemesters(new.uniqueSubtract(old))
} }
suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false) = withContext(dispatchers.backgroundThread) { suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false) =
getSemesters(student, forceRefresh).getCurrentOrLast() withContext(dispatchers.backgroundThread) {
} getSemesters(student, forceRefresh).getCurrentOrLast()
}
} }

View File

@ -1,7 +1,9 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import android.content.Context import android.content.Context
import androidx.room.withTransaction
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.dao.SemesterDao import io.github.wulkanowy.data.db.dao.SemesterDao
import io.github.wulkanowy.data.db.dao.StudentDao import io.github.wulkanowy.data.db.dao.StudentDao
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
@ -25,7 +27,8 @@ class StudentRepository @Inject constructor(
private val studentDb: StudentDao, private val studentDb: StudentDao,
private val semesterDb: SemesterDao, private val semesterDb: SemesterDao,
private val sdk: Sdk, private val sdk: Sdk,
private val appInfo: AppInfo private val appInfo: AppInfo,
private val appDatabase: AppDatabase
) { ) {
suspend fun isStudentSaved() = getSavedStudents(false).isNotEmpty() suspend fun isStudentSaved() = getSavedStudents(false).isNotEmpty()
@ -92,7 +95,7 @@ class StudentRepository @Inject constructor(
return student return student
} }
suspend fun saveStudents(studentsWithSemesters: List<StudentWithSemesters>): List<Long> { suspend fun saveStudents(studentsWithSemesters: List<StudentWithSemesters>) {
val semesters = studentsWithSemesters.flatMap { it.semesters } val semesters = studentsWithSemesters.flatMap { it.semesters }
val students = studentsWithSemesters.map { it.student } val students = studentsWithSemesters.map { it.student }
.map { .map {
@ -104,16 +107,21 @@ class StudentRepository @Inject constructor(
} }
} }
} }
.mapIndexed { index, student ->
if (index == 0) {
student.copy(isCurrent = true).apply { avatarColor = student.avatarColor }
} else student
}
semesterDb.insertSemesters(semesters) appDatabase.withTransaction {
return studentDb.insertAll(students) studentDb.resetCurrent()
semesterDb.insertSemesters(semesters)
studentDb.insertAll(students)
}
} }
suspend fun switchStudent(studentWithSemesters: StudentWithSemesters) { suspend fun switchStudent(studentWithSemesters: StudentWithSemesters) {
with(studentDb) { studentDb.switchCurrent(studentWithSemesters.student.id)
resetCurrent()
updateCurrent(studentWithSemesters.student.id)
}
} }
suspend fun logoutStudent(student: Student) = studentDb.delete(student) suspend fun logoutStudent(student: Student) = studentDb.delete(student)

View File

@ -2,11 +2,14 @@ package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao
import io.github.wulkanowy.data.db.dao.TimetableDao import io.github.wulkanowy.data.db.dao.TimetableDao
import io.github.wulkanowy.data.db.dao.TimetableHeaderDao
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.Timetable import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.db.entities.TimetableAdditional import io.github.wulkanowy.data.db.entities.TimetableAdditional
import io.github.wulkanowy.data.db.entities.TimetableHeader
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.pojos.TimetableFull
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
@ -16,8 +19,8 @@ import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.sunday
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
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
@ -27,6 +30,7 @@ import javax.inject.Singleton
class TimetableRepository @Inject constructor( class TimetableRepository @Inject constructor(
private val timetableDb: TimetableDao, private val timetableDb: TimetableDao,
private val timetableAdditionalDb: TimetableAdditionalDao, private val timetableAdditionalDb: TimetableAdditionalDao,
private val timetableHeaderDb: TimetableHeaderDao,
private val sdk: Sdk, private val sdk: Sdk,
private val schedulerHelper: TimetableNotificationSchedulerHelper, private val schedulerHelper: TimetableNotificationSchedulerHelper,
private val refreshHelper: AutoRefreshHelper, private val refreshHelper: AutoRefreshHelper,
@ -36,53 +40,111 @@ class TimetableRepository @Inject constructor(
private val cacheKey = "timetable" private val cacheKey = "timetable"
fun getTimetable(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean, refreshAdditional: Boolean = false) = networkBoundResource( fun getTimetable(
student: Student, semester: Semester, start: LocalDate, end: LocalDate,
forceRefresh: Boolean, refreshAdditional: Boolean = false
) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
shouldFetch = { (timetable, additional) -> timetable.isEmpty() || (additional.isEmpty() && refreshAdditional) || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) }, shouldFetch = { (timetable, additional, headers) ->
query = { val refreshKey = getRefreshKey(cacheKey, semester, start, end)
timetableDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday) val isShouldRefresh = refreshHelper.isShouldBeRefreshed(refreshKey)
.map { schedulerHelper.scheduleNotifications(it, student); it } val isRefreshAdditional = additional.isEmpty() && refreshAdditional
.combine(timetableAdditionalDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday)) { timetable, additional ->
timetable to additional
}
},
fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
.getTimetable(start.monday, end.sunday)
.let { (normal, additional) -> normal.mapToEntities(semester) to additional.mapToEntities(semester) }
val isNoData = timetable.isEmpty() || isRefreshAdditional || headers.isEmpty()
isNoData || forceRefresh || isShouldRefresh
}, },
saveFetchResult = { (oldTimetable, oldAdditional), (newTimetable, newAdditional) -> query = { getFullTimetableFromDatabase(student, semester, start, end) },
refreshTimetable(student, oldTimetable, newTimetable) fetch = {
refreshAdditional(oldAdditional, newAdditional) val timetableFull = sdk.init(student)
.switchDiary(semester.diaryId, semester.schoolYear)
.getTimetableFull(start.monday, end.sunday)
timetableFull.mapToEntities(semester)
},
saveFetchResult = { timetableOld, timetableNew ->
refreshTimetable(student, timetableOld.lessons, timetableNew.lessons)
refreshAdditional(timetableOld.additional, timetableNew.additional)
refreshDayHeaders(timetableOld.headers, timetableNew.headers)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
}, },
filterResult = { (timetable, additional) -> filterResult = { (timetable, additional, headers) ->
timetable.filter { item -> TimetableFull(
item.date in start..end lessons = timetable.filter { it.date in start..end },
} to additional.filter { item -> additional = additional.filter { it.date in start..end },
item.date in start..end headers = headers.filter { it.date in start..end }
} )
} }
) )
private suspend fun refreshTimetable(student: Student, old: List<Timetable>, new: List<Timetable>) { private fun getFullTimetableFromDatabase(
timetableDb.deleteAll(old.uniqueSubtract(new).also { schedulerHelper.cancelScheduled(it) }) student: Student, semester: Semester,
timetableDb.insertAll(new.uniqueSubtract(old).also { schedulerHelper.scheduleNotifications(it, student) }.map { item -> start: LocalDate, end: LocalDate
item.also { new -> ): Flow<TimetableFull> {
old.singleOrNull { new.start == it.start }?.let { old -> val timetableFlow = timetableDb.loadAll(
return@map new.copy( diaryId = semester.diaryId,
room = if (new.room.isEmpty()) old.room else new.room, studentId = semester.studentId,
teacher = if (new.teacher.isEmpty() && !new.changes && !old.changes) old.teacher else new.teacher from = start.monday,
) end = end.sunday
} )
} val headersFlow = timetableHeaderDb.loadAll(
}) diaryId = semester.diaryId,
studentId = semester.studentId,
from = start.monday,
end = end.sunday
)
val additionalFlow = timetableAdditionalDb.loadAll(
diaryId = semester.diaryId,
studentId = semester.studentId,
from = start.monday,
end = end.sunday
)
return combine(timetableFlow, headersFlow, additionalFlow) { lessons, headers, additional ->
schedulerHelper.scheduleNotifications(lessons, student)
TimetableFull(
lessons = lessons,
headers = headers,
additional = additional
)
}
} }
private suspend fun refreshAdditional(old: List<TimetableAdditional>, new: List<TimetableAdditional>) { private suspend fun refreshTimetable(
timetableAdditionalDb.deleteAll(old.uniqueSubtract(new)) student: Student,
timetableAdditionalDb.insertAll(new.uniqueSubtract(old)) lessonsOld: List<Timetable>, lessonsNew: List<Timetable>
) {
val lessonsToRemove = lessonsOld uniqueSubtract lessonsNew
val lessonsToAdd = (lessonsNew uniqueSubtract lessonsOld).map { new ->
val matchingOld = lessonsOld.singleOrNull { new.start == it.start }
if (matchingOld != null) {
val useOldTeacher = new.teacher.isEmpty() && !new.changes && !matchingOld.changes
new.copy(
room = if (new.room.isEmpty()) matchingOld.room else new.room,
teacher = if (useOldTeacher) matchingOld.teacher
else new.teacher
)
} else new
}
timetableDb.deleteAll(lessonsToRemove)
timetableDb.insertAll(lessonsToAdd)
schedulerHelper.cancelScheduled(lessonsToRemove, student)
schedulerHelper.scheduleNotifications(lessonsToAdd, student)
}
private suspend fun refreshAdditional(
old: List<TimetableAdditional>,
new: List<TimetableAdditional>
) {
timetableAdditionalDb.deleteAll(old uniqueSubtract new)
timetableAdditionalDb.insertAll(new uniqueSubtract old)
}
private suspend fun refreshDayHeaders(old: List<TimetableHeader>, new: List<TimetableHeader>) {
timetableHeaderDb.deleteAll(old uniqueSubtract new)
timetableHeaderDb.insertAll(new uniqueSubtract old)
} }
} }

View File

@ -15,14 +15,19 @@ import dagger.multibindings.IntoSet
import io.github.wulkanowy.services.sync.channels.Channel import io.github.wulkanowy.services.sync.channels.Channel
import io.github.wulkanowy.services.sync.channels.DebugChannel import io.github.wulkanowy.services.sync.channels.DebugChannel
import io.github.wulkanowy.services.sync.channels.LuckyNumberChannel import io.github.wulkanowy.services.sync.channels.LuckyNumberChannel
import io.github.wulkanowy.services.sync.channels.NewConferencesChannel
import io.github.wulkanowy.services.sync.channels.NewExamChannel
import io.github.wulkanowy.services.sync.channels.NewGradesChannel import io.github.wulkanowy.services.sync.channels.NewGradesChannel
import io.github.wulkanowy.services.sync.channels.NewHomeworkChannel
import io.github.wulkanowy.services.sync.channels.NewMessagesChannel import io.github.wulkanowy.services.sync.channels.NewMessagesChannel
import io.github.wulkanowy.services.sync.channels.NewNotesChannel import io.github.wulkanowy.services.sync.channels.NewNotesChannel
import io.github.wulkanowy.services.sync.channels.NewSchoolAnnouncementsChannel
import io.github.wulkanowy.services.sync.channels.PushChannel import io.github.wulkanowy.services.sync.channels.PushChannel
import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel
import io.github.wulkanowy.services.sync.works.AttendanceSummaryWork import io.github.wulkanowy.services.sync.works.AttendanceSummaryWork
import io.github.wulkanowy.services.sync.works.AttendanceWork import io.github.wulkanowy.services.sync.works.AttendanceWork
import io.github.wulkanowy.services.sync.works.CompletedLessonWork import io.github.wulkanowy.services.sync.works.CompletedLessonWork
import io.github.wulkanowy.services.sync.works.ConferenceWork
import io.github.wulkanowy.services.sync.works.ExamWork import io.github.wulkanowy.services.sync.works.ExamWork
import io.github.wulkanowy.services.sync.works.GradeStatisticsWork import io.github.wulkanowy.services.sync.works.GradeStatisticsWork
import io.github.wulkanowy.services.sync.works.GradeWork import io.github.wulkanowy.services.sync.works.GradeWork
@ -31,6 +36,7 @@ import io.github.wulkanowy.services.sync.works.LuckyNumberWork
import io.github.wulkanowy.services.sync.works.MessageWork import io.github.wulkanowy.services.sync.works.MessageWork
import io.github.wulkanowy.services.sync.works.NoteWork import io.github.wulkanowy.services.sync.works.NoteWork
import io.github.wulkanowy.services.sync.works.RecipientWork import io.github.wulkanowy.services.sync.works.RecipientWork
import io.github.wulkanowy.services.sync.works.SchoolAnnouncementWork
import io.github.wulkanowy.services.sync.works.TeacherWork import io.github.wulkanowy.services.sync.works.TeacherWork
import io.github.wulkanowy.services.sync.works.TimetableWork import io.github.wulkanowy.services.sync.works.TimetableWork
import io.github.wulkanowy.services.sync.works.Work import io.github.wulkanowy.services.sync.works.Work
@ -44,15 +50,18 @@ abstract class ServicesModule {
companion object { companion object {
@Provides @Provides
fun provideWorkManager(@ApplicationContext context: Context) = WorkManager.getInstance(context) fun provideWorkManager(@ApplicationContext context: Context) =
WorkManager.getInstance(context)
@Singleton @Singleton
@Provides @Provides
fun provideNotificationManager(@ApplicationContext context: Context) = NotificationManagerCompat.from(context) fun provideNotificationManager(@ApplicationContext context: Context) =
NotificationManagerCompat.from(context)
@Singleton @Singleton
@Provides @Provides
fun provideAlarmManager(@ApplicationContext context: Context): AlarmManager = context.getSystemService()!! fun provideAlarmManager(@ApplicationContext context: Context): AlarmManager =
context.getSystemService()!!
} }
@Binds @Binds
@ -67,6 +76,10 @@ abstract class ServicesModule {
@IntoSet @IntoSet
abstract fun provideAttendanceWork(work: AttendanceWork): Work abstract fun provideAttendanceWork(work: AttendanceWork): Work
@Binds
@IntoSet
abstract fun provideConferenceWork(work: ConferenceWork): Work
@Binds @Binds
@IntoSet @IntoSet
abstract fun provideExamWork(work: ExamWork): Work abstract fun provideExamWork(work: ExamWork): Work
@ -107,6 +120,10 @@ abstract class ServicesModule {
@IntoSet @IntoSet
abstract fun provideGradeStatistics(work: GradeStatisticsWork): Work abstract fun provideGradeStatistics(work: GradeStatisticsWork): Work
@Binds
@IntoSet
abstract fun provideSchoolAnnouncementWork(work: SchoolAnnouncementWork): Work
@Binds @Binds
@IntoSet @IntoSet
abstract fun provideDebugChannel(channel: DebugChannel): Channel abstract fun provideDebugChannel(channel: DebugChannel): Channel
@ -115,6 +132,18 @@ abstract class ServicesModule {
@IntoSet @IntoSet
abstract fun provideLuckyNumberChannel(channel: LuckyNumberChannel): Channel abstract fun provideLuckyNumberChannel(channel: LuckyNumberChannel): Channel
@Binds
@IntoSet
abstract fun provideNewConferenceChannel(channel: NewConferencesChannel): Channel
@Binds
@IntoSet
abstract fun provideNewExamChannel(channel: NewExamChannel): Channel
@Binds
@IntoSet
abstract fun provideNewHomeworkChannel(channel: NewHomeworkChannel): Channel
@Binds @Binds
@IntoSet @IntoSet
abstract fun provideNewGradesChannel(channel: NewGradesChannel): Channel abstract fun provideNewGradesChannel(channel: NewGradesChannel): Channel
@ -127,6 +156,10 @@ abstract class ServicesModule {
@IntoSet @IntoSet
abstract fun provideNewNotesChannel(channel: NewNotesChannel): Channel abstract fun provideNewNotesChannel(channel: NewNotesChannel): Channel
@Binds
@IntoSet
abstract fun provideNewSchoolAnnouncementChannel(channel: NewSchoolAnnouncementsChannel): Channel
@Binds @Binds
@IntoSet @IntoSet
abstract fun providePushChannel(channel: PushChannel): Channel abstract fun providePushChannel(channel: PushChannel): Channel

View File

@ -1,6 +1,5 @@
package io.github.wulkanowy.services.alarm package io.github.wulkanowy.services.alarm
import android.annotation.SuppressLint
import android.app.PendingIntent import android.app.PendingIntent
import android.app.PendingIntent.FLAG_UPDATE_CURRENT import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.content.Context import android.content.Context
@ -20,6 +19,7 @@ import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.flowWithResource
import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.getCompatColor
import io.github.wulkanowy.utils.toLocalDateTime import io.github.wulkanowy.utils.toLocalDateTime
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@ -50,7 +50,7 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
const val LESSON_END = "end_timestamp" const val LESSON_END = "end_timestamp"
} }
@SuppressLint("CheckResult") @OptIn(DelicateCoroutinesApi::class)
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
super.onReceive(context, intent) super.onReceive(context, intent)
Timber.d("Receiving intent... ${intent.toUri(0)}") Timber.d("Receiving intent... ${intent.toUri(0)}")

View File

@ -51,7 +51,8 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
lesson: Timetable lesson: Timetable
) = day.getOrNull(index - 1)?.end ?: lesson.start.minusMinutes(30) ) = day.getOrNull(index - 1)?.end ?: lesson.start.minusMinutes(30)
suspend fun cancelScheduled(lessons: List<Timetable>, studentId: Int = 1) { suspend fun cancelScheduled(lessons: List<Timetable>, student: Student) {
val studentId = student.studentId
withContext(dispatchersProvider.backgroundThread) { withContext(dispatchersProvider.backgroundThread) {
lessons.sortedBy { it.start }.forEachIndexed { index, lesson -> lessons.sortedBy { it.start }.forEachIndexed { index, lesson ->
val upcomingTime = getUpcomingLessonTime(index, lessons, lesson) val upcomingTime = getUpcomingLessonTime(index, lessons, lesson)
@ -78,7 +79,7 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
suspend fun scheduleNotifications(lessons: List<Timetable>, student: Student) { suspend fun scheduleNotifications(lessons: List<Timetable>, student: Student) {
if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) { if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) {
return cancelScheduled(lessons, student.studentId) return cancelScheduled(lessons, student)
} }
withContext(dispatchersProvider.backgroundThread) { withContext(dispatchersProvider.backgroundThread) {
@ -89,7 +90,7 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
val canceled = day.filter { it.canceled } val canceled = day.filter { it.canceled }
val active = day.filter { !it.canceled } val active = day.filter { !it.canceled }
cancelScheduled(canceled) cancelScheduled(canceled, student)
active.forEachIndexed { index, lesson -> active.forEachIndexed { index, lesson ->
val intent = createIntent(student, lesson, active.getOrNull(index + 1)) val intent = createIntent(student, lesson, active.getOrNull(index + 1))

View File

@ -22,6 +22,8 @@ import io.github.wulkanowy.services.sync.works.Work
import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.getCompatColor
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import timber.log.Timber import timber.log.Timber
import java.time.LocalDateTime
import java.time.ZoneId
import kotlin.random.Random import kotlin.random.Random
@HiltWorker @HiltWorker
@ -48,6 +50,7 @@ class SyncWorker @AssistedInject constructor(
Timber.i("${work::class.java.simpleName} is starting") Timber.i("${work::class.java.simpleName} is starting")
work.doWork(student, semester) work.doWork(student, semester)
Timber.i("${work::class.java.simpleName} result: Success") Timber.i("${work::class.java.simpleName} result: Success")
preferencesRepository.lasSyncDate = LocalDateTime.now(ZoneId.systemDefault())
null null
} catch (e: Throwable) { } catch (e: Throwable) {
Timber.w("${work::class.java.simpleName} result: An exception ${e.message} occurred") Timber.w("${work::class.java.simpleName} result: An exception ${e.message} occurred")
@ -81,7 +84,7 @@ class SyncWorker @AssistedInject constructor(
Random.nextInt(Int.MAX_VALUE), Random.nextInt(Int.MAX_VALUE),
NotificationCompat.Builder(applicationContext, DebugChannel.CHANNEL_ID) NotificationCompat.Builder(applicationContext, DebugChannel.CHANNEL_ID)
.setContentTitle("Debug notification") .setContentTitle("Debug notification")
.setSmallIcon(R.drawable.ic_stat_push) .setSmallIcon(R.drawable.ic_stat_all)
.setAutoCancel(true) .setAutoCancel(true)
.setColor(applicationContext.getCompatColor(R.color.colorPrimary)) .setColor(applicationContext.getCompatColor(R.color.colorPrimary))
.setStyle(BigTextStyle().bigText("${SyncWorker::class.java.simpleName} result: $result")) .setStyle(BigTextStyle().bigText("${SyncWorker::class.java.simpleName} result: $result"))

View File

@ -0,0 +1,32 @@
package io.github.wulkanowy.services.sync.channels
import android.annotation.TargetApi
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import androidx.core.app.NotificationManagerCompat
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import javax.inject.Inject
@TargetApi(26)
class NewConferencesChannel @Inject constructor(
private val notificationManager: NotificationManagerCompat,
@ApplicationContext private val context: Context
) : Channel {
companion object {
const val CHANNEL_ID = "new_conferences_channel"
}
override fun create() {
notificationManager.createNotificationChannel(
NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_new_conference), NotificationManager.IMPORTANCE_HIGH)
.apply {
enableLights(true)
enableVibration(true)
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
})
}
}

View File

@ -0,0 +1,32 @@
package io.github.wulkanowy.services.sync.channels
import android.annotation.TargetApi
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import androidx.core.app.NotificationManagerCompat
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import javax.inject.Inject
@TargetApi(26)
class NewExamChannel @Inject constructor(
private val notificationManager: NotificationManagerCompat,
@ApplicationContext private val context: Context
) : Channel {
companion object {
const val CHANNEL_ID = "new_exam_channel"
}
override fun create() {
notificationManager.createNotificationChannel(
NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_new_exam), NotificationManager.IMPORTANCE_HIGH)
.apply {
enableLights(true)
enableVibration(true)
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
})
}
}

View File

@ -0,0 +1,32 @@
package io.github.wulkanowy.services.sync.channels
import android.annotation.TargetApi
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import androidx.core.app.NotificationManagerCompat
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import javax.inject.Inject
@TargetApi(26)
class NewHomeworkChannel @Inject constructor(
private val notificationManager: NotificationManagerCompat,
@ApplicationContext private val context: Context
) : Channel {
companion object {
const val CHANNEL_ID = "new_homework_channel"
}
override fun create() {
notificationManager.createNotificationChannel(
NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_new_homework), NotificationManager.IMPORTANCE_HIGH)
.apply {
enableLights(true)
enableVibration(true)
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
})
}
}

View File

@ -0,0 +1,32 @@
package io.github.wulkanowy.services.sync.channels
import android.annotation.TargetApi
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import androidx.core.app.NotificationManagerCompat
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import javax.inject.Inject
@TargetApi(26)
class NewSchoolAnnouncementsChannel @Inject constructor(
private val notificationManager: NotificationManagerCompat,
@ApplicationContext private val context: Context
) : Channel {
companion object {
const val CHANNEL_ID = "new_school_announcements_channel"
}
override fun create() {
notificationManager.createNotificationChannel(
NotificationChannel(CHANNEL_ID, context.getString(R.string.channel_new_school_announcement), NotificationManager.IMPORTANCE_HIGH)
.apply {
enableLights(true)
enableVibration(true)
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
})
}
}

View File

@ -0,0 +1,102 @@
package io.github.wulkanowy.services.sync.notifications
import android.app.PendingIntent
import android.content.Context
import android.os.Build
import androidx.annotation.PluralsRes
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.pojos.MultipleNotifications
import io.github.wulkanowy.data.pojos.Notification
import io.github.wulkanowy.data.pojos.OneNotification
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.utils.getCompatBitmap
import io.github.wulkanowy.utils.getCompatColor
import io.github.wulkanowy.utils.nickOrName
import kotlin.random.Random
abstract class BaseNotification(
private val context: Context,
private val notificationManager: NotificationManagerCompat,
) {
protected fun sendNotification(notification: Notification, student: Student) =
when (notification) {
is OneNotification -> sendOneNotification(notification, student)
is MultipleNotifications -> sendMultipleNotifications(notification, student)
}
private fun sendOneNotification(notification: OneNotification, student: Student?) {
notificationManager.notify(
Random.nextInt(Int.MAX_VALUE),
getNotificationBuilder(notification).apply {
val content = context.getString(
notification.contentStringRes,
*notification.contentValues.toTypedArray()
)
setContentTitle(context.getString(notification.titleStringRes))
setContentText(content)
setStyle(
NotificationCompat.BigTextStyle()
.setSummaryText(student?.nickOrName)
.bigText(content)
)
}.build()
)
}
private fun sendMultipleNotifications(notification: MultipleNotifications, student: Student) {
val group = notification.type.group + student.id
val groupId = student.id * 100 + notification.type.ordinal
notification.lines.forEach { item ->
notificationManager.notify(
Random.nextInt(Int.MAX_VALUE),
getNotificationBuilder(notification).apply {
setContentTitle(getQuantityString(notification.titleStringRes, 1))
setContentText(item)
setStyle(
NotificationCompat.BigTextStyle()
.setSummaryText(student.nickOrName)
.bigText(item)
)
setGroup(group)
}.build()
)
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return
notificationManager.notify(
groupId.toInt(),
getNotificationBuilder(notification).apply {
setSmallIcon(notification.icon)
setGroup(group)
setStyle(NotificationCompat.InboxStyle().setSummaryText(student.nickOrName))
setGroupSummary(true)
}.build()
)
}
private fun getNotificationBuilder(notification: Notification) = NotificationCompat
.Builder(context, notification.type.channel)
.setLargeIcon(context.getCompatBitmap(notification.icon, R.color.colorPrimary))
.setSmallIcon(R.drawable.ic_stat_all)
.setAutoCancel(true)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setColor(context.getCompatColor(R.color.colorPrimary))
.setContentIntent(
PendingIntent.getActivity(
context, notification.startMenu.id,
MainActivity.getStartIntent(context, notification.startMenu, true),
PendingIntent.FLAG_UPDATE_CURRENT
)
)
private fun getQuantityString(@PluralsRes id: Int, value: Int): String {
return context.resources.getQuantityString(id, value, value)
}
}

View File

@ -0,0 +1,38 @@
package io.github.wulkanowy.services.sync.notifications
import android.content.Context
import androidx.core.app.NotificationManagerCompat
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Conference
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.pojos.MultipleNotifications
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.toFormattedString
import java.time.LocalDateTime
import javax.inject.Inject
class NewConferenceNotification @Inject constructor(
@ApplicationContext private val context: Context,
notificationManager: NotificationManagerCompat,
) : BaseNotification(context, notificationManager) {
fun notify(items: List<Conference>, student: Student) {
val today = LocalDateTime.now()
val lines = items.filter { !it.date.isBefore(today) }.map {
"${it.date.toFormattedString("dd.MM")} - ${it.title}: ${it.subject}"
}.ifEmpty { return }
val notification = MultipleNotifications(
type = NotificationType.NEW_CONFERENCE,
icon = R.drawable.ic_more_conferences,
titleStringRes = R.plurals.conference_notify_new_item_title,
contentStringRes = R.plurals.conference_notify_new_items,
summaryStringRes = R.plurals.conference_number_item,
startMenu = MainView.Section.CONFERENCE,
lines = lines
)
sendNotification(notification, student)
}
}

View File

@ -0,0 +1,38 @@
package io.github.wulkanowy.services.sync.notifications
import android.content.Context
import androidx.core.app.NotificationManagerCompat
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.pojos.MultipleNotifications
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.toFormattedString
import java.time.LocalDate
import javax.inject.Inject
class NewExamNotification @Inject constructor(
@ApplicationContext private val context: Context,
notificationManager: NotificationManagerCompat,
) : BaseNotification(context, notificationManager) {
fun notify(items: List<Exam>, student: Student) {
val today = LocalDate.now()
val lines = items.filter { !it.date.isBefore(today) }.map {
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.description}"
}.ifEmpty { return }
val notification = MultipleNotifications(
type = NotificationType.NEW_EXAM,
icon = R.drawable.ic_main_exam,
titleStringRes = R.plurals.exam_notify_new_item_title,
contentStringRes = R.plurals.exam_notify_new_item_content,
summaryStringRes = R.plurals.exam_number_item,
startMenu = MainView.Section.EXAM,
lines = lines
)
sendNotification(notification, student)
}
}

View File

@ -0,0 +1,66 @@
package io.github.wulkanowy.services.sync.notifications
import android.content.Context
import androidx.core.app.NotificationManagerCompat
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.pojos.MultipleNotifications
import io.github.wulkanowy.ui.modules.main.MainView
import javax.inject.Inject
class NewGradeNotification @Inject constructor(
@ApplicationContext private val context: Context,
notificationManager: NotificationManagerCompat,
) : BaseNotification(context, notificationManager) {
fun notifyDetails(items: List<Grade>, student: Student) {
val notification = MultipleNotifications(
type = NotificationType.NEW_GRADE_DETAILS,
icon = R.drawable.ic_stat_grade,
titleStringRes = R.plurals.grade_new_items,
contentStringRes = R.plurals.grade_notify_new_items,
summaryStringRes = R.plurals.grade_number_item,
startMenu = MainView.Section.GRADE,
lines = items.map {
"${it.subject}: ${it.entry}"
}
)
sendNotification(notification, student)
}
fun notifyPredicted(items: List<GradeSummary>, student: Student) {
val notification = MultipleNotifications(
type = NotificationType.NEW_GRADE_PREDICTED,
icon = R.drawable.ic_stat_grade,
titleStringRes = R.plurals.grade_new_items_predicted,
contentStringRes = R.plurals.grade_notify_new_items_predicted,
summaryStringRes = R.plurals.grade_number_item,
startMenu = MainView.Section.GRADE,
lines = items.map {
"${it.subject}: ${it.predictedGrade}"
}
)
sendNotification(notification, student)
}
fun notifyFinal(items: List<GradeSummary>, student: Student) {
val notification = MultipleNotifications(
type = NotificationType.NEW_GRADE_FINAL,
icon = R.drawable.ic_stat_grade,
titleStringRes = R.plurals.grade_new_items_final,
contentStringRes = R.plurals.grade_notify_new_items_final,
summaryStringRes = R.plurals.grade_number_item,
startMenu = MainView.Section.GRADE,
lines = items.map {
"${it.subject}: ${it.finalGrade}"
}
)
sendNotification(notification, student)
}
}

View File

@ -0,0 +1,38 @@
package io.github.wulkanowy.services.sync.notifications
import android.content.Context
import androidx.core.app.NotificationManagerCompat
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.pojos.MultipleNotifications
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.toFormattedString
import java.time.LocalDate
import javax.inject.Inject
class NewHomeworkNotification @Inject constructor(
@ApplicationContext private val context: Context,
notificationManager: NotificationManagerCompat,
) : BaseNotification(context, notificationManager) {
fun notify(items: List<Homework>, student: Student) {
val today = LocalDate.now()
val lines = items.filter { !it.date.isBefore(today) }.map {
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.content}"
}.ifEmpty { return }
val notification = MultipleNotifications(
type = NotificationType.NEW_HOMEWORK,
icon = R.drawable.ic_more_homework,
titleStringRes = R.plurals.homework_notify_new_item_title,
contentStringRes = R.plurals.homework_notify_new_item_content,
summaryStringRes = R.plurals.homework_number_item,
startMenu = MainView.Section.HOMEWORK,
lines = lines
)
sendNotification(notification, student)
}
}

View File

@ -0,0 +1,30 @@
package io.github.wulkanowy.services.sync.notifications
import android.content.Context
import androidx.core.app.NotificationManagerCompat
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.pojos.OneNotification
import io.github.wulkanowy.ui.modules.main.MainView
import javax.inject.Inject
class NewLuckyNumberNotification @Inject constructor(
@ApplicationContext private val context: Context,
notificationManager: NotificationManagerCompat,
) : BaseNotification(context, notificationManager) {
fun notify(item: LuckyNumber, student: Student) {
val notification = OneNotification(
type = NotificationType.NEW_LUCKY_NUMBER,
icon = R.drawable.ic_stat_luckynumber,
titleStringRes = R.string.lucky_number_notify_new_item_title,
contentStringRes = R.string.lucky_number_notify_new_item,
startMenu = MainView.Section.LUCKY_NUMBER,
contentValues = listOf(item.luckyNumber.toString())
)
sendNotification(notification, student)
}
}

View File

@ -0,0 +1,33 @@
package io.github.wulkanowy.services.sync.notifications
import android.content.Context
import androidx.core.app.NotificationManagerCompat
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.pojos.MultipleNotifications
import io.github.wulkanowy.ui.modules.main.MainView
import javax.inject.Inject
class NewMessageNotification @Inject constructor(
@ApplicationContext private val context: Context,
notificationManager: NotificationManagerCompat,
) : BaseNotification(context, notificationManager) {
fun notify(items: List<Message>, student: Student) {
val notification = MultipleNotifications(
type = NotificationType.NEW_MESSAGE,
icon = R.drawable.ic_stat_message,
titleStringRes = R.plurals.message_new_items,
contentStringRes = R.plurals.message_notify_new_items,
summaryStringRes = R.plurals.message_number_item,
startMenu = MainView.Section.MESSAGE,
lines = items.map {
"${it.sender}: ${it.subject}"
}
)
sendNotification(notification, student)
}
}

View File

@ -0,0 +1,46 @@
package io.github.wulkanowy.services.sync.notifications
import android.content.Context
import androidx.core.app.NotificationManagerCompat
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Note
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.pojos.MultipleNotifications
import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory
import io.github.wulkanowy.ui.modules.main.MainView
import javax.inject.Inject
class NewNoteNotification @Inject constructor(
@ApplicationContext private val context: Context,
notificationManager: NotificationManagerCompat,
) : BaseNotification(context, notificationManager) {
fun notify(items: List<Note>, student: Student) {
val notification = MultipleNotifications(
type = NotificationType.NEW_NOTE,
icon = R.drawable.ic_stat_note,
titleStringRes = when (NoteCategory.getByValue(items.first().categoryType)) {
NoteCategory.POSITIVE -> R.plurals.praise_new_items
NoteCategory.NEUTRAL -> R.plurals.neutral_note_new_items
else -> R.plurals.note_new_items
},
contentStringRes = when (NoteCategory.getByValue(items.first().categoryType)) {
NoteCategory.POSITIVE -> R.plurals.praise_notify_new_items
NoteCategory.NEUTRAL -> R.plurals.neutral_note_notify_new_items
else -> R.plurals.note_notify_new_items
},
summaryStringRes = when (NoteCategory.getByValue(items.first().categoryType)) {
NoteCategory.POSITIVE -> R.plurals.praise_number_item
NoteCategory.NEUTRAL -> R.plurals.neutral_note_number_item
else -> R.plurals.note_number_item
},
startMenu = MainView.Section.NOTE,
lines = items.map {
"${it.teacher}: ${it.category}"
}
)
sendNotification(notification, student)
}
}

View File

@ -0,0 +1,33 @@
package io.github.wulkanowy.services.sync.notifications
import android.content.Context
import androidx.core.app.NotificationManagerCompat
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.pojos.MultipleNotifications
import io.github.wulkanowy.ui.modules.main.MainView
import javax.inject.Inject
class NewSchoolAnnouncementNotification @Inject constructor(
@ApplicationContext private val context: Context,
notificationManager: NotificationManagerCompat,
) : BaseNotification(context, notificationManager) {
fun notify(items: List<SchoolAnnouncement>, student: Student) {
val notification = MultipleNotifications(
type = NotificationType.NEW_ANNOUNCEMENT,
icon = R.drawable.ic_all_about,
titleStringRes = R.plurals.school_announcement_notify_new_item_title,
contentStringRes = R.plurals.school_announcement_notify_new_items,
summaryStringRes = R.plurals.school_announcement_number_item,
startMenu = MainView.Section.SCHOOL_ANNOUNCEMENT,
lines = items.map {
"${it.subject}: ${it.content}"
}
)
sendNotification(notification, student)
}
}

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