1
0
Fork 1

Compare commits

..

184 commits
1.6.3 ... 1.8.0

Author SHA1 Message Date
Mikołaj Pich
4bce35f810 Merge branch 'release/1.8.0' 2022-11-16 20:31:09 +01:00
Mikołaj Pich
2d83218f61 Version 1.8.0 2022-11-16 20:31:02 +01:00
Rafał Borcz
d3e276d6fc
New Crowdin updates (#2049)
Co-authored-by: Mikołaj Pich <m.pich@outlook.com>
2022-11-16 20:13:48 +01:00
Mikołaj Pich
51a1097bb4
Add mailbox chooser to messages (#2002) 2022-11-16 13:46:47 +01:00
Mikołaj Pich
db4f172fb8
Fix unread status in sent messages (#2048) 2022-11-16 12:54:55 +01:00
dependabot[bot]
4d49e956b8
Bump junit from 1.1.3 to 1.1.4 (#2043) 2022-11-14 20:48:12 +00:00
dependabot[bot]
b8296ac02f
Bump kotlin_version from 1.7.20 to 1.7.21 (#2042) 2022-11-14 20:31:26 +00:00
dependabot[bot]
d6385e8cdd
Bump core from 1.4.0 to 1.5.0 (#2045) 2022-11-14 20:31:02 +00:00
dependabot[bot]
885319a885
Bump hilt_version from 2.44 to 2.44.1 (#2044) 2022-11-14 18:36:20 +00:00
dependabot[bot]
fded5007c1
Bump firebase-bom from 31.0.2 to 31.0.3 (#2041) 2022-11-14 18:22:25 +00:00
dependabot[bot]
66ff14f719
Bump agcp from 1.7.3.300 to 1.7.3.301 (#2046) 2022-11-14 18:21:35 +00:00
dependabot[bot]
1257dc63d3
Bump runner from 1.4.0 to 1.5.1 (#2047) 2022-11-14 18:21:17 +00:00
Rafał Borcz
50b6d380b6
Fix unexpected error in support ad when no internet (#2030) 2022-11-02 16:44:05 +01:00
Rafał Borcz
62b7d42a73
New Crowdin updates (#1966) 2022-11-01 20:57:05 +01:00
dependabot[bot]
21fe209246
Bump firebase-bom from 31.0.1 to 31.0.2 (#2032) 2022-11-01 19:51:28 +00:00
dependabot[bot]
02cd4e4e06
Bump sonarqube-gradle-plugin from 3.4.0.2513 to 3.5.0.2730 (#2033) 2022-11-01 19:50:59 +00:00
dependabot[bot]
86fe2b61cb
Bump agcp from 1.7.2.300 to 1.7.3.300 (#2034) 2022-11-01 19:50:37 +00:00
dependabot[bot]
4113bd9b53
Bump agconnect-crash from 1.7.2.300 to 1.7.3.300 (#2035) 2022-11-01 19:50:17 +00:00
dependabot[bot]
d924902dac
Bump CircularImageView from 4.2.0 to 4.3.0 (#2036) 2022-11-01 19:49:56 +00:00
Damian Czupryn
b269360ecb
Langs placement in README adjustments (#2029) 2022-10-30 03:00:39 +01:00
Damian Czupryn
ffd5addadb
Add Crowdin badges to README (#2025) 2022-10-28 11:10:35 +02:00
Mikołaj Pich
c5e2b18695
Fix SSL certificate out-of-date detection (#2028) 2022-10-28 11:10:05 +02:00
Mikołaj Pich
515a3973b7
Use text color, font face and red dot to differentiate unread messages (#2027) 2022-10-28 11:09:38 +02:00
Mikołaj Pich
7bee10d5ce
Hide room view in timetable item if there is no room in API (#2026) 2022-10-28 11:08:40 +02:00
Mikołaj Pich
22a4f509dc
Add installation id to crashlytics and bug report emails (#2024) 2022-10-27 12:41:33 +02:00
Damian Czupryn
3925a6261b
Add missing CS and SK links in German README (#2018) 2022-10-26 22:27:37 +02:00
dependabot[bot]
49b383fbe5
Bump firebase-bom from 31.0.0 to 31.0.1 (#2019) 2022-10-26 18:22:53 +00:00
dependabot[bot]
4a484dc2ce
Bump fragment-ktx from 1.5.3 to 1.5.4 (#2020) 2022-10-26 18:22:34 +00:00
dependabot[bot]
a14c4b489b
Bump material from 1.6.1 to 1.7.0 (#2022) 2022-10-26 18:22:16 +00:00
dependabot[bot]
e91cd18804
Bump firebase-bom from 30.5.0 to 31.0.0 (#2013) 2022-10-18 19:41:15 +00:00
dependabot[bot]
4c24363599
Bump about_libraries from 10.5.0 to 10.5.1 (#2012) 2022-10-18 19:40:28 +00:00
dependabot[bot]
e20c232f8f
Bump kotlinx-serialization-json from 1.4.0 to 1.4.1 (#2014) 2022-10-18 19:39:54 +00:00
dependabot[bot]
1f11eea9b5
Bump gradle from 7.3.0 to 7.3.1 (#2015) 2022-10-18 19:39:36 +00:00
dependabot[bot]
42f9a00e8c
Bump play-services-ads from 21.2.0 to 21.3.0 (#2016) 2022-10-18 19:39:16 +00:00
Daniel Olczyk
ad487e680c
Fix grade weight text truncation in grade dialog with large font set (#2009) 2022-10-05 22:25:09 +02:00
dependabot[bot]
3f431022a5
Bump about_libraries from 10.4.0 to 10.5.0 (#2005) 2022-10-04 08:08:21 +00:00
dependabot[bot]
cd037f0ce0
Bump kotlin_version from 1.7.10 to 1.7.20 (#2003) 2022-10-04 07:58:58 +00:00
Mikołaj Pich
37f7f21a03
Reorder action buttons on the message preview screen to hide the forward button in overflow menu (#2000) 2022-10-04 09:51:30 +02:00
dependabot[bot]
c653039590
Bump coil from 2.2.1 to 2.2.2 (#2004) 2022-10-04 07:50:05 +00:00
dependabot[bot]
95a90a7a79
Bump mockk from 1.13.1 to 1.13.2 (#2006) 2022-10-04 07:49:41 +00:00
dependabot[bot]
4dc80595ac
Bump robolectric from 4.8.2 to 4.9 (#2007) 2022-10-04 07:49:22 +00:00
dependabot[bot]
8114a2376e
Bump gradle from 7.2.2 to 7.3.0 (#1985) 2022-09-28 21:43:45 +00:00
dependabot[bot]
a523850216
Bump annotation from 1.4.0 to 1.5.0 (#1998) 2022-09-28 23:34:06 +02:00
Michael
354f51dd70
Fix student average calculation error in grade statistics (#1981)
Co-authored-by: Mikołaj Pich <m.pich@outlook.com>
2022-09-28 23:33:05 +02:00
dependabot[bot]
b271c12ebc
Bump hilt_version from 2.43.2 to 2.44 (#1994) 2022-09-28 20:28:31 +00:00
dependabot[bot]
8ca41b5ba3
Bump hianalytics from 6.7.0.300 to 6.8.0.300 (#1995) 2022-09-28 20:28:01 +00:00
dependabot[bot]
edbe45332a
Bump mockk from 1.12.8 to 1.13.1 (#1996) 2022-09-28 20:27:43 +00:00
dependabot[bot]
1bbd249275
Bump fragment-ktx from 1.5.2 to 1.5.3 (#1997) 2022-09-28 20:25:58 +00:00
dependabot[bot]
5148ff291b
Bump google-services from 4.3.13 to 4.3.14 (#1992) 2022-09-21 20:59:41 +00:00
dependabot[bot]
a1dc00af42
Bump agcp from 1.7.1.300 to 1.7.2.300 (#1989) 2022-09-21 20:49:36 +00:00
dependabot[bot]
f1db993fee
Bump agconnect-crash from 1.7.1.300 to 1.7.2.300 (#1990) 2022-09-21 20:40:36 +00:00
dependabot[bot]
4f0519552e
Bump firebase-crashlytics-gradle from 2.9.1 to 2.9.2 (#1988) 2022-09-21 20:39:29 +00:00
dependabot[bot]
3625c5c518
Bump firebase-bom from 30.4.1 to 30.5.0 (#1991) 2022-09-21 20:39:08 +00:00
dependabot[bot]
afbfb9761f
Bump mockk from 1.12.7 to 1.12.8 (#1986) 2022-09-21 20:38:55 +00:00
dependabot[bot]
a5c636853a
Bump coil from 2.2.0 to 2.2.1 (#1973) 2022-09-14 11:19:05 +00:00
dependabot[bot]
d5d45ed1ba
Bump play-services-ads from 21.1.0 to 21.2.0 (#1972) 2022-09-14 11:18:29 +00:00
dependabot[bot]
d3f869c6c2
Bump firebase-bom from 30.4.0 to 30.4.1 (#1971) 2022-09-14 11:18:09 +00:00
dependabot[bot]
46c29c438e
Bump appcompat from 1.5.0 to 1.5.1 (#1975) 2022-09-14 11:17:19 +00:00
dependabot[bot]
73a7255d3a
Bump firebase-bom from 30.3.2 to 30.4.0 (#1968) 2022-09-05 19:49:30 +00:00
Mikołaj Pich
c7af85e0e1 Merge branch 'release/1.7.5' into develop 2022-09-02 21:31:39 +02:00
Mikołaj Pich
afc16e3d17 Merge branch 'release/1.7.5' 2022-09-02 21:31:31 +02:00
Mikołaj Pich
59f6f5c212 Version 1.7.5 2022-09-02 21:31:27 +02:00
Mikołaj Pich
86f8763e69
Display lesson number in attendance notification if subject is blank (#1965) 2022-09-02 21:30:30 +02:00
Mikołaj Pich
157becb017
Fix matching mailboxes when there is more than one space between words (#1964) 2022-09-02 20:19:19 +02:00
Mikołaj Pich
83ca9a7060 Merge branch 'release/1.7.4' into develop 2022-09-01 17:57:30 +02:00
Mikołaj Pich
1033be4503 Merge branch 'release/1.7.4' 2022-09-01 17:57:24 +02:00
Mikołaj Pich
e67066f3ae Version 1.7.4 2022-09-01 17:57:20 +02:00
Mikołaj Pich
bc22808b0e
Add support for matching last kingergarten semester if there is no any current (#1962) 2022-09-01 17:48:46 +02:00
Mikołaj Pich
e05abb3539
Fix showing empty view in grade details when there is no grades (#1963) 2022-09-01 17:48:03 +02:00
Mikołaj Pich
6153c7b97d
Add support for match mailboxes with more different names (#1961) 2022-09-01 14:55:00 +02:00
Mikołaj Pich
e574e5e2ec Merge branch 'release/1.7.3' into develop 2022-08-31 19:31:39 +02:00
Mikołaj Pich
e6571a1dfc Merge branch 'release/1.7.3' 2022-08-31 19:31:33 +02:00
Mikołaj Pich
d566de0282 Version 1.7.3 2022-08-31 19:31:28 +02:00
Mikołaj Pich
558db061f5
Fix marking message as read (#1960)
* Fix marking message as read

* Update sdk

* Update sdk

* Fix tests

* Use many recipients strings instead of first recipient
2022-08-31 18:31:12 +02:00
Mikołaj Pich
190f40ede8 Merge branch 'release/1.7.2' into develop 2022-08-30 13:34:28 +02:00
Mikołaj Pich
4b795d6ef5 Merge branch 'release/1.7.2' 2022-08-30 13:34:20 +02:00
Mikołaj Pich
d139bd5b14 Version 1.7.2 2022-08-30 13:34:10 +02:00
Rafał Borcz
bf34cb0c1e
New Crowdin updates (#1953)
Co-authored-by: Mikołaj Pich <m.pich@outlook.com>
2022-08-30 12:11:32 +02:00
Mikołaj Pich
eed091aad2
Update row in mailboxes table on primary key conflict (#1954) 2022-08-30 09:07:24 +02:00
Mikołaj Pich
535206056d
Fix selecting student mailbox based on studentName field (#1958) 2022-08-30 09:07:07 +02:00
Mikołaj Pich
f2cb3b4f9e
Change message draft key in shared preferences (#1959) 2022-08-30 09:06:29 +02:00
dependabot[bot]
54372e0a55
Bump kotlinx-serialization-json from 1.3.3 to 1.4.0 (#1950) 2022-08-29 13:40:14 +00:00
dependabot[bot]
5c17c38d1d
Bump mockk from 1.12.5 to 1.12.7 (#1955) 2022-08-29 13:30:56 +00:00
dependabot[bot]
f68a8e4215
Bump robolectric from 4.8.1 to 4.8.2 (#1956) 2022-08-29 13:30:28 +00:00
dependabot[bot]
70c2cb7dbf
Bump desugar_jdk_libs from 1.1.6 to 1.1.8 (#1957) 2022-08-29 13:30:00 +00:00
Mikołaj Pich
7f6fd60821 Merge branch 'release/1.7.1' into develop 2022-08-23 00:56:29 +02:00
Mikołaj Pich
62c04fb205 Merge branch 'release/1.7.1' 2022-08-23 00:56:20 +02:00
Mikołaj Pich
10c36f19bf Version 1.7.1 2022-08-23 00:56:12 +02:00
Rafał Borcz
37d756b8fe
New Crowdin updates (#1952) 2022-08-23 00:54:19 +02:00
Mikołaj Pich
de1bc4809f
Fix ads translations (#1951) 2022-08-23 00:08:39 +02:00
Mikołaj Pich
3d6ec93cde Merge branch 'release/1.7.0' into develop 2022-08-22 17:58:51 +02:00
Mikołaj Pich
c293c76398 Merge branch 'release/1.7.0' 2022-08-22 17:58:41 +02:00
Mikołaj Pich
09e07a1713 Version 1.7.0 2022-08-22 17:58:35 +02:00
Rafał Borcz
71f1a55437
New Crowdin updates (#1895) 2022-08-22 16:45:59 +02:00
dependabot[bot]
d9e22af5ef
Bump desugar_jdk_libs from 1.1.5 to 1.1.6 (#1948) 2022-08-22 13:28:58 +00:00
dependabot[bot]
bc0689a30d
Bump coil from 2.1.0 to 2.2.0 (#1949) 2022-08-22 13:28:27 +00:00
Mikołaj Pich
9d47127921
Add support for messages plus API (#1945) 2022-08-22 14:30:50 +02:00
dependabot[bot]
08a3bd77bd
Bump appcompat from 1.4.2 to 1.5.0 (#1946) 2022-08-22 07:00:10 +00:00
dependabot[bot]
9fe1151a04
Bump fragment-ktx from 1.5.1 to 1.5.2 (#1947) 2022-08-19 22:22:47 +00:00
Patryk
793952cb44
Fix typo in README DE.md (#1936)
* Update README.de.md

* Change in README-DE.md file
2022-08-14 22:16:47 +02:00
dependabot[bot]
d64a21b50c
Bump hianalytics from 6.6.0.300 to 6.7.0.300 (#1944) 2022-08-10 09:56:20 +00:00
dependabot[bot]
274f9dde07
Bump agconnect-crash from 1.7.0.300 to 1.7.1.300 (#1943) 2022-08-10 09:48:02 +00:00
dependabot[bot]
5a884a4c56
Bump agcp from 1.7.0.300 to 1.7.1.300 (#1938) 2022-08-10 09:38:15 +00:00
dependabot[bot]
c55fd98179
Bump about_libraries from 10.3.1 to 10.4.0 (#1941) 2022-08-10 09:37:53 +00:00
Patryk
ffc0cd840b
Update workflow dependency (#1937)
Co-authored-by: Mikołaj Pich <m.pich@outlook.com>
2022-08-10 11:28:22 +02:00
dependabot[bot]
96067946d0
Bump firebase-bom from 30.3.1 to 30.3.2 (#1940) 2022-08-10 09:27:46 +00:00
dependabot[bot]
9339e7d916
Bump gradle from 7.2.1 to 7.2.2 (#1942) 2022-08-10 09:27:24 +00:00
dependabot[bot]
b4117aa62e
Bump flow-preferences from 1.7.0 to 1.8.0 (#1925) 2022-08-03 10:56:41 +00:00
dependabot[bot]
dc3a941e24
Bump core-splashscreen from 1.0.0-rc01 to 1.0.0 (#1929) 2022-08-01 21:36:28 +00:00
dependabot[bot]
b67ecbba4b
Bump room from 2.4.2 to 2.4.3 (#1928) 2022-08-01 21:26:37 +00:00
dependabot[bot]
1175740ba2
Bump activity-ktx from 1.5.0 to 1.5.1 (#1926) 2022-08-01 21:26:34 +00:00
dependabot[bot]
378ed0100f
Bump huawei-publish-gradle-plugin from 1.3.3 to 1.3.4 (#1934) 2022-08-01 21:26:15 +00:00
dependabot[bot]
cf87339ac4
Bump hilt_version from 2.43 to 2.43.1 (#1927) 2022-08-01 21:18:52 +00:00
dependabot[bot]
c23a90f104
Bump fragment-ktx from 1.5.0 to 1.5.1 (#1930) 2022-08-01 21:17:35 +00:00
dependabot[bot]
d337be0f40
Bump firebase-bom from 30.3.0 to 30.3.1 (#1931) 2022-08-01 21:17:20 +00:00
dependabot[bot]
cf7c6f78ea
Bump lifecycle-livedata-ktx from 2.5.0 to 2.5.1 (#1932) 2022-08-01 21:17:00 +00:00
dependabot[bot]
efa68f5044
Bump mockk from 1.12.4 to 1.12.5 (#1933) 2022-08-01 21:16:46 +00:00
dependabot[bot]
b9be85d99c
Bump hilt_version from 2.42 to 2.43 (#1923) 2022-07-27 10:09:28 +00:00
dependabot[bot]
dfc4553fc6
Bump firebase-bom from 30.2.0 to 30.3.0 (#1920) 2022-07-21 21:28:53 +00:00
dependabot[bot]
08c9539abe
Bump coroutines from 1.6.3 to 1.6.4 (#1921) 2022-07-21 21:28:37 +00:00
dependabot[bot]
fd18583df2
Bump play-services-ads from 21.0.0 to 21.1.0 (#1922) 2022-07-21 21:28:20 +00:00
Mikołaj Pich
7c4f1c7b22
Add auto refresh to reporting units (#1916) 2022-07-12 12:23:55 +02:00
dependabot[bot]
bdb6c962ea
Bump hianalytics from 6.5.0.300 to 6.6.0.300 (#1919) 2022-07-12 10:20:42 +00:00
dependabot[bot]
f1c217b087
Bump kotlin_version from 1.7.0 to 1.7.10 (#1918) 2022-07-12 10:20:28 +00:00
dependabot[bot]
4b6277abf5
Bump activity-ktx from 1.4.0 to 1.5.0 (#1912) 2022-07-09 07:34:24 +00:00
dependabot[bot]
344e0d55ff
Bump lifecycle-livedata-ktx from 2.4.1 to 2.5.0 (#1911) 2022-07-09 07:34:03 +00:00
dependabot[bot]
89a6a98bbf
Bump google-services from 4.3.10 to 4.3.13 (#1913) 2022-07-09 07:33:46 +00:00
dependabot[bot]
fcc7dc0913
Bump fragment-ktx from 1.4.1 to 1.5.0 (#1915) 2022-07-09 07:33:27 +00:00
Rafał Borcz
1b74bffc06
Fix no mobile devices on parent account (#1896) 2022-07-02 19:10:57 +02:00
dependabot[bot]
0f11f14c3e
Bump firebase-bom from 30.1.0 to 30.2.0 (#1909) 2022-06-28 15:07:33 +00:00
dependabot[bot]
e70fe6f097
Bump firebase-crashlytics-gradle from 2.9.0 to 2.9.1 (#1910) 2022-06-28 15:06:20 +00:00
dependabot[bot]
b70649f136
Bump about_libraries from 10.3.0 to 10.3.1 (#1907) 2022-06-28 15:05:51 +00:00
Rafał Borcz
7b13684137
Fix ads tile (#1905) 2022-06-26 15:50:35 +02:00
dependabot[bot]
c4689fcbb3
Bump agconnect-crash from 1.6.6.200 to 1.7.0.300 (#1899) 2022-06-26 12:44:06 +00:00
Rafał Borcz
d8f644c5b4
Add ads to dashboard (#1815) 2022-06-26 13:28:35 +02:00
Michael
c808bf2e61
Fix timetable widget day reset (#1862)
Co-authored-by: Rafał Borcz <RafalBO99@outlook.com>
2022-06-26 13:06:43 +02:00
Rafał Borcz
0fb55bd6c6
Fix doubled announcements (#1897) 2022-06-26 12:12:11 +02:00
dependabot[bot]
c5dfea788c
Bump annotation from 1.3.0 to 1.4.0 (#1900) 2022-06-23 12:09:50 +00:00
dependabot[bot]
120e5c9171
Bump coroutines from 1.6.2 to 1.6.3 (#1902) 2022-06-23 12:09:25 +00:00
Rafał Borcz
a97039a727
Fix jumping point in notes on refresh (#1898) 2022-06-19 21:04:05 +02:00
Rafał Borcz
e9ba65f8f6
Fix multiline address in student info (#1891) 2022-06-18 12:12:21 +02:00
Rafał Borcz
a264abf814
Fix typo in average description (#1892) 2022-06-18 12:12:04 +02:00
Rafał Borcz
0a2eb07844
Fix date in attendance and timetable when day is changing (#1893) 2022-06-18 12:11:46 +02:00
Rafał Borcz
bfab265ccf
Fix case sensitive domain checker (#1894) 2022-06-18 11:54:08 +02:00
dependabot[bot]
cd59166efb
Bump sonarqube-gradle-plugin from 3.3 to 3.4.0.2513 (#1888) 2022-06-16 22:15:18 +00:00
dependabot[bot]
06ed5f6079
Bump logging-interceptor from 4.9.3 to 4.10.0 (#1889) 2022-06-16 21:53:21 +00:00
Rafał Borcz
6b70583573
New Crowdin updates (#1873) 2022-06-13 07:43:40 +02:00
Rafał Borcz
c3cbaa6ac2
Update dependencies (#1887) 2022-06-13 07:43:12 +02:00
dependabot[bot]
f61d820d6f
Bump core-ktx from 1.7.0 to 1.8.0 (#1882) 2022-06-13 05:07:54 +00:00
dependabot[bot]
03ad5527f8
Bump appcompat from 1.4.1 to 1.4.2 (#1883) 2022-06-13 04:59:50 +00:00
dependabot[bot]
cce736410b
Bump material from 1.6.0 to 1.6.1 (#1884) 2022-06-13 04:59:31 +00:00
dependabot[bot]
8c515bd03f
Bump coroutines from 1.6.1 to 1.6.2 (#1875) 2022-05-31 13:52:09 +00:00
dependabot[bot]
8dcb3ed45d
Bump firebase-crashlytics-gradle from 2.8.1 to 2.9.0 (#1874) 2022-05-31 13:51:12 +00:00
dependabot[bot]
4f3f24ac10
Bump about_libraries from 10.2.0 to 10.3.0 (#1876) 2022-05-31 13:50:52 +00:00
dependabot[bot]
d074e5c9b3
Bump play-services-ads from 20.6.0 to 21.0.0 (#1877) 2022-05-31 13:50:22 +00:00
dependabot[bot]
d2d1d1dba7
Bump firebase-bom from 30.0.2 to 30.1.0 (#1878) 2022-05-31 13:50:03 +00:00
Michael
891e241d1a
Hide account selector in MessagePreviewView and SendMessageView (#1866)
Co-authored-by: Rafał Borcz <RafalBO99@outlook.com>
2022-05-28 02:03:42 +02:00
Michael
5c4a3d578b
Add grade sorting by average (#1863) 2022-05-27 22:19:22 +02:00
dependabot[bot]
fcf0adfd80
Bump gradle from 7.1.3 to 7.2.0 (#1857) 2022-05-26 05:26:52 +00:00
dependabot[bot]
c42a47ac48
Bump material from 1.5.0 to 1.6.0 (#1846) 2022-05-26 04:18:37 +00:00
dependabot[bot]
fa48b033af
Bump constraintlayout from 2.1.3 to 2.1.4 (#1869) 2022-05-24 08:44:17 +00:00
dependabot[bot]
808927a58a
Bump coil from 2.0.0 to 2.1.0 (#1870) 2022-05-24 08:43:52 +00:00
dependabot[bot]
dc717c9fb5
Bump core-splashscreen from 1.0.0-beta02 to 1.0.0-rc01 (#1871) 2022-05-24 08:43:32 +00:00
dependabot[bot]
a2804d813a
Bump firebase-bom from 30.0.1 to 30.0.2 (#1872) 2022-05-24 08:41:23 +00:00
Mikołaj Pich
847ab6149a Merge branch 'release/1.6.4' into develop 2022-05-16 23:42:51 +02:00
Mikołaj Pich
dc74d2877b Merge branch 'release/1.6.4' 2022-05-16 23:42:46 +02:00
Mikołaj Pich
6534176685 Version 1.6.4 2022-05-16 23:42:40 +02:00
Mikołaj Pich
9542b9f231
Set "no data" string if attendance subject is blank (#1864) 2022-05-16 23:23:49 +02:00
dependabot[bot]
dbba61a99f
Bump coil from 1.4.0 to 2.0.0 (#1855) 2022-05-14 14:48:58 +00:00
dependabot[bot]
c2496a15b8
Bump flow-preferences from 1.6.0 to 1.7.0 (#1859) 2022-05-14 14:36:09 +00:00
dependabot[bot]
facf84d9a8
Bump about_libraries from 8.9.4 to 10.2.0 (#1858) 2022-05-14 14:35:58 +00:00
dependabot[bot]
459c8330f9
Bump hianalytics from 6.4.1.302 to 6.5.0.300 (#1852) 2022-05-14 13:53:13 +00:00
dependabot[bot]
0c8e2632a2
Bump firebase-bom from 30.0.0 to 30.0.1 (#1851) 2022-05-14 13:52:58 +00:00
dependabot[bot]
b17e9deca0
Bump kotlinx-serialization-json from 1.3.2 to 1.3.3 (#1853) 2022-05-14 13:52:41 +00:00
dependabot[bot]
c296e72c30
Bump mockk from 1.12.2 to 1.12.4 (#1854) 2022-05-14 13:52:16 +00:00
dependabot[bot]
637125e1fc
Bump hilt_version from 2.41 to 2.42 (#1856) 2022-05-14 13:51:39 +00:00
Rafał Borcz
445bfda801
New Crowdin updates (#1840) 2022-05-12 21:07:14 +02:00
dependabot[bot]
ebde42328a
Bump agcp from 1.6.5.300 to 1.6.6.200 (#1845) 2022-05-12 19:06:15 +00:00
dependabot[bot]
a0bf14b576
Bump agconnect-crash from 1.6.5.300 to 1.6.6.200 (#1843) 2022-05-12 19:05:59 +00:00
dependabot[bot]
2e7caabde3
Bump firebase-bom from 29.3.1 to 30.0.0 (#1844) 2022-05-12 19:05:40 +00:00
dependabot[bot]
bb052fd4c9
Bump robolectric from 4.8 to 4.8.1 (#1842) 2022-05-12 19:05:20 +00:00
dependabot[bot]
28ef8c6761
Bump kotlin_version from 1.6.20 to 1.6.21 (#1837) 2022-05-03 11:46:03 +00:00
dependabot[bot]
15e8e096ed
Bump robolectric from 4.7.3 to 4.8 (#1839) 2022-05-03 11:38:18 +00:00
Mikołaj Pich
6007de017f Merge branch 'release/1.6.3' into develop 2022-04-19 09:56:12 +02:00
170 changed files with 16542 additions and 1741 deletions

View file

@ -1,4 +1,4 @@
name: Deploy to app stores name: Deploy release
on: on:
release: release:
@ -7,16 +7,17 @@ on:
jobs: jobs:
deploy-google-play: deploy-google-play:
name: Deploy to google play name: Google Play
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 10 timeout-minutes: 10
environment: google-play environment: google-play
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions/setup-java@v1 - uses: actions/setup-java@v2
with: with:
distribution: 'zulu'
java-version: 11 java-version: 11
- uses: actions/cache@v2 - uses: actions/cache@v3
with: with:
path: | path: |
~/.gradle/caches ~/.gradle/caches
@ -37,20 +38,22 @@ 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 }}
DASHBOARD_TILE_AD_ID: ${{ secrets.DASHBOARD_TILE_AD_ID }}
SET_BUILD_TIMESTAMP: ${{ secrets.SET_BUILD_TIMESTAMP }} SET_BUILD_TIMESTAMP: ${{ secrets.SET_BUILD_TIMESTAMP }}
run: ./gradlew publishPlayReleaseApps -PenableFirebase --stacktrace; run: ./gradlew publishPlayReleaseApps -PenableFirebase --stacktrace;
deploy-app-gallery: deploy-app-gallery:
name: Deploy to AppGallery name: AppGallery
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 10 timeout-minutes: 10
environment: app-gallery environment: app-gallery
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions/setup-java@v1 - uses: actions/setup-java@v2
with: with:
distribution: 'zulu'
java-version: 11 java-version: 11
- uses: actions/cache@v2 - uses: actions/cache@v3
with: with:
path: | path: |
~/.gradle/caches ~/.gradle/caches

View file

@ -1,4 +1,4 @@
name: Deploy to app tests name: Deploy DEV
on: on:
push: push:
@ -18,11 +18,12 @@ jobs:
timeout-minutes: 10 timeout-minutes: 10
environment: app-center environment: app-center
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions/setup-java@v1 - uses: actions/setup-java@v2
with: with:
distribution: 'zulu'
java-version: 11 java-version: 11
- uses: actions/cache@v2 - uses: actions/cache@v3
with: with:
path: | path: |
~/.gradle/caches ~/.gradle/caches
@ -66,7 +67,7 @@ jobs:
BITRISE_KEY_PASSWORD: ${{ secrets.BITRISE_KEY_PASSWORD }} BITRISE_KEY_PASSWORD: ${{ secrets.BITRISE_KEY_PASSWORD }}
run: ./gradlew assembleFdroidDebug --stacktrace run: ./gradlew assembleFdroidDebug --stacktrace
- name: Upload apk to github artifacts - name: Upload apk to github artifacts
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v3
with: with:
name: wulkanowyDEV-${{ env.RUN_NUMBER }}.apk name: wulkanowyDEV-${{ env.RUN_NUMBER }}.apk
path: app/build/outputs/apk/fdroid/debug/app-fdroid-debug.apk path: app/build/outputs/apk/fdroid/debug/app-fdroid-debug.apk
@ -87,11 +88,12 @@ jobs:
environment: app-distribution environment: app-distribution
if: github.event_name != 'pull_request_target' if: github.event_name != 'pull_request_target'
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions/setup-java@v1 - uses: actions/setup-java@v2
with: with:
distribution: 'zulu'
java-version: 11 java-version: 11
- uses: actions/cache@v2 - uses: actions/cache@v3
with: with:
path: | path: |
~/.gradle/caches ~/.gradle/caches
@ -131,7 +133,7 @@ jobs:
BITRISE_KEY_PASSWORD: ${{ secrets.BITRISE_KEY_PASSWORD }} BITRISE_KEY_PASSWORD: ${{ secrets.BITRISE_KEY_PASSWORD }}
run: ./gradlew assemblePlayDebug -PenableFirebase --stacktrace run: ./gradlew assemblePlayDebug -PenableFirebase --stacktrace
- name: Upload apk to github artifacts - name: Upload apk to github artifacts
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v3
with: with:
name: wulkanowyDEV-${{ env.RUN_NUMBER }}-dev.apk name: wulkanowyDEV-${{ env.RUN_NUMBER }}-dev.apk
path: app/build/outputs/apk/play/debug/app-play-debug.apk path: app/build/outputs/apk/play/debug/app-play-debug.apk

View file

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

View file

@ -1,10 +1,4 @@
[English version of README](README.en.md) Česká verze / [Deutsche Version](README.de.md) / [English version](README.en.md) / [Polska wersja](README.md) / [Slovenská verzia](README.sk.md)
[Deutsche Version von README](README.de.md)
[Polska wersja README](README.md)
[Slovenská verzia README](README.sk.md)
# Wulkanowy # Wulkanowy
@ -13,6 +7,7 @@
[![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) [![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr)
[![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/) [![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/)
[![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases) [![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases)
[![Crowdin](https://badges.crowdin.net/wulkanowy2/localized.svg)](https://translate.wulkanowy.net.pl)
Neoficiální klient deníku VULCAN UONET+ pro žáka a rodiče Neoficiální klient deníku VULCAN UONET+ pro žáka a rodiče
@ -57,7 +52,7 @@ Aktuální verzi si můžete stáhnout z Google Play, F-Droid nebo Huawei AppGal
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í 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 ## Postaveno s pomocí
* [Wulkanowy SDK](https://github.com/wulkanowy/sdk) * [Wulkanowy SDK](https://github.com/wulkanowy/sdk)

View file

@ -1,6 +1,4 @@
[Polska wersja README](README.md) [Česká verze](README.cs.md) / Deutsche Version / [English version](README.en.md) / [Polska wersja](README.md) / [Slovenská verzia](README.sk.md)
[English version of README](README.en.md)
# Wulkanowy # Wulkanowy
@ -9,6 +7,7 @@
[![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) [![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr)
[![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/) [![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/)
[![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases) [![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases)
[![Crowdin](https://badges.crowdin.net/wulkanowy2/localized.svg)](https://translate.wulkanowy.net.pl)
Inoffizieller Android VULCAN UONET+ Registrierungsclient für Schüler und ihre Eltern Inoffizieller Android VULCAN UONET+ Registrierungsclient für Schüler und ihre Eltern
@ -51,7 +50,7 @@ Die aktuelle Version können Sie von der Google Play, F-Droid oder Huawei AppGal
alt="Explore it on AppGallery" 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=) 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 Sie können auch eine [Entwicklungsversion herunterladen](https://wulkanowy.github.io/#download) die beinhaltet neue Funktionen, die für die nächste Version vorbereitet werden
## Gebaut mit ## Gebaut mit

View file

@ -1,10 +1,4 @@
[Polska wersja README](README.md) [Česká verze](README.cs.md) / [Deutsche Version](README.de.md) / English version / [Polska wersja](README.md) / [Slovenská verzia](README.sk.md)
[Deutsche Version von README](README.de.md)
[Česká verze README](README.cs.md)
[Slovenská verzia README](README.sk.md)
# Wulkanowy # Wulkanowy
@ -13,6 +7,7 @@
[![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) [![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr)
[![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/) [![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/)
[![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases) [![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases)
[![Crowdin](https://badges.crowdin.net/wulkanowy2/localized.svg)](https://translate.wulkanowy.net.pl)
Unofficial android VULCAN UONET+ register client for both students and their parents Unofficial android VULCAN UONET+ register client for both students and their parents

View file

@ -1,10 +1,4 @@
[English version of README](README.en.md) [Česká verze](README.cs.md) / [Deutsche Version](README.de.md) / [English version](README.en.md) / Polska wersja / [Slovenská verzia](README.sk.md)
[Deutsche Version von README](README.de.md)
[Česká verze README](README.cs.md)
[Slovenská verzia README](README.sk.md)
# Wulkanowy # Wulkanowy
@ -13,6 +7,7 @@
[![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) [![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr)
[![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/) [![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/)
[![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases) [![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases)
[![Crowdin](https://badges.crowdin.net/wulkanowy2/localized.svg)](https://translate.wulkanowy.net.pl)
Nieoficjalny klient dziennika VULCAN UONET+ dla ucznia i rodzica Nieoficjalny klient dziennika VULCAN UONET+ dla ucznia i rodzica

View file

@ -1,10 +1,4 @@
[English version of README](README.en.md) [Česká verze](README.cs.md) / [Deutsche Version](README.de.md) / [English version](README.en.md) / [Polska wersja](README.md) / Slovenská verzia
[Deutsche Version von README](README.de.md)
[Polska wersja README](README.md)
[Česká verze README](README.cs.md)
# Wulkanowy # Wulkanowy
@ -13,6 +7,7 @@
[![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr) [![Discord](https://img.shields.io/discord/390889354199040011.svg?style=flat-square)](https://discord.gg/vccAQBr)
[![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/) [![F-Droid](https://img.shields.io/f-droid/v/io.github.wulkanowy.svg?style=flat-square)](https://f-droid.org/packages/io.github.wulkanowy/)
[![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases) [![Last release](https://img.shields.io/github/release/wulkanowy/wulkanowy.svg?logo=github&style=flat-square)](https://github.com/wulkanowy/wulkanowy/releases)
[![Crowdin](https://badges.crowdin.net/wulkanowy2/localized.svg)](https://translate.wulkanowy.net.pl)
Neoficiálny klient denníka VULCAN UONET+ pre žiaka a rodičov Neoficiálny klient denníka VULCAN UONET+ pre žiaka a rodičov
@ -57,7 +52,7 @@ Aktuálnu verziu si môžete stiahnuť z Google Play, F-Droid alebo Huawei AppGa
Môžete si tiež stiahnuť [vývojovú verziu](https://wulkanowy.github.io/#download), ktorá zahrňuje nové funkcie pripravované pre budúce vydanie 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 ## Postavené s pomocou
* [Wulkanowy SDK](https://github.com/wulkanowy/sdk) * [Wulkanowy SDK](https://github.com/wulkanowy/sdk)

View file

@ -15,33 +15,35 @@ apply from: 'sonarqube.gradle'
apply from: 'hooks.gradle' apply from: 'hooks.gradle'
android { android {
compileSdkVersion 31 namespace 'io.github.wulkanowy'
compileSdkVersion 32
defaultConfig { defaultConfig {
applicationId "io.github.wulkanowy" applicationId "io.github.wulkanowy"
testApplicationId "io.github.tests.wulkanowy" testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 31 targetSdkVersion 32
versionCode 107 versionCode 115
versionName "1.6.3" versionName "1.8.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "app_name", "Wulkanowy" resValue "string", "app_name", "Wulkanowy"
manifestPlaceholders = [ manifestPlaceholders = [
firebase_enabled: project.hasProperty("enableFirebase"), firebase_enabled: project.hasProperty("enableFirebase"),
admob_project_id: "" admob_project_id: ""
] ]
javaCompileOptions { javaCompileOptions {
annotationProcessorOptions { annotationProcessorOptions {
arguments += [ arguments += [
"room.schemaLocation": "$projectDir/schemas".toString(), "room.schemaLocation": "$projectDir/schemas".toString(),
"room.incremental" : "true" "room.incremental" : "true"
] ]
} }
} }
buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "null" buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "null"
buildConfigField "String", "DASHBOARD_TILE_AD_ID", "null"
if (System.env.SET_BUILD_TIMESTAMP) { if (System.env.SET_BUILD_TIMESTAMP) {
buildConfigField "long", "BUILD_TIMESTAMP", String.valueOf(System.currentTimeMillis()) buildConfigField "long", "BUILD_TIMESTAMP", String.valueOf(System.currentTimeMillis())
@ -94,10 +96,12 @@ android {
play { play {
dimension "platform" dimension "platform"
manifestPlaceholders = [ manifestPlaceholders = [
install_channel : "Google Play", install_channel : "Google Play",
admob_project_id: System.getenv("ADMOB_PROJECT_ID") ?: "ca-app-pub-3940256099942544~3347511713" admob_project_id: System.getenv("ADMOB_PROJECT_ID") ?: "ca-app-pub-3940256099942544~3347511713"
] ]
buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "\"${System.getenv("SINGLE_SUPPORT_AD_ID") ?: "ca-app-pub-3940256099942544/5354046379"}\"" buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "\"${System.getenv("SINGLE_SUPPORT_AD_ID") ?: "ca-app-pub-3940256099942544/5354046379"}\""
buildConfigField "String", "DASHBOARD_TILE_AD_ID", "\"${System.getenv("DASHBOARD_TILE_AD_ID") ?: "ca-app-pub-3940256099942544/6300978111"}\""
} }
fdroid { fdroid {
@ -122,6 +126,8 @@ android {
testOptions.unitTests { testOptions.unitTests {
includeAndroidResources = true includeAndroidResources = true
// workaround HMS test errors https://github.com/robolectric/robolectric/issues/2750
all { jvmArgs '-noverify' }
} }
compileOptions { compileOptions {
@ -132,12 +138,14 @@ android {
kotlinOptions { kotlinOptions {
jvmTarget = "11" jvmTarget = "11"
freeCompilerArgs += ["-Xopt-in=kotlin.RequiresOptIn", "-Xjvm-default=all"] freeCompilerArgs += ["-opt-in=kotlin.RequiresOptIn", "-Xjvm-default=all"]
} }
packagingOptions { packagingOptions {
exclude 'META-INF/library_release.kotlin_module' resources {
exclude 'META-INF/library-core_release.kotlin_module' excludes += ['META-INF/library_release.kotlin_module',
'META-INF/library-core_release.kotlin_module']
}
} }
aboutLibraries { aboutLibraries {
@ -153,8 +161,8 @@ play {
defaultToAppBundles = false defaultToAppBundles = false
track = 'production' track = 'production'
releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS releaseStatus = com.github.triplet.gradle.androidpublisher.ReleaseStatus.IN_PROGRESS
userFraction = 0.50d userFraction = 0.25d
updatePriority = 1 updatePriority = 4
enabled.set(false) enabled.set(false)
} }
@ -171,42 +179,42 @@ huaweiPublish {
ext { ext {
work_manager = "2.7.1" work_manager = "2.7.1"
android_hilt = "1.0.0" android_hilt = "1.0.0"
room = "2.4.2" room = "2.4.3"
chucker = "3.5.2" chucker = "3.5.2"
mockk = "1.12.2" mockk = "1.13.2"
coroutines = "1.6.1" coroutines = "1.6.4"
} }
dependencies { dependencies {
implementation "io.github.wulkanowy:sdk:1.6.0" implementation "io.github.wulkanowy:sdk:1.8.0"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2" implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1"
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.8.0"
implementation 'androidx.core:core-splashscreen:1.0.0-beta02' implementation 'androidx.core:core-splashscreen:1.0.0'
implementation "androidx.activity:activity-ktx:1.4.0" implementation "androidx.activity:activity-ktx:1.5.1"
implementation "androidx.appcompat:appcompat:1.4.1" implementation "androidx.appcompat:appcompat:1.5.1"
implementation "androidx.fragment:fragment-ktx:1.4.1" implementation "androidx.fragment:fragment-ktx:1.5.4"
implementation "androidx.annotation:annotation:1.3.0" implementation "androidx.annotation:annotation:1.5.0"
implementation "androidx.preference:preference-ktx:1.2.0" implementation "androidx.preference:preference-ktx:1.2.0"
implementation "androidx.recyclerview:recyclerview:1.2.1" implementation "androidx.recyclerview:recyclerview:1.2.1"
implementation "androidx.viewpager2:viewpager2:1.1.0-beta01" 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.3" implementation "androidx.constraintlayout:constraintlayout:2.1.4"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0" implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
implementation "com.google.android.material:material:1.5.0" implementation "com.google.android.material:material:1.7.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.3.0'
implementation "androidx.work:work-runtime-ktx:$work_manager" implementation "androidx.work:work-runtime-ktx:$work_manager"
playImplementation "androidx.work:work-gcm:$work_manager" playImplementation "androidx.work:work-gcm:$work_manager"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1" implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.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"
@ -222,27 +230,27 @@ 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.3" implementation "com.squareup.okhttp3:logging-interceptor:4.10.0"
implementation "com.jakewharton.timber:timber:5.0.1" implementation "com.jakewharton.timber:timber:5.0.1"
implementation "at.favre.lib:slf4j-timber:1.0.1" implementation "at.favre.lib:slf4j-timber:1.0.1"
implementation 'com.github.bastienpaulfr:Treessence:1.0.5' implementation 'com.github.bastienpaulfr:Treessence:1.0.5'
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:2.2.2"
implementation "io.github.wulkanowy:AppKillerManager:3.0.0" implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'me.xdrop:fuzzywuzzy:1.4.0'
implementation 'com.fredporciuncula:flow-preferences:1.6.0' implementation 'com.fredporciuncula:flow-preferences:1.8.0'
playImplementation platform('com.google.firebase:firebase-bom:29.3.1') playImplementation platform('com.google.firebase:firebase-bom:31.0.3')
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.3' 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.6.0' playImplementation 'com.google.android.gms:play-services-ads:21.3.0'
hmsImplementation 'com.huawei.hms:hianalytics:6.4.1.302' hmsImplementation 'com.huawei.hms:hianalytics:6.8.0.300'
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.5.300' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.7.3.300'
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
@ -255,10 +263,10 @@ dependencies {
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines"
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
testImplementation 'org.robolectric:robolectric:4.7.3' testImplementation 'org.robolectric:robolectric:4.9'
testImplementation "androidx.test:runner:1.4.0" testImplementation "androidx.test:runner:1.5.1"
testImplementation "androidx.test.ext:junit:1.1.3" testImplementation "androidx.test.ext:junit:1.1.4"
testImplementation "androidx.test:core:1.4.0" testImplementation "androidx.test:core:1.5.0"
testImplementation "androidx.room:room-testing:$room" testImplementation "androidx.room:room-testing:$room"
testImplementation "com.google.dagger:hilt-android-testing:$hilt_version" testImplementation "com.google.dagger:hilt-android-testing:$hilt_version"
kaptTest "com.google.dagger:hilt-android-compiler:$hilt_version" kaptTest "com.google.dagger:hilt-android-compiler:$hilt_version"

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

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

View file

@ -0,0 +1,28 @@
package io.github.wulkanowy.utils
import android.content.Context
import android.view.View
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
import javax.inject.Inject
@Suppress("unused")
class AdsHelper @Inject constructor(
@ApplicationContext private val context: Context,
private val preferencesRepository: PreferencesRepository
) {
fun initialize() {
preferencesRepository.isAdsEnabled = false
preferencesRepository.isAgreeToProcessData = false
preferencesRepository.selectedDashboardTiles -= DashboardItem.Tile.ADS
}
@Suppress("RedundantSuspendModifier", "UNUSED_PARAMETER")
suspend fun getDashboardTileAdBanner(width: Int): AdBanner {
throw IllegalStateException("Can't get ad banner (F-droid)")
}
}
data class AdBanner(val view: View)

View file

@ -8,15 +8,7 @@ import javax.inject.Singleton
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
class AnalyticsHelper @Inject constructor() { class AnalyticsHelper @Inject constructor() {
fun logEvent(name: String, vararg params: Pair<String, Any?>) { fun logEvent(name: String, vararg params: Pair<String, Any?>) = Unit
// do nothing fun setCurrentScreen(activity: Activity, name: String?) = Unit
} fun popCurrentScreen(name: String?) = Unit
fun setCurrentScreen(activity: Activity, name: String?) {
// do nothing
}
fun popCurrentScreen(name: String?) {
// do nothing
}
} }

View file

@ -0,0 +1,28 @@
package io.github.wulkanowy.utils
import android.content.Context
import android.view.View
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
import javax.inject.Inject
@Suppress("unused")
class AdsHelper @Inject constructor(
@ApplicationContext private val context: Context,
private val preferencesRepository: PreferencesRepository
) {
fun initialize() {
preferencesRepository.isAdsEnabled = false
preferencesRepository.isAgreeToProcessData = false
preferencesRepository.selectedDashboardTiles -= DashboardItem.Tile.ADS
}
@Suppress("RedundantSuspendModifier", "UNUSED_PARAMETER")
suspend fun getDashboardTileAdBanner(width: Int): AdBanner {
throw IllegalStateException("Can't get ad banner (HMS)")
}
}
data class AdBanner(val view: View)

View file

@ -3,26 +3,38 @@ package io.github.wulkanowy.utils
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import com.huawei.agconnect.crash.AGConnectCrash
import com.huawei.hms.analytics.HiAnalytics import com.huawei.hms.analytics.HiAnalytics
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.data.repositories.PreferencesRepository
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class AnalyticsHelper @Inject constructor( class AnalyticsHelper @Inject constructor(
@ApplicationContext private val context: Context @ApplicationContext private val context: Context,
preferencesRepository: PreferencesRepository,
appInfo: AppInfo,
) { ) {
private val analytics by lazy { HiAnalytics.getInstance(context) } private val analytics by lazy { HiAnalytics.getInstance(context) }
private val connectCrash by lazy { AGConnectCrash.getInstance() }
init {
if (!appInfo.isDebug) {
connectCrash.setUserId(preferencesRepository.installationId)
}
}
fun logEvent(name: String, vararg params: Pair<String, Any?>) { fun logEvent(name: String, vararg params: Pair<String, Any?>) {
Bundle().apply { Bundle().apply {
params.forEach { params.forEach { (key, value) ->
if (it.second == null) return@forEach if (value == null) return@forEach
when (it.second) { when (value) {
is String, is String? -> putString(it.first, it.second as String) is String -> putString(key, value)
is Int, is Int? -> putInt(it.first, it.second as Int) is Int -> putInt(key, value)
is Boolean, is Boolean? -> putBoolean(it.first, it.second as Boolean) is Boolean -> putBoolean(key, value)
} }
} }
analytics.onEvent(name, this) analytics.onEvent(name, this)

View file

@ -3,6 +3,7 @@ package io.github.wulkanowy.utils
import android.util.Log import android.util.Log
import com.huawei.agconnect.crash.AGConnectCrash import com.huawei.agconnect.crash.AGConnectCrash
import fr.bipi.tressence.base.FormatterPriorityTree import fr.bipi.tressence.base.FormatterPriorityTree
import fr.bipi.tressence.common.StackTraceRecorder
class CrashLogTree : FormatterPriorityTree(Log.VERBOSE) { class CrashLogTree : FormatterPriorityTree(Log.VERBOSE) {
@ -22,16 +23,10 @@ class CrashLogExceptionTree : FormatterPriorityTree(Log.ERROR, ExceptionFilter)
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
if (skipLog(priority, tag, message, t)) return if (skipLog(priority, tag, message, t)) return
// Disabled due to a bug in the Huawei library
/*connectCrash.setCustomKey("priority", priority)
connectCrash.setCustomKey("tag", tag.orEmpty())
connectCrash.setCustomKey("message", message)
if (t != null) { if (t != null) {
connectCrash.recordException(t) connectCrash.recordException(t)
} else { } else {
connectCrash.recordException(StackTraceRecorder(format(priority, tag, message))) connectCrash.recordException(StackTraceRecorder(format(priority, tag, message)))
}*/ }
} }
} }

View file

@ -7,6 +7,7 @@ import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
@Suppress("UNUSED_PARAMETER", "unused")
class InAppReviewHelper @Inject constructor( class InAppReviewHelper @Inject constructor(
@ApplicationContext private val context: Context @ApplicationContext private val context: Context
) { ) {
@ -14,4 +15,4 @@ class InAppReviewHelper @Inject constructor(
fun showInAppReview(activity: MainActivity) { fun showInAppReview(activity: MainActivity) {
// do nothing // do nothing
} }
} }

View file

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="io.github.wulkanowy"
android:installLocation="internalOnly"> android:installLocation="internalOnly">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />

View file

@ -31,10 +31,14 @@ class WulkanowyApp : Application(), Configuration.Provider {
@Inject @Inject
lateinit var analyticsHelper: AnalyticsHelper lateinit var analyticsHelper: AnalyticsHelper
@Inject
lateinit var adsHelper: AdsHelper
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
initializeAppLanguage() initializeAppLanguage()
themeManager.applyDefaultTheme() themeManager.applyDefaultTheme()
adsHelper.initialize()
initLogging() initLogging()
} }

View file

@ -19,7 +19,6 @@ import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
@ -110,7 +109,6 @@ internal class DataModule {
fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences = fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(context) PreferenceManager.getDefaultSharedPreferences(context)
@OptIn(ExperimentalCoroutinesApi::class)
@Singleton @Singleton
@Provides @Provides
fun provideFlowSharedPref(sharedPreferences: SharedPreferences) = fun provideFlowSharedPref(sharedPreferences: SharedPreferences) =
@ -197,7 +195,7 @@ internal class DataModule {
@Singleton @Singleton
@Provides @Provides
fun provideReportingUnitDao(database: AppDatabase) = database.reportingUnitDao fun provideMailboxesDao(database: AppDatabase) = database.mailboxDao
@Singleton @Singleton
@Provides @Provides

View file

@ -30,7 +30,7 @@ import javax.inject.Singleton
Subject::class, Subject::class,
LuckyNumber::class, LuckyNumber::class,
CompletedLesson::class, CompletedLesson::class,
ReportingUnit::class, Mailbox::class,
Recipient::class, Recipient::class,
MobileDevice::class, MobileDevice::class,
Teacher::class, Teacher::class,
@ -47,6 +47,7 @@ import javax.inject.Singleton
AutoMigration(from = 44, to = 45), AutoMigration(from = 44, to = 45),
AutoMigration(from = 46, to = 47), AutoMigration(from = 46, to = 47),
AutoMigration(from = 47, to = 48), AutoMigration(from = 47, to = 48),
AutoMigration(from = 51, to = 52),
], ],
version = AppDatabase.VERSION_SCHEMA, version = AppDatabase.VERSION_SCHEMA,
exportSchema = true exportSchema = true
@ -55,7 +56,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
companion object { companion object {
const val VERSION_SCHEMA = 48 const val VERSION_SCHEMA = 53
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
Migration2(), Migration2(),
@ -102,6 +103,10 @@ abstract class AppDatabase : RoomDatabase() {
Migration43(), Migration43(),
Migration44(), Migration44(),
Migration46(), Migration46(),
Migration49(),
Migration50(),
Migration51(),
Migration53(),
) )
fun newInstance( fun newInstance(
@ -152,7 +157,7 @@ abstract class AppDatabase : RoomDatabase() {
abstract val completedLessonsDao: CompletedLessonsDao abstract val completedLessonsDao: CompletedLessonsDao
abstract val reportingUnitDao: ReportingUnitDao abstract val mailboxDao: MailboxDao
abstract val recipientDao: RecipientDao abstract val recipientDao: RecipientDao

View file

@ -2,11 +2,12 @@ package io.github.wulkanowy.data.db.dao
import androidx.room.Delete import androidx.room.Delete
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Update import androidx.room.Update
interface BaseDao<T> { interface BaseDao<T> {
@Insert @Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(items: List<T>): List<Long> suspend fun insertAll(items: List<T>): List<Long>
@Update @Update

View file

@ -0,0 +1,18 @@
package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Query
import io.github.wulkanowy.data.db.entities.Mailbox
import kotlinx.coroutines.flow.Flow
import javax.inject.Singleton
@Singleton
@Dao
interface MailboxDao : BaseDao<Mailbox> {
@Query("SELECT * FROM Mailboxes WHERE email = :email")
suspend fun loadAll(email: String): List<Mailbox>
@Query("SELECT * FROM Mailboxes WHERE email = :email AND symbol = :symbol AND schoolId = :schoolId")
fun loadAll(email: String, symbol: String, schoolId: String): Flow<List<Mailbox>>
}

View file

@ -11,9 +11,12 @@ import kotlinx.coroutines.flow.Flow
interface MessagesDao : BaseDao<Message> { interface MessagesDao : BaseDao<Message> {
@Transaction @Transaction
@Query("SELECT * FROM Messages WHERE student_id = :studentId AND message_id = :messageId") @Query("SELECT * FROM Messages WHERE message_global_key = :messageGlobalKey")
fun loadMessageWithAttachment(studentId: Int, messageId: Int): Flow<MessageWithAttachment?> fun loadMessageWithAttachment(messageGlobalKey: String): Flow<MessageWithAttachment?>
@Query("SELECT * FROM Messages WHERE student_id = :studentId AND folder_id = :folder ORDER BY date DESC") @Query("SELECT * FROM Messages WHERE mailbox_key = :mailboxKey AND folder_id = :folder ORDER BY date DESC")
fun loadAll(studentId: Int, folder: Int): Flow<List<Message>> fun loadAll(mailboxKey: String, folder: Int): Flow<List<Message>>
@Query("SELECT * FROM Messages WHERE email = :email AND folder_id = :folder ORDER BY date DESC")
fun loadAll(folder: Int, email: String): Flow<List<Message>>
} }

View file

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

View file

@ -2,6 +2,7 @@ package io.github.wulkanowy.data.db.dao
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Query import androidx.room.Query
import io.github.wulkanowy.data.db.entities.MailboxType
import io.github.wulkanowy.data.db.entities.Recipient import io.github.wulkanowy.data.db.entities.Recipient
import javax.inject.Singleton import javax.inject.Singleton
@ -9,6 +10,6 @@ import javax.inject.Singleton
@Dao @Dao
interface RecipientDao : BaseDao<Recipient> { interface RecipientDao : BaseDao<Recipient> {
@Query("SELECT * FROM Recipients WHERE student_id = :studentId AND unit_id = :unitId AND role = :role") @Query("SELECT * FROM Recipients WHERE type = :type AND studentMailboxGlobalKey = :studentMailboxGlobalKey")
suspend fun loadAll(studentId: Int, unitId: Int, role: Int): List<Recipient> suspend fun loadAll(type: MailboxType, studentMailboxGlobalKey: String): List<Recipient>
} }

View file

@ -1,17 +0,0 @@
package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Query
import io.github.wulkanowy.data.db.entities.ReportingUnit
import javax.inject.Singleton
@Singleton
@Dao
interface ReportingUnitDao : BaseDao<ReportingUnit> {
@Query("SELECT * FROM ReportingUnits WHERE student_id = :studentId")
suspend fun load(studentId: Int): List<ReportingUnit>
@Query("SELECT * FROM ReportingUnits WHERE student_id = :studentId AND real_id = :unitId")
suspend fun loadOne(studentId: Int, unitId: Int): ReportingUnit?
}

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 ORDER BY date DESC") @Query("SELECT * FROM SchoolAnnouncements WHERE user_login_id = :userLoginId ORDER BY date DESC")
fun loadAll(studentId: Int): Flow<List<SchoolAnnouncement>> fun loadAll(userLoginId: Int): Flow<List<SchoolAnnouncement>>
} }

View file

@ -0,0 +1,32 @@
package io.github.wulkanowy.data.db.entities
import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.parcelize.Parcelize
@Parcelize
@Entity(tableName = "Mailboxes")
data class Mailbox(
@PrimaryKey
val globalKey: String,
val email: String,
val symbol: String,
val schoolId: String,
val fullName: String,
val userName: String,
val studentName: String,
val schoolNameShort: String,
val type: MailboxType,
) : java.io.Serializable, Parcelable
enum class MailboxType {
STUDENT,
PARENT,
GUARDIAN,
EMPLOYEE,
UNKNOWN,
}

View file

@ -9,23 +9,19 @@ import java.time.Instant
@Entity(tableName = "Messages") @Entity(tableName = "Messages")
data class Message( data class Message(
@ColumnInfo(name = "student_id") @ColumnInfo(name = "email")
val studentId: Long, val email: String,
@ColumnInfo(name = "real_id") @ColumnInfo(name = "message_global_key")
val realId: Int, val messageGlobalKey: String,
@ColumnInfo(name = "mailbox_key")
val mailboxKey: String,
@ColumnInfo(name = "message_id") @ColumnInfo(name = "message_id")
val messageId: Int, val messageId: Int,
@ColumnInfo(name = "sender_name") val correspondents: String,
val sender: String,
@ColumnInfo(name = "sender_id")
val senderId: Int,
@ColumnInfo(name = "recipient_name")
val recipient: String,
val subject: String, val subject: String,
@ -36,7 +32,11 @@ data class Message(
var unread: Boolean, var unread: Boolean,
val removed: Boolean, @ColumnInfo(name = "read_by")
val readBy: Int?,
@ColumnInfo(name = "unread_by")
val unreadBy: Int?,
@ColumnInfo(name = "has_attachments") @ColumnInfo(name = "has_attachments")
val hasAttachments: Boolean val hasAttachments: Boolean
@ -48,11 +48,7 @@ data class Message(
@ColumnInfo(name = "is_notified") @ColumnInfo(name = "is_notified")
var isNotified: Boolean = true var isNotified: Boolean = true
@ColumnInfo(name = "unread_by")
var unreadBy: Int = 0
@ColumnInfo(name = "read_by")
var readBy: Int = 0
var content: String = "" var content: String = ""
var sender: String? = null
var recipients: String? = null
} }

View file

@ -12,11 +12,8 @@ data class MessageAttachment(
@ColumnInfo(name = "real_id") @ColumnInfo(name = "real_id")
val realId: Int, val realId: Int,
@ColumnInfo(name = "message_id") @ColumnInfo(name = "message_global_key")
val messageId: Int, val messageGlobalKey: String,
@ColumnInfo(name = "one_drive_id")
val oneDriveId: String,
@ColumnInfo(name = "url") @ColumnInfo(name = "url")
val url: String, val url: String,

View file

@ -7,6 +7,6 @@ data class MessageWithAttachment(
@Embedded @Embedded
val message: Message, val message: Message,
@Relation(parentColumn = "message_id", entityColumn = "message_id") @Relation(parentColumn = "message_global_key", entityColumn = "message_global_key")
val attachments: List<MessageAttachment> val attachments: List<MessageAttachment>
) )

View file

@ -9,7 +9,7 @@ import java.time.Instant
@Entity(tableName = "MobileDevices") @Entity(tableName = "MobileDevices")
data class MobileDevice( data class MobileDevice(
@ColumnInfo(name = "student_id") @ColumnInfo(name = "user_login_id")
val userLoginId: Int, val userLoginId: Int,
@ColumnInfo(name = "device_id") @ColumnInfo(name = "device_id")

View file

@ -1,6 +1,5 @@
package io.github.wulkanowy.data.db.entities package io.github.wulkanowy.data.db.entities
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
@ -8,32 +7,16 @@ import java.io.Serializable
@kotlinx.serialization.Serializable @kotlinx.serialization.Serializable
@Entity(tableName = "Recipients") @Entity(tableName = "Recipients")
data class Recipient( data class Recipient(
val mailboxGlobalKey: String,
@ColumnInfo(name = "student_id") val studentMailboxGlobalKey: String,
val studentId: Int, val fullName: String,
val userName: String,
@ColumnInfo(name = "real_id") val schoolShortName: String,
val realId: String, val type: MailboxType,
val name: String,
@ColumnInfo(name = "real_name")
val realName: String,
@ColumnInfo(name = "login_id")
val loginId: Int,
@ColumnInfo(name = "unit_id")
val unitId: Int,
val role: Int,
val hash: String
) : Serializable { ) : Serializable {
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
var id: Long = 0 var id: Long = 0
override fun toString() = name override fun toString() = userName
} }

View file

@ -1,32 +0,0 @@
package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable
@Entity(tableName = "ReportingUnits")
data class ReportingUnit(
@ColumnInfo(name = "student_id")
val studentId: Int,
@ColumnInfo(name = "real_id")
val unitId: Int,
@ColumnInfo(name = "short")
val shortName: String,
@ColumnInfo(name = "sender_id")
val senderId: Int,
@ColumnInfo(name = "sender_name")
val senderName: String,
val roles: List<Int>
) : Serializable {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}

View file

@ -9,8 +9,8 @@ import java.time.LocalDate
@Entity(tableName = "SchoolAnnouncements") @Entity(tableName = "SchoolAnnouncements")
data class SchoolAnnouncement( data class SchoolAnnouncement(
@ColumnInfo(name = "student_id") @ColumnInfo(name = "user_login_id")
val studentId: Int, val userLoginId: Int,
val date: LocalDate, val date: LocalDate,

View file

@ -0,0 +1,23 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration49 : Migration(48, 49) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS SchoolAnnouncements")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `SchoolAnnouncements` (
`user_login_id` INTEGER NOT NULL,
`date` INTEGER NOT NULL,
`subject` TEXT NOT NULL,
`content` TEXT NOT NULL,
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`is_notified` INTEGER NOT NULL)
""".trimIndent()
)
}
}

View file

@ -0,0 +1,21 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration50 : Migration(49, 50) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS MobileDevices")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `MobileDevices` (
`user_login_id` INTEGER NOT NULL,
`device_id` INTEGER NOT NULL,
`name` TEXT NOT NULL,
`date` INTEGER NOT NULL,
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)
""".trimIndent()
)
}
}

View file

@ -0,0 +1,88 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration51 : Migration(50, 51) {
override fun migrate(database: SupportSQLiteDatabase) {
createMailboxTable(database)
recreateMessagesTable(database)
recreateMessageAttachmentsTable(database)
recreateRecipientsTable(database)
deleteReportingUnitTable(database)
}
private fun createMailboxTable(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS Mailboxes")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `Mailboxes` (
`globalKey` TEXT NOT NULL,
`fullName` TEXT NOT NULL,
`userName` TEXT NOT NULL,
`userLoginId` INTEGER NOT NULL,
`studentName` TEXT NOT NULL,
`schoolNameShort` TEXT NOT NULL,
`type` TEXT NOT NULL,
PRIMARY KEY(`globalKey`)
)""".trimIndent()
)
}
private fun recreateMessagesTable(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS Messages")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `Messages` (
`message_global_key` TEXT NOT NULL,
`mailbox_key` TEXT NOT NULL,
`message_id` INTEGER NOT NULL,
`correspondents` TEXT NOT NULL,
`subject` TEXT NOT NULL,
`date` INTEGER NOT NULL,
`folder_id` INTEGER NOT NULL,
`unread` INTEGER NOT NULL,
`has_attachments` INTEGER NOT NULL,
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`is_notified` INTEGER NOT NULL,
`content` TEXT NOT NULL,
`sender` TEXT, `recipients` TEXT
)""".trimIndent()
)
}
private fun recreateMessageAttachmentsTable(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS MessageAttachments")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `MessageAttachments` (
`real_id` INTEGER NOT NULL,
`message_global_key` TEXT NOT NULL,
`url` TEXT NOT NULL,
`filename` TEXT NOT NULL,
PRIMARY KEY(`real_id`)
)""".trimIndent()
)
}
private fun recreateRecipientsTable(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS Recipients")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `Recipients` (
`mailboxGlobalKey` TEXT NOT NULL,
`studentMailboxGlobalKey` TEXT NOT NULL,
`fullName` TEXT NOT NULL,
`userName` TEXT NOT NULL,
`schoolShortName` TEXT NOT NULL,
`type` TEXT NOT NULL,
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL
)""".trimIndent()
)
}
private fun deleteReportingUnitTable(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS ReportingUnits")
}
}

View file

@ -0,0 +1,57 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration53 : Migration(52, 53) {
override fun migrate(database: SupportSQLiteDatabase) {
createMailboxTable(database)
recreateMessagesTable(database)
}
private fun createMailboxTable(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS Mailboxes")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `Mailboxes` (
`globalKey` TEXT NOT NULL,
`email` TEXT NOT NULL,
`symbol` TEXT NOT NULL,
`schoolId` TEXT NOT NULL,
`fullName` TEXT NOT NULL,
`userName` TEXT NOT NULL,
`studentName` TEXT NOT NULL,
`schoolNameShort` TEXT NOT NULL,
`type` TEXT NOT NULL,
PRIMARY KEY(`globalKey`)
)""".trimIndent()
)
}
private fun recreateMessagesTable(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS Messages")
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `Messages` (
`email` TEXT NOT NULL,
`message_global_key` TEXT NOT NULL,
`mailbox_key` TEXT NOT NULL,
`message_id` INTEGER NOT NULL,
`correspondents` TEXT NOT NULL,
`subject` TEXT NOT NULL,
`date` INTEGER NOT NULL,
`folder_id` INTEGER NOT NULL,
`unread` INTEGER NOT NULL,
`read_by` INTEGER,
`unread_by` INTEGER,
`has_attachments` INTEGER NOT NULL,
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`is_notified` INTEGER NOT NULL,
`content` TEXT NOT NULL,
`sender` TEXT,
`recipients` TEXT
)""".trimIndent()
)
}
}

View file

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

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.userLoginId, userLoginId = student.userLoginId,
date = it.date, date = it.date,
subject = it.subject, subject = it.subject,
content = it.content, content = it.content,

View file

@ -0,0 +1,20 @@
package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.MailboxType
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.sdk.pojo.Mailbox as SdkMailbox
fun List<SdkMailbox>.mapToEntities(student: Student) = map {
Mailbox(
globalKey = it.globalKey,
fullName = it.fullName,
userName = it.userName,
studentName = it.studentName,
schoolNameShort = it.schoolNameShort,
type = MailboxType.valueOf(it.type.name),
email = student.email,
symbol = student.symbol,
schoolId = student.schoolSymbol,
)
}

View file

@ -1,40 +1,36 @@
package io.github.wulkanowy.data.mappers package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.*
import io.github.wulkanowy.data.db.entities.MessageAttachment import io.github.wulkanowy.sdk.pojo.MailboxType
import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.Student
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
fun List<SdkMessage>.mapToEntities(student: Student) = map { fun List<SdkMessage>.mapToEntities(student: Student, mailbox: Mailbox?, allMailboxes: List<Mailbox>) = map {
Message( Message(
studentId = student.id, messageGlobalKey = it.globalKey,
realId = it.id ?: 0, mailboxKey = mailbox?.globalKey ?: allMailboxes.find { box ->
messageId = it.messageId ?: 0, box.fullName == it.mailbox
sender = it.sender?.name.orEmpty(), }?.globalKey!!,
senderId = it.sender?.loginId ?: 0, email = student.email,
recipient = it.recipients.singleOrNull()?.name ?: "Wielu adresatów", messageId = it.id,
correspondents = it.correspondents,
subject = it.subject.trim(), subject = it.subject.trim(),
date = it.dateZoned?.toInstant() ?: Instant.now(), date = it.dateZoned.toInstant(),
folderId = it.folderId, folderId = it.folderId,
unread = it.unread ?: false, unread = it.unread,
removed = it.removed, unreadBy = it.unreadBy,
hasAttachments = it.hasAttachments readBy = it.readBy,
hasAttachments = it.hasAttachments,
).apply { ).apply {
content = it.content.orEmpty() content = it.content.orEmpty()
unreadBy = it.unreadBy ?: 0
readBy = it.readBy ?: 0
} }
} }
fun List<SdkMessageAttachment>.mapToEntities() = map { fun List<SdkMessageAttachment>.mapToEntities(messageGlobalKey: String) = map {
MessageAttachment( MessageAttachment(
realId = it.id, messageGlobalKey = messageGlobalKey,
messageId = it.messageId, realId = it.url.hashCode(),
oneDriveId = it.oneDriveId,
url = it.url, url = it.url,
filename = it.filename filename = it.filename
) )
@ -42,12 +38,11 @@ fun List<SdkMessageAttachment>.mapToEntities() = map {
fun List<Recipient>.mapFromEntities() = map { fun List<Recipient>.mapFromEntities() = map {
SdkRecipient( SdkRecipient(
id = it.realId, fullName = it.fullName,
name = it.realName, userName = it.userName,
loginId = it.loginId, studentName = it.userName,
reportingUnitId = it.unitId, mailboxGlobalKey = it.mailboxGlobalKey,
role = it.role, schoolNameShort = it.schoolShortName,
hash = it.hash, type = MailboxType.valueOf(it.type.name),
shortName = it.name
) )
} }

View file

@ -1,14 +1,14 @@
package io.github.wulkanowy.data.mappers 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.Student
import io.github.wulkanowy.data.pojos.MobileDeviceToken import io.github.wulkanowy.data.pojos.MobileDeviceToken
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 import io.github.wulkanowy.sdk.pojo.Token as SdkToken
fun List<SdkDevice>.mapToEntities(semester: Semester) = map { fun List<SdkDevice>.mapToEntities(student: Student) = map {
MobileDevice( MobileDevice(
userLoginId = semester.studentId, userLoginId = student.userLoginId,
date = it.createDateZoned.toInstant(), date = it.createDateZoned.toInstant(),
deviceId = it.id, deviceId = it.id,
name = it.name name = it.name

View file

@ -1,17 +1,16 @@
package io.github.wulkanowy.data.mappers package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.MailboxType
import io.github.wulkanowy.data.db.entities.Recipient import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient import io.github.wulkanowy.sdk.pojo.Recipient as SdkRecipient
fun List<SdkRecipient>.mapToEntities(userLoginId: Int) = map { fun List<SdkRecipient>.mapToEntities(studentMailboxGlobalKey: String) = map {
Recipient( Recipient(
studentId = userLoginId, mailboxGlobalKey = it.mailboxGlobalKey,
realId = it.id, fullName = it.fullName,
realName = it.name, userName = it.userName,
name = it.shortName, studentMailboxGlobalKey = studentMailboxGlobalKey,
hash = it.hash, schoolShortName = it.schoolNameShort,
loginId = it.loginId, type = MailboxType.valueOf(it.type.name),
role = it.role,
unitId = it.reportingUnitId ?: 0
) )
} }

View file

@ -1,16 +0,0 @@
package io.github.wulkanowy.data.mappers
import io.github.wulkanowy.data.db.entities.ReportingUnit
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.sdk.pojo.ReportingUnit as SdkReportingUnit
fun List<SdkReportingUnit>.mapToEntities(student: Student) = map {
ReportingUnit(
studentId = student.id.toInt(),
unitId = it.id,
roles = it.roles,
senderId = it.senderId,
senderName = it.senderName,
shortName = it.short
)
}

View file

@ -5,29 +5,32 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.Resource
import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.db.dao.MailboxDao
import io.github.wulkanowy.data.db.dao.MessageAttachmentDao import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
import io.github.wulkanowy.data.db.dao.MessagesDao import io.github.wulkanowy.data.db.dao.MessagesDao
import io.github.wulkanowy.data.db.entities.* import io.github.wulkanowy.data.db.entities.*
import io.github.wulkanowy.data.enums.MessageFolder import io.github.wulkanowy.data.enums.MessageFolder
import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
import io.github.wulkanowy.data.enums.MessageFolder.TRASHED
import io.github.wulkanowy.data.mappers.mapFromEntities import io.github.wulkanowy.data.mappers.mapFromEntities
import io.github.wulkanowy.data.mappers.mapToEntities import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.data.networkBoundResource import io.github.wulkanowy.data.networkBoundResource
import io.github.wulkanowy.data.pojos.MessageDraft import io.github.wulkanowy.data.pojos.MessageDraft
import io.github.wulkanowy.data.toFirstResult
import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase
import io.github.wulkanowy.sdk.Sdk import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.sdk.pojo.Folder import io.github.wulkanowy.sdk.pojo.Folder
import io.github.wulkanowy.sdk.pojo.SentMessage
import io.github.wulkanowy.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.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import timber.log.Timber import timber.log.Timber
import java.time.LocalDateTime.now
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -40,16 +43,18 @@ class MessageRepository @Inject constructor(
private val refreshHelper: AutoRefreshHelper, private val refreshHelper: AutoRefreshHelper,
private val sharedPrefProvider: SharedPrefProvider, private val sharedPrefProvider: SharedPrefProvider,
private val json: Json, private val json: Json,
private val mailboxDao: MailboxDao,
private val getMailboxByStudentUseCase: GetMailboxByStudentUseCase,
) { ) {
private val saveFetchResultMutex = Mutex() private val saveFetchResultMutex = Mutex()
private val cacheKey = "message" private val messagesCacheKey = "message"
private val mailboxCacheKey = "mailboxes"
@Suppress("UNUSED_PARAMETER")
fun getMessages( fun getMessages(
student: Student, student: Student,
semester: Semester, mailbox: Mailbox?,
folder: MessageFolder, folder: MessageFolder,
forceRefresh: Boolean, forceRefresh: Boolean,
notify: Boolean = false, notify: Boolean = false,
@ -58,46 +63,33 @@ class MessageRepository @Inject constructor(
isResultEmpty = { it.isEmpty() }, isResultEmpty = { it.isEmpty() },
shouldFetch = { shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed( val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(cacheKey, student, folder) key = getRefreshKey(messagesCacheKey, mailbox, folder)
) )
it.isEmpty() || forceRefresh || isExpired it.isEmpty() || forceRefresh || isExpired
}, },
query = { messagesDb.loadAll(student.id.toInt(), folder.id) }, query = {
if (mailbox == null) {
messagesDb.loadAll(folder.id, student.email)
} else messagesDb.loadAll(mailbox.globalKey, folder.id)
},
fetch = { fetch = {
sdk.init(student).getMessages(Folder.valueOf(folder.name), now().minusMonths(3), now()) sdk.init(student).getMessages(
.mapToEntities(student) folder = Folder.valueOf(folder.name),
mailboxKey = mailbox?.globalKey,
).mapToEntities(student, mailbox, mailboxDao.loadAll(student.email))
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
messagesDb.deleteAll(old uniqueSubtract new) messagesDb.deleteAll(old uniqueSubtract new)
messagesDb.insertAll((new uniqueSubtract old).onEach { messagesDb.insertAll((new uniqueSubtract old).onEach {
it.isNotified = !notify it.isNotified = !notify
}) })
messagesDb.updateAll(getMessagesWithReadByChange(old, new, !notify))
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student, folder)) refreshHelper.updateLastRefreshTimestamp(
getRefreshKey(messagesCacheKey, mailbox, folder)
)
} }
) )
private fun getMessagesWithReadByChange(
old: List<Message>,
new: List<Message>,
setNotified: Boolean
): List<Message> {
val oldMeta = old.map { Triple(it, it.readBy, it.unreadBy) }
val newMeta = new.map { Triple(it, it.readBy, it.unreadBy) }
val updatedItems = newMeta uniqueSubtract oldMeta
return updatedItems.map {
val oldItem = old.find { item -> item.messageId == it.first.messageId }
it.first.apply {
id = oldItem?.id ?: 0
isNotified = oldItem?.isNotified ?: setNotified
content = oldItem?.content.orEmpty()
}
}
}
fun getMessage( fun getMessage(
student: Student, student: Student,
message: Message, message: Message,
@ -106,34 +98,38 @@ class MessageRepository @Inject constructor(
isResultEmpty = { it?.message?.content.isNullOrBlank() }, 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.isBlank()}")
it.message.unread || it.message.content.isEmpty() it.message.unread || it.message.content.isBlank()
},
query = {
messagesDb.loadMessageWithAttachment(message.messageGlobalKey)
}, },
query = { messagesDb.loadMessageWithAttachment(student.id.toInt(), message.messageId) },
fetch = { fetch = {
sdk.init(student).getMessageDetails( sdk.init(student).getMessageDetails(it!!.message.messageGlobalKey, markAsRead)
messageId = it!!.message.messageId,
folderId = message.folderId,
read = markAsRead,
id = message.realId
).let { details ->
details.content to details.attachments.mapToEntities()
}
}, },
saveFetchResult = { old, (downloadedMessage, attachments) -> saveFetchResult = { old, new ->
checkNotNull(old) { "Fetched message no longer exist!" } checkNotNull(old) { "Fetched message no longer exist!" }
messagesDb.updateAll(listOf(old.message.apply { messagesDb.updateAll(
id = old.message.id listOf(old.message.apply {
unread = !markAsRead id = message.id
content = content.ifBlank { downloadedMessage } unread = !markAsRead
})) sender = new.sender
messageAttachmentDao.insertAttachments(attachments) recipients = new.recipients.singleOrNull() ?: "Wielu adresatów"
content = content.ifBlank { new.content }
})
)
messageAttachmentDao.insertAttachments(
items = new.attachments.mapToEntities(message.messageGlobalKey),
)
Timber.d("Message ${message.messageId} with blank content: ${old.message.content.isBlank()}, marked as read") Timber.d("Message ${message.messageId} with blank content: ${old.message.content.isBlank()}, marked as read")
} }
) )
fun getMessagesFromDatabase(student: Student): Flow<List<Message>> { fun getMessagesFromDatabase(student: Student, mailbox: Mailbox?): Flow<List<Message>> {
return messagesDb.loadAll(student.id.toInt(), RECEIVED.id) return if (mailbox == null) {
messagesDb.loadAll(RECEIVED.id, student.email)
} else messagesDb.loadAll(mailbox.globalKey, RECEIVED.id)
} }
suspend fun updateMessages(messages: List<Message>) { suspend fun updateMessages(messages: List<Message>) {
@ -145,38 +141,82 @@ class MessageRepository @Inject constructor(
subject: String, subject: String,
content: String, content: String,
recipients: List<Recipient>, recipients: List<Recipient>,
): SentMessage = sdk.init(student).sendMessage( mailboxId: String,
subject = subject, ) {
content = content, sdk.init(student).sendMessage(
recipients = recipients.mapFromEntities() subject = subject,
) content = content,
recipients = recipients.mapFromEntities(),
mailboxId = mailboxId,
)
}
suspend fun deleteMessages(student: Student, messages: List<Message>) { suspend fun deleteMessages(student: Student, mailbox: Mailbox?, messages: List<Message>) {
val folderId = messages.first().folderId val firstMessage = messages.first()
val isDeleted = sdk.init(student) sdk.init(student).deleteMessages(
.deleteMessages(messages = messages.map { it.messageId }, folderId = folderId) messages = messages.map { it.messageGlobalKey },
removeForever = firstMessage.folderId == TRASHED.id,
)
if (folderId != MessageFolder.TRASHED.id && isDeleted) { if (firstMessage.folderId != TRASHED.id) {
val deletedMessages = messages.map { val deletedMessages = messages.map {
it.copy(folderId = MessageFolder.TRASHED.id) it.copy(folderId = TRASHED.id)
.apply { .apply {
id = it.id id = it.id
content = it.content content = it.content
sender = it.sender
recipients = it.recipients
} }
} }
messagesDb.updateAll(deletedMessages) messagesDb.updateAll(deletedMessages)
} else messagesDb.deleteAll(messages) } else messagesDb.deleteAll(messages)
getMessages(
student = student,
mailbox = mailbox,
folder = TRASHED,
forceRefresh = true,
).first()
} }
suspend fun deleteMessage(student: Student, message: Message) = suspend fun deleteMessage(student: Student, mailbox: Mailbox, message: Message) {
deleteMessages(student, listOf(message)) deleteMessages(student, mailbox, listOf(message))
}
suspend fun getMailboxes(student: Student, forceRefresh: Boolean) = networkBoundResource(
mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() },
shouldFetch = {
val isExpired = refreshHelper.shouldBeRefreshed(
key = getRefreshKey(mailboxCacheKey, student),
)
it.isEmpty() || isExpired || forceRefresh
},
query = { mailboxDao.loadAll(student.email, student.symbol, student.schoolSymbol) },
fetch = { sdk.init(student).getMailboxes().mapToEntities(student) },
saveFetchResult = { old, new ->
mailboxDao.deleteAll(old uniqueSubtract new)
mailboxDao.insertAll(new uniqueSubtract old)
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(mailboxCacheKey, student))
}
)
suspend fun getMailboxByStudent(student: Student): Mailbox? {
val mailbox = getMailboxByStudentUseCase(student)
return if (mailbox == null) {
getMailboxes(student, forceRefresh = true).toFirstResult()
getMailboxByStudentUseCase(student)
} else mailbox
}
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_draft))
?.let { json.decodeFromString(it) } ?.let { json.decodeFromString(it) }
set(value) = sharedPrefProvider.putString( set(value) = sharedPrefProvider.putString(
context.getString(R.string.pref_key_message_send_draft), context.getString(R.string.pref_key_message_draft),
value?.let { json.encodeToString(it) } value?.let { json.encodeToString(it) }
) )
} }

View file

@ -39,12 +39,12 @@ class MobileDeviceRepository @Inject constructor(
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
it.isEmpty() || forceRefresh || isExpired it.isEmpty() || forceRefresh || isExpired
}, },
query = { mobileDb.loadAll(student.userLoginId.takeIf { it != 0 } ?: student.studentId) }, query = { mobileDb.loadAll(student.userLoginId) },
fetch = { fetch = {
sdk.init(student) sdk.init(student)
.switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear) .switchDiary(semester.diaryId, semester.kindergartenDiaryId, semester.schoolYear)
.getRegisteredDevices() .getRegisteredDevices()
.mapToEntities(semester) .mapToEntities(student)
}, },
saveFetchResult = { old, new -> saveFetchResult = { old, new ->
mobileDb.deleteAll(old uniqueSubtract new) mobileDb.deleteAll(old uniqueSubtract new)

View file

@ -10,17 +10,16 @@ import io.github.wulkanowy.R
import io.github.wulkanowy.data.enums.* 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 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.time.Instant import java.time.Instant
import java.util.*
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@OptIn(ExperimentalCoroutinesApi::class)
@Singleton @Singleton
class PreferencesRepository @Inject constructor( class PreferencesRepository @Inject constructor(
@ApplicationContext val context: Context, @ApplicationContext val context: Context,
@ -222,19 +221,31 @@ class PreferencesRepository @Inject constructor(
get() = selectedDashboardTilesPreference.asFlow() get() = selectedDashboardTilesPreference.asFlow()
.map { set -> .map { set ->
set.map { DashboardItem.Tile.valueOf(it) } set.map { DashboardItem.Tile.valueOf(it) }
.plus(DashboardItem.Tile.ACCOUNT) .plus(
.plus(DashboardItem.Tile.ADMIN_MESSAGE) listOfNotNull(
DashboardItem.Tile.ACCOUNT,
DashboardItem.Tile.ADMIN_MESSAGE,
DashboardItem.Tile.ADS.takeIf { isAdsEnabled }
)
)
.toSet() .toSet()
} }
var selectedDashboardTiles: Set<DashboardItem.Tile> var selectedDashboardTiles: Set<DashboardItem.Tile>
get() = selectedDashboardTilesPreference.get() get() = selectedDashboardTilesPreference.get()
.map { DashboardItem.Tile.valueOf(it) } .map { DashboardItem.Tile.valueOf(it) }
.plus(DashboardItem.Tile.ACCOUNT) .plus(
.plus(DashboardItem.Tile.ADMIN_MESSAGE) listOfNotNull(
DashboardItem.Tile.ACCOUNT,
DashboardItem.Tile.ADMIN_MESSAGE,
DashboardItem.Tile.ADS.takeIf { isAdsEnabled }
)
)
.toSet() .toSet()
set(value) { set(value) {
val filteredValue = value.filterNot { it == DashboardItem.Tile.ACCOUNT } val filteredValue = value.filterNot {
it == DashboardItem.Tile.ACCOUNT || it == DashboardItem.Tile.ADMIN_MESSAGE
}
.map { it.name } .map { it.name }
.toSet() .toSet()
@ -271,7 +282,48 @@ class PreferencesRepository @Inject constructor(
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)
set(value) = sharedPref.edit().putBoolean(PREF_KEY_IN_APP_REVIEW_DONE, value).apply() set(value) = sharedPref.edit { putBoolean(PREF_KEY_IN_APP_REVIEW_DONE, value) }
var isAppSupportShown: Boolean
get() = sharedPref.getBoolean(PREF_KEY_APP_SUPPORT_SHOWN, false)
set(value) = sharedPref.edit { putBoolean(PREF_KEY_APP_SUPPORT_SHOWN, value) }
var isAgreeToProcessData: Boolean
get() = getBoolean(
R.string.pref_key_ads_consent_data_processing,
R.bool.pref_default_ads_consent_data_processing
)
set(value) = sharedPref.edit {
putBoolean(context.getString(R.string.pref_key_ads_consent_data_processing), value)
}
var isPersonalizedAdsEnabled: Boolean
get() = sharedPref.getBoolean(PREF_KEY_PERSONALIZED_ADS_ENABLED, false)
set(value) = sharedPref.edit { putBoolean(PREF_KEY_PERSONALIZED_ADS_ENABLED, value) }
val isAdsEnabledFlow = flowSharedPref.getBoolean(
context.getString(R.string.pref_key_ads_enabled),
context.resources.getBoolean(R.bool.pref_default_ads_enabled)
).asFlow()
var isAdsEnabled: Boolean
get() = getBoolean(
R.string.pref_key_ads_enabled,
R.bool.pref_default_ads_enabled
)
set(value) = sharedPref.edit {
putBoolean(context.getString(R.string.pref_key_ads_enabled), value)
}
var installationId: String
get() = sharedPref.getString(PREF_KEY_INSTALLATION_ID, null).orEmpty()
private set(value) = sharedPref.edit { putString(PREF_KEY_INSTALLATION_ID, value) }
init {
if (installationId.isEmpty()) {
installationId = UUID.randomUUID().toString()
}
}
private fun getLong(id: Int, default: Int) = getLong(context.getString(id), default) private fun getLong(id: Int, default: Int) = getLong(context.getString(id), default)
@ -288,19 +340,14 @@ class PreferencesRepository @Inject constructor(
private fun getBoolean(id: String, default: Int) = private fun getBoolean(id: String, default: Int) =
sharedPref.getBoolean(id, context.resources.getBoolean(default)) sharedPref.getBoolean(id, context.resources.getBoolean(default))
private fun getBoolean(id: Int, default: Boolean) =
sharedPref.getBoolean(context.getString(id), default)
private companion object { private companion object {
private const val PREF_KEY_INSTALLATION_ID = "installation_id"
private const val PREF_KEY_DASHBOARD_ITEMS_POSITION = "dashboard_items_position" private const val PREF_KEY_DASHBOARD_ITEMS_POSITION = "dashboard_items_position"
private const val PREF_KEY_IN_APP_REVIEW_COUNT = "in_app_review_count" private const val PREF_KEY_IN_APP_REVIEW_COUNT = "in_app_review_count"
private const val PREF_KEY_IN_APP_REVIEW_DATE = "in_app_review_date" private const val PREF_KEY_IN_APP_REVIEW_DATE = "in_app_review_date"
private const val PREF_KEY_IN_APP_REVIEW_DONE = "in_app_review_done" private const val PREF_KEY_IN_APP_REVIEW_DONE = "in_app_review_done"
private const val PREF_KEY_APP_SUPPORT_SHOWN = "app_support_shown"
private const val PREF_KEY_PERSONALIZED_ADS_ENABLED = "personalized_ads_enabled"
private const val PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS = "admin_message_dismissed_ids" private const val PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS = "admin_message_dismissed_ids"
} }
} }

View file

@ -1,10 +1,7 @@
package io.github.wulkanowy.data.repositories package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.db.dao.RecipientDao import io.github.wulkanowy.data.db.dao.RecipientDao
import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.*
import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.ReportingUnit
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.AutoRefreshHelper import io.github.wulkanowy.utils.AutoRefreshHelper
@ -23,9 +20,10 @@ class RecipientRepository @Inject constructor(
private val cacheKey = "recipient" private val cacheKey = "recipient"
suspend fun refreshRecipients(student: Student, unit: ReportingUnit, role: Int) { suspend fun refreshRecipients(student: Student, mailbox: Mailbox, type: MailboxType) {
val new = sdk.init(student).getRecipients(unit.unitId, role).mapToEntities(unit.studentId) val new = sdk.init(student).getRecipients(mailbox.globalKey)
val old = recipientDb.loadAll(unit.studentId, unit.unitId, role) .mapToEntities(mailbox.globalKey)
val old = recipientDb.loadAll(type, mailbox.globalKey)
recipientDb.deleteAll(old uniqueSubtract new) recipientDb.deleteAll(old uniqueSubtract new)
recipientDb.insertAll(new uniqueSubtract old) recipientDb.insertAll(new uniqueSubtract old)
@ -33,18 +31,33 @@ class RecipientRepository @Inject constructor(
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student)) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
} }
suspend fun getRecipients(student: Student, unit: ReportingUnit, role: Int): List<Recipient> { suspend fun getRecipients(
val cached = recipientDb.loadAll(unit.studentId, unit.unitId, role) student: Student,
mailbox: Mailbox?,
type: MailboxType,
): List<Recipient> {
mailbox ?: return emptyList()
val cached = recipientDb.loadAll(type, mailbox.globalKey)
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student)) val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
return if (cached.isEmpty() || isExpired) { return if (cached.isEmpty() || isExpired) {
refreshRecipients(student, unit, role) refreshRecipients(student, mailbox, type)
recipientDb.loadAll(unit.studentId, unit.unitId, role) recipientDb.loadAll(type, mailbox.globalKey)
} else cached } else cached
} }
suspend fun getMessageRecipients(student: Student, message: Message): List<Recipient> { suspend fun getMessageSender(
return sdk.init(student).getMessageRecipients(message.messageId, message.senderId) student: Student,
.mapToEntities(student.studentId) mailbox: Mailbox?,
message: Message,
): List<Recipient> {
mailbox ?: return emptyList()
return sdk.init(student)
.getMessageReplayDetails(message.messageGlobalKey)
.sender
.let(::listOf)
.mapToEntities(mailbox.globalKey)
} }
} }

View file

@ -1,42 +0,0 @@
package io.github.wulkanowy.data.repositories
import io.github.wulkanowy.data.db.dao.ReportingUnitDao
import io.github.wulkanowy.data.db.entities.ReportingUnit
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.mappers.mapToEntities
import io.github.wulkanowy.sdk.Sdk
import io.github.wulkanowy.utils.init
import io.github.wulkanowy.utils.uniqueSubtract
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ReportingUnitRepository @Inject constructor(
private val reportingUnitDb: ReportingUnitDao,
private val sdk: Sdk
) {
suspend fun refreshReportingUnits(student: Student) {
val new = sdk.init(student).getReportingUnits().mapToEntities(student)
val old = reportingUnitDb.load(student.id.toInt())
reportingUnitDb.deleteAll(old.uniqueSubtract(new))
reportingUnitDb.insertAll(new.uniqueSubtract(old))
}
suspend fun getReportingUnits(student: Student): List<ReportingUnit> {
return reportingUnitDb.load(student.id.toInt()).ifEmpty {
refreshReportingUnits(student)
reportingUnitDb.load(student.id.toInt())
}
}
suspend fun getReportingUnit(student: Student, unitId: Int): ReportingUnit? {
return reportingUnitDb.loadOne(student.id.toInt(), unitId) ?: run {
refreshReportingUnits(student)
return reportingUnitDb.loadOne(student.id.toInt(), unitId)
}
}
}

View file

@ -28,7 +28,8 @@ class SchoolAnnouncementRepository @Inject constructor(
fun getSchoolAnnouncements( fun getSchoolAnnouncements(
student: Student, student: Student,
forceRefresh: Boolean, notify: Boolean = false forceRefresh: Boolean,
notify: Boolean = false
) = networkBoundResource( ) = networkBoundResource(
mutex = saveFetchResultMutex, mutex = saveFetchResultMutex,
isResultEmpty = { it.isEmpty() }, isResultEmpty = { it.isEmpty() },
@ -37,7 +38,7 @@ class SchoolAnnouncementRepository @Inject constructor(
it.isEmpty() || forceRefresh || isExpired it.isEmpty() || forceRefresh || isExpired
}, },
query = { query = {
schoolAnnouncementDb.loadAll(student.studentId) schoolAnnouncementDb.loadAll(student.userLoginId)
}, },
fetch = { fetch = {
sdk.init(student) sdk.init(student)
@ -56,7 +57,7 @@ class SchoolAnnouncementRepository @Inject constructor(
) )
fun getSchoolAnnouncementFromDatabase(student: Student): Flow<List<SchoolAnnouncement>> { fun getSchoolAnnouncementFromDatabase(student: Student): Flow<List<SchoolAnnouncement>> {
return schoolAnnouncementDb.loadAll(student.studentId) return schoolAnnouncementDb.loadAll(student.userLoginId)
} }
suspend fun updateSchoolAnnouncement(schoolAnnouncement: List<SchoolAnnouncement>) = suspend fun updateSchoolAnnouncement(schoolAnnouncement: List<SchoolAnnouncement>) =

View file

@ -0,0 +1,52 @@
package io.github.wulkanowy.domain.messages
import io.github.wulkanowy.data.db.dao.MailboxDao
import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.Student
import javax.inject.Inject
class GetMailboxByStudentUseCase @Inject constructor(
private val mailboxDao: MailboxDao,
) {
suspend operator fun invoke(student: Student): Mailbox? {
return mailboxDao.loadAll(student.email)
.filterByStudent(student)
}
private fun List<Mailbox>.filterByStudent(student: Student): Mailbox? {
val normalizedStudentName = student.studentName.normalizeStudentName()
return find {
it.studentName.normalizeStudentName() == normalizedStudentName
} ?: singleOrNull {
it.studentName.getFirstAndLastPart() == normalizedStudentName.getFirstAndLastPart()
} ?: singleOrNull {
it.studentName.getUnauthorizedVersion() == normalizedStudentName
}
}
private fun String.normalizeStudentName(): String {
return trim().split(" ")
.filter { it.isNotBlank() }
.joinToString(" ") { part ->
part.lowercase().replaceFirstChar { it.uppercase() }
}
}
private fun String.getFirstAndLastPart(): String {
val parts = normalizeStudentName().split(" ")
val endParts = parts.filterIndexed { i, _ ->
i == 0 || parts.size - 1 == i
}
return endParts.joinToString(" ")
}
private fun String.getUnauthorizedVersion(): String {
return normalizeStudentName().split(" ")
.joinToString(" ") {
it.first() + "*".repeat(it.length - 1)
}
}
}

View file

@ -8,7 +8,6 @@ import io.github.wulkanowy.data.db.entities.Student
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.descriptionRes import io.github.wulkanowy.utils.descriptionRes
import io.github.wulkanowy.utils.getPlural import io.github.wulkanowy.utils.getPlural
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
@ -22,8 +21,9 @@ class NewAttendanceNotification @Inject constructor(
suspend fun notify(items: List<Attendance>, student: Student) { suspend fun notify(items: List<Attendance>, student: Student) {
val lines = items.filterNot { it.presence || it.name == "UNKNOWN" } val lines = items.filterNot { it.presence || it.name == "UNKNOWN" }
.map { .map {
val lesson = it.subject.ifBlank { "Lekcja ${it.number}" }
val description = context.getString(it.descriptionRes) val description = context.getString(it.descriptionRes)
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: $description" "${it.date.toFormattedString("dd.MM")} - $lesson: $description"
} }
.ifEmpty { return } .ifEmpty { return }

View file

@ -8,7 +8,6 @@ import io.github.wulkanowy.data.db.entities.Student
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 javax.inject.Inject import javax.inject.Inject
@ -21,7 +20,7 @@ class NewMessageNotification @Inject constructor(
val notificationDataList = items.map { val notificationDataList = items.map {
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.correspondents}: ${it.subject}",
destination = Destination.Message, destination = Destination.Message,
) )
} }

View file

@ -15,15 +15,16 @@ class MessageWork @Inject constructor(
) : Work { ) : Work {
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
val mailbox = messageRepository.getMailboxByStudent(student)
messageRepository.getMessages( messageRepository.getMessages(
student = student, student = student,
semester = semester, mailbox = mailbox,
folder = RECEIVED, folder = RECEIVED,
forceRefresh = true, forceRefresh = true,
notify = notify notify = notify
).waitForResult() ).waitForResult()
messageRepository.getMessagesFromDatabase(student).first() messageRepository.getMessagesFromDatabase(student, mailbox).first()
.filter { !it.isNotified && it.unread }.let { .filter { !it.isNotified && it.unread }.let {
if (it.isNotEmpty()) newMessageNotification.notify(it, student) if (it.isNotEmpty()) newMessageNotification.notify(it, student)
messageRepository.updateMessages(it.onEach { message -> message.isNotified = true }) messageRepository.updateMessages(it.onEach { message -> message.isNotified = true })

View file

@ -1,23 +1,23 @@
package io.github.wulkanowy.services.sync.works package io.github.wulkanowy.services.sync.works
import io.github.wulkanowy.data.dataOrNull
import io.github.wulkanowy.data.db.entities.MailboxType
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.MessageRepository
import io.github.wulkanowy.data.repositories.RecipientRepository import io.github.wulkanowy.data.repositories.RecipientRepository
import io.github.wulkanowy.data.repositories.ReportingUnitRepository import io.github.wulkanowy.data.toFirstResult
import javax.inject.Inject import javax.inject.Inject
class RecipientWork @Inject constructor( class RecipientWork @Inject constructor(
private val reportingUnitRepository: ReportingUnitRepository, private val messageRepository: MessageRepository,
private val recipientRepository: RecipientRepository private val recipientRepository: RecipientRepository
) : Work { ) : Work {
override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) { override suspend fun doWork(student: Student, semester: Semester, notify: Boolean) {
reportingUnitRepository.refreshReportingUnits(student) val mailboxes = messageRepository.getMailboxes(student, forceRefresh = true).toFirstResult()
mailboxes.dataOrNull?.forEach {
reportingUnitRepository.getReportingUnits(student).let { units -> recipientRepository.refreshRecipients(student, it, MailboxType.EMPLOYEE)
units.map {
recipientRepository.refreshRecipients(student, it, 2)
}
} }
} }
} }

View file

@ -6,6 +6,7 @@ import io.github.wulkanowy.data.repositories.SchoolAnnouncementRepository
import io.github.wulkanowy.data.waitForResult import io.github.wulkanowy.data.waitForResult
import io.github.wulkanowy.services.sync.notifications.NewSchoolAnnouncementNotification import io.github.wulkanowy.services.sync.notifications.NewSchoolAnnouncementNotification
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import java.time.LocalDate
import javax.inject.Inject import javax.inject.Inject
class SchoolAnnouncementWork @Inject constructor( class SchoolAnnouncementWork @Inject constructor(
@ -20,10 +21,13 @@ class SchoolAnnouncementWork @Inject constructor(
notify = notify, notify = notify,
).waitForResult() ).waitForResult()
schoolAnnouncementRepository.getSchoolAnnouncementFromDatabase(student)
schoolAnnouncementRepository.getSchoolAnnouncementFromDatabase(student).first() .first()
.filter { !it.isNotified }.let { .filter { !it.isNotified && it.date >= LocalDate.now() }
if (it.isNotEmpty()) newSchoolAnnouncementNotification.notify(it, student) .let {
if (it.isNotEmpty()) {
newSchoolAnnouncementNotification.notify(it, student)
}
schoolAnnouncementRepository.updateSchoolAnnouncement(it.onEach { schoolAnnouncement -> schoolAnnouncementRepository.updateSchoolAnnouncement(it.onEach { schoolAnnouncement ->
schoolAnnouncement.isNotified = true schoolAnnouncement.isNotified = true

View file

@ -4,7 +4,6 @@ import android.app.Dialog
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.widget.Toast import android.widget.Toast
import android.widget.Toast.LENGTH_LONG import android.widget.Toast.LENGTH_LONG
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
@ -15,6 +14,7 @@ import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
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.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.DialogErrorBinding import io.github.wulkanowy.databinding.DialogErrorBinding
import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.*
import javax.inject.Inject import javax.inject.Inject
@ -25,6 +25,9 @@ class ErrorDialog : DialogFragment() {
@Inject @Inject
lateinit var appInfo: AppInfo lateinit var appInfo: AppInfo
@Inject
lateinit var preferencesRepository: PreferencesRepository
companion object { companion object {
private const val ARGUMENT_KEY = "error" private const val ARGUMENT_KEY = "error"
@ -36,7 +39,7 @@ class ErrorDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val error = requireArguments().getSerializable(ARGUMENT_KEY) as Throwable val error = requireArguments().getSerializable(ARGUMENT_KEY) as Throwable
val binding = DialogErrorBinding.inflate(LayoutInflater.from(context)) val binding = DialogErrorBinding.inflate(layoutInflater)
binding.bindErrorDetails(error) binding.bindErrorDetails(error)
return getAlertDialog(binding, error).apply { return getAlertDialog(binding, error).apply {
@ -99,7 +102,8 @@ class ErrorDialog : DialogFragment() {
R.string.about_feedback_template, R.string.about_feedback_template,
"${appInfo.systemManufacturer} ${appInfo.systemModel}", "${appInfo.systemManufacturer} ${appInfo.systemModel}",
appInfo.systemVersion.toString(), appInfo.systemVersion.toString(),
"${appInfo.versionName}-${appInfo.buildFlavor}" "${appInfo.versionName}-${appInfo.buildFlavor}",
preferencesRepository.installationId,
) + "\n" + content, ) + "\n" + content,
onActivityNotFound = { onActivityNotFound = {
requireContext().openInternetBrowser( requireContext().openInternetBrowser(

View file

@ -6,6 +6,7 @@ import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
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.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.FragmentAboutBinding import io.github.wulkanowy.databinding.FragmentAboutBinding
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.about.contributor.ContributorFragment import io.github.wulkanowy.ui.modules.about.contributor.ContributorFragment
@ -30,6 +31,9 @@ class AboutFragment : BaseFragment<FragmentAboutBinding>(R.layout.fragment_about
@Inject @Inject
lateinit var appInfo: AppInfo lateinit var appInfo: AppInfo
@Inject
lateinit var preferencesRepository: PreferencesRepository
override val versionRes: Triple<String, String, Drawable?>? override val versionRes: Triple<String, String, Drawable?>?
get() = context?.run { get() = context?.run {
val buildTimestamp = val buildTimestamp =
@ -185,7 +189,8 @@ class AboutFragment : BaseFragment<FragmentAboutBinding>(R.layout.fragment_about
R.string.about_feedback_template, R.string.about_feedback_template,
"${appInfo.systemManufacturer} ${appInfo.systemModel}", "${appInfo.systemManufacturer} ${appInfo.systemModel}",
appInfo.systemVersion.toString(), appInfo.systemVersion.toString(),
"${appInfo.versionName}-${appInfo.buildFlavor}" "${appInfo.versionName}-${appInfo.buildFlavor}",
preferencesRepository.installationId,
), ),
onActivityNotFound = { onActivityNotFound = {
requireContext().openInternetBrowser( requireContext().openInternetBrowser(

View file

@ -23,8 +23,9 @@ class LicenseAdapter @Inject constructor() : RecyclerView.Adapter<LicenseAdapter
val item = items[position] val item = items[position]
with(holder.binding) { with(holder.binding) {
licenseItemName.text = item.libraryName licenseItemName.text = item.name
licenseItemSummary.text = item.licenses?.firstOrNull()?.licenseName?.takeIf { it.isNotBlank() } ?: item.libraryVersion licenseItemSummary.text = item.licenses.firstOrNull()?.name?.takeIf { it.isNotBlank() }
?: item.artifactVersion
root.setOnClickListener { onClickListener(item) } root.setOnClickListener { onClickListener(item) }
} }

View file

@ -9,6 +9,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.mikepenz.aboutlibraries.Libs import com.mikepenz.aboutlibraries.Libs
import com.mikepenz.aboutlibraries.entity.Library import com.mikepenz.aboutlibraries.entity.Library
import com.mikepenz.aboutlibraries.util.withContext
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.databinding.FragmentLicenseBinding import io.github.wulkanowy.databinding.FragmentLicenseBinding
@ -28,7 +29,9 @@ class LicenseFragment : BaseFragment<FragmentLicenseBinding>(R.layout.fragment_l
override val titleStringId get() = R.string.license_title override val titleStringId get() = R.string.license_title
override val appLibraries by lazy { Libs(requireContext()).libraries } override val appLibraries by lazy {
Libs.Builder().withContext(requireContext()).build().libraries
}
companion object { companion object {
fun newInstance() = LicenseFragment() fun newInstance() = LicenseFragment()

View file

@ -22,7 +22,7 @@ class LicensePresenter @Inject constructor(
} }
fun onItemSelected(library: Library) { fun onItemSelected(library: Library) {
view?.run { library.licenses?.firstOrNull()?.licenseDescription?.let { openLicense(it) } } view?.run { library.licenses.firstOrNull()?.licenseContent?.let { openLicense(it) } }
} }
private fun loadData() { private fun loadData() {

View file

@ -35,9 +35,11 @@ class AttendanceAdapter @Inject constructor() :
with(holder.binding) { with(holder.binding) {
attendanceItemNumber.text = item.number.toString() attendanceItemNumber.text = item.number.toString()
attendanceItemSubject.text = item.subject attendanceItemSubject.text = item.subject.ifBlank {
root.context.getString(R.string.all_no_data)
}
attendanceItemDescription.setText(item.descriptionRes) attendanceItemDescription.setText(item.descriptionRes)
attendanceItemAlert.visibility = item.run { if (absence && !excused) View.VISIBLE else View.INVISIBLE } attendanceItemAlert.isVisible = item.let { it.absence && !it.excused }
attendanceItemNumber.visibility = View.GONE attendanceItemNumber.visibility = View.GONE
attendanceItemExcuseInfo.visibility = View.GONE attendanceItemExcuseInfo.visibility = View.GONE
attendanceItemExcuseCheckbox.visibility = View.GONE attendanceItemExcuseCheckbox.visibility = View.GONE
@ -46,7 +48,7 @@ class AttendanceAdapter @Inject constructor() :
onExcuseCheckboxSelect(item, checked) onExcuseCheckboxSelect(item, checked)
} }
when (item.excuseStatus?.let { SentExcuseStatus.valueOf(it)}) { when (item.excuseStatus?.let { SentExcuseStatus.valueOf(it) }) {
SentExcuseStatus.WAITING -> { SentExcuseStatus.WAITING -> {
attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_waiting) attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_waiting)
attendanceItemExcuseInfo.visibility = View.VISIBLE attendanceItemExcuseInfo.visibility = View.VISIBLE

View file

@ -91,15 +91,19 @@ class AttendancePresenter @Inject constructor(
fun onViewReselected() { fun onViewReselected() {
Timber.i("Attendance view is reselected") Timber.i("Attendance view is reselected")
view?.also { view -> view?.let { view ->
if (view.currentStackSize == 1) { if (view.currentStackSize == 1) {
baseDate.also { baseDate = now().previousOrSameSchoolDay
if (currentDate != it) {
reloadView(it) if (currentDate != baseDate) {
loadData() reloadView(baseDate)
} else if (!view.isViewEmpty) view.resetView() loadData()
} else if (!view.isViewEmpty) {
view.resetView()
} }
} else view.popView() } else {
view.popView()
}
} }
} }

View file

@ -18,6 +18,7 @@ import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment
import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment
import io.github.wulkanowy.ui.modules.conference.ConferenceFragment import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
import io.github.wulkanowy.ui.modules.dashboard.adapters.DashboardAdapter
import io.github.wulkanowy.ui.modules.exam.ExamFragment import io.github.wulkanowy.ui.modules.exam.ExamFragment
import io.github.wulkanowy.ui.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment
import io.github.wulkanowy.ui.modules.homework.HomeworkFragment import io.github.wulkanowy.ui.modules.homework.HomeworkFragment
@ -47,6 +48,14 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
override var subtitleString = override var subtitleString =
LocalDate.now().toFormattedString("EEEE, d MMMM yyyy").capitalise() LocalDate.now().toFormattedString("EEEE, d MMMM yyyy").capitalise()
override val tileWidth: Int
get() {
val recyclerWidth = binding.dashboardRecycler.width
val margin = requireContext().dpToPx(24f).toInt()
return ((recyclerWidth - margin) / resources.displayMetrics.density).toInt()
}
companion object { companion object {
fun newInstance() = DashboardFragment() fun newInstance() = DashboardFragment()

View file

@ -1,13 +1,9 @@
package io.github.wulkanowy.ui.modules.dashboard package io.github.wulkanowy.ui.modules.dashboard
import io.github.wulkanowy.data.db.entities.AdminMessage import io.github.wulkanowy.data.db.entities.*
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.SchoolAnnouncement
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.enums.GradeColorTheme import io.github.wulkanowy.data.enums.GradeColorTheme
import io.github.wulkanowy.data.pojos.TimetableFull import io.github.wulkanowy.data.pojos.TimetableFull
import io.github.wulkanowy.utils.AdBanner
import io.github.wulkanowy.data.db.entities.Homework as EntitiesHomework import io.github.wulkanowy.data.db.entities.Homework as EntitiesHomework
sealed class DashboardItem(val type: Type) { sealed class DashboardItem(val type: Type) {
@ -106,17 +102,26 @@ sealed class DashboardItem(val type: Type) {
override val isDataLoaded get() = conferences != null override val isDataLoaded get() = conferences != null
} }
data class Ads(
val adBanner: AdBanner? = null,
override val error: Throwable? = null,
override val isLoading: Boolean = false
) : DashboardItem(Type.ADS) {
override val isDataLoaded get() = adBanner != null
}
enum class Type { enum class Type {
ADMIN_MESSAGE, ADMIN_MESSAGE,
ACCOUNT, ACCOUNT,
HORIZONTAL_GROUP, HORIZONTAL_GROUP,
LESSONS, LESSONS,
ADS,
GRADES, GRADES,
HOMEWORK, HOMEWORK,
ANNOUNCEMENTS, ANNOUNCEMENTS,
EXAMS, EXAMS,
CONFERENCES, CONFERENCES,
ADS
} }
enum class Tile { enum class Tile {
@ -126,12 +131,12 @@ sealed class DashboardItem(val type: Type) {
MESSAGES, MESSAGES,
ATTENDANCE, ATTENDANCE,
LESSONS, LESSONS,
ADS,
GRADES, GRADES,
HOMEWORK, HOMEWORK,
ANNOUNCEMENTS, ANNOUNCEMENTS,
EXAMS, EXAMS,
CONFERENCES, CONFERENCES,
ADS
} }
} }
@ -148,4 +153,4 @@ fun DashboardItem.Tile.toDashboardItemType() = when (this) {
DashboardItem.Tile.EXAMS -> DashboardItem.Type.EXAMS DashboardItem.Tile.EXAMS -> DashboardItem.Type.EXAMS
DashboardItem.Tile.CONFERENCES -> DashboardItem.Type.CONFERENCES DashboardItem.Tile.CONFERENCES -> DashboardItem.Type.CONFERENCES
DashboardItem.Tile.ADS -> DashboardItem.Type.ADS DashboardItem.Tile.ADS -> DashboardItem.Type.ADS
} }

View file

@ -2,7 +2,8 @@ package io.github.wulkanowy.ui.modules.dashboard
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import java.util.Collections import io.github.wulkanowy.ui.modules.dashboard.adapters.DashboardAdapter
import java.util.*
class DashboardItemMoveCallback( class DashboardItemMoveCallback(
private val dashboardAdapter: DashboardAdapter, private val dashboardAdapter: DashboardAdapter,

View file

@ -8,6 +8,7 @@ import io.github.wulkanowy.data.enums.MessageFolder
import io.github.wulkanowy.data.repositories.* import io.github.wulkanowy.data.repositories.*
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AdsHelper
import io.github.wulkanowy.utils.calculatePercentage import io.github.wulkanowy.utils.calculatePercentage
import io.github.wulkanowy.utils.nextOrSameSchoolDay import io.github.wulkanowy.utils.nextOrSameSchoolDay
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
@ -31,7 +32,8 @@ class DashboardPresenter @Inject constructor(
private val conferenceRepository: ConferenceRepository, private val conferenceRepository: ConferenceRepository,
private val preferencesRepository: PreferencesRepository, private val preferencesRepository: PreferencesRepository,
private val schoolAnnouncementRepository: SchoolAnnouncementRepository, private val schoolAnnouncementRepository: SchoolAnnouncementRepository,
private val adminMessageRepository: AdminMessageRepository private val adminMessageRepository: AdminMessageRepository,
private val adsHelper: AdsHelper
) : BasePresenter<DashboardView>(errorHandler, studentRepository) { ) : BasePresenter<DashboardView>(errorHandler, studentRepository) {
private val dashboardItemLoadedList = mutableListOf<DashboardItem>() private val dashboardItemLoadedList = mutableListOf<DashboardItem>()
@ -55,7 +57,11 @@ class DashboardPresenter @Inject constructor(
showContent(false) showContent(false)
} }
preferencesRepository.selectedDashboardTilesFlow merge(
preferencesRepository.selectedDashboardTilesFlow,
preferencesRepository.isAdsEnabledFlow
.map { preferencesRepository.selectedDashboardTiles }
)
.onEach { loadData(tilesToLoad = it) } .onEach { loadData(tilesToLoad = it) }
.launch("dashboard_pref") .launch("dashboard_pref")
} }
@ -166,7 +172,7 @@ class DashboardPresenter @Inject constructor(
DashboardItem.Type.CONFERENCES -> { DashboardItem.Type.CONFERENCES -> {
loadConferences(student, forceRefresh) loadConferences(student, forceRefresh)
} }
DashboardItem.Type.ADS -> TODO() DashboardItem.Type.ADS -> loadAds(forceRefresh)
DashboardItem.Type.ADMIN_MESSAGE -> loadAdminMessage(student, forceRefresh) DashboardItem.Type.ADMIN_MESSAGE -> loadAdminMessage(student, forceRefresh)
} }
} }
@ -221,6 +227,7 @@ class DashboardPresenter @Inject constructor(
private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) { private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) {
flow { flow {
val semester = semesterRepository.getCurrentSemester(student) val semester = semesterRepository.getCurrentSemester(student)
val mailbox = messageRepository.getMailboxByStudent(student)
val selectedTiles = preferencesRepository.selectedDashboardTiles val selectedTiles = preferencesRepository.selectedDashboardTiles
val flowSuccess = flowOf(Resource.Success(null)) val flowSuccess = flowOf(Resource.Success(null))
@ -232,7 +239,7 @@ class DashboardPresenter @Inject constructor(
val messageFLow = messageRepository.getMessages( val messageFLow = messageRepository.getMessages(
student = student, student = student,
semester = semester, mailbox = mailbox,
folder = MessageFolder.RECEIVED, folder = MessageFolder.RECEIVED,
forceRefresh = forceRefresh forceRefresh = forceRefresh
).takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowSuccess ).takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowSuccess
@ -595,6 +602,23 @@ class DashboardPresenter @Inject constructor(
.launchWithUniqueRefreshJob("dashboard_admin_messages", forceRefresh) .launchWithUniqueRefreshJob("dashboard_admin_messages", forceRefresh)
} }
private fun loadAds(forceRefresh: Boolean) {
presenterScope.launch {
if (!forceRefresh) {
updateData(DashboardItem.Ads(), forceRefresh)
}
val dashboardAdItem =
runCatching {
DashboardItem.Ads(adsHelper.getDashboardTileAdBanner(view!!.tileWidth))
}
.onFailure { Timber.e(it) }
.getOrElse { DashboardItem.Ads(error = it) }
updateData(dashboardAdItem, forceRefresh)
}
}
private fun updateData(dashboardItem: DashboardItem, forceRefresh: Boolean) { private fun updateData(dashboardItem: DashboardItem, forceRefresh: Boolean) {
val isForceRefreshError = forceRefresh && dashboardItem.error != null val isForceRefreshError = forceRefresh && dashboardItem.error != null
val isFirstRunDataLoadedError = val isFirstRunDataLoadedError =
@ -619,6 +643,18 @@ class DashboardPresenter @Inject constructor(
} }
} }
if (dashboardItem is DashboardItem.Ads) {
if (!dashboardItem.isDataLoaded) {
dashboardItemsToLoad = dashboardItemsToLoad - DashboardItem.Type.ADS
dashboardTileLoadedList = dashboardTileLoadedList - DashboardItem.Tile.ADS
dashboardItemLoadedList.removeAll { it.type == DashboardItem.Type.ADS }
} else {
dashboardItemsToLoad = dashboardItemsToLoad + DashboardItem.Type.ADS
dashboardTileLoadedList = dashboardTileLoadedList + DashboardItem.Tile.ADS
}
}
if (forceRefresh) { if (forceRefresh) {
updateForceRefreshData(dashboardItem) updateForceRefreshData(dashboardItem)
} else { } else {

View file

@ -4,6 +4,8 @@ import io.github.wulkanowy.ui.base.BaseView
interface DashboardView : BaseView { interface DashboardView : BaseView {
val tileWidth: Int
fun initView() fun initView()
fun updateData(data: List<DashboardItem>) fun updateData(data: List<DashboardItem>)
@ -27,4 +29,4 @@ interface DashboardView : BaseView {
fun openNotificationsCenterView() fun openNotificationsCenterView()
fun openInternetBrowser(url: String) fun openInternetBrowser(url: String)
} }

View file

@ -1,4 +1,4 @@
package io.github.wulkanowy.ui.modules.dashboard package io.github.wulkanowy.ui.modules.dashboard.adapters
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.res.ColorStateList import android.content.res.ColorStateList
@ -22,24 +22,15 @@ import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.db.entities.TimetableHeader import io.github.wulkanowy.data.db.entities.TimetableHeader
import io.github.wulkanowy.data.enums.GradeColorTheme import io.github.wulkanowy.data.enums.GradeColorTheme
import io.github.wulkanowy.databinding.ItemDashboardAccountBinding import io.github.wulkanowy.databinding.*
import io.github.wulkanowy.databinding.ItemDashboardAdminMessageBinding import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
import io.github.wulkanowy.databinding.ItemDashboardAnnouncementsBinding import io.github.wulkanowy.utils.*
import io.github.wulkanowy.databinding.ItemDashboardConferencesBinding
import io.github.wulkanowy.databinding.ItemDashboardExamsBinding
import io.github.wulkanowy.databinding.ItemDashboardGradesBinding
import io.github.wulkanowy.databinding.ItemDashboardHomeworkBinding
import io.github.wulkanowy.databinding.ItemDashboardHorizontalGroupBinding
import io.github.wulkanowy.databinding.ItemDashboardLessonsBinding
import io.github.wulkanowy.utils.createNameInitialsDrawable
import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.left
import io.github.wulkanowy.utils.nickOrName
import io.github.wulkanowy.utils.toFormattedString
import timber.log.Timber import timber.log.Timber
import java.time.* import java.time.Duration
import java.util.Timer import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.util.*
import javax.inject.Inject import javax.inject.Inject
import kotlin.concurrent.timer import kotlin.concurrent.timer
@ -120,6 +111,9 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
DashboardItem.Type.ADMIN_MESSAGE.ordinal -> AdminMessageViewHolder( DashboardItem.Type.ADMIN_MESSAGE.ordinal -> AdminMessageViewHolder(
ItemDashboardAdminMessageBinding.inflate(inflater, parent, false) ItemDashboardAdminMessageBinding.inflate(inflater, parent, false)
) )
DashboardItem.Type.ADS.ordinal -> AdsViewHolder(
ItemDashboardAdsBinding.inflate(inflater, parent, false)
)
else -> throw IllegalArgumentException() else -> throw IllegalArgumentException()
} }
} }
@ -135,6 +129,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
is ExamsViewHolder -> bindExamsViewHolder(holder, position) is ExamsViewHolder -> bindExamsViewHolder(holder, position)
is ConferencesViewHolder -> bindConferencesViewHolder(holder, position) is ConferencesViewHolder -> bindConferencesViewHolder(holder, position)
is AdminMessageViewHolder -> bindAdminMessage(holder, position) is AdminMessageViewHolder -> bindAdminMessage(holder, position)
is AdsViewHolder -> bindAdsViewHolder(holder, position)
} }
} }
@ -746,6 +741,20 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
} }
} }
private fun bindAdsViewHolder(adsViewHolder: AdsViewHolder, position: Int) {
val item = (items[position] as DashboardItem.Ads).adBanner ?: return
val binding = adsViewHolder.binding
binding.dashboardAdminMessageItemContent.removeAllViews()
binding.dashboardAdminMessageItemContent.addView(
item.view,
ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
)
}
class AccountViewHolder(val binding: ItemDashboardAccountBinding) : class AccountViewHolder(val binding: ItemDashboardAccountBinding) :
RecyclerView.ViewHolder(binding.root) RecyclerView.ViewHolder(binding.root)
@ -788,6 +797,9 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
class AdminMessageViewHolder(val binding: ItemDashboardAdminMessageBinding) : class AdminMessageViewHolder(val binding: ItemDashboardAdminMessageBinding) :
RecyclerView.ViewHolder(binding.root) RecyclerView.ViewHolder(binding.root)
class AdsViewHolder(val binding: ItemDashboardAdsBinding) :
RecyclerView.ViewHolder(binding.root)
private class DiffCallback( private class DiffCallback(
private val newList: List<DashboardItem>, private val newList: List<DashboardItem>,
private val oldList: List<DashboardItem> private val oldList: List<DashboardItem>

View file

@ -1,4 +1,4 @@
package io.github.wulkanowy.ui.modules.dashboard package io.github.wulkanowy.ui.modules.dashboard.adapters
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
@ -33,4 +33,4 @@ class DashboardAnnouncementsAdapter :
class ViewHolder(val binding: SubitemDashboardAnnouncementsBinding) : class ViewHolder(val binding: SubitemDashboardAnnouncementsBinding) :
RecyclerView.ViewHolder(binding.root) RecyclerView.ViewHolder(binding.root)
} }

View file

@ -1,4 +1,4 @@
package io.github.wulkanowy.ui.modules.dashboard package io.github.wulkanowy.ui.modules.dashboard.adapters
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
@ -33,4 +33,4 @@ class DashboardConferencesAdapter :
class ViewHolder(val binding: SubitemDashboardConferencesBinding) : class ViewHolder(val binding: SubitemDashboardConferencesBinding) :
RecyclerView.ViewHolder(binding.root) RecyclerView.ViewHolder(binding.root)
} }

View file

@ -1,4 +1,4 @@
package io.github.wulkanowy.ui.modules.dashboard package io.github.wulkanowy.ui.modules.dashboard.adapters
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.view.LayoutInflater import android.view.LayoutInflater
@ -56,4 +56,4 @@ class DashboardExamsAdapter :
class ViewHolder(val binding: SubitemDashboardExamsBinding) : class ViewHolder(val binding: SubitemDashboardExamsBinding) :
RecyclerView.ViewHolder(binding.root) RecyclerView.ViewHolder(binding.root)
} }

View file

@ -1,4 +1,4 @@
package io.github.wulkanowy.ui.modules.dashboard package io.github.wulkanowy.ui.modules.dashboard.adapters
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup

View file

@ -1,4 +1,4 @@
package io.github.wulkanowy.ui.modules.dashboard package io.github.wulkanowy.ui.modules.dashboard.adapters
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.view.LayoutInflater import android.view.LayoutInflater
@ -53,4 +53,4 @@ class DashboardHomeworkAdapter : RecyclerView.Adapter<DashboardHomeworkAdapter.V
class ViewHolder(val binding: SubitemDashboardHomeworkBinding) : class ViewHolder(val binding: SubitemDashboardHomeworkBinding) :
RecyclerView.ViewHolder(binding.root) RecyclerView.ViewHolder(binding.root)
} }

View file

@ -17,16 +17,16 @@ val debugMessageItems = listOf(
) )
private fun generateMessage(sender: String, subject: String) = Message( private fun generateMessage(sender: String, subject: String) = Message(
sender = sender,
subject = subject, subject = subject,
studentId = 0, messageId = 123,
realId = 0, email = "",
messageId = 0,
senderId = 0,
recipient = "",
date = Instant.now(), date = Instant.now(),
folderId = 0, folderId = 0,
unread = true, unread = true,
removed = false, readBy = 2,
hasAttachments = false unreadBy = 2,
hasAttachments = false,
messageGlobalKey = "",
correspondents = sender,
mailboxKey = "",
) )

View file

@ -19,6 +19,6 @@ val debugSchoolAnnouncementItems = listOf(
private fun generateAnnouncement(subject: String, content: String) = SchoolAnnouncement( private fun generateAnnouncement(subject: String, content: String) = SchoolAnnouncement(
subject = subject, subject = subject,
content = content, content = content,
studentId = 0, userLoginId = 0,
date = LocalDate.now() date = LocalDate.now()
) )

View file

@ -3,8 +3,7 @@ package io.github.wulkanowy.ui.modules.grade.details
import io.github.wulkanowy.data.* import io.github.wulkanowy.data.*
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.enums.GradeExpandMode import io.github.wulkanowy.data.enums.GradeExpandMode
import io.github.wulkanowy.data.enums.GradeSortingMode.ALPHABETIC import io.github.wulkanowy.data.enums.GradeSortingMode.*
import io.github.wulkanowy.data.enums.GradeSortingMode.DATE
import io.github.wulkanowy.data.repositories.GradeRepository import io.github.wulkanowy.data.repositories.GradeRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.SemesterRepository
@ -132,16 +131,17 @@ class GradeDetailsPresenter @Inject constructor(
} }
.logResourceStatus("load grade details") .logResourceStatus("load grade details")
.onResourceData { .onResourceData {
val gradeItems = createGradeItems(it)
view?.run { view?.run {
enableSwipe(true) enableSwipe(true)
showProgress(false) showProgress(false)
showErrorView(false) showErrorView(false)
showContent(it.isNotEmpty()) showContent(gradeItems.isNotEmpty())
showEmpty(it.isEmpty()) showEmpty(gradeItems.isEmpty())
updateNewGradesAmount(it) updateNewGradesAmount(it)
updateMarkAsDoneButton() updateMarkAsDoneButton()
updateData( updateData(
data = createGradeItems(it), data = gradeItems,
expandMode = preferencesRepository.gradeExpandMode, expandMode = preferencesRepository.gradeExpandMode,
preferencesRepository.gradeColorTheme preferencesRepository.gradeColorTheme
) )
@ -204,6 +204,7 @@ class GradeDetailsPresenter @Inject constructor(
ALPHABETIC -> gradeSubjects.sortedBy { gradeDetailsWithAverage -> ALPHABETIC -> gradeSubjects.sortedBy { gradeDetailsWithAverage ->
gradeDetailsWithAverage.subject.lowercase() gradeDetailsWithAverage.subject.lowercase()
} }
AVERAGE -> gradeSubjects.sortedByDescending { it.average }
} }
} }
.map { (subject, average, points, _, grades) -> .map { (subject, average, points, _, grades) ->

View file

@ -10,6 +10,7 @@ import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.ItemGradeSummaryBinding import io.github.wulkanowy.databinding.ItemGradeSummaryBinding
import io.github.wulkanowy.databinding.ScrollableHeaderGradeSummaryBinding import io.github.wulkanowy.databinding.ScrollableHeaderGradeSummaryBinding
import io.github.wulkanowy.sdk.scrapper.grades.isGradeValid
import io.github.wulkanowy.utils.calcFinalAverage import io.github.wulkanowy.utils.calcFinalAverage
import java.util.Locale import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
@ -61,7 +62,7 @@ class GradeSummaryAdapter @Inject constructor(
if (items.isEmpty()) return if (items.isEmpty()) return
val context = binding.root.context val context = binding.root.context
val finalItemsCount = items.count { it.finalGrade.matches("[0-6][+-]?".toRegex()) } val finalItemsCount = items.count { isGradeValid(it.finalGrade) }
val calculatedItemsCount = items.count { value -> value.average != 0.0 } val calculatedItemsCount = items.count { value -> value.average != 0.0 }
val allItemsCount = items.count { !it.subject.equals("zachowanie", true) } val allItemsCount = items.count { !it.subject.equals("zachowanie", true) }
val finalAverage = items.calcFinalAverage( val finalAverage = items.calcFinalAverage(

View file

@ -2,6 +2,9 @@ package io.github.wulkanowy.ui.modules.grade.summary
import io.github.wulkanowy.data.* import io.github.wulkanowy.data.*
import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.enums.GradeSortingMode
import io.github.wulkanowy.data.enums.GradeSortingMode.*
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.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
@ -14,6 +17,7 @@ import javax.inject.Inject
class GradeSummaryPresenter @Inject constructor( class GradeSummaryPresenter @Inject constructor(
errorHandler: ErrorHandler, errorHandler: ErrorHandler,
studentRepository: StudentRepository, studentRepository: StudentRepository,
private val preferencesRepository: PreferencesRepository,
private val averageProvider: GradeAverageProvider, private val averageProvider: GradeAverageProvider,
private val analytics: AnalyticsHelper private val analytics: AnalyticsHelper
) : BasePresenter<GradeSummaryView>(errorHandler, studentRepository) { ) : BasePresenter<GradeSummaryView>(errorHandler, studentRepository) {
@ -127,7 +131,17 @@ class GradeSummaryPresenter @Inject constructor(
private fun createGradeSummaryItems(items: List<GradeSubject>): List<GradeSummary> { private fun createGradeSummaryItems(items: List<GradeSubject>): List<GradeSummary> {
return items return items
.filter { !checkEmpty(it) } .filter { !checkEmpty(it) }
.sortedBy { it.subject } .let { gradeSubjects ->
when (preferencesRepository.gradeSortingMode) {
DATE -> gradeSubjects.sortedByDescending { gradeDetailsWithAverage ->
gradeDetailsWithAverage.grades.maxByOrNull { it.date }?.date
}
ALPHABETIC -> gradeSubjects.sortedBy { gradeDetailsWithAverage ->
gradeDetailsWithAverage.subject.lowercase()
}
AVERAGE -> gradeSubjects.sortedByDescending { it.average }
}
}
.map { it.summary.copy(average = it.average) } .map { it.summary.copy(average = it.average) }
} }

View file

@ -93,6 +93,7 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
} }
//https://developer.android.com/guide/playcore/in-app-updates#status_callback //https://developer.android.com/guide/playcore/in-app-updates#status_callback
@Deprecated("Deprecated in Java")
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)

View file

@ -10,6 +10,7 @@ import androidx.core.widget.doOnTextChanged
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.db.entities.StudentWithSemesters import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.FragmentLoginFormBinding import io.github.wulkanowy.databinding.FragmentLoginFormBinding
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginActivity
@ -32,6 +33,9 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
@Inject @Inject
lateinit var appInfo: AppInfo lateinit var appInfo: AppInfo
@Inject
lateinit var preferencesRepository: PreferencesRepository
companion object { companion object {
fun newInstance() = LoginFormFragment() fun newInstance() = LoginFormFragment()
} }
@ -260,8 +264,9 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
R.string.login_email_text, R.string.login_email_text,
"${appInfo.systemManufacturer} ${appInfo.systemModel}", "${appInfo.systemManufacturer} ${appInfo.systemModel}",
appInfo.systemVersion.toString(), appInfo.systemVersion.toString(),
appInfo.versionName, "${appInfo.versionName}-${appInfo.buildFlavor}",
"$formHostValue/$formHostSymbol", "$formHostValue/$formHostSymbol",
preferencesRepository.installationId,
lastError lastError
) )
) )

View file

@ -172,7 +172,7 @@ class LoginFormPresenter @Inject constructor(
if ("@" in login && "||" !in login && "login" !in host && "email" !in host) { if ("@" in login && "||" !in login && "login" !in host && "email" !in host) {
val emailHost = login.substringAfter("@") val emailHost = login.substringAfter("@")
val emailDomain = URL(host).host val emailDomain = URL(host).host
if (emailHost != emailDomain) { if (!emailHost.equals(emailDomain, true)) {
view?.setErrorEmailInvalid(domain = emailDomain) view?.setErrorEmailInvalid(domain = emailDomain)
isCorrect = false isCorrect = false
} }

View file

@ -6,9 +6,7 @@ import android.os.Bundle
import android.view.View import android.view.View
import android.view.View.GONE import android.view.View.GONE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.webkit.JavascriptInterface import android.webkit.*
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
import com.yariksoffice.lingver.Lingver import com.yariksoffice.lingver.Lingver
@ -206,10 +204,9 @@ class LoginRecoverFragment :
} }
override fun onReceivedError( override fun onReceivedError(
view: WebView, view: WebView?,
errorCode: Int, request: WebResourceRequest?,
description: String, error: WebResourceError?
failingUrl: String
) { ) {
recoverWebViewSuccess = false recoverWebViewSuccess = false
} }

View file

@ -9,6 +9,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
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.db.entities.StudentWithSemesters import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.FragmentLoginStudentSelectBinding import io.github.wulkanowy.databinding.FragmentLoginStudentSelectBinding
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginActivity
@ -32,6 +33,9 @@ class LoginStudentSelectFragment :
@Inject @Inject
lateinit var appInfo: AppInfo lateinit var appInfo: AppInfo
@Inject
lateinit var preferencesRepository: PreferencesRepository
companion object { companion object {
const val ARG_STUDENTS = "STUDENTS" const val ARG_STUDENTS = "STUDENTS"
@ -111,10 +115,12 @@ class LoginStudentSelectFragment :
email = "wulkanowyinc@gmail.com", email = "wulkanowyinc@gmail.com",
subject = requireContext().getString(R.string.login_email_subject), subject = requireContext().getString(R.string.login_email_subject),
body = requireContext().getString( body = requireContext().getString(
R.string.login_email_text, appInfo.systemModel, R.string.login_email_text,
"${appInfo.systemManufacturer} ${appInfo.systemModel}",
appInfo.systemVersion.toString(), appInfo.systemVersion.toString(),
appInfo.versionName, "${appInfo.versionName}-${appInfo.buildFlavor}",
"Select users to log in", "Select users to log in",
preferencesRepository.installationId,
lastError lastError
) )
) )

View file

@ -13,6 +13,7 @@ import androidx.core.widget.doOnTextChanged
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.db.entities.StudentWithSemesters import io.github.wulkanowy.data.db.entities.StudentWithSemesters
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.FragmentLoginSymbolBinding import io.github.wulkanowy.databinding.FragmentLoginSymbolBinding
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginActivity
@ -34,6 +35,9 @@ class LoginSymbolFragment :
@Inject @Inject
lateinit var appInfo: AppInfo lateinit var appInfo: AppInfo
@Inject
lateinit var preferencesRepository: PreferencesRepository
companion object { companion object {
private const val SAVED_LOGIN_DATA = "LOGIN_DATA" private const val SAVED_LOGIN_DATA = "LOGIN_DATA"
@ -159,8 +163,9 @@ class LoginSymbolFragment :
R.string.login_email_text, R.string.login_email_text,
"${appInfo.systemManufacturer} ${appInfo.systemModel}", "${appInfo.systemManufacturer} ${appInfo.systemModel}",
appInfo.systemVersion.toString(), appInfo.systemVersion.toString(),
appInfo.versionName, "${appInfo.versionName}-${appInfo.buildFlavor}",
"$host/${binding.loginSymbolName.text}", "$host/${binding.loginSymbolName.text}",
preferencesRepository.installationId,
lastError lastError
) )
) )

View file

@ -12,6 +12,7 @@ import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.elevation.ElevationOverlayProvider import com.google.android.material.elevation.ElevationOverlayProvider
import com.ncapdevi.fragnav.FragNavController import com.ncapdevi.fragnav.FragNavController
import com.ncapdevi.fragnav.FragNavController.Companion.HIDE import com.ncapdevi.fragnav.FragNavController.Companion.HIDE
@ -20,6 +21,7 @@ import io.github.wulkanowy.R
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 io.github.wulkanowy.databinding.ActivityMainBinding import io.github.wulkanowy.databinding.ActivityMainBinding
import io.github.wulkanowy.databinding.DialogAdsConsentBinding
import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.Destination
import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog
@ -100,6 +102,7 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
} }
//https://developer.android.com/guide/playcore/in-app-updates#status_callback //https://developer.android.com/guide/playcore/in-app-updates#status_callback
@Deprecated("Deprecated in Java")
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
@ -287,6 +290,50 @@ class MainActivity : BaseActivity<MainPresenter, ActivityMainBinding>(), MainVie
inAppReviewHelper.showInAppReview(this) inAppReviewHelper.showInAppReview(this)
} }
override fun showAppSupport() {
MaterialAlertDialogBuilder(this)
.setTitle(R.string.main_support_title)
.setMessage(R.string.main_support_description)
.setPositiveButton(R.string.main_support_positive) { _, _ -> presenter.onEnableAdsSelected() }
.setNegativeButton(android.R.string.cancel) { _, _ -> }
.setOnDismissListener { }
.show()
}
override fun showPrivacyPolicyDialog() {
val dialogAdsConsentBinding = DialogAdsConsentBinding.inflate(layoutInflater)
val dialog = MaterialAlertDialogBuilder(this)
.setTitle(R.string.pref_ads_consent_title)
.setMessage(R.string.pref_ads_consent_description)
.setView(dialogAdsConsentBinding.root)
.show()
dialogAdsConsentBinding.adsConsentOver.setOnCheckedChangeListener { _, isChecked ->
dialogAdsConsentBinding.adsConsentPersonalised.isEnabled = isChecked
}
dialogAdsConsentBinding.adsConsentPersonalised.setOnClickListener {
presenter.onPrivacyAgree(true)
dialog.dismiss()
}
dialogAdsConsentBinding.adsConsentNonPersonalised.setOnClickListener {
presenter.onPrivacyAgree(false)
dialog.dismiss()
}
dialogAdsConsentBinding.adsConsentPrivacy.setOnClickListener { presenter.onPrivacySelected() }
dialogAdsConsentBinding.adsConsentCancel.setOnClickListener { dialog.cancel() }
}
override fun openPrivacyPolicy() {
openInternetBrowser(
"https://wulkanowy.github.io/polityka-prywatnosci.html",
::showMessage
)
}
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
navController.onSaveInstanceState(outState) navController.onSaveInstanceState(outState)

View file

@ -18,7 +18,10 @@ import io.github.wulkanowy.ui.modules.grade.GradeView
import io.github.wulkanowy.ui.modules.message.MessageView import io.github.wulkanowy.ui.modules.message.MessageView
import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersView import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersView
import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView
import io.github.wulkanowy.utils.AdsHelper
import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.AppInfo
import kotlinx.coroutines.launch
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import timber.log.Timber import timber.log.Timber
@ -29,10 +32,12 @@ import javax.inject.Inject
class MainPresenter @Inject constructor( class MainPresenter @Inject constructor(
errorHandler: ErrorHandler, errorHandler: ErrorHandler,
studentRepository: StudentRepository, studentRepository: StudentRepository,
private val prefRepository: PreferencesRepository, private val preferencesRepository: PreferencesRepository,
private val syncManager: SyncManager, private val syncManager: SyncManager,
private val analytics: AnalyticsHelper, private val analytics: AnalyticsHelper,
private val json: Json private val json: Json,
private val adsHelper: AdsHelper,
private val appInfo: AppInfo
) : BasePresenter<MainView>(errorHandler, studentRepository) { ) : BasePresenter<MainView>(errorHandler, studentRepository) {
private var studentsWitSemesters: List<StudentWithSemesters>? = null private var studentsWitSemesters: List<StudentWithSemesters>? = null
@ -47,7 +52,7 @@ class MainPresenter @Inject constructor(
private val Destination?.startMenuIndex private val Destination?.startMenuIndex
get() = when { get() = when {
this == null -> prefRepository.startMenuIndex this == null -> preferencesRepository.startMenuIndex
destinationType in rootDestinationTypeList -> { destinationType in rootDestinationTypeList -> {
rootDestinationTypeList.indexOf(destinationType) rootDestinationTypeList.indexOf(destinationType)
} }
@ -71,6 +76,8 @@ class MainPresenter @Inject constructor(
syncManager.startPeriodicSyncWorker() syncManager.startPeriodicSyncWorker()
checkAppSupport()
analytics.logEvent("app_open", "destination" to initDestination.toString()) analytics.logEvent("app_open", "destination" to initDestination.toString())
Timber.i("Main view was initialized with $initDestination") Timber.i("Main view was initialized with $initDestination")
} }
@ -155,18 +162,52 @@ class MainPresenter @Inject constructor(
} == true } == true
} }
private fun checkInAppReview() { fun onEnableAdsSelected() {
prefRepository.inAppReviewCount++ view?.showPrivacyPolicyDialog()
}
if (prefRepository.inAppReviewDate == null) { fun onPrivacyAgree(isPersonalizedAds: Boolean) {
prefRepository.inAppReviewDate = Instant.now() preferencesRepository.isAgreeToProcessData = true
preferencesRepository.isPersonalizedAdsEnabled = isPersonalizedAds
adsHelper.initialize()
preferencesRepository.isAdsEnabled = true
}
fun onPrivacySelected() {
view?.openPrivacyPolicy()
}
private fun checkInAppReview() {
preferencesRepository.inAppReviewCount++
if (preferencesRepository.inAppReviewDate == null) {
preferencesRepository.inAppReviewDate = Instant.now()
} }
if (!prefRepository.isAppReviewDone && prefRepository.inAppReviewCount >= 50 && if (!preferencesRepository.isAppReviewDone && preferencesRepository.inAppReviewCount >= 50 &&
Instant.now().minus(Duration.ofDays(14)).isAfter(prefRepository.inAppReviewDate) Instant.now().minus(Duration.ofDays(14)).isAfter(preferencesRepository.inAppReviewDate)
) { ) {
view?.showInAppReview() view?.showInAppReview()
prefRepository.isAppReviewDone = true preferencesRepository.isAppReviewDone = true
}
}
private fun checkAppSupport() {
if (!preferencesRepository.isAppSupportShown && !preferencesRepository.isAdsEnabled
&& appInfo.buildFlavor == "play"
) {
presenterScope.launch {
val student = runCatching { studentRepository.getCurrentStudent(false) }
.onFailure { Timber.e(it) }
.getOrElse { return@launch }
if (Instant.now().minus(Duration.ofDays(28)).isAfter(student.registrationDate)) {
view?.showAppSupport()
preferencesRepository.isAppSupportShown = true
}
}
} }
} }

View file

@ -41,6 +41,12 @@ interface MainView : BaseView {
fun showInAppReview() fun showInAppReview()
fun showAppSupport()
fun showPrivacyPolicyDialog()
fun openPrivacyPolicy()
fun openMoreDestination(destination: Destination) fun openMoreDestination(destination: Destination)
interface MainChildView { interface MainChildView {

View file

@ -0,0 +1,81 @@
package io.github.wulkanowy.ui.modules.message.mailboxchooser
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil.ItemCallback
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.db.entities.MailboxType
import io.github.wulkanowy.databinding.ItemMailboxChooserBinding
import javax.inject.Inject
class MailboxChooserAdapter @Inject constructor() :
ListAdapter<MailboxChooserItem, MailboxChooserAdapter.ItemViewHolder>(Differ) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
ItemMailboxChooserBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
)
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
holder.bind(getItem(position))
}
class ItemViewHolder(
private val binding: ItemMailboxChooserBinding,
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: MailboxChooserItem) {
with(binding) {
mailboxItemName.text = item.mailbox?.getFirstLine()
?: root.resources.getString(R.string.message_chip_all_mailboxes)
mailboxItemSchool.text = item.mailbox?.getSecondLine()
mailboxItemSchool.isVisible = !item.isAll
root.setOnClickListener { item.onClickListener(item.mailbox) }
}
}
private fun Mailbox.getFirstLine() = buildString {
if (studentName.isNotBlank() && studentName != userName) {
append(studentName)
append(" - ")
}
append(userName)
}
private fun Mailbox.getSecondLine() = buildString {
append(schoolNameShort)
append(" - ")
append(getMailboxType(type))
}
private fun getMailboxType(type: MailboxType): String = when (type) {
MailboxType.STUDENT -> R.string.message_mailbox_type_student
MailboxType.PARENT -> R.string.message_mailbox_type_parent
MailboxType.GUARDIAN -> R.string.message_mailbox_type_guardian
MailboxType.EMPLOYEE -> R.string.message_mailbox_type_employee
MailboxType.UNKNOWN -> null
}.let { it?.let { it1 -> binding.root.resources.getString(it1) }.orEmpty() }
}
private object Differ : ItemCallback<MailboxChooserItem>() {
override fun areItemsTheSame(
oldItem: MailboxChooserItem,
newItem: MailboxChooserItem
): Boolean {
return oldItem.mailbox?.globalKey == newItem.mailbox?.globalKey
}
override fun areContentsTheSame(
oldItem: MailboxChooserItem,
newItem: MailboxChooserItem
): Boolean {
return oldItem == newItem
}
}
}

View file

@ -0,0 +1,75 @@
package io.github.wulkanowy.ui.modules.message.mailboxchooser
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.fragment.app.setFragmentResult
import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.databinding.DialogMailboxChooserBinding
import io.github.wulkanowy.ui.base.BaseDialogFragment
import javax.inject.Inject
@AndroidEntryPoint
class MailboxChooserDialog : BaseDialogFragment<DialogMailboxChooserBinding>(), MailboxChooserView {
@Inject
lateinit var presenter: MailboxChooserPresenter
@Inject
lateinit var mailboxAdapter: MailboxChooserAdapter
companion object {
const val LISTENER_KEY = "mailbox_selected"
const val MAILBOX_KEY = "selected_mailbox"
const val REQUIRED_KEY = "is_mailbox_required"
fun newInstance(mailboxes: List<Mailbox>, isMailboxRequired: Boolean, folder: String) =
MailboxChooserDialog().apply {
arguments = bundleOf(
MAILBOX_KEY to mailboxes.toTypedArray(),
REQUIRED_KEY to isMailboxRequired,
LISTENER_KEY to folder,
)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
) = DialogMailboxChooserBinding.inflate(inflater).apply { binding = this }.root
@Suppress("UNCHECKED_CAST")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
presenter.onAttachView(
view = this,
requireMailbox = requireArguments().getBoolean(REQUIRED_KEY, false),
mailboxes = requireArguments().getParcelableArray(MAILBOX_KEY).orEmpty()
.toList() as List<Mailbox>,
)
}
override fun initView() {
binding.accountQuickDialogRecycler.adapter = mailboxAdapter
}
override fun submitData(items: List<MailboxChooserItem>) {
mailboxAdapter.submitList(items)
}
override fun onMailboxSelected(item: Mailbox?) {
setFragmentResult(
requestKey = requireArguments().getString(LISTENER_KEY).orEmpty(),
result = bundleOf(MAILBOX_KEY to item),
)
dismiss()
}
}

View file

@ -0,0 +1,9 @@
package io.github.wulkanowy.ui.modules.message.mailboxchooser
import io.github.wulkanowy.data.db.entities.Mailbox
data class MailboxChooserItem(
val mailbox: Mailbox? = null,
val isAll: Boolean = false,
val onClickListener: (Mailbox?) -> Unit,
)

View file

@ -0,0 +1,38 @@
package io.github.wulkanowy.ui.modules.message.mailboxchooser
import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import timber.log.Timber
import javax.inject.Inject
class MailboxChooserPresenter @Inject constructor(
errorHandler: ErrorHandler,
studentRepository: StudentRepository
) : BasePresenter<MailboxChooserView>(errorHandler, studentRepository) {
fun onAttachView(view: MailboxChooserView, mailboxes: List<Mailbox>, requireMailbox: Boolean) {
super.onAttachView(view)
view.initView()
Timber.i("Mailbox chooser view was initialized")
view.submitData(getMailboxItems(mailboxes, requireMailbox))
}
private fun getMailboxItems(
mailboxes: List<Mailbox>,
requireMailbox: Boolean,
): List<MailboxChooserItem> = buildList {
if (!requireMailbox) {
add(MailboxChooserItem(isAll = true, onClickListener = ::onMailboxSelect))
}
addAll(mailboxes.map {
MailboxChooserItem(mailbox = it, isAll = false, onClickListener = ::onMailboxSelect)
})
}
fun onMailboxSelect(item: Mailbox?) {
view?.onMailboxSelected(item)
}
}

View file

@ -0,0 +1,13 @@
package io.github.wulkanowy.ui.modules.message.mailboxchooser
import io.github.wulkanowy.data.db.entities.Mailbox
import io.github.wulkanowy.ui.base.BaseView
interface MailboxChooserView : BaseView {
fun initView()
fun submitData(items: List<MailboxChooserItem>)
fun onMailboxSelected(item: Mailbox?)
}

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