1
0

Compare commits

...

156 Commits
1.4.1 ... 1.6.1

Author SHA1 Message Date
ae65228805 Merge branch 'release/1.6.1' 2022-04-06 17:28:50 +02:00
391ee6e621 Version 1.6.1 2022-04-06 17:27:27 +02:00
0a87df3d82 New Crowdin updates (#1817) 2022-04-06 17:19:38 +02:00
cb4ae21903 Replace Serializable to Parcelable in Destination (#1823) 2022-04-06 08:01:41 +02:00
679cf2554d Fix text in lucky number widget (#1825) 2022-04-06 08:01:11 +02:00
d473d53879 Add standard register variant as translatable string (#1824) 2022-04-05 13:56:11 +02:00
6531061b48 Fix text aligment in exam dialog (#1821) 2022-04-05 10:30:49 +02:00
3347e8fba8 Bump kotlin_version from 1.6.10 to 1.6.20 (#1818) 2022-04-05 08:10:07 +00:00
84067126a1 Bump hianalytics from 6.4.1.300 to 6.4.1.301 (#1819) 2022-04-05 08:09:46 +00:00
da9bebe923 Merge branch 'release/1.6.0' into develop 2022-04-02 22:02:01 +02:00
b371fd6709 Merge branch 'release/1.6.0' 2022-04-02 22:01:53 +02:00
884d443c5b Version 1.6.0 2022-04-02 22:01:34 +02:00
df58aa78ae New Crowdin updates (#1783) 2022-03-31 09:00:41 +02:00
2131e892ad Add option to select multiple messages to delete (#1780) 2022-03-28 19:30:20 +02:00
63380d3e12 Bump agcp from 1.6.4.300 to 1.6.5.200 (#1812) 2022-03-28 13:53:57 +00:00
c572a91b38 Bump agconnect-crash from 1.6.4.300 to 1.6.5.200 (#1811) 2022-03-28 13:53:42 +00:00
20dde6e896 Resource refactor (#1589) 2022-03-27 15:33:32 +02:00
042b66ca5c Bump core-splashscreen from 1.0.0-beta01 to 1.0.0-beta02 (#1810) 2022-03-26 22:37:53 +00:00
8d8990761a Bump firebase-bom from 29.2.1 to 29.3.0 (#1809) 2022-03-26 22:37:22 +00:00
a07741b5c5 Bump WhatTheStack from 1.0.0-alpha03 to 1.0.0-alpha04 (#1807) 2022-03-22 17:49:32 +00:00
f851a4d2c5 Bump huawei-publish-gradle-plugin from 1.3.1 to 1.3.3 (#1806) 2022-03-22 12:47:27 -05:00
c8d069c787 Bump hianalytics from 6.4.0.300 to 6.4.1.300 (#1805) 2022-03-20 00:25:31 +00:00
5ce30a3000 Bump firebase-bom from 29.2.0 to 29.2.1 (#1804) 2022-03-20 00:25:11 +00:00
26e0f43fa0 Bump firebase-bom from 29.1.0 to 29.2.0 (#1803) 2022-03-17 18:28:26 +00:00
08c1bedca1 Add the option to quickly add a calendar event from the exam details (#1802)
* Extract intent utils to separate file

* Add add to calendar button in exam details dialog

* Set 8:00-8:45 start/end time
2022-03-14 00:38:40 +01:00
15537586c4 Add exam date field to exam details dialog (#1801) 2022-03-13 22:47:54 +01:00
a04ba4ae10 Login improvements (#1800)
* Update sdk

* Change default register variant name

* Change symbol hint message and email template
2022-03-13 22:43:57 +01:00
57ea6379ab Timetable timer refactor (#1785) 2022-03-13 04:01:14 +01:00
c3abe50ed4 Bump gradle from 7.1.1 to 7.1.2 (#1790) 2022-02-28 18:59:53 +00:00
f48caf9f70 Bump play-services-ads from 20.5.0 to 20.6.0 (#1792) 2022-02-28 18:41:16 +00:00
9a413c14c3 Bump agcp from 1.6.4.200 to 1.6.4.300 (#1791) 2022-02-28 18:37:29 +00:00
8915c5dd8e Bump agconnect-crash from 1.6.4.200 to 1.6.4.300 (#1793) 2022-02-28 18:32:31 +00:00
e7561d4794 Bump room from 2.4.1 to 2.4.2 (#1794) 2022-02-28 18:32:08 +00:00
820b99dbc7 Bump hilt_version from 2.40.5 to 2.41 (#1786) 2022-02-21 19:35:02 +00:00
aff0fb3a60 Add information about student in grade statistics pie chart (#1749)
Co-authored-by: Mikołaj Pich <m.pich@outlook.com>
2022-02-15 13:08:44 +01:00
d3bf5c3e0a Bump lifecycle-livedata-ktx from 2.4.0 to 2.4.1 (#1781) 2022-02-14 20:06:05 +00:00
dec2703cc7 Bump firebase-bom from 29.0.4 to 29.1.0 (#1782) 2022-02-14 20:05:45 +00:00
edd1c9442e Fix calc vulcan average in second semester (#1779) 2022-02-12 22:22:15 +01:00
18568c86be New Crowdin updates (#1776) 2022-02-12 12:19:25 +01:00
84d0ba525f Fix blank description in exam details (#1778) 2022-02-10 07:36:44 +01:00
6290663f02 Bump gradle from 7.1.0 to 7.1.1 (#1777) 2022-02-07 04:41:09 +00:00
be046a1ddd Update date in LICENSE file (#1775) 2022-02-07 03:16:17 +01:00
96ee4bd9e5 Keep reacting to live changes in dashboard after a force refresh (#1594)
Co-authored-by: Rafał Borcz <RafalBO99@outlook.com>
2022-02-02 02:44:14 +00:00
923af85d18 Bump fragment-ktx from 1.4.0 to 1.4.1 (#1774) 2022-02-02 01:53:16 +00:00
ce36e86bb2 Bump preference-ktx from 1.1.1 to 1.2.0 (#1773) 2022-01-29 05:38:55 +00:00
a4f455b38f Bump hianalytics from 6.3.2.300 to 6.4.0.300 (#1771) 2022-01-29 04:55:02 +00:00
01b8bd9d4a Bump agconnect-crash from 1.6.3.300 to 1.6.4.200 (#1770) 2022-01-29 04:47:56 +00:00
cfcc051ce4 Bump gradle from 7.0.4 to 7.1.0 (#1769) 2022-01-29 04:47:26 +00:00
0b0993be9a Bump agcp from 1.6.3.300 to 1.6.4.200 (#1772) 2022-01-29 04:47:00 +00:00
d07b0dbc98 Bump WhatTheStack from 1.0.0-alpha02 to 1.0.0-alpha03 (#1768) 2022-01-29 04:46:09 +00:00
daa7b54dab Refactor notification destinations (#1709)
Co-authored-by: Rafał Borcz <RafalBO99@outlook.com>
2022-01-28 13:43:56 +01:00
de9fcb9af9 Add what-the-stack library (#1767) 2022-01-28 12:07:48 +01:00
40e0934504 Bump fuzzywuzzy from 1.3.3 to 1.4.0 (#1765) 2022-01-28 11:07:22 +00:00
009ec433be Bump firebase-bom from 29.0.3 to 29.0.4 (#1766) 2022-01-27 12:06:30 +00:00
e1d82d70ee New translations strings.xml (German) (#1746) 2022-01-23 20:04:01 +01:00
7a9ba04ff4 Bump material from 1.4.0 to 1.5.0 (#1757) 2022-01-23 02:17:14 +00:00
513b4b7d3e Bump coordinatorlayout from 1.1.0 to 1.2.0 (#1756) 2022-01-23 01:43:30 +00:00
ce4157933f Bump core-splashscreen from 1.0.0-alpha02 to 1.0.0-beta01 (#1752) 2022-01-23 01:39:16 +00:00
5d1085a64a Bump fuzzywuzzy from 1.3.1 to 1.3.3 (#1754) 2022-01-23 00:58:12 +00:00
a00f2dcbda Bump core from 1.10.2 to 1.10.3 (#1753) 2022-01-23 00:57:40 +00:00
b52a6f7f61 Bump room from 2.4.0 to 2.4.1 (#1755) 2022-01-23 00:57:17 +00:00
5146e44574 Bump agconnect-crash from 1.6.3.200 to 1.6.3.300 (#1758) 2022-01-23 00:56:57 +00:00
90e1cea679 Bump appcompat from 1.4.0 to 1.4.1 (#1759) 2022-01-23 00:56:37 +00:00
c9b506ae10 Bump agcp from 1.6.3.200 to 1.6.3.300 (#1760) 2022-01-23 00:56:16 +00:00
14ebdad7b2 Bump constraintlayout from 2.1.2 to 2.1.3 (#1761) 2022-01-23 00:55:58 +00:00
210308695b Bump huawei-publish-gradle-plugin from 1.3.0 to 1.3.1 (#1750) 2022-01-11 11:12:48 +00:00
d5cc2263f5 Bump mockk from 1.12.1 to 1.12.2 (#1747) 2022-01-04 10:13:31 +00:00
47e3f2dc58 Merge branch 'release/1.5.0' into develop 2022-01-01 17:51:19 +01:00
6dc16b288d Merge branch 'release/1.5.0' 2022-01-01 17:51:11 +01:00
daf97be9ad Version 1.5.0 2022-01-01 17:50:02 +01:00
2bb2190410 New Crowdin updates (#1745) 2022-01-01 17:48:58 +01:00
aff1a7030d Add a custom error message for ssl errors due to invalid clock setting (#1742) 2022-01-01 15:46:08 +01:00
8877322357 Differentiate school announcements by userLoginId (#1744) 2022-01-01 13:52:51 +01:00
bc672e94f8 Strip html from school announcements notifications (#1743) 2022-01-01 13:48:58 +01:00
a03bcf8e62 Display comment after entry in grade notifications (#1741) 2021-12-31 11:36:14 +00:00
20673c4ead Add basic support for kindergarten students (#1738) 2021-12-31 12:21:52 +01:00
5321d00ee9 Fix play flavor build (#1740) 2021-12-31 12:10:56 +01:00
e6b2acabd5 Block app timezone to polish timezone (#1598) 2021-12-31 11:53:09 +01:00
bfd7f688ab Move webview locale fix to account recovery fragment (#1739) 2021-12-31 10:24:02 +00:00
ba02531aa4 Fix timetable widget crash when there are no lessons for the day (#1737) 2021-12-31 09:40:15 +01:00
210c3a0e28 Remove HiltBroadcastReceiver (#1736) 2021-12-30 12:50:06 +01:00
68f0ecc45c If only one student exists, don't show student name in timetable notification (#1711)
Co-authored-by: Rafał Borcz <RafalBO99@outlook.com>
2021-12-29 15:44:43 +01:00
0965d03f1a New Crowdin updates (#1734) 2021-12-29 11:57:17 +01:00
496641f594 Fix notification spam after login (#1715) 2021-12-29 08:31:43 +01:00
684c258e2d Remove admin message offline first cache (#1735) 2021-12-28 18:56:59 +01:00
5e96917508 Fix overlapping text in the error dialog (#1708) 2021-12-28 12:16:52 +01:00
17096ad11b New Crowdin updates (#1729) 2021-12-27 14:06:20 +01:00
bd883c9f38 Add option to remove notifications captured from vulcan.hebe (#1716) 2021-12-27 07:48:47 +00:00
6520f8a0d7 Fix that an incorrect day would be selected in MaterialDatePicker (#1723) 2021-12-27 08:10:30 +01:00
2eee50ad81 Replace view pager in login activity with simple fragment transactions (#1686) 2021-12-27 07:58:57 +01:00
8560fd7e81 Bump coroutines from 1.5.2 to 1.6.0 (#1731) 2021-12-25 08:38:27 +00:00
f718147ae9 Bump agcp from 1.6.2.300 to 1.6.3.200 (#1730) 2021-12-25 05:55:01 +00:00
cd12c4c891 Bump agconnect-crash from 1.6.2.300 to 1.6.3.200 (#1732) 2021-12-25 05:54:34 +00:00
65f114ce05 Bump kotlinx-serialization-json from 1.3.1 to 1.3.2 (#1733) 2021-12-25 05:54:17 +00:00
497083be97 Update timetable to next day if there is no more lessons today (#1551) 2021-12-25 05:46:24 +00:00
e26860ea5a Fix state restoring in GradeStatistics (#1667)
Co-authored-by: Mikołaj Pich <m.pich@outlook.com>
2021-12-23 13:58:26 +01:00
094df212b4 Add additional lessons feature (#1550) 2021-12-21 00:36:59 +01:00
cc22985dc5 Bump firebase-bom from 29.0.2 to 29.0.3 (#1728) 2021-12-20 20:22:56 +00:00
ad9a2711c4 New Crowdin updates (#1687) 2021-12-19 22:46:41 +01:00
daf44c531c Merge branch 'release/1.4.4' into develop 2021-12-19 22:25:56 +01:00
4e12eb1552 Merge branch 'release/1.4.4' 2021-12-19 22:25:52 +01:00
c846cc999f Version 1.4.4 2021-12-19 22:25:47 +01:00
47d430292c Update template of login issue email (#1724)
* Update sdk

* Update template of login issue email
2021-12-19 22:08:39 +01:00
5e1ff2243f Fix exception in TimetableAdapter (#1721) 2021-12-12 23:09:34 +01:00
a35bef58f2 Bump gradle from 7.0.3 to 7.0.4 (#1718) 2021-12-12 14:06:15 +00:00
0005d84974 Bump hilt_version from 2.40.4 to 2.40.5 (#1719) 2021-12-12 14:05:53 +00:00
19558cb871 Bump firebase-bom from 29.0.1 to 29.0.2 (#1720) 2021-12-12 14:05:35 +00:00
45e884127f After deselecting fakelog, clear credentials from login form (#1713) 2021-12-12 15:04:31 +01:00
c87085a226 Fix invalid order of school announcements (#1689) 2021-12-11 17:35:55 +01:00
79b970256f More strongly typed data in preferences (#1697) 2021-12-11 16:14:46 +00:00
70f038f15f Remove useless group property from NotificationType (#1714) 2021-12-11 17:12:47 +01:00
9cabd7ef08 Deduplicate timetable time left string (#1710) 2021-12-11 16:42:33 +01:00
d89e4ccfdf Fix grade sorting by date (#1717) 2021-12-11 11:54:07 +01:00
1bcc4d199e When replying to a message use Re instead of RE (#1712) 2021-12-10 16:24:39 +01:00
2e85e88c5d Bump agcp from 1.6.2.200 to 1.6.2.300 (#1693) 2021-12-08 10:21:00 +00:00
6ab67fe25b Bump firebase-bom from 29.0.0 to 29.0.1 (#1696) 2021-12-08 10:04:29 +00:00
8563277a89 Bump robolectric from 4.7.2 to 4.7.3 (#1695) 2021-12-08 10:04:10 +00:00
e458cc90b0 Bump agconnect-crash from 1.6.2.200 to 1.6.2.300 (#1694) 2021-12-08 10:03:42 +00:00
651e3a21b9 Bump firebase-crashlytics-gradle from 2.8.0 to 2.8.1 (#1692) 2021-12-08 10:03:06 +00:00
f6b969cfb1 Bump hilt_version from 2.40.2 to 2.40.4 (#1691) 2021-12-08 10:02:43 +00:00
d2aa940d46 Convert from a stringly typed grade color to enum GradeColorTheme (#1672) 2021-11-27 10:11:17 +01:00
ab435a72ea Merge branch 'release/1.4.3' into develop 2021-11-26 22:29:13 +01:00
a56f4b8745 Merge branch 'release/1.4.3' 2021-11-26 22:29:09 +01:00
d003b0897c Version 1.4.3 2021-11-26 22:29:03 +01:00
581bb2de77 New Crowdin updates (#1669) 2021-11-26 20:22:54 +01:00
495e385228 Fix snackbar crash in grade statistics view (#1682) 2021-11-25 23:48:08 +01:00
10ba36ba44 Bump hianalytics from 6.3.0.303 to 6.3.2.300 (#1684) 2021-11-25 22:46:30 +00:00
eae396424f Bump hilt_version from 2.40.1 to 2.40.2 (#1683) 2021-11-25 22:46:17 +00:00
a7891bb266 Update viewpager2 library to fix duplicated menu bug (#1681) 2021-11-24 09:53:16 +01:00
6e82409dbc Bump play-services-ads from 20.4.0 to 20.5.0 (#1675) 2021-11-23 02:02:03 +00:00
984db18be3 Bump agcp from 1.6.1.300 to 1.6.2.200 (#1674) 2021-11-23 01:55:49 +00:00
c99bc96c08 Bump logging-interceptor from 4.9.2 to 4.9.3 (#1676) 2021-11-23 01:55:10 +00:00
3e7030abc2 Bump agconnect-crash from 1.6.1.300 to 1.6.2.200 (#1677) 2021-11-23 01:54:47 +00:00
6dad3b299b Bump robolectric from 4.7 to 4.7.2 (#1678) 2021-11-23 01:54:26 +00:00
5e997f5a3e Merge branch 'release/1.4.2' into develop 2021-11-21 13:31:56 +01:00
601d573283 Merge branch 'release/1.4.2' 2021-11-21 13:31:51 +01:00
6ae6ca7fbb Version 1.4.2 2021-11-21 13:31:45 +01:00
c3d38afc3d New Crowdin updates (#1658) 2021-11-21 13:23:43 +01:00
4e19964249 Make admin messages dissmisable (#1661) 2021-11-21 09:02:12 +01:00
a6c0efcb81 Fix empty student list in LoginStudentSelect view (#1668) 2021-11-21 07:47:23 +00:00
fcc71c0d5f Add ads limit (#1662) 2021-11-21 08:34:28 +01:00
a59d10b6c1 Disable personalized ads in single support advert (#1665) 2021-11-20 16:46:14 +01:00
a48e4eb4ee Probably fix snackbar crash in grade statistics view (#1663) 2021-11-20 16:42:21 +01:00
2a3668bb18 Fix nul login data in login symbol view (#1664) 2021-11-20 16:41:12 +01:00
804d0d9113 Add multiline to support ad preference (#1651) 2021-11-18 20:23:09 +01:00
88b893e6c0 Fix upcoming lesson notifications on Android 12 (#1650) 2021-11-18 20:22:15 +01:00
2874a7495e Add Czech and Slovak README (#1631) 2021-11-18 19:38:51 +01:00
40d8f7a93d German readme version (#1629) 2021-11-18 16:31:59 +01:00
84cd51205f Bump appcompat from 1.4.0-rc01 to 1.4.0 (#1654) 2021-11-18 01:02:12 +00:00
bac1832f27 Bump mockk from 1.12.0 to 1.12.1 (#1653) 2021-11-18 00:40:59 +00:00
8bf1e22407 Bump flow-preferences from 1.5.0 to 1.6.0 (#1657) 2021-11-18 00:40:38 +00:00
e9f43f925c Bump constraintlayout from 2.1.1 to 2.1.2 (#1656) 2021-11-18 00:37:26 +00:00
aa632edf5c Bump fragment-ktx from 1.4.0-rc01 to 1.4.0 (#1655) 2021-11-18 00:36:18 +00:00
57315d75c6 Bump work_manager from 2.7.0 to 2.7.1 (#1652) 2021-11-18 00:36:00 +00:00
4552bc85b0 Bump kotlin_version from 1.5.31 to 1.6.0 (#1635) 2021-11-18 01:34:55 +01:00
6b7795118c Merge branch 'release/1.4.1' into develop 2021-11-16 23:29:22 +01:00
316 changed files with 18951 additions and 5146 deletions

12
.editorconfig Normal file
View File

@ -0,0 +1,12 @@
[*]
charset=utf-8
end_of_line=lf
insert_final_newline=true
indent_style=space
indent_size=4
[*.json]
indent_size=2
[*.{kt,kts}]
disabled_rules=import-ordering,no-wildcard-imports

View File

@ -37,6 +37,7 @@ jobs:
ANDROID_PUBLISHER_CREDENTIALS: ${{ secrets.ANDROID_PUBLISHER_CREDENTIALS }} ANDROID_PUBLISHER_CREDENTIALS: ${{ secrets.ANDROID_PUBLISHER_CREDENTIALS }}
ADMOB_PROJECT_ID: ${{ secrets.ADMOB_PROJECT_ID }} ADMOB_PROJECT_ID: ${{ secrets.ADMOB_PROJECT_ID }}
SINGLE_SUPPORT_AD_ID: ${{ secrets.SINGLE_SUPPORT_AD_ID }} SINGLE_SUPPORT_AD_ID: ${{ secrets.SINGLE_SUPPORT_AD_ID }}
SET_BUILD_TIMESTAMP: ${{ secrets.SET_BUILD_TIMESTAMP }}
run: ./gradlew publishPlayReleaseApps -PenableFirebase --stacktrace; run: ./gradlew publishPlayReleaseApps -PenableFirebase --stacktrace;
deploy-app-gallery: deploy-app-gallery:
@ -71,4 +72,5 @@ jobs:
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }} PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }} PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }} PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
SET_BUILD_TIMESTAMP: ${{ secrets.SET_BUILD_TIMESTAMP }}
run: ./gradlew bundleHmsRelease --stacktrace && ./gradlew publishHuaweiAppGalleryHmsRelease --stacktrace run: ./gradlew bundleHmsRelease --stacktrace && ./gradlew publishHuaweiAppGalleryHmsRelease --stacktrace

View File

@ -2,14 +2,6 @@
<code_scheme name="Project" version="173"> <code_scheme name="Project" version="173">
<option name="LINE_SEPARATOR" value="&#10;" /> <option name="LINE_SEPARATOR" value="&#10;" />
<JetCodeStyleSettings> <JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value>
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
</value>
</option>
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
<option name="WRAP_ELVIS_EXPRESSIONS" value="0" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings> </JetCodeStyleSettings>
<codeStyleSettings language="XML"> <codeStyleSettings language="XML">
@ -126,13 +118,6 @@
</codeStyleSettings> </codeStyleSettings>
<codeStyleSettings language="kotlin"> <codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
</codeStyleSettings> </codeStyleSettings>
</code_scheme> </code_scheme>
</component> </component>

View File

@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier same "printed page" as the copyright notice for easier
identification within third-party archives. identification within third-party archives.
Copyright 2021 Wulkanowy Copyright 2022 Wulkanowy
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

78
README.cs.md Normal file
View File

@ -0,0 +1,78 @@
[English version of README](README.en.md)
[Deutsche Version von README](README.de.md)
[Polska wersja README](README.md)
[Slovenská verzia README](README.sk.md)
# Wulkanowy
[![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)
[![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/)
[![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases)
Neoficiální klient deníku VULCAN UONET+ pro žáka a rodiče
## Funkce
* přihlášení pomocí emailu a hesla
* funkce z webové stránky deníku:
* známky
* statistiky známek
* frekvence
* procento frekvence
* zkoušky
* plán lekce
* dokončené lekce
* zprávy
* domácí úkoly
* poznámky
* šťastné číslo
* další lekce
* školní setkání
* informace o žáku a škole
* výpočet průměru nezávisle na preferencích školy
* upozornění, např. o nových známkách
* podpora více účtů s možností přejmenování žáků
* tmavý a černý (AMOLED) motiv
* offline režim
* žádné reklamy
## Stáhnout
Aktuální verzi si můžete stáhnout z Google Play, F-Droid nebo Huawei AppGallery
[<img src="https://play.google.com/intl/cs-CZ/badges/images/generic/cs_badge_web_generic.png"
alt="Nyní na Google Play"
height="80">](https://play.google.com/store/apps/details?id=io.github.wulkanowy)
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Stáhnout s F-Droid"
height="80">](https://f-droid.org/packages/io.github.wulkanowy/)
[<img src="https://i.imgur.com/baTGiDP.png"
alt="Objevuj v AppGallery"
height="80">](https://appgallery.cloud.huawei.com/ag/n/app/C101440411?channelId=Badge&id=1b3f7fbb700849a9be0dba6b520b2282&s=EB1D3BF9ED9D1564D869B7B94B18016D3CABFCA5AEFB8E29F675FA04E0DC131D&detailType=0&v=)
Můžete si také stáhnout [vývojovou verzi](https://wulkanowy.github.io/#download), která zahrnuje nové funkce připravované pro příští vydání
## Postaveno s
* [Wulkanowy SDK](https://github.com/wulkanowy/sdk)
* [Kotlin Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html)
* [Hilt](https://dagger.dev/hilt/)
* [Room](https://developer.android.com/topic/libraries/architecture/room)
* [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager)
## Spolupráce
Přispějte do projektu vytvořením PR nebo odesláním issue na GitHub.
Pro zájemce o překlad aplikace do různých jazyků poskytujeme Crowdin:
https://crowdin.com/project/wulkanowy2
## Licence
Tento projekt je licencován pod licencí Apache License 2.0 - podrobnosti v souboru [LICENSE](LICENSE)

74
README.de.md Normal file
View File

@ -0,0 +1,74 @@
[Polska wersja README](README.md)
[English version of README](README.en.md)
# Wulkanowy
[![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)
[![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/)
[![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases)
Inoffizieller Android VULCAN UONET+ Registrierungsclient für Schüler und ihre Eltern
## Merkmale
* Einloggen mit E-Mail und Passwort
* Funktionen von der Registerwebsite:
* Noten
* Notenstatistik
* Anwesenheit
* Prozentsatz der Anwesenheit
* Prüfungen
* Stundenplan
* Unterricht abgeschlossen
* Nachrichten
* Hausaufgaben
* Anmerkungen
* Glückszahl
* Zusätzliche Lektionen
* Schulkonferenzen
* Schüler- und Schulinformationen
* Berechnung des Durchschnitts unabhängig von den Präferenzen der Schule
* Benachrichtigungen, z. B. über eine neue Note
* Unterstützung für mehrere Konten mit der Möglichkeit, den Namen des Schülers zu ändern
* dunkles und schwarzes (AMOLED) Thema
* Offline-Modus
* keine Werbung
## Herunterladen
Die aktuelle Version können Sie von der Google Play, F-Droid oder Huawei AppGallery store herunterladen
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png"
alt="Get it on Google Play"
height="80">](https://play.google.com/store/apps/details?id=io.github.wulkanowy)
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Get it on F-Droid"
height="80">](https://f-droid.org/packages/io.github.wulkanowy/)
[<img src="appgallery_badge.png"
alt="Explore it on AppGallery"
height="80">](https://appgallery.cloud.huawei.com/ag/n/app/C101440411?channelId=Badge&id=1b3f7fbb700849a9be0dba6b520b2282&s=EB1D3BF9ED9D1564D869B7B94B18016D3CABFCA5AEFB8E29F675FA04E0DC131D&detailType=0&v=)
Sie können auch ein [Entwicklungsversion herunterladen](https://wulkanowy.github.io/#download) das beinhaltet neue Funktionen, die für die nächste Version vorbereitet werden
## Gebaut mit
* [Wulkanowy SDK](https://github.com/wulkanowy/sdk)
* [Kotlin Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html)
* [Hilt](https://dagger.dev/hilt/)
* [Room](https://developer.android.com/topic/libraries/architecture/room)
* [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager)
## Beitragen
Bitte tragen Sie zum Projekt bei, indem Sie entweder eine PR erstellen oder ein Issue auf GitHub einreichen.
Für Personen, die daran interessiert sind, die Anwendung in verschiedene Sprachen zu übersetzen, bieten wir Crowdin
https://crowdin.com/project/wulkanowy2
## Lizenz
Dieses Projekt ist unter der Apache License 2.0 lizenziert - siehe die [LIZENZ](LICENSE) Datei für Details

View File

@ -1,5 +1,11 @@
[Polska wersja README](README.md) [Polska wersja README](README.md)
[Deutsche Version von README](README.de.md)
[Česká verze README](README.cs.md)
[Slovenská verzia README](README.sk.md)
# Wulkanowy # Wulkanowy
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/wulkanowy/wulkanowy/Tests/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)

View File

@ -1,5 +1,11 @@
[English version of README](README.en.md) [English version of README](README.en.md)
[Deutsche Version von README](README.de.md)
[Česká verze README](README.cs.md)
[Slovenská verzia README](README.sk.md)
# Wulkanowy # Wulkanowy
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/wulkanowy/wulkanowy/Tests/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)

78
README.sk.md Normal file
View File

@ -0,0 +1,78 @@
[English version of README](README.en.md)
[Deutsche Version von README](README.de.md)
[Polska wersja README](README.md)
[Česká verze README](README.cs.md)
# Wulkanowy
[![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)
[![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/)
[![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases)
Neoficiálny klient denníka VULCAN UONET+ pre žiaka a rodičov
## Funkcie
* prihlásenie pomocou emailu a hesla
* funkcie z webovej stránky denníka:
* známky
* štatistiky známok
* frekvencia
* percento frekvencie
* skúšky
* plán lekcie
* dokončené lekcie
* správy
* domáce úlohy
* poznámky
* šťastné číslo
* ďalšie lekcie
* školské stretnutie
* informácie o žiakovi a škole
* výpočet priemeru nezávisle od preferencií školy
* upozornenia, napr. o nových známkach
* podpora viacerých účtov s možnosťou premenovania žiakov
* tmavý a čierny (AMOLED) motív
* offline režim
* žiadne reklamy
## Stiahnuť
Aktuálnu verziu si môžete stiahnuť z Google Play, F-Droid alebo Huawei AppGallery
[<img src="https://play.google.com/intl/sk/badges/images/generic/sk_badge_web_generic.png"
alt="Nyní na Google Play"
height="80">](https://play.google.com/store/apps/details?id=io.github.wulkanowy)
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Stiahnuť s F-Droid"
height="80">](https://f-droid.org/packages/io.github.wulkanowy/)
[<img src="https://i.imgur.com/sX8UyAw.png"
alt="Objavíte v AppGallery"
height="80">](https://appgallery.cloud.huawei.com/ag/n/app/C101440411?channelId=Badge&id=1b3f7fbb700849a9be0dba6b520b2282&s=EB1D3BF9ED9D1564D869B7B94B18016D3CABFCA5AEFB8E29F675FA04E0DC131D&detailType=0&v=)
Môžete si tiež stiahnuť [vývojovú verziu](https://wulkanowy.github.io/#download), ktorá zahrňuje nové funkcie pripravované pre budúce vydanie
## Postavené s
* [Wulkanowy SDK](https://github.com/wulkanowy/sdk)
* [Kotlin Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html)
* [Hilt](https://dagger.dev/hilt/)
* [Room](https://developer.android.com/topic/libraries/architecture/room)
* [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager)
## Spolupráca
Prispejte do projektu vytvorením PR alebo odoslaním issue na GitHub.
Pre záujemcov o preklad aplikácie do rôznych jazykov poskytujeme Crowdin:
https://crowdin.com/project/wulkanowy2
## Licencia
Tento projekt je licencovaný pod licenciou Apache License 2.0 - podrobnosti v súbore [LICENSE](LICENSE)

View File

@ -22,8 +22,8 @@ android {
testApplicationId "io.github.tests.wulkanowy" testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 31 targetSdkVersion 31
versionCode 99 versionCode 105
versionName "1.4.1" versionName "1.6.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "app_name", "Wulkanowy" resValue "string", "app_name", "Wulkanowy"
@ -73,6 +73,8 @@ android {
buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\"" buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
} }
debug { debug {
minifyEnabled false
shrinkResources false
resValue "string", "app_name", "Wulkanowy DEV" resValue "string", "app_name", "Wulkanowy DEV"
applicationIdSuffix ".dev" applicationIdSuffix ".dev"
versionNameSuffix "-dev" versionNameSuffix "-dev"
@ -149,7 +151,9 @@ kapt {
play { play {
defaultToAppBundles = false defaultToAppBundles = false
track = 'beta' track = 'production'
releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS
userFraction = 0.25d
updatePriority = 1 updatePriority = 1
enabled.set(false) enabled.set(false)
} }
@ -165,36 +169,36 @@ huaweiPublish {
} }
ext { ext {
work_manager = "2.7.0" work_manager = "2.7.1"
android_hilt = "1.0.0" android_hilt = "1.0.0"
room = "2.3.0" room = "2.4.2"
chucker = "3.5.2" chucker = "3.5.2"
mockk = "1.12.0" mockk = "1.12.2"
coroutines = "1.5.2" coroutines = "1.6.0"
} }
dependencies { dependencies {
implementation "io.github.wulkanowy:sdk:1.4.0" implementation "io.github.wulkanowy:sdk:1.6.0"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1" implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
implementation "androidx.core:core-ktx:1.7.0" implementation "androidx.core:core-ktx:1.7.0"
implementation 'androidx.core:core-splashscreen:1.0.0-alpha02' implementation 'androidx.core:core-splashscreen:1.0.0-beta02'
implementation "androidx.activity:activity-ktx:1.4.0" implementation "androidx.activity:activity-ktx:1.4.0"
implementation "androidx.appcompat:appcompat:1.4.0-rc01" implementation "androidx.appcompat:appcompat:1.4.1"
implementation "androidx.fragment:fragment-ktx:1.4.0-rc01" implementation "androidx.fragment:fragment-ktx:1.4.1"
implementation "androidx.annotation:annotation:1.3.0" implementation "androidx.annotation:annotation:1.3.0"
implementation "androidx.preference:preference-ktx:1.1.1" implementation "androidx.preference:preference-ktx:1.2.0"
implementation "androidx.recyclerview:recyclerview:1.2.1" implementation "androidx.recyclerview:recyclerview:1.2.1"
implementation "androidx.viewpager:viewpager:1.0.0" implementation "androidx.viewpager2:viewpager2:1.1.0-beta01"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "androidx.constraintlayout:constraintlayout:2.1.1" implementation "androidx.constraintlayout:constraintlayout:2.1.3"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0" implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
implementation "com.google.android.material:material:1.4.0" implementation "com.google.android.material:material:1.5.0"
implementation "com.github.wulkanowy:material-chips-input:2.3.1" 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.github.lopspower:CircularImageView:4.2.0' implementation 'com.github.lopspower:CircularImageView:4.2.0'
@ -202,7 +206,7 @@ dependencies {
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"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0" implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1"
implementation "androidx.room:room-runtime:$room" implementation "androidx.room:room-runtime:$room"
implementation "androidx.room:room-ktx:$room" implementation "androidx.room:room-ktx:$room"
@ -218,7 +222,7 @@ dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0" implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0"
implementation "com.squareup.okhttp3:logging-interceptor:4.9.2" implementation "com.squareup.okhttp3:logging-interceptor:4.9.3"
implementation "com.jakewharton.timber:timber:5.0.1" implementation "com.jakewharton.timber:timber:5.0.1"
implementation "at.favre.lib:slf4j-timber:1.0.1" implementation "at.favre.lib:slf4j-timber:1.0.1"
@ -226,31 +230,32 @@ dependencies {
implementation "com.mikepenz:aboutlibraries-core:$about_libraries" implementation "com.mikepenz:aboutlibraries-core:$about_libraries"
implementation "io.coil-kt:coil:1.4.0" implementation "io.coil-kt:coil:1.4.0"
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.4.0'
implementation 'com.fredporciuncula:flow-preferences:1.5.0' implementation 'com.fredporciuncula:flow-preferences:1.6.0'
playImplementation platform('com.google.firebase:firebase-bom:29.0.0') playImplementation platform('com.google.firebase:firebase-bom:29.3.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.2' playImplementation 'com.google.android.play:core:1.10.3'
playImplementation 'com.google.android.play:core-ktx:1.8.1' playImplementation 'com.google.android.play:core-ktx:1.8.1'
playImplementation 'com.google.android.gms:play-services-ads:20.4.0' playImplementation 'com.google.android.gms:play-services-ads:20.6.0'
hmsImplementation 'com.huawei.hms:hianalytics:6.3.0.303' hmsImplementation 'com.huawei.hms:hianalytics:6.4.1.301'
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.1.300' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.5.200'
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.github.amitshekhariitbhu.Android-Debug-Database:debug-db:1.0.6' debugImplementation 'com.github.amitshekhariitbhu.Android-Debug-Database:debug-db:1.0.6'
debugImplementation 'com.github.haroldadmin:WhatTheStack:1.0.0-alpha04'
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:$coroutines" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines"
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
testImplementation 'org.robolectric:robolectric:4.7' testImplementation 'org.robolectric:robolectric:4.7.3'
testImplementation "androidx.test:runner:1.4.0" testImplementation "androidx.test:runner:1.4.0"
testImplementation "androidx.test.ext:junit:1.1.3" testImplementation "androidx.test.ext:junit:1.1.3"
testImplementation "androidx.test:core:1.4.0" testImplementation "androidx.test:core:1.4.0"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -6,6 +6,7 @@ import io.github.wulkanowy.ui.modules.main.MainActivity
import javax.inject.Singleton import javax.inject.Singleton
import javax.inject.Inject import javax.inject.Inject
@Suppress("UNUSED_PARAMETER", "unused")
@Singleton @Singleton
class InAppReviewHelper @Inject constructor( class InAppReviewHelper @Inject constructor(
@ApplicationContext private val context: Context @ApplicationContext private val context: Context

View File

@ -9,6 +9,7 @@
<uses-permission android:name="android.permission.WAKE_LOCK" /> <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" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
<queries> <queries>
<intent> <intent>
@ -113,7 +114,6 @@
</intent-filter> </intent-filter>
</service> </service>
<receiver <receiver
android:name=".ui.modules.timetablewidget.TimetableWidgetProvider" android:name=".ui.modules.timetablewidget.TimetableWidgetProvider"
android:exported="true" android:exported="true"

View File

@ -1,10 +1,7 @@
package io.github.wulkanowy package io.github.wulkanowy
import android.app.Application import android.app.Application
import android.util.Log.DEBUG import android.util.Log.*
import android.util.Log.INFO
import android.util.Log.VERBOSE
import android.webkit.WebView
import androidx.hilt.work.HiltWorkerFactory import androidx.hilt.work.HiltWorkerFactory
import androidx.work.Configuration import androidx.work.Configuration
import com.yariksoffice.lingver.Lingver import com.yariksoffice.lingver.Lingver
@ -12,12 +9,7 @@ import dagger.hilt.android.HiltAndroidApp
import fr.bipi.tressence.file.FileLoggerTree import fr.bipi.tressence.file.FileLoggerTree
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.ui.base.ThemeManager import io.github.wulkanowy.ui.base.ThemeManager
import io.github.wulkanowy.utils.ActivityLifecycleLogger import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.CrashLogExceptionTree
import io.github.wulkanowy.utils.CrashLogTree
import io.github.wulkanowy.utils.DebugLogTree
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -44,7 +36,6 @@ class WulkanowyApp : Application(), Configuration.Provider {
initializeAppLanguage() initializeAppLanguage()
themeManager.applyDefaultTheme() themeManager.applyDefaultTheme()
initLogging() initLogging()
fixWebViewLocale()
} }
private fun initLogging() { private fun initLogging() {
@ -76,15 +67,6 @@ class WulkanowyApp : Application(), Configuration.Provider {
} }
} }
private fun fixWebViewLocale() {
//https://stackoverflow.com/questions/40398528/android-webview-language-changes-abruptly-on-android-7-0-and-above
try {
WebView(this).destroy()
} catch (e: Throwable) {
//Ignore exceptions
}
}
override fun getWorkManagerConfiguration() = Configuration.Builder() override fun getWorkManagerConfiguration() = Configuration.Builder()
.setWorkerFactory(workerFactory) .setWorkerFactory(workerFactory)
.setMinimumLoggingLevel(if (appInfo.isDebug) VERBOSE else INFO) .setMinimumLoggingLevel(if (appInfo.isDebug) VERBOSE else INFO)

View File

@ -1,23 +1,173 @@
package io.github.wulkanowy.data package io.github.wulkanowy.data
data class Resource<T>(val status: Status, val data: T?, val error: Throwable?) { import kotlinx.coroutines.flow.*
companion object { import kotlinx.coroutines.sync.Mutex
fun <T> success(data: T?): Resource<T> { import kotlinx.coroutines.sync.withLock
return Resource(Status.SUCCESS, data, null) import timber.log.Timber
}
fun <T> error(error: Throwable?, data: T? = null): Resource<T> { sealed class Resource<T> {
return Resource(Status.ERROR, data, error)
}
fun <T> loading(data: T? = null): Resource<T> { open class Loading<T> : Resource<T>()
return Resource(Status.LOADING, data, null)
} data class Intermediate<T>(val data: T) : Loading<T>()
data class Success<T>(val data: T) : Resource<T>()
data class Error<T>(val error: Throwable) : Resource<T>()
}
val <T> Resource<T>.dataOrNull: T?
get() = when (this) {
is Resource.Success -> this.data
is Resource.Intermediate -> this.data
is Resource.Loading -> null
is Resource.Error -> null
}
val <T> Resource<T>.errorOrNull: Throwable?
get() = when (this) {
is Resource.Error -> this.error
else -> null
}
fun <T> resourceFlow(block: suspend () -> T) = flow {
emit(Resource.Loading())
emit(Resource.Success(block()))
}.catch { emit(Resource.Error(it)) }
fun <T> flatResourceFlow(block: suspend () -> Flow<Resource<T>>) = flow {
emit(Resource.Loading())
emitAll(block().filter { it is Resource.Intermediate || it !is Resource.Loading })
}.catch { emit(Resource.Error(it)) }
fun <T, U> Resource<T>.mapData(block: (T) -> U) = when (this) {
is Resource.Success -> Resource.Success(block(this.data))
is Resource.Intermediate -> Resource.Intermediate(block(this.data))
is Resource.Loading -> Resource.Loading()
is Resource.Error -> Resource.Error(this.error)
}
fun <T> Flow<Resource<T>>.logResourceStatus(name: String, showData: Boolean = false) = onEach {
val description = when (it) {
is Resource.Loading -> "started"
is Resource.Intermediate -> "intermediate data received" + if (showData) " (data: `${it.data}`)" else ""
is Resource.Success -> "success" + if (showData) " (data: `${it.data}`)" else ""
is Resource.Error -> "exception occurred: ${it.error}"
}
Timber.i("$name: $description")
}
fun <T, U> Flow<Resource<T>>.mapResourceData(block: (T) -> U) = map {
it.mapData(block)
}
fun <T> Flow<Resource<T>>.onResourceData(block: suspend (T) -> Unit) = onEach {
when (it) {
is Resource.Success -> block(it.data)
is Resource.Intermediate -> block(it.data)
is Resource.Error,
is Resource.Loading -> Unit
} }
} }
enum class Status { fun <T> Flow<Resource<T>>.onResourceLoading(block: suspend () -> Unit) = onEach {
LOADING, if (it is Resource.Loading) {
SUCCESS, block()
ERROR }
}
fun <T> Flow<Resource<T>>.onResourceIntermediate(block: suspend (T) -> Unit) = onEach {
if (it is Resource.Intermediate) {
block(it.data)
}
}
fun <T> Flow<Resource<T>>.onResourceSuccess(block: suspend (T) -> Unit) = onEach {
if (it is Resource.Success) {
block(it.data)
}
}
fun <T> Flow<Resource<T>>.onResourceError(block: (Throwable) -> Unit) = onEach {
if (it is Resource.Error) {
block(it.error)
}
}
fun <T> Flow<Resource<T>>.onResourceNotLoading(block: () -> Unit) = onEach {
if (it !is Resource.Loading) {
block()
}
}
suspend fun <T> Flow<Resource<T>>.toFirstResult() = filter { it !is Resource.Loading }.first()
suspend fun <T> Flow<Resource<T>>.waitForResult() = takeWhile { it is Resource.Loading }.collect()
inline fun <ResultType, RequestType> networkBoundResource(
mutex: Mutex = Mutex(),
showSavedOnLoading: Boolean = true,
crossinline isResultEmpty: (ResultType) -> Boolean,
crossinline query: () -> Flow<ResultType>,
crossinline fetch: suspend (ResultType) -> RequestType,
crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit,
crossinline onFetchFailed: (Throwable) -> Unit = { },
crossinline shouldFetch: (ResultType) -> Boolean = { true },
crossinline filterResult: (ResultType) -> ResultType = { it }
) = flow {
emit(Resource.Loading())
val data = query().first()
emitAll(if (shouldFetch(data)) {
val filteredResult = filterResult(data)
if (showSavedOnLoading && !isResultEmpty(filteredResult)) {
emit(Resource.Intermediate(filteredResult))
}
try {
val newData = fetch(data)
mutex.withLock { saveFetchResult(query().first(), newData) }
query().map { Resource.Success(filterResult(it)) }
} catch (throwable: Throwable) {
onFetchFailed(throwable)
query().map { Resource.Error(throwable) }
}
} else {
query().map { Resource.Success(filterResult(it)) }
})
}
@JvmName("networkBoundResourceWithMap")
inline fun <ResultType, RequestType, T> networkBoundResource(
mutex: Mutex = Mutex(),
showSavedOnLoading: Boolean = true,
crossinline isResultEmpty: (T) -> Boolean,
crossinline query: () -> Flow<ResultType>,
crossinline fetch: suspend (ResultType) -> RequestType,
crossinline saveFetchResult: suspend (old: ResultType, new: RequestType) -> Unit,
crossinline onFetchFailed: (Throwable) -> Unit = { },
crossinline shouldFetch: (ResultType) -> Boolean = { true },
crossinline mapResult: (ResultType) -> T
) = flow {
emit(Resource.Loading())
val data = query().first()
emitAll(if (shouldFetch(data)) {
val mappedResult = mapResult(data)
if (showSavedOnLoading && !isResultEmpty(mappedResult)) {
emit(Resource.Intermediate(mappedResult))
}
try {
val newData = fetch(data)
mutex.withLock { saveFetchResult(query().first(), newData) }
query().map { Resource.Success(mapResult(it)) }
} catch (throwable: Throwable) {
onFetchFailed(throwable)
query().map { Resource.Error(throwable) }
}
} else {
query().map { Resource.Success(mapResult(it)) }
})
} }

View File

@ -1,113 +1,11 @@
package io.github.wulkanowy.data.db package io.github.wulkanowy.data.db
import android.content.Context import android.content.Context
import androidx.room.Database import androidx.room.*
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.RoomDatabase.JournalMode.TRUNCATE import androidx.room.RoomDatabase.JournalMode.TRUNCATE
import androidx.room.TypeConverters import io.github.wulkanowy.data.db.dao.*
import io.github.wulkanowy.data.db.dao.AdminMessageDao import io.github.wulkanowy.data.db.entities.*
import io.github.wulkanowy.data.db.dao.AttendanceDao import io.github.wulkanowy.data.db.migrations.*
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
import io.github.wulkanowy.data.db.dao.ConferenceDao
import io.github.wulkanowy.data.db.dao.ExamDao
import io.github.wulkanowy.data.db.dao.GradeDao
import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao
import io.github.wulkanowy.data.db.dao.GradePointsStatisticsDao
import io.github.wulkanowy.data.db.dao.GradeSemesterStatisticsDao
import io.github.wulkanowy.data.db.dao.GradeSummaryDao
import io.github.wulkanowy.data.db.dao.HomeworkDao
import io.github.wulkanowy.data.db.dao.LuckyNumberDao
import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
import io.github.wulkanowy.data.db.dao.MessagesDao
import io.github.wulkanowy.data.db.dao.MobileDeviceDao
import io.github.wulkanowy.data.db.dao.NoteDao
import io.github.wulkanowy.data.db.dao.NotificationDao
import io.github.wulkanowy.data.db.dao.RecipientDao
import io.github.wulkanowy.data.db.dao.ReportingUnitDao
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
import io.github.wulkanowy.data.db.dao.SchoolDao
import io.github.wulkanowy.data.db.dao.SemesterDao
import io.github.wulkanowy.data.db.dao.StudentDao
import io.github.wulkanowy.data.db.dao.StudentInfoDao
import io.github.wulkanowy.data.db.dao.SubjectDao
import io.github.wulkanowy.data.db.dao.TeacherDao
import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao
import io.github.wulkanowy.data.db.dao.TimetableDao
import io.github.wulkanowy.data.db.dao.TimetableHeaderDao
import io.github.wulkanowy.data.db.entities.AdminMessage
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.AttendanceSummary
import io.github.wulkanowy.data.db.entities.CompletedLesson
import io.github.wulkanowy.data.db.entities.Conference
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
import io.github.wulkanowy.data.db.entities.GradePointsStatistics
import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.MessageAttachment
import io.github.wulkanowy.data.db.entities.MobileDevice
import io.github.wulkanowy.data.db.entities.Note
import io.github.wulkanowy.data.db.entities.Notification
import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.ReportingUnit
import io.github.wulkanowy.data.db.entities.School
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentInfo
import io.github.wulkanowy.data.db.entities.Subject
import io.github.wulkanowy.data.db.entities.Teacher
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.db.entities.TimetableAdditional
import io.github.wulkanowy.data.db.entities.TimetableHeader
import io.github.wulkanowy.data.db.migrations.Migration10
import io.github.wulkanowy.data.db.migrations.Migration11
import io.github.wulkanowy.data.db.migrations.Migration12
import io.github.wulkanowy.data.db.migrations.Migration13
import io.github.wulkanowy.data.db.migrations.Migration14
import io.github.wulkanowy.data.db.migrations.Migration15
import io.github.wulkanowy.data.db.migrations.Migration16
import io.github.wulkanowy.data.db.migrations.Migration17
import io.github.wulkanowy.data.db.migrations.Migration18
import io.github.wulkanowy.data.db.migrations.Migration19
import io.github.wulkanowy.data.db.migrations.Migration2
import io.github.wulkanowy.data.db.migrations.Migration20
import io.github.wulkanowy.data.db.migrations.Migration21
import io.github.wulkanowy.data.db.migrations.Migration22
import io.github.wulkanowy.data.db.migrations.Migration23
import io.github.wulkanowy.data.db.migrations.Migration24
import io.github.wulkanowy.data.db.migrations.Migration25
import io.github.wulkanowy.data.db.migrations.Migration26
import io.github.wulkanowy.data.db.migrations.Migration27
import io.github.wulkanowy.data.db.migrations.Migration28
import io.github.wulkanowy.data.db.migrations.Migration29
import io.github.wulkanowy.data.db.migrations.Migration3
import io.github.wulkanowy.data.db.migrations.Migration30
import io.github.wulkanowy.data.db.migrations.Migration31
import io.github.wulkanowy.data.db.migrations.Migration32
import io.github.wulkanowy.data.db.migrations.Migration33
import io.github.wulkanowy.data.db.migrations.Migration34
import io.github.wulkanowy.data.db.migrations.Migration35
import io.github.wulkanowy.data.db.migrations.Migration36
import io.github.wulkanowy.data.db.migrations.Migration37
import io.github.wulkanowy.data.db.migrations.Migration38
import io.github.wulkanowy.data.db.migrations.Migration39
import io.github.wulkanowy.data.db.migrations.Migration4
import io.github.wulkanowy.data.db.migrations.Migration40
import io.github.wulkanowy.data.db.migrations.Migration41
import io.github.wulkanowy.data.db.migrations.Migration42
import io.github.wulkanowy.data.db.migrations.Migration43
import io.github.wulkanowy.data.db.migrations.Migration5
import io.github.wulkanowy.data.db.migrations.Migration6
import io.github.wulkanowy.data.db.migrations.Migration7
import io.github.wulkanowy.data.db.migrations.Migration8
import io.github.wulkanowy.data.db.migrations.Migration9
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import javax.inject.Singleton import javax.inject.Singleton
@ -145,6 +43,11 @@ import javax.inject.Singleton
Notification::class, Notification::class,
AdminMessage::class AdminMessage::class
], ],
autoMigrations = [
AutoMigration(from = 44, to = 45),
AutoMigration(from = 46, to = 47),
AutoMigration(from = 47, to = 48),
],
version = AppDatabase.VERSION_SCHEMA, version = AppDatabase.VERSION_SCHEMA,
exportSchema = true exportSchema = true
) )
@ -152,7 +55,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
companion object { companion object {
const val VERSION_SCHEMA = 43 const val VERSION_SCHEMA = 48
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
Migration2(), Migration2(),
@ -196,7 +99,9 @@ abstract class AppDatabase : RoomDatabase() {
Migration40(), Migration40(),
Migration41(sharedPrefProvider), Migration41(sharedPrefProvider),
Migration42(), Migration42(),
Migration43() Migration43(),
Migration44(),
Migration46(),
) )
fun newInstance( fun newInstance(

View File

@ -1,40 +1,36 @@
package io.github.wulkanowy.data.db package io.github.wulkanowy.data.db
import androidx.room.TypeConverter import androidx.room.TypeConverter
import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.utils.toTimestamp
import kotlinx.serialization.SerializationException import kotlinx.serialization.SerializationException
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.time.*
import java.util.*
import java.time.Instant import java.time.Instant
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalDateTime
import java.time.Month import java.time.Month
import java.time.ZoneOffset import java.time.ZoneOffset
import java.util.Date import java.util.*
class Converters { class Converters {
private val json = Json private val json = Json
@TypeConverter @TypeConverter
fun timestampToDate(value: Long?): LocalDate? = value?.run { fun timestampToLocalDate(value: Long?): LocalDate? =
Date(value).toInstant().atZone(ZoneOffset.UTC).toLocalDate() value?.let(::Date)?.toInstant()?.atZone(ZoneOffset.UTC)?.toLocalDate()
}
@TypeConverter @TypeConverter
fun dateToTimestamp(date: LocalDate?): Long? { fun dateToTimestamp(date: LocalDate?): Long? = date?.toTimestamp()
return date?.atStartOfDay()?.toInstant(ZoneOffset.UTC)?.toEpochMilli()
}
@TypeConverter @TypeConverter
fun timestampToTime(value: Long?): LocalDateTime? = value?.let { fun instantToTimestamp(instant: Instant?): Long? = instant?.toEpochMilli()
LocalDateTime.ofInstant(Instant.ofEpochMilli(value), ZoneOffset.UTC)
}
@TypeConverter @TypeConverter
fun timeToTimestamp(date: LocalDateTime?): Long? { fun timestampToInstant(timestamp: Long?): Instant? = timestamp?.let(Instant::ofEpochMilli)
return date?.atZone(ZoneOffset.UTC)?.toInstant()?.toEpochMilli()
}
@TypeConverter @TypeConverter
fun monthToInt(month: Month?) = month?.value fun monthToInt(month: Month?) = month?.value
@ -65,4 +61,11 @@ class Converters {
emptyList() // handle errors from old gson Pair serialized data emptyList() // handle errors from old gson Pair serialized data
} }
} }
@TypeConverter
fun destinationToString(destination: Destination) = json.encodeToString(destination)
@TypeConverter
fun stringToDestination(destination: String): Destination = json.decodeFromString(destination)
} }

View File

@ -4,7 +4,7 @@ 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 java.time.Instant
import javax.inject.Singleton import javax.inject.Singleton
@Dao @Dao
@ -12,5 +12,5 @@ import javax.inject.Singleton
interface ConferenceDao : BaseDao<Conference> { interface ConferenceDao : BaseDao<Conference> {
@Query("SELECT * FROM Conferences WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :startDate") @Query("SELECT * FROM Conferences WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :startDate")
fun loadAll(diaryId: Int, studentId: Int, startDate: LocalDateTime): Flow<List<Conference>> fun loadAll(diaryId: Int, studentId: Int, startDate: Instant): Flow<List<Conference>>
} }

View File

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

View File

@ -1,12 +1,7 @@
package io.github.wulkanowy.data.db.dao package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.*
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy.ABORT import androidx.room.OnConflictStrategy.ABORT
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar import io.github.wulkanowy.data.db.entities.StudentNickAndAvatar
import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.db.entities.StudentWithSemesters
@ -38,6 +33,10 @@ abstract class StudentDao {
@Query("SELECT * FROM Students") @Query("SELECT * FROM Students")
abstract suspend fun loadStudentsWithSemesters(): List<StudentWithSemesters> abstract suspend fun loadStudentsWithSemesters(): List<StudentWithSemesters>
@Transaction
@Query("SELECT * FROM Students WHERE id = :id")
abstract suspend fun loadStudentWithSemestersById(id: Long): StudentWithSemesters?
@Query("UPDATE Students SET is_current = 1 WHERE id = :id") @Query("UPDATE Students SET is_current = 1 WHERE id = :id")
abstract suspend fun updateCurrent(id: Long) abstract suspend fun updateCurrent(id: Long)

View File

@ -5,6 +5,7 @@ import androidx.room.Query
import io.github.wulkanowy.data.db.entities.TimetableAdditional import io.github.wulkanowy.data.db.entities.TimetableAdditional
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import java.time.LocalDate import java.time.LocalDate
import java.util.UUID
import javax.inject.Singleton import javax.inject.Singleton
@Dao @Dao
@ -12,5 +13,13 @@ import javax.inject.Singleton
interface TimetableAdditionalDao : BaseDao<TimetableAdditional> { interface TimetableAdditionalDao : BaseDao<TimetableAdditional> {
@Query("SELECT * FROM TimetableAdditional WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") @Query("SELECT * FROM TimetableAdditional 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<TimetableAdditional>> fun loadAll(
diaryId: Int,
studentId: Int,
from: LocalDate,
end: LocalDate
): Flow<List<TimetableAdditional>>
@Query("DELETE FROM TimetableAdditional WHERE repeat_id = :repeatId")
suspend fun deleteAllByRepeatId(repeatId: UUID)
} }

View File

@ -33,5 +33,8 @@ data class AdminMessage(
val priority: String, val priority: String,
val type: String val type: String,
@ColumnInfo(name = "is_dismissible")
val isDismissible: Boolean = false
) )

View File

@ -4,7 +4,7 @@ import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import java.io.Serializable import java.io.Serializable
import java.time.LocalDateTime import java.time.Instant
@Entity(tableName = "Conferences") @Entity(tableName = "Conferences")
data class Conference( data class Conference(
@ -27,7 +27,7 @@ data class Conference(
@ColumnInfo(name = "conference_id") @ColumnInfo(name = "conference_id")
val conferenceId: Int, val conferenceId: Int,
val date: LocalDateTime val date: Instant,
) : Serializable { ) : Serializable {
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)

View File

@ -24,5 +24,8 @@ data class GradeSemesterStatistics(
var id: Long = 0 var id: Long = 0
@Transient @Transient
var average: String = "" var classAverage: String = ""
@Transient
var studentAverage: String = ""
} }

View File

@ -3,7 +3,7 @@ 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 java.time.LocalDateTime import java.time.Instant
@Entity(tableName = "GradesSummary") @Entity(tableName = "GradesSummary")
data class GradeSummary( data class GradeSummary(
@ -45,8 +45,8 @@ data class GradeSummary(
var isFinalGradeNotified: Boolean = true var isFinalGradeNotified: Boolean = true
@ColumnInfo(name = "predicted_grade_last_change") @ColumnInfo(name = "predicted_grade_last_change")
var predictedGradeLastChange: LocalDateTime = LocalDateTime.now() var predictedGradeLastChange: Instant = Instant.now()
@ColumnInfo(name = "final_grade_last_change") @ColumnInfo(name = "final_grade_last_change")
var finalGradeLastChange: LocalDateTime = LocalDateTime.now() var finalGradeLastChange: Instant = Instant.now()
} }

View File

@ -4,7 +4,7 @@ import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import java.io.Serializable import java.io.Serializable
import java.time.LocalDateTime import java.time.Instant
@Entity(tableName = "Messages") @Entity(tableName = "Messages")
data class Message( data class Message(
@ -29,7 +29,7 @@ data class Message(
val subject: String, val subject: String,
val date: LocalDateTime, val date: Instant,
@ColumnInfo(name = "folder_id") @ColumnInfo(name = "folder_id")
val folderId: Int, val folderId: Int,

View File

@ -4,7 +4,7 @@ import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import java.io.Serializable import java.io.Serializable
import java.time.LocalDateTime import java.time.Instant
@Entity(tableName = "MobileDevices") @Entity(tableName = "MobileDevices")
data class MobileDevice( data class MobileDevice(
@ -17,7 +17,7 @@ data class MobileDevice(
val name: String, val name: String,
val date: LocalDateTime val date: Instant,
) : Serializable { ) : Serializable {
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)

View File

@ -4,7 +4,8 @@ import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import io.github.wulkanowy.services.sync.notifications.NotificationType import io.github.wulkanowy.services.sync.notifications.NotificationType
import java.time.LocalDateTime import io.github.wulkanowy.ui.modules.Destination
import java.time.Instant
@Entity(tableName = "Notifications") @Entity(tableName = "Notifications")
data class Notification( data class Notification(
@ -18,7 +19,10 @@ data class Notification(
val type: NotificationType, val type: NotificationType,
val date: LocalDateTime, @ColumnInfo(defaultValue = "{\"type\":\"io.github.wulkanowy.ui.modules.Destination.Dashboard\"}")
val destination: Destination,
val date: Instant,
val data: String? = null val data: String? = null
) { ) {

View File

@ -7,7 +7,12 @@ import androidx.room.PrimaryKey
import java.io.Serializable import java.io.Serializable
import java.time.LocalDate import java.time.LocalDate
@Entity(tableName = "Semesters", indices = [Index(value = ["student_id", "diary_id", "semester_id"], unique = true)]) @Entity(
tableName = "Semesters", indices = [Index(
value = ["student_id", "diary_id", "kindergarten_diary_id", "semester_id"],
unique = true
)]
)
data class Semester( data class Semester(
@ColumnInfo(name = "student_id") @ColumnInfo(name = "student_id")
@ -16,6 +21,9 @@ data class Semester(
@ColumnInfo(name = "diary_id") @ColumnInfo(name = "diary_id")
val diaryId: Int, val diaryId: Int,
@ColumnInfo(name = "kindergarten_diary_id", defaultValue = "0")
val kindergartenDiaryId: Int,
@ColumnInfo(name = "diary_name") @ColumnInfo(name = "diary_name")
val diaryName: String, val diaryName: String,
@ -37,12 +45,11 @@ data class Semester(
@ColumnInfo(name = "unit_id") @ColumnInfo(name = "unit_id")
val unitId: Int val unitId: Int
): Serializable { ) : Serializable {
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
var id: Long = 0 var id: Long = 0
@ColumnInfo(name = "is_current") @ColumnInfo(name = "is_current")
var current: Boolean = false var current: Boolean = false
} }

View File

@ -5,7 +5,7 @@ import androidx.room.Entity
import androidx.room.Index import androidx.room.Index
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import java.io.Serializable import java.io.Serializable
import java.time.LocalDateTime import java.time.Instant
@Entity( @Entity(
tableName = "Students", tableName = "Students",
@ -74,7 +74,7 @@ data class Student(
val isCurrent: Boolean, val isCurrent: Boolean,
@ColumnInfo(name = "registration_date") @ColumnInfo(name = "registration_date")
val registrationDate: LocalDateTime val registrationDate: Instant,
) : Serializable { ) : Serializable {
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)

View File

@ -4,8 +4,8 @@ import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import java.io.Serializable import java.io.Serializable
import java.time.Instant
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalDateTime
@Entity(tableName = "Timetable") @Entity(tableName = "Timetable")
data class Timetable( data class Timetable(
@ -18,9 +18,9 @@ data class Timetable(
val number: Int, val number: Int,
val start: LocalDateTime, val start: Instant,
val end: LocalDateTime, val end: Instant,
val date: LocalDate, val date: LocalDate,

View File

@ -4,8 +4,9 @@ import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import java.io.Serializable import java.io.Serializable
import java.time.Instant
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalDateTime import java.util.*
@Entity(tableName = "TimetableAdditional") @Entity(tableName = "TimetableAdditional")
data class TimetableAdditional( data class TimetableAdditional(
@ -16,9 +17,9 @@ data class TimetableAdditional(
@ColumnInfo(name = "diary_id") @ColumnInfo(name = "diary_id")
val diaryId: Int, val diaryId: Int,
val start: LocalDateTime, val start: Instant,
val end: LocalDateTime, val end: Instant,
val date: LocalDate, val date: LocalDate,
@ -27,4 +28,10 @@ data class TimetableAdditional(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
var id: Long = 0 var id: Long = 0
@ColumnInfo(name = "repeat_id", defaultValue = "NULL")
var repeatId: UUID? = null
@ColumnInfo(name = "is_added_by_user", defaultValue = "0")
var isAddedByUser: Boolean = false
} }

View File

@ -43,12 +43,14 @@ class Migration12 : Migration(11, 12) {
private fun getStudentsIds(database: SupportSQLiteDatabase): List<Int> { private fun getStudentsIds(database: SupportSQLiteDatabase): List<Int> {
val students = mutableListOf<Int>() val students = mutableListOf<Int>()
val studentsCursor = database.query("SELECT student_id FROM Students") database.query("SELECT student_id FROM Students").use {
if (studentsCursor.moveToFirst()) { if (it.moveToFirst()) {
do { do {
students.add(studentsCursor.getInt(0)) students.add(it.getInt(0))
} while (studentsCursor.moveToNext()) } while (it.moveToNext())
}
} }
return students return students
} }

View File

@ -25,12 +25,14 @@ class Migration13 : Migration(12, 13) {
private fun getStudentsIds(database: SupportSQLiteDatabase): MutableList<Pair<Int, String>> { private fun getStudentsIds(database: SupportSQLiteDatabase): MutableList<Pair<Int, String>> {
val students = mutableListOf<Pair<Int, String>>() val students = mutableListOf<Pair<Int, String>>()
val studentsCursor = database.query("SELECT id, school_name FROM Students") database.query("SELECT id, school_name FROM Students").use {
if (studentsCursor.moveToFirst()) { if (it.moveToFirst()) {
do { do {
students.add(studentsCursor.getInt(0) to studentsCursor.getString(1)) students.add(it.getInt(0) to it.getString(1))
} while (studentsCursor.moveToNext()) } while (it.moveToNext())
}
} }
return students return students
} }
@ -42,12 +44,14 @@ class Migration13 : Migration(12, 13) {
private fun getStudentsAndClassIds(database: SupportSQLiteDatabase): List<Pair<Int, Int>> { private fun getStudentsAndClassIds(database: SupportSQLiteDatabase): List<Pair<Int, Int>> {
val students = mutableListOf<Pair<Int, Int>>() val students = mutableListOf<Pair<Int, Int>>()
val studentsCursor = database.query("SELECT student_id, class_id FROM Students") database.query("SELECT student_id, class_id FROM Students").use {
if (studentsCursor.moveToFirst()) { if (it.moveToFirst()) {
do { do {
students.add(studentsCursor.getInt(0) to studentsCursor.getInt(1)) students.add(it.getInt(0) to it.getInt(1))
} while (studentsCursor.moveToNext()) } while (it.moveToNext())
}
} }
return students return students
} }

View File

@ -22,24 +22,28 @@ class Migration27 : Migration(26, 27) {
private fun getStudentsIdsAndNames(database: SupportSQLiteDatabase): MutableList<Triple<Long, Int, String>> { private fun getStudentsIdsAndNames(database: SupportSQLiteDatabase): MutableList<Triple<Long, Int, String>> {
val students = mutableListOf<Triple<Long, Int, String>>() val students = mutableListOf<Triple<Long, Int, String>>()
val studentsCursor = database.query("SELECT id, user_login_id, student_name FROM Students") database.query("SELECT id, user_login_id, student_name FROM Students").use {
if (studentsCursor.moveToFirst()) { if (it.moveToFirst()) {
do { do {
students.add(Triple(studentsCursor.getLong(0), studentsCursor.getInt(1), studentsCursor.getString(2))) students.add(Triple(it.getLong(0), it.getInt(1), it.getString(2)))
} while (studentsCursor.moveToNext()) } while (it.moveToNext())
}
} }
return students return students
} }
private fun getReportingUnits(database: SupportSQLiteDatabase): MutableList<Pair<Int, String>> { private fun getReportingUnits(database: SupportSQLiteDatabase): MutableList<Pair<Int, String>> {
val units = mutableListOf<Pair<Int, String>>() val units = mutableListOf<Pair<Int, String>>()
val unitsCursor = database.query("SELECT sender_id, sender_name FROM ReportingUnits") database.query("SELECT sender_id, sender_name FROM ReportingUnits").use {
if (unitsCursor.moveToFirst()) { if (it.moveToFirst()) {
do { do {
units.add(unitsCursor.getInt(0) to unitsCursor.getString(1)) units.add(it.getInt(0) to it.getString(1))
} while (unitsCursor.moveToNext()) } while (it.moveToNext())
}
} }
return units return units
} }
} }

View File

@ -10,15 +10,17 @@ class Migration35(private val appInfo: AppInfo) : Migration(34, 35) {
override fun migrate(database: SupportSQLiteDatabase) { override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Students ADD COLUMN `avatar_color` INTEGER NOT NULL DEFAULT 0") database.execSQL("ALTER TABLE Students ADD COLUMN `avatar_color` INTEGER NOT NULL DEFAULT 0")
val studentsCursor = database.query("SELECT * FROM Students") database.query("SELECT * FROM Students").use {
while (it.moveToNext()) {
while (studentsCursor.moveToNext()) { val studentId = it.getLongOrNull(0)
val studentId = studentsCursor.getLongOrNull(0) database.execSQL(
database.execSQL( """
"""UPDATE Students UPDATE Students
SET avatar_color = ${appInfo.defaultColorsForAvatar.random()} SET avatar_color = ${appInfo.defaultColorsForAvatar.random()}
WHERE id = $studentId""" WHERE id = $studentId
) """
)
}
} }
} }
} }

View File

@ -3,7 +3,7 @@ package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteDatabase
import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.ui.modules.grade.GradeExpandMode import io.github.wulkanowy.data.enums.GradeExpandMode
class Migration41(private val sharedPrefProvider: SharedPrefProvider) : Migration(40, 41) { class Migration41(private val sharedPrefProvider: SharedPrefProvider) : Migration(40, 41) {

View File

@ -0,0 +1,11 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration44 : Migration(43, 44) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE AdminMessages ADD COLUMN is_dismissible INTEGER NOT NULL DEFAULT 0")
}
}

View File

@ -0,0 +1,102 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import java.time.Instant
import java.time.ZoneId
import java.time.ZoneOffset
class Migration46 : Migration(45, 46) {
override fun migrate(database: SupportSQLiteDatabase) {
migrateConferences(database)
migrateMessages(database)
migrateMobileDevices(database)
migrateNotifications(database)
migrateTimetable(database)
migrateTimetableAdditional(database)
}
private fun migrateConferences(database: SupportSQLiteDatabase) {
database.query("SELECT * FROM Conferences").use {
while (it.moveToNext()) {
val id = it.getLong(it.getColumnIndexOrThrow("id"))
val timestampLocal = it.getLong(it.getColumnIndexOrThrow("date"))
val timestampUtc = timestampLocal.timestampLocalToUTC()
database.execSQL("UPDATE Conferences SET date = $timestampUtc WHERE id = $id")
}
}
}
private fun migrateMessages(database: SupportSQLiteDatabase) {
database.query("SELECT * FROM Messages").use {
while (it.moveToNext()) {
val id = it.getLong(it.getColumnIndexOrThrow("id"))
val timestampLocal = it.getLong(it.getColumnIndexOrThrow("date"))
val timestampUtc = timestampLocal.timestampLocalToUTC()
database.execSQL("UPDATE Messages SET date = $timestampUtc WHERE id = $id")
}
}
}
private fun migrateMobileDevices(database: SupportSQLiteDatabase) {
database.query("SELECT * FROM MobileDevices").use {
while (it.moveToNext()) {
val id = it.getLong(it.getColumnIndexOrThrow("id"))
val timestampLocal = it.getLong(it.getColumnIndexOrThrow("date"))
val timestampUtc = timestampLocal.timestampLocalToUTC()
database.execSQL("UPDATE MobileDevices SET date = $timestampUtc WHERE id = $id")
}
}
}
private fun migrateNotifications(database: SupportSQLiteDatabase) {
database.query("SELECT * FROM Notifications").use {
while (it.moveToNext()) {
val id = it.getLong(it.getColumnIndexOrThrow("id"))
val timestampLocal = it.getLong(it.getColumnIndexOrThrow("date"))
val timestampUtc = timestampLocal.timestampLocalToUTC()
database.execSQL("UPDATE Notifications SET date = $timestampUtc WHERE id = $id")
}
}
}
private fun migrateTimetable(database: SupportSQLiteDatabase) {
database.query("SELECT * FROM Timetable").use {
while (it.moveToNext()) {
val id = it.getLong(it.getColumnIndexOrThrow("id"))
val timestampLocalStart = it.getLong(it.getColumnIndexOrThrow("start"))
val timestampLocalEnd = it.getLong(it.getColumnIndexOrThrow("end"))
val timestampUtcStart = timestampLocalStart.timestampLocalToUTC()
val timestampUtcEnd = timestampLocalEnd.timestampLocalToUTC()
database.execSQL("UPDATE Timetable SET start = $timestampUtcStart, end = $timestampUtcEnd WHERE id = $id")
}
}
}
private fun migrateTimetableAdditional(database: SupportSQLiteDatabase) {
database.query("SELECT * FROM TimetableAdditional").use {
while (it.moveToNext()) {
val id = it.getLong(it.getColumnIndexOrThrow("id"))
val timestampLocalStart = it.getLong(it.getColumnIndexOrThrow("start"))
val timestampLocalEnd = it.getLong(it.getColumnIndexOrThrow("end"))
val timestampUtcStart = timestampLocalStart.timestampLocalToUTC()
val timestampUtcEnd = timestampLocalEnd.timestampLocalToUTC()
database.execSQL("UPDATE TimetableAdditional SET start = $timestampUtcStart, end = $timestampUtcEnd WHERE id = $id")
}
}
}
private fun Long.timestampLocalToUTC(): Long = Instant.ofEpochMilli(this)
.atZone(ZoneOffset.UTC)
.withZoneSameLocal(ZoneId.of("Europe/Warsaw"))
.withZoneSameInstant(ZoneId.systemDefault())
.toInstant()
.toEpochMilli()
}

View File

@ -0,0 +1,12 @@
package io.github.wulkanowy.data.enums
enum class AppTheme(val value: String) {
SYSTEM("system"),
LIGHT("light"),
DARK("dark"),
BLACK("black");
companion object {
fun getByValue(value: String) = values().find { it.value == value } ?: LIGHT
}
}

View File

@ -0,0 +1,13 @@
package io.github.wulkanowy.data.enums
import java.io.Serializable
enum class GradeColorTheme(val value: String) : Serializable {
VULCAN("vulcan"),
MATERIAL("material"),
GRADE_COLOR("grade_color");
companion object {
fun getByValue(value: String) = values().find { it.value == value } ?: VULCAN
}
}

View File

@ -0,0 +1,11 @@
package io.github.wulkanowy.data.enums
enum class GradeExpandMode(val value: String) {
ONE("one"),
UNLIMITED("any"),
ALWAYS_EXPANDED("always");
companion object {
fun getByValue(value: String) = values().find { it.value == value } ?: ONE
}
}

View File

@ -0,0 +1,10 @@
package io.github.wulkanowy.data.enums
enum class GradeSortingMode(val value: String) {
ALPHABETIC("alphabetic"),
DATE("date");
companion object {
fun getByValue(value: String) = values().find { it.value == value } ?: ALPHABETIC
}
}

View File

@ -0,0 +1,11 @@
package io.github.wulkanowy.data.enums
enum class TimetableMode(val value: String) {
WHOLE_PLAN("yes"),
ONLY_CURRENT_GROUP("no"),
SMALL_OTHER_GROUP("small");
companion object {
fun getByValue(value: String) = values().find { it.value == value } ?: ONLY_CURRENT_GROUP
}
}

View File

@ -10,7 +10,7 @@ fun List<SdkConference>.mapToEntities(semester: Semester) = map {
diaryId = semester.diaryId, diaryId = semester.diaryId,
agenda = it.agenda, agenda = it.agenda,
conferenceId = it.id, conferenceId = it.id,
date = it.date, date = it.dateZoned.toInstant(),
presentOnConference = it.presentOnConference, presentOnConference = it.presentOnConference,
subject = it.subject, subject = it.subject,
title = it.title title = it.title

View File

@ -6,7 +6,7 @@ import io.github.wulkanowy.sdk.pojo.DirectorInformation as SdkDirectorInformatio
fun List<SdkDirectorInformation>.mapToEntities(student: Student) = map { fun List<SdkDirectorInformation>.mapToEntities(student: Student) = map {
SchoolAnnouncement( SchoolAnnouncement(
studentId = student.studentId, studentId = student.userLoginId,
date = it.date, date = it.date,
subject = it.subject, subject = it.subject,
content = it.content, content = it.content,

View File

@ -4,7 +4,7 @@ import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.MessageAttachment import io.github.wulkanowy.data.db.entities.MessageAttachment
import io.github.wulkanowy.data.db.entities.Recipient import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import java.time.LocalDateTime import java.time.Instant
import io.github.wulkanowy.sdk.pojo.Message as SdkMessage import io.github.wulkanowy.sdk.pojo.Message as SdkMessage
import io.github.wulkanowy.sdk.pojo.MessageAttachment as SdkMessageAttachment import io.github.wulkanowy.sdk.pojo.MessageAttachment as SdkMessageAttachment
import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient
@ -18,7 +18,7 @@ fun List<SdkMessage>.mapToEntities(student: Student) = map {
senderId = it.sender?.loginId ?: 0, senderId = it.sender?.loginId ?: 0,
recipient = it.recipients.singleOrNull()?.name ?: "Wielu adresatów", recipient = it.recipients.singleOrNull()?.name ?: "Wielu adresatów",
subject = it.subject.trim(), subject = it.subject.trim(),
date = it.date ?: LocalDateTime.now(), date = it.dateZoned?.toInstant() ?: Instant.now(),
folderId = it.folderId, folderId = it.folderId,
unread = it.unread ?: false, unread = it.unread ?: false,
removed = it.removed, removed = it.removed,

View File

@ -3,13 +3,13 @@ package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.MobileDevice import io.github.wulkanowy.data.db.entities.MobileDevice
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.pojos.MobileDeviceToken import io.github.wulkanowy.data.pojos.MobileDeviceToken
import io.github.wulkanowy.sdk.pojo.Token as SdkToken
import io.github.wulkanowy.sdk.pojo.Device as SdkDevice import io.github.wulkanowy.sdk.pojo.Device as SdkDevice
import io.github.wulkanowy.sdk.pojo.Token as SdkToken
fun List<SdkDevice>.mapToEntities(semester: Semester) = map { fun List<SdkDevice>.mapToEntities(semester: Semester) = map {
MobileDevice( MobileDevice(
userLoginId = semester.studentId, userLoginId = semester.studentId,
date = it.createDate, date = it.createDateZoned.toInstant(),
deviceId = it.id, deviceId = it.id,
name = it.name name = it.name
) )

View File

@ -7,6 +7,7 @@ fun List<SdkSemester>.mapToEntities(studentId: Int) = map {
Semester( Semester(
studentId = studentId, studentId = studentId,
diaryId = it.diaryId, diaryId = it.diaryId,
kindergartenDiaryId = it.kindergartenDiaryId,
diaryName = it.diaryName, diaryName = it.diaryName,
schoolYear = it.schoolYear, schoolYear = it.schoolYear,
semesterId = it.semesterId, semesterId = it.semesterId,

View File

@ -2,7 +2,7 @@ package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import java.time.LocalDateTime import java.time.Instant
import io.github.wulkanowy.sdk.pojo.Student as SdkStudent import io.github.wulkanowy.sdk.pojo.Student as SdkStudent
fun List<SdkStudent>.mapToEntities(password: String = "", colors: List<Long>) = map { fun List<SdkStudent>.mapToEntities(password: String = "", colors: List<Long>) = map {
@ -24,7 +24,7 @@ fun List<SdkStudent>.mapToEntities(password: String = "", colors: List<Long>) =
scrapperBaseUrl = it.scrapperBaseUrl, scrapperBaseUrl = it.scrapperBaseUrl,
loginType = it.loginType.name, loginType = it.loginType.name,
isCurrent = false, isCurrent = false,
registrationDate = LocalDateTime.now(), registrationDate = Instant.now(),
mobileBaseUrl = it.mobileBaseUrl, mobileBaseUrl = it.mobileBaseUrl,
privateKey = it.privateKey, privateKey = it.privateKey,
certificateKey = it.certificateKey, certificateKey = it.certificateKey,

View File

@ -21,8 +21,8 @@ fun List<SdkTimetable>.mapToEntities(semester: Semester) = map {
studentId = semester.studentId, studentId = semester.studentId,
diaryId = semester.diaryId, diaryId = semester.diaryId,
number = it.number, number = it.number,
start = it.start, start = it.startZoned.toInstant(),
end = it.end, end = it.endZoned.toInstant(),
date = it.date, date = it.date,
subject = it.subject, subject = it.subject,
subjectOld = it.subjectOld, subjectOld = it.subjectOld,
@ -45,8 +45,8 @@ fun List<SdkTimetableAdditional>.mapToEntities(semester: Semester) = map {
diaryId = semester.diaryId, diaryId = semester.diaryId,
subject = it.subject, subject = it.subject,
date = it.date, date = it.date,
start = it.start, start = it.startZoned.toInstant(),
end = it.end end = it.endZoned.toInstant(),
) )
} }

View File

@ -1,10 +1,10 @@
package io.github.wulkanowy.data.pojos package io.github.wulkanowy.data.pojos
import android.content.Intent
import io.github.wulkanowy.services.sync.notifications.NotificationType import io.github.wulkanowy.services.sync.notifications.NotificationType
import io.github.wulkanowy.ui.modules.Destination
data class NotificationData( data class NotificationData(
val intentToStart: Intent, val destination: Destination,
val title: String, val title: String,
val content: String val content: String
) )
@ -13,7 +13,7 @@ data class GroupNotificationData(
val notificationDataList: List<NotificationData>, val notificationDataList: List<NotificationData>,
val title: String, val title: String,
val content: String, val content: String,
val intentToStart: Intent, val destination: Destination,
val type: NotificationType val type: NotificationType
) )

View File

@ -3,9 +3,8 @@ package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.api.AdminMessageService import io.github.wulkanowy.data.api.AdminMessageService
import io.github.wulkanowy.data.db.dao.AdminMessageDao import io.github.wulkanowy.data.db.dao.AdminMessageDao
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.networkBoundResource
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -14,23 +13,18 @@ import javax.inject.Singleton
class AdminMessageRepository @Inject constructor( class AdminMessageRepository @Inject constructor(
private val adminMessageService: AdminMessageService, private val adminMessageService: AdminMessageService,
private val adminMessageDao: AdminMessageDao, private val adminMessageDao: AdminMessageDao,
private val appInfo: AppInfo, private val appInfo: AppInfo
private val refreshHelper: AutoRefreshHelper,
) { ) {
private val saveFetchResultMutex = Mutex() private val saveFetchResultMutex = Mutex()
private val cacheKey = "admin_messages" suspend fun getAdminMessages(student: Student) = networkBoundResource(
suspend fun getAdminMessages(student: Student, forceRefresh: Boolean) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it == null },
query = { adminMessageDao.loadAll() }, query = { adminMessageDao.loadAll() },
fetch = { adminMessageService.getAdminMessages() }, fetch = { adminMessageService.getAdminMessages() },
shouldFetch = { shouldFetch = { true },
refreshHelper.shouldBeRefreshed(cacheKey) || forceRefresh
},
saveFetchResult = { oldItems, newItems -> saveFetchResult = { oldItems, newItems ->
adminMessageDao.removeOldAndSaveNew(oldItems, newItems) adminMessageDao.removeOldAndSaveNew(oldItems, newItems)
refreshHelper.updateLastRefreshTimestamp(cacheKey)
}, },
showSavedOnLoading = false, showSavedOnLoading = false,
mapResult = { adminMessages -> mapResult = { adminMessages ->

View File

@ -5,15 +5,10 @@ import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.pojo.Absent import io.github.wulkanowy.sdk.pojo.Absent
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.sunday
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import java.time.LocalDate import java.time.LocalDate
@ -42,6 +37,7 @@ class AttendanceRepository @Inject constructor(
notify: Boolean = false, notify: Boolean = false,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(cacheKey, semester, start, end) key = getRefreshKey(cacheKey, semester, start, end)
@ -52,7 +48,8 @@ class AttendanceRepository @Inject constructor(
attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday) attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday)
}, },
fetch = { fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getAttendance(start.monday, end.sunday, semester.semesterId) .getAttendance(start.monday, end.sunday, semester.semesterId)
.mapToEntities(semester) .mapToEntities(semester)
}, },
@ -90,7 +87,8 @@ class AttendanceRepository @Inject constructor(
timeId = attendance.timeId timeId = attendance.timeId
) )
} }
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.excuseForAbsence(items, reason) .excuseForAbsence(items, reason)
} }
} }

View File

@ -4,11 +4,11 @@ import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject import javax.inject.Inject
@ -32,13 +32,15 @@ class AttendanceSummaryRepository @Inject constructor(
forceRefresh: Boolean, forceRefresh: Boolean,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
it.isEmpty() || forceRefresh || isExpired it.isEmpty() || forceRefresh || isExpired
}, },
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.kindergartenDiaryId, semester.schoolYear)
.getAttendanceSummary(subjectId) .getAttendanceSummary(subjectId)
.mapToEntities(semester, subjectId) .mapToEntities(semester, subjectId)
}, },

View File

@ -4,14 +4,9 @@ import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.sunday
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import java.time.LocalDate import java.time.LocalDate
import javax.inject.Inject import javax.inject.Inject
@ -36,6 +31,7 @@ class CompletedLessonsRepository @Inject constructor(
forceRefresh: Boolean, forceRefresh: Boolean,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(cacheKey, semester, start, end) key = getRefreshKey(cacheKey, semester, start, end)
@ -51,7 +47,8 @@ class CompletedLessonsRepository @Inject constructor(
) )
}, },
fetch = { fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getCompletedLessons(start.monday, end.sunday) .getCompletedLessons(start.monday, end.sunday)
.mapToEntities(semester) .mapToEntities(semester)
}, },

View File

@ -5,17 +5,15 @@ import io.github.wulkanowy.data.db.entities.Conference
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.init
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.sync.Mutex import kotlinx.coroutines.sync.Mutex
import java.time.Instant 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
@ -35,9 +33,10 @@ class ConferenceRepository @Inject constructor(
semester: Semester, semester: Semester,
forceRefresh: Boolean, forceRefresh: Boolean,
notify: Boolean = false, notify: Boolean = false,
startDate: LocalDateTime = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC), startDate: Instant = Instant.EPOCH,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
it.isEmpty() || forceRefresh || isExpired it.isEmpty() || forceRefresh || isExpired
@ -46,7 +45,8 @@ class ConferenceRepository @Inject constructor(
conferenceDb.loadAll(semester.diaryId, student.studentId, startDate) conferenceDb.loadAll(semester.diaryId, student.studentId, startDate)
}, },
fetch = { fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getConferences() .getConferences()
.mapToEntities(semester) .mapToEntities(semester)
.filter { it.date >= startDate } .filter { it.date >= startDate }
@ -66,7 +66,7 @@ class ConferenceRepository @Inject constructor(
conferenceDb.loadAll( conferenceDb.loadAll(
diaryId = semester.diaryId, diaryId = semester.diaryId,
studentId = semester.studentId, studentId = semester.studentId,
startDate = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC) startDate = Instant.EPOCH,
) )
suspend fun updateConference(conference: List<Conference>) = conferenceDb.updateAll(conference) suspend fun updateConference(conference: List<Conference>) = conferenceDb.updateAll(conference)

View File

@ -5,14 +5,9 @@ import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.endExamsDay
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.startExamsDay
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import java.time.LocalDate import java.time.LocalDate
@ -39,6 +34,7 @@ class ExamRepository @Inject constructor(
notify: Boolean = false, notify: Boolean = false,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(cacheKey, semester, start, end) key = getRefreshKey(cacheKey, semester, start, end)
@ -54,7 +50,8 @@ class ExamRepository @Inject constructor(
) )
}, },
fetch = { fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getExams(start.startExamsDay, start.endExamsDay, semester.semesterId) .getExams(start.startExamsDay, start.endExamsDay, semester.semesterId)
.mapToEntities(semester) .mapToEntities(semester)
}, },

View File

@ -7,17 +7,14 @@ import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import java.time.LocalDateTime import java.time.Instant
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -40,6 +37,10 @@ class GradeRepository @Inject constructor(
notify: Boolean = false, notify: Boolean = false,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = {
//When details is empty and summary is not, app will not use summary cache - edge case
it.first.isEmpty()
},
shouldFetch = { (details, summaries) -> shouldFetch = { (details, summaries) ->
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
details.isEmpty() || summaries.isEmpty() || forceRefresh || isExpired details.isEmpty() || summaries.isEmpty() || forceRefresh || isExpired
@ -51,7 +52,7 @@ class GradeRepository @Inject constructor(
}, },
fetch = { fetch = {
val (details, summary) = sdk.init(student) val (details, summary) = sdk.init(student)
.switchDiary(semester.diaryId, semester.schoolYear) .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getGrades(semester.semesterId) .getGrades(semester.semesterId)
details.mapToEntities(semester) to summary.mapToEntities(semester) details.mapToEntities(semester) to summary.mapToEntities(semester)
@ -70,8 +71,8 @@ class GradeRepository @Inject constructor(
newDetails: List<Grade>, newDetails: List<Grade>,
notify: Boolean notify: Boolean
) { ) {
val notifyBreakDate = oldGrades.maxByOrNull {it.date } val notifyBreakDate = oldGrades.maxByOrNull { it.date }?.date
?.date ?: student.registrationDate.toLocalDate() ?: 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 {
@ -101,13 +102,13 @@ class GradeRepository @Inject constructor(
} }
summary.predictedGradeLastChange = when { summary.predictedGradeLastChange = when {
oldSummary == null -> LocalDateTime.now() oldSummary == null -> Instant.now()
summary.predictedGrade != oldSummary.predictedGrade -> LocalDateTime.now() summary.predictedGrade != oldSummary.predictedGrade -> Instant.now()
else -> oldSummary.predictedGradeLastChange else -> oldSummary.predictedGradeLastChange
} }
summary.finalGradeLastChange = when { summary.finalGradeLastChange = when {
oldSummary == null -> LocalDateTime.now() oldSummary == null -> Instant.now()
summary.finalGrade != oldSummary.finalGrade -> LocalDateTime.now() summary.finalGrade != oldSummary.finalGrade -> Instant.now()
else -> oldSummary.finalGradeLastChange else -> oldSummary.finalGradeLastChange
} }
}) })

View File

@ -11,14 +11,14 @@ import io.github.wulkanowy.data.mappers.mapPartialToStatisticItems
import io.github.wulkanowy.data.mappers.mapPointsToStatisticsItems import io.github.wulkanowy.data.mappers.mapPointsToStatisticsItems
import io.github.wulkanowy.data.mappers.mapSemesterToStatisticItems import io.github.wulkanowy.data.mappers.mapSemesterToStatisticItems
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import java.util.Locale import java.util.*
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -46,6 +46,7 @@ class GradeStatisticsRepository @Inject constructor(
forceRefresh: Boolean, forceRefresh: Boolean,
) = networkBoundResource( ) = networkBoundResource(
mutex = partialMutex, mutex = partialMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(partialCacheKey, semester) key = getRefreshKey(partialCacheKey, semester)
@ -54,7 +55,8 @@ class GradeStatisticsRepository @Inject constructor(
}, },
query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) }, query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
fetch = { fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getGradesPartialStatistics(semester.semesterId) .getGradesPartialStatistics(semester.semesterId)
.mapToEntities(semester) .mapToEntities(semester)
}, },
@ -66,20 +68,16 @@ class GradeStatisticsRepository @Inject constructor(
mapResult = { items -> mapResult = { items ->
when (subjectName) { when (subjectName) {
"Wszystkie" -> { "Wszystkie" -> {
val numerator = items.map { val summaryItem = GradePartialStatistics(
it.classAverage.replace(",", ".").toDoubleOrNull() ?: .0
}.filterNot { it == .0 }
(items.reversed() + GradePartialStatistics(
studentId = semester.studentId, studentId = semester.studentId,
semesterId = semester.semesterId, semesterId = semester.semesterId,
subject = subjectName, subject = subjectName,
classAverage = if (numerator.isEmpty()) "" else numerator.average().let { classAverage = items.map { it.classAverage }.getSummaryAverage(),
"%.2f".format(Locale.FRANCE, it) studentAverage = items.map { it.studentAverage }.getSummaryAverage(),
},
studentAverage = "",
classAmounts = items.map { it.classAmounts }.sumGradeAmounts(), classAmounts = items.map { it.classAmounts }.sumGradeAmounts(),
studentAmounts = items.map { it.studentAmounts }.sumGradeAmounts() studentAmounts = items.map { it.studentAmounts }.sumGradeAmounts()
)).reversed() )
listOf(summaryItem) + items
} }
else -> items.filter { it.subject == subjectName } else -> items.filter { it.subject == subjectName }
}.mapPartialToStatisticItems() }.mapPartialToStatisticItems()
@ -93,6 +91,7 @@ class GradeStatisticsRepository @Inject constructor(
forceRefresh: Boolean, forceRefresh: Boolean,
) = networkBoundResource( ) = networkBoundResource(
mutex = semesterMutex, mutex = semesterMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(semesterCacheKey, semester) key = getRefreshKey(semesterCacheKey, semester)
@ -101,7 +100,8 @@ class GradeStatisticsRepository @Inject constructor(
}, },
query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) }, query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
fetch = { fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getGradesSemesterStatistics(semester.semesterId) .getGradesSemesterStatistics(semester.semesterId)
.mapToEntities(semester) .mapToEntities(semester)
}, },
@ -114,29 +114,29 @@ class GradeStatisticsRepository @Inject constructor(
val itemsWithAverage = items.map { item -> val itemsWithAverage = items.map { item ->
item.copy().apply { item.copy().apply {
val denominator = item.amounts.sum() val denominator = item.amounts.sum()
average = if (denominator == 0) "" else { classAverage = if (denominator == 0) "" else {
(item.amounts.mapIndexed { gradeValue, amount -> (item.amounts.mapIndexed { gradeValue, amount ->
(gradeValue + 1) * amount (gradeValue + 1) * amount
}.sum().toDouble() / denominator).let { }.sum().toDouble() / denominator).asAverageString()
"%.2f".format(Locale.FRANCE, it)
}
} }
} }
} }
when (subjectName) { when (subjectName) {
"Wszystkie" -> (itemsWithAverage.reversed() + GradeSemesterStatistics( "Wszystkie" -> {
studentId = semester.studentId, val summaryItem = GradeSemesterStatistics(
semesterId = semester.semesterId, studentId = semester.studentId,
subject = subjectName, semesterId = semester.semesterId,
amounts = itemsWithAverage.map { it.amounts }.sumGradeAmounts(), subject = subjectName,
studentGrade = 0 amounts = itemsWithAverage.map { it.amounts }.sumGradeAmounts(),
).apply { studentGrade = 0,
average = itemsWithAverage.mapNotNull { ).apply {
it.average.replace(",", ".").toDoubleOrNull() classAverage = itemsWithAverage.map { it.classAverage }.getSummaryAverage()
}.average().let { studentAverage = items
"%.2f".format(Locale.FRANCE, it) .mapNotNull { summary -> summary.studentGrade.takeIf { it != 0 } }
.average().asAverageString()
} }
}).reversed() listOf(summaryItem) + itemsWithAverage
}
else -> itemsWithAverage.filter { it.subject == subjectName } else -> itemsWithAverage.filter { it.subject == subjectName }
}.mapSemesterToStatisticItems() }.mapSemesterToStatisticItems()
} }
@ -149,13 +149,15 @@ class GradeStatisticsRepository @Inject constructor(
forceRefresh: Boolean, forceRefresh: Boolean,
) = networkBoundResource( ) = networkBoundResource(
mutex = pointsMutex, mutex = pointsMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(pointsCacheKey, semester)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(pointsCacheKey, semester))
it.isEmpty() || forceRefresh || isExpired it.isEmpty() || forceRefresh || isExpired
}, },
query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) }, query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
fetch = { fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getGradesPointsStatistics(semester.semesterId) .getGradesPointsStatistics(semester.semesterId)
.mapToEntities(semester) .mapToEntities(semester)
}, },
@ -172,6 +174,19 @@ class GradeStatisticsRepository @Inject constructor(
} }
) )
private fun List<String>.getSummaryAverage(): String {
val averages = mapNotNull {
it.replace(",", ".").toDoubleOrNull()
}
return averages.average()
.asAverageString()
.takeIf { averages.isNotEmpty() }
.orEmpty()
}
private fun Double.asAverageString(): String = "%.2f".format(Locale.FRANCE, this)
private fun List<List<Int>>.sumGradeAmounts(): List<Int> { private fun List<List<Int>>.sumGradeAmounts(): List<Int> {
val result = mutableListOf(0, 0, 0, 0, 0, 0) val result = mutableListOf(0, 0, 0, 0, 0, 0)
forEach { forEach {

View File

@ -5,14 +5,9 @@ import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.sunday
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import java.time.LocalDate import java.time.LocalDate
import javax.inject.Inject import javax.inject.Inject
@ -38,6 +33,7 @@ class HomeworkRepository @Inject constructor(
notify: Boolean = false, notify: Boolean = false,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(cacheKey, semester, start, end) key = getRefreshKey(cacheKey, semester, start, end)
@ -53,7 +49,8 @@ class HomeworkRepository @Inject constructor(
) )
}, },
fetch = { fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getHomework(start.monday, end.sunday) .getHomework(start.monday, end.sunday)
.mapToEntities(semester) .mapToEntities(semester)
}, },

View File

@ -4,9 +4,9 @@ import io.github.wulkanowy.data.db.dao.LuckyNumberDao
import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntity import io.github.wulkanowy.data.mappers.mapToEntity
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
@ -29,6 +29,7 @@ class LuckyNumberRepository @Inject constructor(
notify: Boolean = false, notify: Boolean = false,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it == null },
shouldFetch = { it == null || forceRefresh }, shouldFetch = { it == null || forceRefresh },
query = { luckyNumberDb.load(student.studentId, now()) }, query = { luckyNumberDb.load(student.studentId, now()) },
fetch = { fetch = {

View File

@ -7,15 +7,12 @@ import io.github.wulkanowy.data.Resource
import io.github.wulkanowy.data.db.SharedPrefProvider 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.*
import io.github.wulkanowy.data.db.entities.MessageWithAttachment
import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.enums.MessageFolder import io.github.wulkanowy.data.enums.MessageFolder
import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
import io.github.wulkanowy.data.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.networkBoundResource
import io.github.wulkanowy.data.pojos.MessageDraft import io.github.wulkanowy.data.pojos.MessageDraft
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
@ -23,7 +20,6 @@ import io.github.wulkanowy.sdk.pojo.SentMessage
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.init
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.sync.Mutex import kotlinx.coroutines.sync.Mutex
@ -59,6 +55,7 @@ class MessageRepository @Inject constructor(
notify: Boolean = false, notify: Boolean = false,
): Flow<Resource<List<Message>>> = networkBoundResource( ): Flow<Resource<List<Message>>> = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(cacheKey, student, folder) key = getRefreshKey(cacheKey, student, folder)
@ -106,8 +103,9 @@ class MessageRepository @Inject constructor(
message: Message, message: Message,
markAsRead: Boolean = false, markAsRead: Boolean = false,
): Flow<Resource<MessageWithAttachment?>> = networkBoundResource( ): Flow<Resource<MessageWithAttachment?>> = networkBoundResource(
isResultEmpty = { it?.message?.content.isNullOrBlank() },
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()}")
it.message.unread || it.message.content.isEmpty() it.message.unread || it.message.content.isEmpty()
}, },
@ -123,7 +121,7 @@ class MessageRepository @Inject constructor(
} }
}, },
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.apply { messagesDb.updateAll(listOf(old.message.apply {
id = old.message.id id = old.message.id
unread = !markAsRead unread = !markAsRead
@ -153,20 +151,27 @@ class MessageRepository @Inject constructor(
recipients = recipients.mapFromEntities() recipients = recipients.mapFromEntities()
) )
suspend fun deleteMessage(student: Student, message: Message) { suspend fun deleteMessages(student: Student, messages: List<Message>) {
val isDeleted = sdk.init(student).deleteMessages( val folderId = messages.first().folderId
messages = listOf(message.messageId), message.folderId val isDeleted = sdk.init(student)
) .deleteMessages(messages = messages.map { it.messageId }, folderId = folderId)
if (message.folderId != MessageFolder.TRASHED.id && isDeleted) { if (folderId != MessageFolder.TRASHED.id && isDeleted) {
val deletedMessage = message.copy(folderId = MessageFolder.TRASHED.id).apply { val deletedMessages = messages.map {
id = message.id it.copy(folderId = MessageFolder.TRASHED.id)
content = message.content .apply {
id = it.id
content = it.content
}
} }
messagesDb.updateAll(listOf(deletedMessage))
} else messagesDb.deleteAll(listOf(message)) messagesDb.updateAll(deletedMessages)
} else messagesDb.deleteAll(messages)
} }
suspend fun deleteMessage(student: Student, message: Message) =
deleteMessages(student, listOf(message))
var draftMessage: MessageDraft? var draftMessage: MessageDraft?
get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_send_draft)) get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_send_draft))
?.let { json.decodeFromString(it) } ?.let { json.decodeFromString(it) }

View File

@ -6,12 +6,12 @@ import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.mappers.mapToMobileDeviceToken import io.github.wulkanowy.data.mappers.mapToMobileDeviceToken
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.data.pojos.MobileDeviceToken import io.github.wulkanowy.data.pojos.MobileDeviceToken
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject import javax.inject.Inject
@ -34,13 +34,15 @@ class MobileDeviceRepository @Inject constructor(
forceRefresh: Boolean, forceRefresh: Boolean,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
it.isEmpty() || forceRefresh || isExpired it.isEmpty() || forceRefresh || isExpired
}, },
query = { mobileDb.loadAll(student.userLoginId.takeIf { it != 0 } ?: student.studentId) }, query = { mobileDb.loadAll(student.userLoginId.takeIf { it != 0 } ?: student.studentId) },
fetch = { fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getRegisteredDevices() .getRegisteredDevices()
.mapToEntities(semester) .mapToEntities(semester)
}, },
@ -53,14 +55,16 @@ class MobileDeviceRepository @Inject constructor(
) )
suspend fun unregisterDevice(student: Student, semester: Semester, device: MobileDevice) { suspend fun unregisterDevice(student: Student, semester: Semester, device: MobileDevice) {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.unregisterDevice(device.deviceId) .unregisterDevice(device.deviceId)
mobileDb.deleteAll(listOf(device)) mobileDb.deleteAll(listOf(device))
} }
suspend fun getToken(student: Student, semester: Semester): MobileDeviceToken { suspend fun getToken(student: Student, semester: Semester): MobileDeviceToken {
return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) return sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getToken() .getToken()
.mapToMobileDeviceToken() .mapToMobileDeviceToken()
} }

View File

@ -5,12 +5,9 @@ import io.github.wulkanowy.data.db.entities.Note
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject import javax.inject.Inject
@ -34,6 +31,7 @@ class NoteRepository @Inject constructor(
notify: Boolean = false, notify: Boolean = false,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
getRefreshKey(cacheKey, semester) getRefreshKey(cacheKey, semester)
@ -42,7 +40,8 @@ class NoteRepository @Inject constructor(
}, },
query = { noteDb.loadAll(student.studentId) }, query = { noteDb.loadAll(student.studentId) },
fetch = { fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getNotes(semester.semesterId) .getNotes(semester.semesterId)
.mapToEntities(semester) .mapToEntities(semester)
}, },

View File

@ -7,23 +7,16 @@ import com.fredporciuncula.flow.preferences.FlowSharedPreferences
import com.fredporciuncula.flow.preferences.Preference import com.fredporciuncula.flow.preferences.Preference
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.data.enums.*
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
import io.github.wulkanowy.ui.modules.grade.GradeExpandMode
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode
import io.github.wulkanowy.utils.toLocalDateTime
import io.github.wulkanowy.utils.toTimestamp
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.lang.ClassCastException import java.time.Instant
import java.lang.IllegalStateException
import java.time.LocalDate
import java.time.LocalDateTime
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -74,13 +67,15 @@ class PreferencesRepository @Inject constructor(
) )
val appThemeKey = context.getString(R.string.pref_key_app_theme) val appThemeKey = context.getString(R.string.pref_key_app_theme)
val appTheme: String val appTheme: AppTheme
get() = getString(appThemeKey, R.string.pref_default_app_theme) get() = AppTheme.getByValue(getString(appThemeKey, R.string.pref_default_app_theme))
val gradeColorTheme: String val gradeColorTheme: GradeColorTheme
get() = getString( get() = GradeColorTheme.getByValue(
R.string.pref_key_grade_color_scheme, getString(
R.string.pref_default_grade_color_scheme R.string.pref_key_grade_color_scheme,
R.string.pref_default_grade_color_scheme
)
) )
val appLanguageKey = context.getString(R.string.pref_key_app_language) val appLanguageKey = context.getString(R.string.pref_key_app_language)
@ -105,7 +100,10 @@ class PreferencesRepository @Inject constructor(
val isUpcomingLessonsNotificationsEnableKey = val isUpcomingLessonsNotificationsEnableKey =
context.getString(R.string.pref_key_notifications_upcoming_lessons_enable) context.getString(R.string.pref_key_notifications_upcoming_lessons_enable)
val isUpcomingLessonsNotificationsEnable: Boolean var isUpcomingLessonsNotificationsEnable: Boolean
set(value) {
sharedPref.edit { putBoolean(isUpcomingLessonsNotificationsEnableKey, value) }
}
get() = getBoolean( get() = getBoolean(
isUpcomingLessonsNotificationsEnableKey, isUpcomingLessonsNotificationsEnableKey,
R.bool.pref_default_notification_upcoming_lessons_enable R.bool.pref_default_notification_upcoming_lessons_enable
@ -127,6 +125,12 @@ class PreferencesRepository @Inject constructor(
R.bool.pref_default_notification_piggyback R.bool.pref_default_notification_piggyback
) )
val isNotificationPiggybackRemoveOriginalEnabled: Boolean
get() = getBoolean(
R.string.pref_key_notifications_piggyback_cancel_original,
R.bool.pref_default_notification_piggyback_cancel_original
)
val isDebugNotificationEnableKey = context.getString(R.string.pref_key_notification_debug) val isDebugNotificationEnableKey = context.getString(R.string.pref_key_notification_debug)
val isDebugNotificationEnable: Boolean val isDebugNotificationEnable: Boolean
get() = getBoolean(isDebugNotificationEnableKey, R.bool.pref_default_notification_debug) get() = getBoolean(isDebugNotificationEnableKey, R.bool.pref_default_notification_debug)
@ -155,10 +159,12 @@ class PreferencesRepository @Inject constructor(
R.bool.pref_default_timetable_show_groups R.bool.pref_default_timetable_show_groups
) )
val showWholeClassPlan: String val showWholeClassPlan: TimetableMode
get() = getString( get() = TimetableMode.getByValue(
R.string.pref_key_timetable_show_whole_class, getString(
R.string.pref_default_timetable_show_whole_class R.string.pref_key_timetable_show_whole_class,
R.string.pref_default_timetable_show_whole_class
)
) )
val gradeSortingMode: GradeSortingMode val gradeSortingMode: GradeSortingMode
@ -194,10 +200,10 @@ class PreferencesRepository @Inject constructor(
R.bool.pref_default_optional_arithmetic_average R.bool.pref_default_optional_arithmetic_average
) )
var lasSyncDate: LocalDateTime var lasSyncDate: Instant?
get() = getLong(R.string.pref_key_last_sync_date, R.string.pref_default_last_sync_date) get() = getLong(R.string.pref_key_last_sync_date, R.string.pref_default_last_sync_date)
.toLocalDateTime() .takeIf { it != 0L }?.let(Instant::ofEpochMilli)
set(value) = sharedPref.edit().putLong("last_sync_date", value.toTimestamp()).apply() set(value) = sharedPref.edit().putLong("last_sync_date", value?.toEpochMilli() ?: 0).apply()
var dashboardItemsPosition: Map<DashboardItem.Type, Int>? var dashboardItemsPosition: Map<DashboardItem.Type, Int>?
get() { get() {
@ -244,15 +250,24 @@ class PreferencesRepository @Inject constructor(
return flowSharedPref.getStringSet(prefKey, defaultSet) return flowSharedPref.getStringSet(prefKey, defaultSet)
} }
var dismissedAdminMessageIds: List<Int>
get() = sharedPref.getStringSet(PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS, emptySet())
.orEmpty()
.map { it.toInt() }
set(value) = sharedPref.edit {
putStringSet(PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS, value.map { it.toString() }.toSet())
}
var inAppReviewCount: Int var inAppReviewCount: Int
get() = sharedPref.getInt(PREF_KEY_IN_APP_REVIEW_COUNT, 0) get() = sharedPref.getInt(PREF_KEY_IN_APP_REVIEW_COUNT, 0)
set(value) = sharedPref.edit().putInt(PREF_KEY_IN_APP_REVIEW_COUNT, value).apply() set(value) = sharedPref.edit().putInt(PREF_KEY_IN_APP_REVIEW_COUNT, value).apply()
var inAppReviewDate: LocalDate? var inAppReviewDate: Instant?
get() = sharedPref.getLong(PREF_KEY_IN_APP_REVIEW_DATE, 0).takeIf { it != 0L } get() = sharedPref.getLong(PREF_KEY_IN_APP_REVIEW_DATE, 0).takeIf { it != 0L }
?.toLocalDate() ?.let(Instant::ofEpochMilli)
set(value) = sharedPref.edit().putLong(PREF_KEY_IN_APP_REVIEW_DATE, value!!.toTimestamp()) set(value) = sharedPref.edit {
.apply() putLong(PREF_KEY_IN_APP_REVIEW_DATE, value?.toEpochMilli() ?: 0)
}
var isAppReviewDone: Boolean var isAppReviewDone: Boolean
get() = sharedPref.getBoolean(PREF_KEY_IN_APP_REVIEW_DONE, false) get() = sharedPref.getBoolean(PREF_KEY_IN_APP_REVIEW_DONE, false)
@ -285,5 +300,7 @@ class PreferencesRepository @Inject constructor(
private const val PREF_KEY_IN_APP_REVIEW_DATE = "in_app_review_date" private const val PREF_KEY_IN_APP_REVIEW_DATE = "in_app_review_date"
private const val PREF_KEY_IN_APP_REVIEW_DONE = "in_app_review_done" private const val PREF_KEY_IN_APP_REVIEW_DONE = "in_app_review_done"
private const val PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS = "admin_message_dismissed_ids"
} }
} }

View File

@ -4,11 +4,11 @@ import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.init
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.sync.Mutex import kotlinx.coroutines.sync.Mutex
@ -31,6 +31,7 @@ class SchoolAnnouncementRepository @Inject constructor(
forceRefresh: Boolean, notify: Boolean = false forceRefresh: Boolean, notify: Boolean = false
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
it.isEmpty() || forceRefresh || isExpired it.isEmpty() || forceRefresh || isExpired

View File

@ -4,11 +4,11 @@ import io.github.wulkanowy.data.db.dao.SchoolDao
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntity import io.github.wulkanowy.data.mappers.mapToEntity
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -30,6 +30,7 @@ class SchoolRepository @Inject constructor(
forceRefresh: Boolean, forceRefresh: Boolean,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it == null },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(cacheKey, student) key = getRefreshKey(cacheKey, student)
@ -38,7 +39,9 @@ class SchoolRepository @Inject constructor(
}, },
query = { schoolDb.load(semester.studentId, semester.classId) }, query = { schoolDb.load(semester.studentId, semester.classId) },
fetch = { fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).getSchool() sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getSchool()
.mapToEntity(semester) .mapToEntity(semester)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->

View File

@ -5,11 +5,7 @@ import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.DispatchersProvider import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.getCurrentOrLast
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.isCurrent
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -43,10 +39,14 @@ class SemesterRepository @Inject constructor(
): Boolean { ): Boolean {
val isNoSemesters = semesters.isEmpty() val isNoSemesters = semesters.isEmpty()
val isRefreshOnModeChangeRequired = val isRefreshOnModeChangeRequired = when {
if (Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) { Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API -> {
semesters.firstOrNull { it.isCurrent }?.diaryId == 0 semesters.firstOrNull { it.isCurrent }?.let {
} else false 0 == it.diaryId && 0 == it.kindergartenDiaryId
} == true
}
else -> false
}
val isRefreshOnNoCurrentAppropriate = val isRefreshOnNoCurrentAppropriate =
refreshOnNoCurrent && !semesters.any { semester -> semester.isCurrent } refreshOnNoCurrent && !semesters.any { semester -> semester.isCurrent }

View File

@ -4,9 +4,9 @@ import io.github.wulkanowy.data.db.dao.StudentInfoDao
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntity import io.github.wulkanowy.data.mappers.mapToEntity
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -25,10 +25,12 @@ class StudentInfoRepository @Inject constructor(
forceRefresh: Boolean, forceRefresh: Boolean,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it == null },
shouldFetch = { it == null || forceRefresh }, shouldFetch = { it == null || forceRefresh },
query = { studentInfoDao.loadStudentInfo(student.studentId) }, query = { studentInfoDao.loadStudentInfo(student.studentId) },
fetch = { fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getStudentInfo().mapToEntity(semester) .getStudentInfo().mapToEntity(semester)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->

View File

@ -73,6 +73,15 @@ class StudentRepository @Inject constructor(
} }
} }
suspend fun getSavedStudentById(id: Long, decryptPass: Boolean = true) =
studentDb.loadStudentWithSemestersById(id)?.apply {
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
student.password = withContext(dispatchers.io) {
decrypt(student.password)
}
}
}
suspend fun getStudentById(id: Long, decryptPass: Boolean = true): Student { suspend fun getStudentById(id: Long, decryptPass: Boolean = true): Student {
val student = studentDb.loadById(id) ?: throw NoCurrentStudentException() val student = studentDb.loadById(id) ?: throw NoCurrentStudentException()
@ -128,4 +137,7 @@ class StudentRepository @Inject constructor(
suspend fun updateStudentNickAndAvatar(studentNickAndAvatar: StudentNickAndAvatar) = suspend fun updateStudentNickAndAvatar(studentNickAndAvatar: StudentNickAndAvatar) =
studentDb.update(studentNickAndAvatar) studentDb.update(studentNickAndAvatar)
suspend fun isOneUniqueStudent() = getSavedStudents(false)
.distinctBy { it.student.studentName }.size == 1
} }

View File

@ -4,11 +4,11 @@ import io.github.wulkanowy.data.db.dao.SubjectDao
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject import javax.inject.Inject
@ -31,13 +31,15 @@ class SubjectRepository @Inject constructor(
forceRefresh: Boolean = false, forceRefresh: Boolean = false,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
it.isEmpty() || forceRefresh || isExpired it.isEmpty() || forceRefresh || isExpired
}, },
query = { subjectDao.loadAll(semester.diaryId, semester.studentId) }, query = { subjectDao.loadAll(semester.diaryId, semester.studentId) },
fetch = { fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getSubjects().mapToEntities(semester) .getSubjects().mapToEntities(semester)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->

View File

@ -4,11 +4,11 @@ import io.github.wulkanowy.data.db.dao.TeacherDao
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
import io.github.wulkanowy.utils.getRefreshKey import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import javax.inject.Inject import javax.inject.Inject
@ -31,13 +31,15 @@ class TeacherRepository @Inject constructor(
forceRefresh: Boolean, forceRefresh: Boolean,
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
it.isEmpty() || forceRefresh || isExpired it.isEmpty() || forceRefresh || isExpired
}, },
query = { teacherDb.loadAll(semester.studentId, semester.classId) }, query = { teacherDb.loadAll(semester.studentId, semester.classId) },
fetch = { fetch = {
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear) sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getTeachers(semester.semesterId) .getTeachers(semester.semesterId)
.mapToEntities(semester) .mapToEntities(semester)
}, },

View File

@ -3,22 +3,13 @@ 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.dao.TimetableHeaderDao
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.*
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.db.entities.TimetableAdditional
import io.github.wulkanowy.data.db.entities.TimetableHeader
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.data.pojos.TimetableFull 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.*
import io.github.wulkanowy.utils.getRefreshKey
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.monday
import io.github.wulkanowy.utils.networkBoundResource
import io.github.wulkanowy.utils.sunday
import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
@ -40,6 +31,10 @@ class TimetableRepository @Inject constructor(
private val cacheKey = "timetable" private val cacheKey = "timetable"
enum class TimetableType {
NORMAL, ADDITIONAL
}
fun getTimetable( fun getTimetable(
student: Student, student: Student,
semester: Semester, semester: Semester,
@ -47,9 +42,16 @@ class TimetableRepository @Inject constructor(
end: LocalDate, end: LocalDate,
forceRefresh: Boolean, forceRefresh: Boolean,
refreshAdditional: Boolean = false, refreshAdditional: Boolean = false,
notify: Boolean = false notify: Boolean = false,
timetableType: TimetableType = TimetableType.NORMAL
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = {
when (timetableType) {
TimetableType.NORMAL -> it.lessons.isEmpty()
TimetableType.ADDITIONAL -> it.additional.isEmpty()
}
},
shouldFetch = { (timetable, additional, headers) -> shouldFetch = { (timetable, additional, headers) ->
val refreshKey = getRefreshKey(cacheKey, semester, start, end) val refreshKey = getRefreshKey(cacheKey, semester, start, end)
val isExpired = refreshHelper.shouldBeRefreshed(refreshKey) val isExpired = refreshHelper.shouldBeRefreshed(refreshKey)
@ -62,7 +64,7 @@ class TimetableRepository @Inject constructor(
query = { getFullTimetableFromDatabase(student, semester, start, end) }, query = { getFullTimetableFromDatabase(student, semester, start, end) },
fetch = { fetch = {
val timetableFull = sdk.init(student) val timetableFull = sdk.init(student)
.switchDiary(semester.diaryId, semester.schoolYear) .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getTimetableFull(start.monday, end.sunday) .getTimetableFull(start.monday, end.sunday)
timetableFull.mapToEntities(semester) timetableFull.mapToEntities(semester)
@ -152,7 +154,8 @@ class TimetableRepository @Inject constructor(
old: List<TimetableAdditional>, old: List<TimetableAdditional>,
new: List<TimetableAdditional> new: List<TimetableAdditional>
) { ) {
timetableAdditionalDb.deleteAll(old uniqueSubtract new) val oldFiltered = old.filter { !it.isAddedByUser }
timetableAdditionalDb.deleteAll(oldFiltered uniqueSubtract new)
timetableAdditionalDb.insertAll(new uniqueSubtract old) timetableAdditionalDb.insertAll(new uniqueSubtract old)
} }
@ -160,4 +163,14 @@ class TimetableRepository @Inject constructor(
timetableHeaderDb.deleteAll(old uniqueSubtract new) timetableHeaderDb.deleteAll(old uniqueSubtract new)
timetableHeaderDb.insertAll(new uniqueSubtract old) timetableHeaderDb.insertAll(new uniqueSubtract old)
} }
suspend fun saveAdditionalList(additionalList: List<TimetableAdditional>) =
timetableAdditionalDb.insertAll(additionalList)
suspend fun deleteAdditional(additional: TimetableAdditional, deleteSeries: Boolean) =
if (deleteSeries) {
timetableAdditionalDb.deleteAllByRepeatId(additional.repeatId!!)
} else {
timetableAdditionalDb.deleteAll(listOf(additional))
}
} }

View File

@ -0,0 +1,32 @@
package io.github.wulkanowy.data.serializers
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.nullable
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import java.time.LocalDate
@OptIn(ExperimentalSerializationApi::class)
object LocalDateSerializer : KSerializer<LocalDate?> {
override val descriptor = PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.LONG).nullable
override fun serialize(encoder: Encoder, value: LocalDate?) {
if (value == null) {
encoder.encodeNull()
} else {
encoder.encodeNotNullMark()
encoder.encodeLong(value.toEpochDay())
}
}
override fun deserialize(decoder: Decoder): LocalDate? =
if (decoder.decodeNotNullMark()) {
LocalDate.ofEpochDay(decoder.decodeLong())
} else {
decoder.decodeNull()
}
}

View File

@ -1,9 +0,0 @@
package io.github.wulkanowy.services
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
abstract class HiltBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {}
}

View File

@ -1,6 +1,7 @@
package io.github.wulkanowy.services.alarm package io.github.wulkanowy.services.alarm
import android.app.PendingIntent import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
@ -9,26 +10,23 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.onResourceError
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.services.HiltBroadcastReceiver import io.github.wulkanowy.data.resourceFlow
import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel.Companion.CHANNEL_ID import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel.Companion.CHANNEL_ID
import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.ui.modules.splash.SplashActivity import io.github.wulkanowy.ui.modules.splash.SplashActivity
import io.github.wulkanowy.utils.PendingIntentCompat import io.github.wulkanowy.utils.PendingIntentCompat
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 kotlinx.coroutines.DelicateCoroutinesApi 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 timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class TimetableNotificationReceiver : HiltBroadcastReceiver() { class TimetableNotificationReceiver : BroadcastReceiver() {
@Inject @Inject
lateinit var studentRepository: StudentRepository lateinit var studentRepository: StudentRepository
@ -41,6 +39,8 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
const val NOTIFICATION_TYPE_UPCOMING = 2 const val NOTIFICATION_TYPE_UPCOMING = 2
const val NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION = 3 const val NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION = 3
// FIXME only shows one notification even if there are multiple students.
// Probably want to fix after #721 is merged.
const val NOTIFICATION_ID = 2137 const val NOTIFICATION_ID = 2137
const val STUDENT_NAME = "student_name" const val STUDENT_NAME = "student_name"
@ -56,20 +56,24 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
super.onReceive(context, intent)
Timber.d("Receiving intent... ${intent.toUri(0)}") Timber.d("Receiving intent... ${intent.toUri(0)}")
flowWithResource { resourceFlow {
val showStudentName = !studentRepository.isOneUniqueStudent()
val student = studentRepository.getCurrentStudent(false) val student = studentRepository.getCurrentStudent(false)
val studentId = intent.getIntExtra(STUDENT_ID, 0) val studentId = intent.getIntExtra(STUDENT_ID, 0)
if (student.studentId == studentId) prepareNotification(context, intent)
else Timber.d("Notification studentId($studentId) differs from current(${student.studentId})") if (student.studentId == studentId) {
}.onEach { prepareNotification(context, intent, showStudentName)
if (it.status == Status.ERROR) Timber.e(it.error!!) } else {
}.launchIn(GlobalScope) Timber.d("Notification studentId($studentId) differs from current(${student.studentId})")
}
}
.onResourceError { Timber.e(it) }
.launchIn(GlobalScope)
} }
private fun prepareNotification(context: Context, intent: Intent) { private fun prepareNotification(context: Context, intent: Intent, showStudentName: Boolean) {
val type = intent.getIntExtra(LESSON_TYPE, 0) val type = intent.getIntExtra(LESSON_TYPE, 0)
val isPersistent = preferencesRepository.isUpcomingLessonsNotificationsPersistent val isPersistent = preferencesRepository.isUpcomingLessonsNotificationsPersistent
@ -78,7 +82,7 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
} }
val studentId = intent.getIntExtra(STUDENT_ID, 0) val studentId = intent.getIntExtra(STUDENT_ID, 0)
val studentName = intent.getStringExtra(STUDENT_NAME) val studentName = intent.getStringExtra(STUDENT_NAME).takeIf { showStudentName }
val subject = intent.getStringExtra(LESSON_TITLE) val subject = intent.getStringExtra(LESSON_TITLE)
val room = intent.getStringExtra(LESSON_ROOM) val room = intent.getStringExtra(LESSON_ROOM)
@ -89,21 +93,28 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
val nextSubject = intent.getStringExtra(LESSON_NEXT_TITLE) val nextSubject = intent.getStringExtra(LESSON_NEXT_TITLE)
val nextRoom = intent.getStringExtra(LESSON_NEXT_ROOM) val nextRoom = intent.getStringExtra(LESSON_NEXT_ROOM)
Timber.d("TimetableNotification receive: type: $type, subject: $subject, start: ${start.toLocalDateTime()}, student: $studentId") Timber.d("TimetableNotification receive: type: $type, subject: $subject, start: $start, student: $studentId")
val notificationTitleResId =
if (type == NOTIFICATION_TYPE_CURRENT) R.string.timetable_now else R.string.timetable_next
val notificationTitle =
context.getString(notificationTitleResId, "($room) $subject".removePrefix("()"))
val nextLessonText = nextSubject?.let {
context.getString(
R.string.timetable_later,
"($nextRoom) $nextSubject".removePrefix("()")
)
}
showNotification( showNotification(
context, isPersistent, studentName, context = context,
if (type == NOTIFICATION_TYPE_CURRENT) end else start, end - start, isPersistent = isPersistent,
context.getString( studentName = studentName,
if (type == NOTIFICATION_TYPE_CURRENT) R.string.timetable_now else R.string.timetable_next, countDown = if (type == NOTIFICATION_TYPE_CURRENT) end else start,
"($room) $subject".removePrefix("()") timeout = end - start,
), title = notificationTitle,
nextSubject?.let { next = nextLessonText
context.getString(
R.string.timetable_later,
"($nextRoom) $nextSubject".removePrefix("()")
)
}
) )
} }
@ -130,10 +141,11 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
.setTimeoutAfter(timeout) .setTimeoutAfter(timeout)
.setSmallIcon(R.drawable.ic_stat_timetable) .setSmallIcon(R.drawable.ic_stat_timetable)
.setColor(context.getCompatColor(R.color.colorPrimary)) .setColor(context.getCompatColor(R.color.colorPrimary))
.setStyle(NotificationCompat.InboxStyle().also { .setStyle(NotificationCompat.InboxStyle()
it.setSummaryText(studentName) .addLine(next)
it.addLine(next) .also { inboxStyle ->
}) studentName?.let { inboxStyle.setSummaryText(it) }
})
.setContentIntent( .setContentIntent(
PendingIntent.getActivity( PendingIntent.getActivity(
context, context,

View File

@ -5,6 +5,7 @@ import android.app.AlarmManager.RTC_WAKEUP
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build
import androidx.core.app.AlarmManagerCompat import androidx.core.app.AlarmManagerCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
@ -27,12 +28,12 @@ import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companio
import io.github.wulkanowy.utils.DispatchersProvider import io.github.wulkanowy.utils.DispatchersProvider
import io.github.wulkanowy.utils.PendingIntentCompat import io.github.wulkanowy.utils.PendingIntentCompat
import io.github.wulkanowy.utils.nickOrName import io.github.wulkanowy.utils.nickOrName
import io.github.wulkanowy.utils.toTimestamp
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
import java.time.Duration.ofMinutes
import java.time.Instant
import java.time.Instant.now
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalDateTime.now
import javax.inject.Inject import javax.inject.Inject
class TimetableNotificationSchedulerHelper @Inject constructor( class TimetableNotificationSchedulerHelper @Inject constructor(
@ -42,14 +43,14 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
private val dispatchersProvider: DispatchersProvider, private val dispatchersProvider: DispatchersProvider,
) { ) {
private fun getRequestCode(time: LocalDateTime, studentId: Int) = private fun getRequestCode(time: Instant, studentId: Int): Int =
(time.toTimestamp() * studentId).toInt() (time.toEpochMilli() * studentId).toInt()
private fun getUpcomingLessonTime( private fun getUpcomingLessonTime(
index: Int, index: Int,
day: List<Timetable>, day: List<Timetable>,
lesson: Timetable lesson: Timetable
) = day.getOrNull(index - 1)?.end ?: lesson.start.minusMinutes(30) ): Instant = day.getOrNull(index - 1)?.end ?: lesson.start.minus(ofMinutes(30))
suspend fun cancelScheduled(lessons: List<Timetable>, student: Student) { suspend fun cancelScheduled(lessons: List<Timetable>, student: Student) {
val studentId = student.studentId val studentId = student.studentId
@ -70,7 +71,7 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
} }
} }
private fun cancelScheduledTo(range: ClosedRange<LocalDateTime>, requestCode: Int) { private fun cancelScheduledTo(range: ClosedRange<Instant>, requestCode: Int) {
if (now() in range) cancelNotification() if (now() in range) cancelNotification()
alarmManager.cancel( alarmManager.cancel(
@ -91,6 +92,12 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
return cancelScheduled(lessons, student) return cancelScheduled(lessons, student)
} }
if (!canScheduleExactAlarms()) {
Timber.w("Exact alarms are disabled by user")
preferencesRepository.isUpcomingLessonsNotificationsEnable = false
return
}
if (lessons.firstOrNull()?.date?.isAfter(LocalDate.now().plusDays(2)) == true) { if (lessons.firstOrNull()?.date?.isAfter(LocalDate.now().plusDays(2)) == true) {
Timber.d("Timetable notification scheduling skipped - lessons are too far") Timber.d("Timetable notification scheduling skipped - lessons are too far")
return return
@ -143,8 +150,8 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
putExtra(STUDENT_ID, student.studentId) putExtra(STUDENT_ID, student.studentId)
putExtra(STUDENT_NAME, student.nickOrName) putExtra(STUDENT_NAME, student.nickOrName)
putExtra(LESSON_ROOM, lesson.room) putExtra(LESSON_ROOM, lesson.room)
putExtra(LESSON_START, lesson.start.toTimestamp()) putExtra(LESSON_START, lesson.start.toEpochMilli())
putExtra(LESSON_END, lesson.end.toTimestamp()) putExtra(LESSON_END, lesson.end.toEpochMilli())
putExtra(LESSON_TITLE, lesson.subject) putExtra(LESSON_TITLE, lesson.subject)
putExtra(LESSON_NEXT_TITLE, nextLesson?.subject) putExtra(LESSON_NEXT_TITLE, nextLesson?.subject)
putExtra(LESSON_NEXT_ROOM, nextLesson?.room) putExtra(LESSON_NEXT_ROOM, nextLesson?.room)
@ -155,11 +162,11 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
intent: Intent, intent: Intent,
studentId: Int, studentId: Int,
notificationType: Int, notificationType: Int,
time: LocalDateTime time: Instant
) { ) {
try { try {
AlarmManagerCompat.setExactAndAllowWhileIdle( AlarmManagerCompat.setExactAndAllowWhileIdle(
alarmManager, RTC_WAKEUP, time.toTimestamp(), alarmManager, RTC_WAKEUP, time.toEpochMilli(),
PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also { PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also {
it.putExtra(LESSON_TYPE, notificationType) it.putExtra(LESSON_TYPE, notificationType)
}, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE) }, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE)
@ -169,8 +176,18 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
intent.getStringExtra(LESSON_TITLE) intent.getStringExtra(LESSON_TITLE)
}, start: $time, student: $studentId" }, start: $time, student: $studentId"
) )
} catch (e: IllegalStateException) { } catch (e: Throwable) {
Timber.e(e) Timber.e(e)
} }
} }
fun canScheduleExactAlarms(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
try {
alarmManager.canScheduleExactAlarms()
} catch (e: Throwable) {
false
}
} else true
}
} }

View File

@ -19,6 +19,9 @@ class VulcanNotificationListenerService : NotificationListenerService() {
override fun onNotificationPosted(statusBarNotification: StatusBarNotification?) { override fun onNotificationPosted(statusBarNotification: StatusBarNotification?) {
if (statusBarNotification?.packageName == "pl.edu.vulcan.hebe" && preferenceRepository.isNotificationPiggybackEnabled) { if (statusBarNotification?.packageName == "pl.edu.vulcan.hebe" && preferenceRepository.isNotificationPiggybackEnabled) {
syncManager.startOneTimeSyncWorker() syncManager.startOneTimeSyncWorker()
if (preferenceRepository.isNotificationPiggybackRemoveOriginalEnabled) {
cancelNotification(statusBarNotification.key)
}
} }
} }
} }

View File

@ -15,6 +15,9 @@ import javax.inject.Singleton
@Singleton @Singleton
class ShortcutsHelper @Inject constructor(@ApplicationContext private val context: Context) { class ShortcutsHelper @Inject constructor(@ApplicationContext private val context: Context) {
// Destination cannot be used here as shortcuts
// require their intents to only use primitive types (see PersistableBundle.isValidType).
private val destinations = mapOf( private val destinations = mapOf(
"grade" to Destination.Grade, "grade" to Destination.Grade,
"attendance" to Destination.Attendance, "attendance" to Destination.Attendance,

View File

@ -74,10 +74,12 @@ class SyncManager @Inject constructor(
} }
} }
fun startOneTimeSyncWorker(): Flow<WorkInfo?> { // if quiet, no notifications will be sent
fun startOneTimeSyncWorker(quiet: Boolean = false): Flow<WorkInfo?> {
val work = OneTimeWorkRequestBuilder<SyncWorker>() val work = OneTimeWorkRequestBuilder<SyncWorker>()
.setInputData( .setInputData(
Data.Builder() Data.Builder()
.putBoolean("quiet", quiet)
.putBoolean("one_time", true) .putBoolean("one_time", true)
.build() .build()
) )

View File

@ -23,7 +23,7 @@ import io.github.wulkanowy.utils.DispatchersProvider
import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.getCompatColor
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
import java.time.LocalDateTime import java.time.Instant
import kotlin.random.Random import kotlin.random.Random
@HiltWorker @HiltWorker
@ -38,18 +38,23 @@ class SyncWorker @AssistedInject constructor(
private val dispatchersProvider: DispatchersProvider private val dispatchersProvider: DispatchersProvider
) : CoroutineWorker(appContext, workerParameters) { ) : CoroutineWorker(appContext, workerParameters) {
override suspend fun doWork() = withContext(dispatchersProvider.io) { override suspend fun doWork(): Result = withContext(dispatchersProvider.io) {
Timber.i("SyncWorker is starting") Timber.i("SyncWorker is starting")
if (!studentRepository.isCurrentStudentSet()) return@withContext Result.failure() if (!studentRepository.isCurrentStudentSet()) return@withContext Result.failure()
val student = studentRepository.getCurrentStudent() val (student, semester) = try {
val semester = semesterRepository.getCurrentSemester(student, true) val student = studentRepository.getCurrentStudent()
val semester = semesterRepository.getCurrentSemester(student, true)
student to semester
} catch (e: Throwable) {
return@withContext getResultFromErrors(listOf(e))
}
val exceptions = works.mapNotNull { work -> val exceptions = works.mapNotNull { work ->
try { try {
Timber.i("${work::class.java.simpleName} is starting") Timber.i("${work::class.java.simpleName} is starting")
work.doWork(student, semester) work.doWork(student, semester, isNotificationsEnabled())
Timber.i("${work::class.java.simpleName} result: Success") Timber.i("${work::class.java.simpleName} result: Success")
null null
} catch (e: Throwable) { } catch (e: Throwable) {
@ -62,20 +67,7 @@ class SyncWorker @AssistedInject constructor(
} }
} }
} }
val result = when { val result = getResultFromErrors(exceptions)
exceptions.isNotEmpty() && inputData.getBoolean("one_time", false) -> {
Result.failure(
Data.Builder()
.putString("error", exceptions.map { it.stackTraceToString() }.toString())
.build()
)
}
exceptions.isNotEmpty() -> Result.retry()
else -> {
preferencesRepository.lasSyncDate = LocalDateTime.now()
Result.success()
}
}
if (preferencesRepository.isDebugNotificationEnable) notify(result) if (preferencesRepository.isDebugNotificationEnable) notify(result)
Timber.i("SyncWorker result: $result") Timber.i("SyncWorker result: $result")
@ -83,6 +75,27 @@ class SyncWorker @AssistedInject constructor(
return@withContext result return@withContext result
} }
private fun isNotificationsEnabled(): Boolean {
val quiet = inputData.getBoolean("quiet", false)
return preferencesRepository.isNotificationsEnable && !quiet
}
private fun getResultFromErrors(errors: List<Throwable>): Result = when {
errors.isNotEmpty() && inputData.getBoolean("one_time", false) -> {
Result.failure(
Data.Builder()
.putString("error_message", errors.joinToString { it.message.toString() })
.putString("error_stack", errors.map { it.stackTraceToString() }.toString())
.build()
)
}
errors.isNotEmpty() -> Result.retry()
else -> {
preferencesRepository.lasSyncDate = Instant.now()
Result.success()
}
}
private fun notify(result: Result) { private fun notify(result: Result) {
notificationManager.notify( notificationManager.notify(
Random.nextInt(Int.MAX_VALUE), Random.nextInt(Int.MAX_VALUE),

View File

@ -14,11 +14,12 @@ import io.github.wulkanowy.data.pojos.GroupNotificationData
import io.github.wulkanowy.data.pojos.NotificationData import io.github.wulkanowy.data.pojos.NotificationData
import io.github.wulkanowy.data.repositories.NotificationRepository import io.github.wulkanowy.data.repositories.NotificationRepository
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.modules.splash.SplashActivity
import io.github.wulkanowy.utils.PendingIntentCompat import io.github.wulkanowy.utils.PendingIntentCompat
import io.github.wulkanowy.utils.getCompatBitmap import io.github.wulkanowy.utils.getCompatBitmap
import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.getCompatColor
import io.github.wulkanowy.utils.nickOrName import io.github.wulkanowy.utils.nickOrName
import java.time.LocalDateTime import java.time.Instant
import javax.inject.Inject import javax.inject.Inject
import kotlin.random.Random import kotlin.random.Random
@ -47,7 +48,7 @@ class AppNotificationManager @Inject constructor(
PendingIntent.getActivity( PendingIntent.getActivity(
context, context,
Random.nextInt(), Random.nextInt(),
notificationData.intentToStart, SplashActivity.getStartIntent(context, notificationData.destination),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
) )
) )
@ -57,7 +58,7 @@ class AppNotificationManager @Inject constructor(
NotificationCompat.BigTextStyle() NotificationCompat.BigTextStyle()
.bigText(notificationData.content) .bigText(notificationData.content)
.also { builder -> .also { builder ->
if (shouldShowStudentName()) { if (!studentRepository.isOneUniqueStudent()) {
builder.setSummaryText(student.nickOrName) builder.setSummaryText(student.nickOrName)
} }
} }
@ -74,7 +75,7 @@ class AppNotificationManager @Inject constructor(
student: Student student: Student
) { ) {
val notificationType = groupNotificationData.type val notificationType = groupNotificationData.type
val groupType = notificationType.group ?: return val groupType = notificationType.channel
val group = "${groupType}_${student.id}" val group = "${groupType}_${student.id}"
sendSummaryNotification(groupNotificationData, group, student) sendSummaryNotification(groupNotificationData, group, student)
@ -92,7 +93,7 @@ class AppNotificationManager @Inject constructor(
PendingIntent.getActivity( PendingIntent.getActivity(
context, context,
Random.nextInt(), Random.nextInt(),
notificationData.intentToStart, SplashActivity.getStartIntent(context, notificationData.destination),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
) )
) )
@ -102,7 +103,7 @@ class AppNotificationManager @Inject constructor(
NotificationCompat.BigTextStyle() NotificationCompat.BigTextStyle()
.bigText(notificationData.content) .bigText(notificationData.content)
.also { builder -> .also { builder ->
if (shouldShowStudentName()) { if (!studentRepository.isOneUniqueStudent()) {
builder.setSummaryText(student.nickOrName) builder.setSummaryText(student.nickOrName)
} }
} }
@ -134,7 +135,7 @@ class AppNotificationManager @Inject constructor(
.setStyle( .setStyle(
NotificationCompat.InboxStyle() NotificationCompat.InboxStyle()
.also { builder -> .also { builder ->
if (shouldShowStudentName()) { if (!studentRepository.isOneUniqueStudent()) {
builder.setSummaryText(student.nickOrName) builder.setSummaryText(student.nickOrName)
} }
groupNotificationData.notificationDataList.forEach { groupNotificationData.notificationDataList.forEach {
@ -146,7 +147,7 @@ class AppNotificationManager @Inject constructor(
PendingIntent.getActivity( PendingIntent.getActivity(
context, context,
Random.nextInt(), Random.nextInt(),
groupNotificationData.intentToStart, SplashActivity.getStartIntent(context, groupNotificationData.destination),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
) )
) )
@ -168,13 +169,11 @@ class AppNotificationManager @Inject constructor(
studentId = student.id, studentId = student.id,
title = notificationData.title, title = notificationData.title,
content = notificationData.content, content = notificationData.content,
destination = notificationData.destination,
type = notificationType, type = notificationType,
date = LocalDateTime.now() date = Instant.now(),
) )
notificationRepository.saveNotification(notificationEntity) notificationRepository.saveNotification(notificationEntity)
} }
private suspend fun shouldShowStudentName(): Boolean =
studentRepository.getSavedStudents(decryptPass = false).size > 1
} }

View File

@ -8,11 +8,10 @@ import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.pojos.GroupNotificationData import io.github.wulkanowy.data.pojos.GroupNotificationData
import io.github.wulkanowy.data.pojos.NotificationData import io.github.wulkanowy.data.pojos.NotificationData
import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.ui.modules.splash.SplashActivity
import io.github.wulkanowy.utils.getPlural import io.github.wulkanowy.utils.getPlural
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
import java.time.Instant
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalDateTime
import javax.inject.Inject import javax.inject.Inject
class ChangeTimetableNotification @Inject constructor( class ChangeTimetableNotification @Inject constructor(
@ -21,10 +20,11 @@ class ChangeTimetableNotification @Inject constructor(
) { ) {
suspend fun notify(items: List<Timetable>, student: Student) { suspend fun notify(items: List<Timetable>, student: Student) {
val currentTime = LocalDateTime.now() val currentTime = Instant.now()
val changedLessons = items.filter { (it.canceled || it.changes) && it.start > currentTime } val changedLessons = items.filter { (it.canceled || it.changes) && it.start > currentTime }
val notificationDataList = changedLessons.groupBy { it.date } val lessonsByDate = changedLessons.groupBy { it.date }
.map { (date, lessons) -> val notificationDataList = lessonsByDate
.flatMap { (date, lessons) ->
getNotificationContents(date, lessons).map { getNotificationContents(date, lessons).map {
NotificationData( NotificationData(
title = context.getPlural( title = context.getPlural(
@ -32,14 +32,10 @@ class ChangeTimetableNotification @Inject constructor(
1 1
), ),
content = it, content = it,
intentToStart = SplashActivity.getStartIntent( destination = Destination.Timetable(date)
context = context,
destination = Destination.Timetable(date)
)
) )
} }
} }
.flatten()
.ifEmpty { return } .ifEmpty { return }
val groupNotificationData = GroupNotificationData( val groupNotificationData = GroupNotificationData(
@ -53,7 +49,7 @@ class ChangeTimetableNotification @Inject constructor(
changedLessons.size, changedLessons.size,
changedLessons.size changedLessons.size
), ),
intentToStart = SplashActivity.getStartIntent(context, Destination.Timetable()), destination = Destination.Timetable(lessonsByDate.toSortedMap().firstKey()),
type = NotificationType.CHANGE_TIMETABLE type = NotificationType.CHANGE_TIMETABLE
) )

View File

@ -31,7 +31,7 @@ class NewAttendanceNotification @Inject constructor(
NotificationData( NotificationData(
title = context.getPlural(R.plurals.attendance_notify_new_items_title, 1), title = context.getPlural(R.plurals.attendance_notify_new_items_title, 1),
content = it, content = it,
intentToStart = SplashActivity.getStartIntent(context, Destination.Attendance) destination = Destination.Attendance
) )
} }
@ -46,7 +46,7 @@ class NewAttendanceNotification @Inject constructor(
notificationDataList.size, notificationDataList.size,
notificationDataList.size notificationDataList.size
), ),
intentToStart = SplashActivity.getStartIntent(context, Destination.Attendance), destination = Destination.Attendance,
type = NotificationType.NEW_ATTENDANCE type = NotificationType.NEW_ATTENDANCE
) )

View File

@ -11,7 +11,7 @@ import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.ui.modules.splash.SplashActivity import io.github.wulkanowy.ui.modules.splash.SplashActivity
import io.github.wulkanowy.utils.getPlural import io.github.wulkanowy.utils.getPlural
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
import java.time.LocalDateTime import java.time.Instant
import javax.inject.Inject import javax.inject.Inject
class NewConferenceNotification @Inject constructor( class NewConferenceNotification @Inject constructor(
@ -20,7 +20,7 @@ class NewConferenceNotification @Inject constructor(
) { ) {
suspend fun notify(items: List<Conference>, student: Student) { suspend fun notify(items: List<Conference>, student: Student) {
val today = LocalDateTime.now() val today = Instant.now()
val lines = items.filter { !it.date.isBefore(today) } val lines = items.filter { !it.date.isBefore(today) }
.map { .map {
"${it.date.toFormattedString("dd.MM")} - ${it.title}: ${it.subject}" "${it.date.toFormattedString("dd.MM")} - ${it.title}: ${it.subject}"
@ -31,7 +31,7 @@ class NewConferenceNotification @Inject constructor(
NotificationData( NotificationData(
title = context.getPlural(R.plurals.conference_notify_new_item_title, 1), title = context.getPlural(R.plurals.conference_notify_new_item_title, 1),
content = it, content = it,
intentToStart = SplashActivity.getStartIntent(context, Destination.Conference) destination = Destination.Conference
) )
} }
@ -43,7 +43,7 @@ class NewConferenceNotification @Inject constructor(
lines.size, lines.size,
lines.size lines.size
), ),
intentToStart = SplashActivity.getStartIntent(context, Destination.Conference), destination = Destination.Conference,
type = NotificationType.NEW_CONFERENCE type = NotificationType.NEW_CONFERENCE
) )

View File

@ -31,7 +31,7 @@ class NewExamNotification @Inject constructor(
NotificationData( NotificationData(
title = context.getPlural(R.plurals.exam_notify_new_item_title, 1), title = context.getPlural(R.plurals.exam_notify_new_item_title, 1),
content = it, content = it,
intentToStart = SplashActivity.getStartIntent(context, Destination.Exam), destination = Destination.Exam,
) )
} }
@ -43,7 +43,7 @@ class NewExamNotification @Inject constructor(
lines.size, lines.size,
lines.size lines.size
), ),
intentToStart = SplashActivity.getStartIntent(context, Destination.Exam), destination = Destination.Exam,
type = NotificationType.NEW_EXAM type = NotificationType.NEW_EXAM
) )

View File

@ -22,8 +22,11 @@ class NewGradeNotification @Inject constructor(
val notificationDataList = items.map { val notificationDataList = items.map {
NotificationData( NotificationData(
title = context.getPlural(R.plurals.grade_new_items, 1), title = context.getPlural(R.plurals.grade_new_items, 1),
content = "${it.subject}: ${it.entry}", content = buildString {
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade), append("${it.subject}: ${it.entry}")
if (it.comment.isNotBlank()) append(" (${it.comment})")
},
destination = Destination.Grade,
) )
} }
@ -31,7 +34,7 @@ class NewGradeNotification @Inject constructor(
notificationDataList = notificationDataList, notificationDataList = notificationDataList,
title = context.getPlural(R.plurals.grade_new_items, items.size), title = context.getPlural(R.plurals.grade_new_items, items.size),
content = context.getPlural(R.plurals.grade_notify_new_items, items.size, items.size), content = context.getPlural(R.plurals.grade_notify_new_items, items.size, items.size),
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade), destination = Destination.Grade,
type = NotificationType.NEW_GRADE_DETAILS type = NotificationType.NEW_GRADE_DETAILS
) )
@ -43,7 +46,7 @@ class NewGradeNotification @Inject constructor(
NotificationData( NotificationData(
title = context.getPlural(R.plurals.grade_new_items_predicted, 1), title = context.getPlural(R.plurals.grade_new_items_predicted, 1),
content = "${it.subject}: ${it.predictedGrade}", content = "${it.subject}: ${it.predictedGrade}",
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade), destination = Destination.Grade,
) )
} }
@ -55,7 +58,7 @@ class NewGradeNotification @Inject constructor(
items.size, items.size,
items.size items.size
), ),
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade), destination = Destination.Grade,
type = NotificationType.NEW_GRADE_PREDICTED type = NotificationType.NEW_GRADE_PREDICTED
) )
@ -67,7 +70,7 @@ class NewGradeNotification @Inject constructor(
NotificationData( NotificationData(
title = context.getPlural(R.plurals.grade_new_items_final, 1), title = context.getPlural(R.plurals.grade_new_items_final, 1),
content = "${it.subject}: ${it.finalGrade}", content = "${it.subject}: ${it.finalGrade}",
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade), destination = Destination.Grade,
) )
} }
@ -79,7 +82,7 @@ class NewGradeNotification @Inject constructor(
items.size, items.size,
items.size items.size
), ),
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade), destination = Destination.Grade,
type = NotificationType.NEW_GRADE_FINAL type = NotificationType.NEW_GRADE_FINAL
) )

View File

@ -31,7 +31,7 @@ class NewHomeworkNotification @Inject constructor(
NotificationData( NotificationData(
title = context.getPlural(R.plurals.homework_notify_new_item_title, 1), title = context.getPlural(R.plurals.homework_notify_new_item_title, 1),
content = it, content = it,
intentToStart = SplashActivity.getStartIntent(context, Destination.Homework), destination = Destination.Homework,
) )
} }
@ -42,7 +42,7 @@ class NewHomeworkNotification @Inject constructor(
lines.size, lines.size,
lines.size lines.size
), ),
intentToStart = SplashActivity.getStartIntent(context, Destination.Homework), destination = Destination.Homework,
type = NotificationType.NEW_HOMEWORK, type = NotificationType.NEW_HOMEWORK,
notificationDataList = notificationDataList notificationDataList = notificationDataList
) )

View File

@ -22,7 +22,7 @@ class NewLuckyNumberNotification @Inject constructor(
R.string.lucky_number_notify_new_item, R.string.lucky_number_notify_new_item,
item.luckyNumber.toString() item.luckyNumber.toString()
), ),
intentToStart = SplashActivity.getStartIntent(context, Destination.LuckyNumber) destination = Destination.LuckyNumber
) )
appNotificationManager.sendSingleNotification( appNotificationManager.sendSingleNotification(

View File

@ -22,7 +22,7 @@ class NewMessageNotification @Inject constructor(
NotificationData( NotificationData(
title = context.getPlural(R.plurals.message_new_items, 1), title = context.getPlural(R.plurals.message_new_items, 1),
content = "${it.sender}: ${it.subject}", content = "${it.sender}: ${it.subject}",
intentToStart = SplashActivity.getStartIntent(context, Destination.Message), destination = Destination.Message,
) )
} }
@ -30,7 +30,7 @@ class NewMessageNotification @Inject constructor(
notificationDataList = notificationDataList, notificationDataList = notificationDataList,
title = context.getPlural(R.plurals.message_new_items, items.size), title = context.getPlural(R.plurals.message_new_items, items.size),
content = context.getPlural(R.plurals.message_notify_new_items, items.size, items.size), content = context.getPlural(R.plurals.message_notify_new_items, items.size, items.size),
intentToStart = SplashActivity.getStartIntent(context, Destination.Message), destination = Destination.Message,
type = NotificationType.NEW_MESSAGE type = NotificationType.NEW_MESSAGE
) )

View File

@ -29,13 +29,13 @@ class NewNoteNotification @Inject constructor(
NotificationData( NotificationData(
title = context.getPlural(titleRes, 1), title = context.getPlural(titleRes, 1),
content = "${it.teacher}: ${it.category}", content = "${it.teacher}: ${it.category}",
intentToStart = SplashActivity.getStartIntent(context, Destination.Note), destination = Destination.Note,
) )
} }
val groupNotificationData = GroupNotificationData( val groupNotificationData = GroupNotificationData(
notificationDataList = notificationDataList, notificationDataList = notificationDataList,
intentToStart = SplashActivity.getStartIntent(context, Destination.Note), destination = Destination.Note,
title = context.getPlural(R.plurals.note_new_items, items.size), title = context.getPlural(R.plurals.note_new_items, items.size),
content = context.getPlural(R.plurals.note_notify_new_items, items.size, items.size), content = context.getPlural(R.plurals.note_notify_new_items, items.size, items.size),
type = NotificationType.NEW_NOTE type = NotificationType.NEW_NOTE

View File

@ -1,6 +1,7 @@
package io.github.wulkanowy.services.sync.notifications package io.github.wulkanowy.services.sync.notifications
import android.content.Context import android.content.Context
import androidx.core.text.parseAsHtml
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
@ -20,23 +21,17 @@ class NewSchoolAnnouncementNotification @Inject constructor(
suspend fun notify(items: List<SchoolAnnouncement>, student: Student) { suspend fun notify(items: List<SchoolAnnouncement>, student: Student) {
val notificationDataList = items.map { val notificationDataList = items.map {
NotificationData( NotificationData(
intentToStart = SplashActivity.getStartIntent( destination = Destination.SchoolAnnouncement,
context = context,
destination = Destination.SchoolAnnouncement
),
title = context.getPlural( title = context.getPlural(
R.plurals.school_announcement_notify_new_item_title, R.plurals.school_announcement_notify_new_item_title,
1 1
), ),
content = "${it.subject}: ${it.content}" content = "${it.subject}: ${it.content.parseAsHtml()}"
) )
} }
val groupNotificationData = GroupNotificationData( val groupNotificationData = GroupNotificationData(
type = NotificationType.NEW_ANNOUNCEMENT, type = NotificationType.NEW_ANNOUNCEMENT,
intentToStart = SplashActivity.getStartIntent( destination = Destination.SchoolAnnouncement,
context = context,
destination = Destination.SchoolAnnouncement
),
title = context.getPlural( title = context.getPlural(
R.plurals.school_announcement_notify_new_item_title, R.plurals.school_announcement_notify_new_item_title,
items.size items.size

View File

@ -14,72 +14,58 @@ import io.github.wulkanowy.services.sync.channels.PushChannel
import io.github.wulkanowy.services.sync.channels.TimetableChangeChannel import io.github.wulkanowy.services.sync.channels.TimetableChangeChannel
enum class NotificationType( enum class NotificationType(
val group: String?,
val channel: String, val channel: String,
val icon: Int val icon: Int
) { ) {
NEW_CONFERENCE( NEW_CONFERENCE(
group = "new_conferences_group",
channel = NewConferencesChannel.CHANNEL_ID, channel = NewConferencesChannel.CHANNEL_ID,
icon = R.drawable.ic_more_conferences, icon = R.drawable.ic_more_conferences,
), ),
NEW_EXAM( NEW_EXAM(
group = "new_exam_group",
channel = NewExamChannel.CHANNEL_ID, channel = NewExamChannel.CHANNEL_ID,
icon = R.drawable.ic_main_exam icon = R.drawable.ic_main_exam
), ),
NEW_GRADE_DETAILS( NEW_GRADE_DETAILS(
group = "new_grade_details_group",
channel = NewGradesChannel.CHANNEL_ID, channel = NewGradesChannel.CHANNEL_ID,
icon = R.drawable.ic_stat_grade, icon = R.drawable.ic_stat_grade,
), ),
NEW_GRADE_PREDICTED( NEW_GRADE_PREDICTED(
group = "new_grade_predicted_group",
channel = NewGradesChannel.CHANNEL_ID, channel = NewGradesChannel.CHANNEL_ID,
icon = R.drawable.ic_stat_grade, icon = R.drawable.ic_stat_grade,
), ),
NEW_GRADE_FINAL( NEW_GRADE_FINAL(
group = "new_grade_final_group",
channel = NewGradesChannel.CHANNEL_ID, channel = NewGradesChannel.CHANNEL_ID,
icon = R.drawable.ic_stat_grade, icon = R.drawable.ic_stat_grade,
), ),
NEW_HOMEWORK( NEW_HOMEWORK(
group = "new_homework_group",
channel = NewHomeworkChannel.CHANNEL_ID, channel = NewHomeworkChannel.CHANNEL_ID,
icon = R.drawable.ic_more_homework, icon = R.drawable.ic_more_homework,
), ),
NEW_LUCKY_NUMBER( NEW_LUCKY_NUMBER(
group = null,
channel = LuckyNumberChannel.CHANNEL_ID, channel = LuckyNumberChannel.CHANNEL_ID,
icon = R.drawable.ic_stat_luckynumber, icon = R.drawable.ic_stat_luckynumber,
), ),
NEW_MESSAGE( NEW_MESSAGE(
group = "new_message_group",
channel = NewMessagesChannel.CHANNEL_ID, channel = NewMessagesChannel.CHANNEL_ID,
icon = R.drawable.ic_stat_message, icon = R.drawable.ic_stat_message,
), ),
NEW_NOTE( NEW_NOTE(
group = "new_notes_group",
channel = NewNotesChannel.CHANNEL_ID, channel = NewNotesChannel.CHANNEL_ID,
icon = R.drawable.ic_stat_note icon = R.drawable.ic_stat_note
), ),
NEW_ANNOUNCEMENT( NEW_ANNOUNCEMENT(
group = "new_school_announcements_group",
channel = NewSchoolAnnouncementsChannel.CHANNEL_ID, channel = NewSchoolAnnouncementsChannel.CHANNEL_ID,
icon = R.drawable.ic_all_about icon = R.drawable.ic_all_about
), ),
CHANGE_TIMETABLE( CHANGE_TIMETABLE(
group = "change_timetable_group",
channel = TimetableChangeChannel.CHANNEL_ID, channel = TimetableChangeChannel.CHANNEL_ID,
icon = R.drawable.ic_main_timetable icon = R.drawable.ic_main_timetable
), ),
NEW_ATTENDANCE( NEW_ATTENDANCE(
group = "new_attendance_group",
channel = NewAttendanceChannel.CHANNEL_ID, channel = NewAttendanceChannel.CHANNEL_ID,
icon = R.drawable.ic_main_attendance icon = R.drawable.ic_main_attendance
), ),
PUSH( PUSH(
group = null,
channel = PushChannel.CHANNEL_ID, channel = PushChannel.CHANNEL_ID,
icon = R.drawable.ic_stat_all icon = R.drawable.ic_stat_all
) )

View File

@ -3,14 +3,19 @@ package io.github.wulkanowy.services.sync.works
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.repositories.AttendanceSummaryRepository import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository
import io.github.wulkanowy.utils.waitForResult import io.github.wulkanowy.data.waitForResult
import javax.inject.Inject import javax.inject.Inject
class AttendanceSummaryWork @Inject constructor( class AttendanceSummaryWork @Inject constructor(
private val attendanceSummaryRepository: AttendanceSummaryRepository private val attendanceSummaryRepository: AttendanceSummaryRepository
) : Work { ) : Work {
override suspend fun doWork(student: Student, semester: Semester) { override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
attendanceSummaryRepository.getAttendanceSummary(student, semester, -1, true).waitForResult() attendanceSummaryRepository.getAttendanceSummary(
student = student,
semester = semester,
subjectId = -1,
forceRefresh = true,
).waitForResult()
} }
} }

View File

@ -3,10 +3,9 @@ package io.github.wulkanowy.services.sync.works
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.repositories.AttendanceRepository import io.github.wulkanowy.data.repositories.AttendanceRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.waitForResult
import io.github.wulkanowy.services.sync.notifications.NewAttendanceNotification import io.github.wulkanowy.services.sync.notifications.NewAttendanceNotification
import io.github.wulkanowy.utils.previousOrSameSchoolDay import io.github.wulkanowy.utils.previousOrSameSchoolDay
import io.github.wulkanowy.utils.waitForResult
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import java.time.LocalDate.now import java.time.LocalDate.now
import javax.inject.Inject import javax.inject.Inject
@ -14,17 +13,16 @@ import javax.inject.Inject
class AttendanceWork @Inject constructor( class AttendanceWork @Inject constructor(
private val attendanceRepository: AttendanceRepository, private val attendanceRepository: AttendanceRepository,
private val newAttendanceNotification: NewAttendanceNotification, private val newAttendanceNotification: NewAttendanceNotification,
private val preferencesRepository: PreferencesRepository
) : Work { ) : Work {
override suspend fun doWork(student: Student, semester: Semester) { override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
attendanceRepository.getAttendance( attendanceRepository.getAttendance(
student = student, student = student,
semester = semester, semester = semester,
start = now().previousOrSameSchoolDay, start = now().previousOrSameSchoolDay,
end = now().previousOrSameSchoolDay, end = now().previousOrSameSchoolDay,
forceRefresh = true, forceRefresh = true,
notify = preferencesRepository.isNotificationsEnable notify = notify,
) )
.waitForResult() .waitForResult()

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