Compare commits
158 Commits
Author | SHA1 | Date | |
---|---|---|---|
a56f4b8745 | |||
d003b0897c | |||
581bb2de77 | |||
495e385228 | |||
10ba36ba44 | |||
eae396424f | |||
a7891bb266 | |||
6e82409dbc | |||
984db18be3 | |||
c99bc96c08 | |||
3e7030abc2 | |||
6dad3b299b | |||
5e997f5a3e | |||
601d573283 | |||
6ae6ca7fbb | |||
c3d38afc3d | |||
4e19964249 | |||
a6c0efcb81 | |||
fcc71c0d5f | |||
a59d10b6c1 | |||
a48e4eb4ee | |||
2a3668bb18 | |||
804d0d9113 | |||
88b893e6c0 | |||
2874a7495e | |||
40d8f7a93d | |||
84cd51205f | |||
bac1832f27 | |||
8bf1e22407 | |||
e9f43f925c | |||
aa632edf5c | |||
57315d75c6 | |||
4552bc85b0 | |||
6b7795118c | |||
1960782d8e | |||
8836be3766 | |||
8697993149 | |||
b88c7eb4e4 | |||
9066bce0d5 | |||
f15b90782a | |||
0642bf7d73 | |||
981d6d559c | |||
39327ff3ea | |||
b098ac029b | |||
7e0e2fbb67 | |||
8a181c747c | |||
68fdb167c2 | |||
17563d1a4b | |||
370881104e | |||
62b1b18326 | |||
214e43bd4b | |||
358e0850ad | |||
c183428107 | |||
6de937703a | |||
10cb2b70f1 | |||
9230db3f99 | |||
66e58ab74e | |||
99b7af64c0 | |||
4295dd6246 | |||
fb2d92c749 | |||
07b1969a35 | |||
886403bf1e | |||
e8b21c1429 | |||
007d62e61d | |||
f88d44f0ec | |||
4401df6203 | |||
e6f23ab35b | |||
eea20ced57 | |||
b7134221cb | |||
8be605629a | |||
1a3d580116 | |||
a62ed54d07 | |||
36a570eeb0 | |||
8ed8b5a33c | |||
26c749c219 | |||
1d910f8d66 | |||
de11644e9b | |||
621db49fbf | |||
b593795844 | |||
0f800b61f6 | |||
94fd303f8e | |||
09a134d442 | |||
58ea2c530e | |||
84e4167dbd | |||
54e9ea6478 | |||
4c8d9c8f7f | |||
e7550f7a43 | |||
ac86737050 | |||
e3122127c0 | |||
d6918077bf | |||
9d0366d010 | |||
3d0cd11ba4 | |||
64dbbd54b4 | |||
539be586ce | |||
a240fd5d5f | |||
4e69cfe23c | |||
2ab0a57a41 | |||
ebf9e741c2 | |||
e8075e30e4 | |||
1839d7cb8f | |||
2d84b0775a | |||
426379ec17 | |||
8315759c83 | |||
04393e60bb | |||
031a17ea50 | |||
60501fcd72 | |||
8e607d48f7 | |||
e02d93f979 | |||
76514e2d72 | |||
689012131f | |||
6cdcf92782 | |||
9c8bcbfdd3 | |||
0b83a66b85 | |||
9711cc868c | |||
f8cb7599e6 | |||
7636618e23 | |||
5bc54c12f1 | |||
e10e530dee | |||
d69118b085 | |||
dc90549b9d | |||
b552dbc904 | |||
a6a1678b47 | |||
7a46ef5f19 | |||
f9e0f7b390 | |||
9211baf7ec | |||
de6131f4f5 | |||
2cb11e443c | |||
a43ffcdef4 | |||
6615e68430 | |||
36daa7ccc1 | |||
6e5481f345 | |||
ba1c14ca0e | |||
c69bb2ef71 | |||
9cb4754132 | |||
5ba8289c87 | |||
258782c648 | |||
c568bc1515 | |||
da668f93cf | |||
037dbd792f | |||
7ec7afed87 | |||
bea50e6db5 | |||
6a00e75816 | |||
957adaf6ee | |||
827fb33eeb | |||
19c96ee83f | |||
5a7f52c773 | |||
dddeff802f | |||
91f6310892 | |||
0389642543 | |||
8528e0beff | |||
e665a8f18b | |||
6d5acbad2c | |||
7217d0f753 | |||
16a5d88dfb | |||
646a46727f | |||
f5e9197f98 | |||
b47f26684b | |||
3a03b5f1c6 |
16
.github/workflows/deploy-store.yml
vendored
16
.github/workflows/deploy-store.yml
vendored
@ -28,15 +28,17 @@ jobs:
|
|||||||
SERVICES_ENCRYPT_KEY: ${{ secrets.SERVICES_ENCRYPT_KEY }}
|
SERVICES_ENCRYPT_KEY: ${{ secrets.SERVICES_ENCRYPT_KEY }}
|
||||||
run: |
|
run: |
|
||||||
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/google-services.json.gpg
|
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/google-services.json.gpg
|
||||||
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg
|
|
||||||
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg
|
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg
|
||||||
- name: Upload apk to google play
|
- name: Upload apk to google play
|
||||||
env:
|
env:
|
||||||
|
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
||||||
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
|
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
|
||||||
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
|
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
|
||||||
PLAY_SERVICE_ACCOUNT_EMAIL: ${{ secrets.PLAY_SERVICE_ACCOUNT_EMAIL }}
|
ANDROID_PUBLISHER_CREDENTIALS: ${{ secrets.ANDROID_PUBLISHER_CREDENTIALS }}
|
||||||
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
ADMOB_PROJECT_ID: ${{ secrets.ADMOB_PROJECT_ID }}
|
||||||
run: ./gradlew publishPlayRelease -PenableFirebase --stacktrace;
|
SINGLE_SUPPORT_AD_ID: ${{ secrets.SINGLE_SUPPORT_AD_ID }}
|
||||||
|
SET_BUILD_TIMESTAMP: ${{ secrets.SET_BUILD_TIMESTAMP }}
|
||||||
|
run: ./gradlew publishPlayReleaseApps -PenableFirebase --stacktrace;
|
||||||
|
|
||||||
deploy-app-gallery:
|
deploy-app-gallery:
|
||||||
name: Deploy to AppGallery
|
name: Deploy to AppGallery
|
||||||
@ -60,7 +62,6 @@ jobs:
|
|||||||
SERVICES_ENCRYPT_KEY: ${{ secrets.SERVICES_ENCRYPT_KEY }}
|
SERVICES_ENCRYPT_KEY: ${{ secrets.SERVICES_ENCRYPT_KEY }}
|
||||||
run: |
|
run: |
|
||||||
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/agconnect-services.json.gpg
|
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/agconnect-services.json.gpg
|
||||||
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg
|
|
||||||
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg
|
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg
|
||||||
- name: Prepare credentials
|
- name: Prepare credentials
|
||||||
env:
|
env:
|
||||||
@ -68,7 +69,8 @@ jobs:
|
|||||||
run: echo $AGC_CREDENTIALS > ./app/src/release/agconnect-credentials.json
|
run: echo $AGC_CREDENTIALS > ./app/src/release/agconnect-credentials.json
|
||||||
- name: Build and publish HMS version
|
- name: Build and publish HMS version
|
||||||
env:
|
env:
|
||||||
|
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
||||||
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
|
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
|
||||||
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
|
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
|
||||||
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
SET_BUILD_TIMESTAMP: ${{ secrets.SET_BUILD_TIMESTAMP }}
|
||||||
run: ./gradlew assembleHmsRelease --stacktrace && ./gradlew publishHuaweiAppGalleryHmsRelease --stacktrace
|
run: ./gradlew bundleHmsRelease --stacktrace && ./gradlew publishHuaweiAppGalleryHmsRelease --stacktrace
|
||||||
|
2
LICENSE
2
LICENSE
@ -186,7 +186,7 @@
|
|||||||
same "printed page" as the copyright notice for easier
|
same "printed page" as the copyright notice for easier
|
||||||
identification within third-party archives.
|
identification within third-party archives.
|
||||||
|
|
||||||
Copyright 2019 Wulkanowy
|
Copyright 2021 Wulkanowy
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
78
README.cs.md
Normal file
78
README.cs.md
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
[English version of README](README.en.md)
|
||||||
|
|
||||||
|
[Deutsche Version von README](README.de.md)
|
||||||
|
|
||||||
|
[Polska wersja README](README.md)
|
||||||
|
|
||||||
|
[Slovenská verzia README](README.sk.md)
|
||||||
|
|
||||||
|
# Wulkanowy
|
||||||
|
|
||||||
|
[](https://github.com/wulkanowy/wulkanowy/actions)
|
||||||
|
[](https://codecov.io/gh/wulkanowy/wulkanowy)
|
||||||
|
[](https://discord.gg/vccAQBr)
|
||||||
|
[](https://f-droid.org/packages/io.github.wulkanowy/)
|
||||||
|
[](https://github.com/wulkanowy/wulkanowy/releases)
|
||||||
|
|
||||||
|
Neoficiální klient deníku VULCAN UONET+ pro žáka a rodiče
|
||||||
|
|
||||||
|
## Funkce
|
||||||
|
|
||||||
|
* přihlášení pomocí emailu a hesla
|
||||||
|
* funkce z webové stránky deníku:
|
||||||
|
* známky
|
||||||
|
* statistiky známek
|
||||||
|
* frekvence
|
||||||
|
* procento frekvence
|
||||||
|
* zkoušky
|
||||||
|
* plán lekce
|
||||||
|
* dokončené lekce
|
||||||
|
* zprávy
|
||||||
|
* domácí úkoly
|
||||||
|
* poznámky
|
||||||
|
* šťastné číslo
|
||||||
|
* další lekce
|
||||||
|
* školní setkání
|
||||||
|
* informace o žáku a škole
|
||||||
|
* výpočet průměru nezávisle na preferencích školy
|
||||||
|
* upozornění, např. o nových známkách
|
||||||
|
* podpora více účtů s možností přejmenování žáků
|
||||||
|
* tmavý a černý (AMOLED) motiv
|
||||||
|
* offline režim
|
||||||
|
* žádné reklamy
|
||||||
|
|
||||||
|
## Stáhnout
|
||||||
|
|
||||||
|
Aktuální verzi si můžete stáhnout z Google Play, F-Droid nebo Huawei AppGallery
|
||||||
|
|
||||||
|
[<img src="https://play.google.com/intl/cs-CZ/badges/images/generic/cs_badge_web_generic.png"
|
||||||
|
alt="Nyní na Google Play"
|
||||||
|
height="80">](https://play.google.com/store/apps/details?id=io.github.wulkanowy)
|
||||||
|
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||||
|
alt="Stáhnout s F-Droid"
|
||||||
|
height="80">](https://f-droid.org/packages/io.github.wulkanowy/)
|
||||||
|
[<img src="https://i.imgur.com/baTGiDP.png"
|
||||||
|
alt="Objevuj v AppGallery"
|
||||||
|
height="80">](https://appgallery.cloud.huawei.com/ag/n/app/C101440411?channelId=Badge&id=1b3f7fbb700849a9be0dba6b520b2282&s=EB1D3BF9ED9D1564D869B7B94B18016D3CABFCA5AEFB8E29F675FA04E0DC131D&detailType=0&v=)
|
||||||
|
|
||||||
|
Můžete si také stáhnout [vývojovou verzi](https://wulkanowy.github.io/#download), která zahrnuje nové funkce připravované pro příští vydání
|
||||||
|
|
||||||
|
## Postaveno s
|
||||||
|
|
||||||
|
|
||||||
|
* [Wulkanowy SDK](https://github.com/wulkanowy/sdk)
|
||||||
|
* [Kotlin Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html)
|
||||||
|
* [Hilt](https://dagger.dev/hilt/)
|
||||||
|
* [Room](https://developer.android.com/topic/libraries/architecture/room)
|
||||||
|
* [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager)
|
||||||
|
|
||||||
|
## Spolupráce
|
||||||
|
|
||||||
|
Přispějte do projektu vytvořením PR nebo odesláním issue na GitHub.
|
||||||
|
|
||||||
|
Pro zájemce o překlad aplikace do různých jazyků poskytujeme Crowdin:
|
||||||
|
https://crowdin.com/project/wulkanowy2
|
||||||
|
|
||||||
|
## Licence
|
||||||
|
|
||||||
|
Tento projekt je licencován pod licencí Apache License 2.0 - podrobnosti v souboru [LICENSE](LICENSE)
|
74
README.de.md
Normal file
74
README.de.md
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
[Polska wersja README](README.md)
|
||||||
|
|
||||||
|
[English version of README](README.en.md)
|
||||||
|
|
||||||
|
# Wulkanowy
|
||||||
|
|
||||||
|
[](https://github.com/wulkanowy/wulkanowy/actions)
|
||||||
|
[](https://codecov.io/gh/wulkanowy/wulkanowy)
|
||||||
|
[](https://discord.gg/vccAQBr)
|
||||||
|
[](https://f-droid.org/packages/io.github.wulkanowy/)
|
||||||
|
[](https://github.com/wulkanowy/wulkanowy/releases)
|
||||||
|
|
||||||
|
Inoffizieller Android VULCAN UONET+ Registrierungsclient für Schüler und ihre Eltern
|
||||||
|
|
||||||
|
## Merkmale
|
||||||
|
|
||||||
|
* Einloggen mit E-Mail und Passwort
|
||||||
|
* Funktionen von der Registerwebsite:
|
||||||
|
* Noten
|
||||||
|
* Notenstatistik
|
||||||
|
* Anwesenheit
|
||||||
|
* Prozentsatz der Anwesenheit
|
||||||
|
* Prüfungen
|
||||||
|
* Stundenplan
|
||||||
|
* Unterricht abgeschlossen
|
||||||
|
* Nachrichten
|
||||||
|
* Hausaufgaben
|
||||||
|
* Anmerkungen
|
||||||
|
* Glückszahl
|
||||||
|
* Zusätzliche Lektionen
|
||||||
|
* Schulkonferenzen
|
||||||
|
* Schüler- und Schulinformationen
|
||||||
|
* Berechnung des Durchschnitts unabhängig von den Präferenzen der Schule
|
||||||
|
* Benachrichtigungen, z. B. über eine neue Note
|
||||||
|
* Unterstützung für mehrere Konten mit der Möglichkeit, den Namen des Schülers zu ändern
|
||||||
|
* dunkles und schwarzes (AMOLED) Thema
|
||||||
|
* Offline-Modus
|
||||||
|
* keine Werbung
|
||||||
|
|
||||||
|
## Herunterladen
|
||||||
|
|
||||||
|
Die aktuelle Version können Sie von der Google Play, F-Droid oder Huawei AppGallery store herunterladen
|
||||||
|
|
||||||
|
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png"
|
||||||
|
alt="Get it on Google Play"
|
||||||
|
height="80">](https://play.google.com/store/apps/details?id=io.github.wulkanowy)
|
||||||
|
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||||
|
alt="Get it on F-Droid"
|
||||||
|
height="80">](https://f-droid.org/packages/io.github.wulkanowy/)
|
||||||
|
[<img src="appgallery_badge.png"
|
||||||
|
alt="Explore it on AppGallery"
|
||||||
|
height="80">](https://appgallery.cloud.huawei.com/ag/n/app/C101440411?channelId=Badge&id=1b3f7fbb700849a9be0dba6b520b2282&s=EB1D3BF9ED9D1564D869B7B94B18016D3CABFCA5AEFB8E29F675FA04E0DC131D&detailType=0&v=)
|
||||||
|
|
||||||
|
Sie können auch ein [Entwicklungsversion herunterladen](https://wulkanowy.github.io/#download) das beinhaltet neue Funktionen, die für die nächste Version vorbereitet werden
|
||||||
|
|
||||||
|
## Gebaut mit
|
||||||
|
|
||||||
|
|
||||||
|
* [Wulkanowy SDK](https://github.com/wulkanowy/sdk)
|
||||||
|
* [Kotlin Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html)
|
||||||
|
* [Hilt](https://dagger.dev/hilt/)
|
||||||
|
* [Room](https://developer.android.com/topic/libraries/architecture/room)
|
||||||
|
* [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager)
|
||||||
|
|
||||||
|
## Beitragen
|
||||||
|
|
||||||
|
Bitte tragen Sie zum Projekt bei, indem Sie entweder eine PR erstellen oder ein Issue auf GitHub einreichen.
|
||||||
|
|
||||||
|
Für Personen, die daran interessiert sind, die Anwendung in verschiedene Sprachen zu übersetzen, bieten wir Crowdin
|
||||||
|
https://crowdin.com/project/wulkanowy2
|
||||||
|
|
||||||
|
## Lizenz
|
||||||
|
|
||||||
|
Dieses Projekt ist unter der Apache License 2.0 lizenziert - siehe die [LIZENZ](LICENSE) Datei für Details
|
@ -1,5 +1,11 @@
|
|||||||
[Polska wersja README](README.md)
|
[Polska wersja README](README.md)
|
||||||
|
|
||||||
|
[Deutsche Version von README](README.de.md)
|
||||||
|
|
||||||
|
[Česká verze README](README.cs.md)
|
||||||
|
|
||||||
|
[Slovenská verzia README](README.sk.md)
|
||||||
|
|
||||||
# Wulkanowy
|
# Wulkanowy
|
||||||
|
|
||||||
[](https://github.com/wulkanowy/wulkanowy/actions)
|
[](https://github.com/wulkanowy/wulkanowy/actions)
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
[English version of README](README.en.md)
|
[English version of README](README.en.md)
|
||||||
|
|
||||||
|
[Deutsche Version von README](README.de.md)
|
||||||
|
|
||||||
|
[Česká verze README](README.cs.md)
|
||||||
|
|
||||||
|
[Slovenská verzia README](README.sk.md)
|
||||||
|
|
||||||
# Wulkanowy
|
# Wulkanowy
|
||||||
|
|
||||||
[](https://github.com/wulkanowy/wulkanowy/actions)
|
[](https://github.com/wulkanowy/wulkanowy/actions)
|
||||||
|
78
README.sk.md
Normal file
78
README.sk.md
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
[English version of README](README.en.md)
|
||||||
|
|
||||||
|
[Deutsche Version von README](README.de.md)
|
||||||
|
|
||||||
|
[Polska wersja README](README.md)
|
||||||
|
|
||||||
|
[Česká verze README](README.cs.md)
|
||||||
|
|
||||||
|
# Wulkanowy
|
||||||
|
|
||||||
|
[](https://github.com/wulkanowy/wulkanowy/actions)
|
||||||
|
[](https://codecov.io/gh/wulkanowy/wulkanowy)
|
||||||
|
[](https://discord.gg/vccAQBr)
|
||||||
|
[](https://f-droid.org/packages/io.github.wulkanowy/)
|
||||||
|
[](https://github.com/wulkanowy/wulkanowy/releases)
|
||||||
|
|
||||||
|
Neoficiálny klient denníka VULCAN UONET+ pre žiaka a rodičov
|
||||||
|
|
||||||
|
## Funkcie
|
||||||
|
|
||||||
|
* prihlásenie pomocou emailu a hesla
|
||||||
|
* funkcie z webovej stránky denníka:
|
||||||
|
* známky
|
||||||
|
* štatistiky známok
|
||||||
|
* frekvencia
|
||||||
|
* percento frekvencie
|
||||||
|
* skúšky
|
||||||
|
* plán lekcie
|
||||||
|
* dokončené lekcie
|
||||||
|
* správy
|
||||||
|
* domáce úlohy
|
||||||
|
* poznámky
|
||||||
|
* šťastné číslo
|
||||||
|
* ďalšie lekcie
|
||||||
|
* školské stretnutie
|
||||||
|
* informácie o žiakovi a škole
|
||||||
|
* výpočet priemeru nezávisle od preferencií školy
|
||||||
|
* upozornenia, napr. o nových známkach
|
||||||
|
* podpora viacerých účtov s možnosťou premenovania žiakov
|
||||||
|
* tmavý a čierny (AMOLED) motív
|
||||||
|
* offline režim
|
||||||
|
* žiadne reklamy
|
||||||
|
|
||||||
|
## Stiahnuť
|
||||||
|
|
||||||
|
Aktuálnu verziu si môžete stiahnuť z Google Play, F-Droid alebo Huawei AppGallery
|
||||||
|
|
||||||
|
[<img src="https://play.google.com/intl/sk/badges/images/generic/sk_badge_web_generic.png"
|
||||||
|
alt="Nyní na Google Play"
|
||||||
|
height="80">](https://play.google.com/store/apps/details?id=io.github.wulkanowy)
|
||||||
|
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||||
|
alt="Stiahnuť s F-Droid"
|
||||||
|
height="80">](https://f-droid.org/packages/io.github.wulkanowy/)
|
||||||
|
[<img src="https://i.imgur.com/sX8UyAw.png"
|
||||||
|
alt="Objavíte v AppGallery"
|
||||||
|
height="80">](https://appgallery.cloud.huawei.com/ag/n/app/C101440411?channelId=Badge&id=1b3f7fbb700849a9be0dba6b520b2282&s=EB1D3BF9ED9D1564D869B7B94B18016D3CABFCA5AEFB8E29F675FA04E0DC131D&detailType=0&v=)
|
||||||
|
|
||||||
|
Môžete si tiež stiahnuť [vývojovú verziu](https://wulkanowy.github.io/#download), ktorá zahrňuje nové funkcie pripravované pre budúce vydanie
|
||||||
|
|
||||||
|
## Postavené s
|
||||||
|
|
||||||
|
|
||||||
|
* [Wulkanowy SDK](https://github.com/wulkanowy/sdk)
|
||||||
|
* [Kotlin Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html)
|
||||||
|
* [Hilt](https://dagger.dev/hilt/)
|
||||||
|
* [Room](https://developer.android.com/topic/libraries/architecture/room)
|
||||||
|
* [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager)
|
||||||
|
|
||||||
|
## Spolupráca
|
||||||
|
|
||||||
|
Prispejte do projektu vytvorením PR alebo odoslaním issue na GitHub.
|
||||||
|
|
||||||
|
Pre záujemcov o preklad aplikácie do rôznych jazykov poskytujeme Crowdin:
|
||||||
|
https://crowdin.com/project/wulkanowy2
|
||||||
|
|
||||||
|
## Licencia
|
||||||
|
|
||||||
|
Tento projekt je licencovaný pod licenciou Apache License 2.0 - podrobnosti v súbore [LICENSE](LICENSE)
|
115
app/build.gradle
115
app/build.gradle
@ -1,5 +1,6 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlinx-serialization'
|
||||||
apply plugin: 'kotlin-parcelize'
|
apply plugin: 'kotlin-parcelize'
|
||||||
apply plugin: 'kotlin-kapt'
|
apply plugin: 'kotlin-kapt'
|
||||||
apply plugin: 'dagger.hilt.android.plugin'
|
apply plugin: 'dagger.hilt.android.plugin'
|
||||||
@ -14,23 +15,22 @@ apply from: 'sonarqube.gradle'
|
|||||||
apply from: 'hooks.gradle'
|
apply from: 'hooks.gradle'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 30
|
compileSdkVersion 31
|
||||||
|
|
||||||
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 30
|
targetSdkVersion 31
|
||||||
versionCode 94
|
versionCode 101
|
||||||
versionName "1.2.1"
|
versionName "1.4.3"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables.useSupportLibrary = true
|
|
||||||
|
|
||||||
resValue "string", "app_name", "Wulkanowy"
|
resValue "string", "app_name", "Wulkanowy"
|
||||||
buildConfigField "long", "BUILD_TIMESTAMP", String.valueOf(System.currentTimeMillis())
|
|
||||||
|
|
||||||
manifestPlaceholders = [
|
manifestPlaceholders = [
|
||||||
firebase_enabled: project.hasProperty("enableFirebase")
|
firebase_enabled: project.hasProperty("enableFirebase"),
|
||||||
|
admob_project_id: ""
|
||||||
]
|
]
|
||||||
javaCompileOptions {
|
javaCompileOptions {
|
||||||
annotationProcessorOptions {
|
annotationProcessorOptions {
|
||||||
@ -40,6 +40,14 @@ android {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "null"
|
||||||
|
|
||||||
|
if (System.env.SET_BUILD_TIMESTAMP) {
|
||||||
|
buildConfigField "long", "BUILD_TIMESTAMP", String.valueOf(System.currentTimeMillis())
|
||||||
|
} else {
|
||||||
|
buildConfigField "long", "BUILD_TIMESTAMP", "1486235849000"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
@ -62,12 +70,14 @@ android {
|
|||||||
shrinkResources true
|
shrinkResources true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
signingConfig signingConfigs.release
|
signingConfig signingConfigs.release
|
||||||
|
buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
|
||||||
}
|
}
|
||||||
debug {
|
debug {
|
||||||
resValue "string", "app_name", "Wulkanowy DEV " + defaultConfig.versionCode
|
resValue "string", "app_name", "Wulkanowy DEV"
|
||||||
applicationIdSuffix ".dev"
|
applicationIdSuffix ".dev"
|
||||||
versionNameSuffix "-dev"
|
versionNameSuffix "-dev"
|
||||||
ext.enableCrashlytics = project.hasProperty("enableFirebase")
|
ext.enableCrashlytics = project.hasProperty("enableFirebase")
|
||||||
|
buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,30 +86,38 @@ android {
|
|||||||
productFlavors {
|
productFlavors {
|
||||||
hms {
|
hms {
|
||||||
dimension "platform"
|
dimension "platform"
|
||||||
manifestPlaceholders = [
|
manifestPlaceholders = [install_channel: "AppGallery"]
|
||||||
install_channel: "AppGallery"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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"
|
||||||
]
|
]
|
||||||
|
buildConfigField "String", "SINGLE_SUPPORT_AD_ID", "\"${System.getenv("SINGLE_SUPPORT_AD_ID") ?: "ca-app-pub-3940256099942544/5354046379"}\""
|
||||||
}
|
}
|
||||||
|
|
||||||
fdroid {
|
fdroid {
|
||||||
dimension "platform"
|
dimension "platform"
|
||||||
manifestPlaceholders = [
|
manifestPlaceholders = [install_channel: "F-Droid"]
|
||||||
install_channel: "F-Droid"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
playConfigs {
|
||||||
|
play { enabled.set(true) }
|
||||||
|
}
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding true
|
viewBinding true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bundle {
|
||||||
|
language {
|
||||||
|
enableSplit = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
testOptions.unitTests {
|
testOptions.unitTests {
|
||||||
includeAndroidResources = true
|
includeAndroidResources = true
|
||||||
}
|
}
|
||||||
@ -130,61 +148,61 @@ kapt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
play {
|
play {
|
||||||
serviceAccountEmail = System.getenv("PLAY_SERVICE_ACCOUNT_EMAIL") ?: "jan@fakelog.cf"
|
|
||||||
serviceAccountCredentials = file('key.p12')
|
|
||||||
defaultToAppBundles = false
|
defaultToAppBundles = false
|
||||||
track = 'production'
|
track = 'beta'
|
||||||
updatePriority = 3
|
updatePriority = 1
|
||||||
|
enabled.set(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
huaweiPublish {
|
huaweiPublish {
|
||||||
instances {
|
instances {
|
||||||
hmsRelease {
|
hmsRelease {
|
||||||
credentialsPath = "$rootDir/app/src/release/agconnect-credentials.json"
|
credentialsPath = "$rootDir/app/src/release/agconnect-credentials.json"
|
||||||
buildFormat = "apk"
|
buildFormat = "aab"
|
||||||
deployType = "draft"
|
deployType = "draft"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
work_manager = "2.5.0"
|
work_manager = "2.7.1"
|
||||||
android_hilt = "1.0.0"
|
android_hilt = "1.0.0"
|
||||||
room = "2.3.0"
|
room = "2.3.0"
|
||||||
chucker = "3.5.2"
|
chucker = "3.5.2"
|
||||||
mockk = "1.12.0"
|
mockk = "1.12.1"
|
||||||
moshi = "1.12.0"
|
coroutines = "1.5.2"
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "io.github.wulkanowy:sdk:1.2.1"
|
implementation "io.github.wulkanowy:sdk:1.4.3"
|
||||||
|
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2"
|
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1"
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
|
||||||
|
|
||||||
implementation "androidx.core:core-ktx:1.6.0"
|
implementation "androidx.core:core-ktx:1.7.0"
|
||||||
implementation "androidx.activity:activity-ktx:1.3.1"
|
implementation 'androidx.core:core-splashscreen:1.0.0-alpha02'
|
||||||
implementation "androidx.appcompat:appcompat:1.3.1"
|
implementation "androidx.activity:activity-ktx:1.4.0"
|
||||||
implementation "androidx.appcompat:appcompat-resources:1.3.1"
|
implementation "androidx.appcompat:appcompat:1.4.0"
|
||||||
implementation "androidx.fragment:fragment-ktx:1.3.6"
|
implementation "androidx.fragment:fragment-ktx:1.4.0"
|
||||||
implementation "androidx.annotation:annotation:1.2.0"
|
implementation "androidx.annotation:annotation:1.3.0"
|
||||||
|
|
||||||
implementation "androidx.preference:preference-ktx:1.1.1"
|
implementation "androidx.preference:preference-ktx:1.1.1"
|
||||||
implementation "androidx.recyclerview:recyclerview:1.2.1"
|
implementation "androidx.recyclerview:recyclerview:1.2.1"
|
||||||
implementation "androidx.viewpager:viewpager:1.0.0"
|
implementation "androidx.viewpager2:viewpager2:1.1.0-beta01"
|
||||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||||
implementation "androidx.constraintlayout:constraintlayout:2.1.0"
|
implementation "androidx.constraintlayout:constraintlayout:2.1.2"
|
||||||
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
|
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
|
||||||
implementation "com.google.android.material:material:1.4.0"
|
implementation "com.google.android.material:material:1.4.0"
|
||||||
implementation "com.github.wulkanowy:material-chips-input:2.2.0"
|
implementation "com.github.wulkanowy:material-chips-input:2.3.1"
|
||||||
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
|
implementation "com.github.PhilJay:MPAndroidChart:v3.1.0"
|
||||||
implementation 'com.github.lopspower:CircularImageView:4.2.0'
|
implementation 'com.github.lopspower:CircularImageView:4.2.0'
|
||||||
|
|
||||||
implementation "androidx.work:work-runtime-ktx:$work_manager"
|
implementation "androidx.work:work-runtime-ktx:$work_manager"
|
||||||
playImplementation "androidx.work:work-gcm:$work_manager"
|
playImplementation "androidx.work:work-gcm:$work_manager"
|
||||||
|
|
||||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
|
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0"
|
||||||
|
|
||||||
implementation "androidx.room:room-runtime:$room"
|
implementation "androidx.room:room-runtime:$room"
|
||||||
implementation "androidx.room:room-ktx:$room"
|
implementation "androidx.room:room-ktx:$room"
|
||||||
@ -198,40 +216,41 @@ dependencies {
|
|||||||
implementation 'com.github.ncapdevi:FragNav:3.3.0'
|
implementation 'com.github.ncapdevi:FragNav:3.3.0'
|
||||||
implementation "com.github.YarikSOffice:lingver:1.3.0"
|
implementation "com.github.YarikSOffice:lingver:1.3.0"
|
||||||
|
|
||||||
implementation "com.squareup.moshi:moshi:$moshi"
|
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||||
implementation "com.squareup.moshi:moshi-adapters:$moshi"
|
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0"
|
||||||
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi"
|
implementation "com.squareup.okhttp3:logging-interceptor:4.9.3"
|
||||||
|
|
||||||
implementation "com.jakewharton.timber:timber:5.0.1"
|
implementation "com.jakewharton.timber:timber:5.0.1"
|
||||||
implementation "at.favre.lib:slf4j-timber:1.0.1"
|
implementation "at.favre.lib:slf4j-timber:1.0.1"
|
||||||
implementation 'com.github.bastienpaulfr:Treessence:1.0.4'
|
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.3.2"
|
implementation "io.coil-kt:coil:1.4.0"
|
||||||
implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
|
implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
|
||||||
implementation 'me.xdrop:fuzzywuzzy:1.3.1'
|
implementation 'me.xdrop:fuzzywuzzy:1.3.1'
|
||||||
implementation 'com.fredporciuncula:flow-preferences:1.5.0'
|
implementation 'com.fredporciuncula:flow-preferences:1.6.0'
|
||||||
|
|
||||||
playImplementation platform('com.google.firebase:firebase-bom:28.4.0')
|
playImplementation platform('com.google.firebase:firebase-bom:29.0.0')
|
||||||
playImplementation 'com.google.firebase:firebase-analytics-ktx'
|
playImplementation 'com.google.firebase:firebase-analytics-ktx'
|
||||||
playImplementation 'com.google.firebase:firebase-messaging:'
|
playImplementation 'com.google.firebase:firebase-messaging:'
|
||||||
playImplementation 'com.google.firebase:firebase-crashlytics:'
|
playImplementation 'com.google.firebase:firebase-crashlytics:'
|
||||||
playImplementation 'com.google.android.play:core:1.10.1'
|
playImplementation 'com.google.android.play:core:1.10.2'
|
||||||
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.5.0'
|
||||||
|
|
||||||
hmsImplementation 'com.huawei.hms:hianalytics:6.2.0.301'
|
hmsImplementation 'com.huawei.hms:hianalytics:6.3.2.300'
|
||||||
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.0.300'
|
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.2.200'
|
||||||
|
|
||||||
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
|
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
|
||||||
|
|
||||||
debugImplementation "com.github.ChuckerTeam.Chucker:library:$chucker"
|
debugImplementation "com.github.ChuckerTeam.Chucker:library:$chucker"
|
||||||
debugImplementation 'com.github.amitshekhariitbhu.Android-Debug-Database:debug-db:v1.0.6'
|
debugImplementation 'com.github.amitshekhariitbhu.Android-Debug-Database:debug-db:1.0.6'
|
||||||
|
|
||||||
testImplementation "junit:junit:4.13.2"
|
testImplementation "junit:junit:4.13.2"
|
||||||
testImplementation "io.mockk:mockk:$mockk"
|
testImplementation "io.mockk:mockk:$mockk"
|
||||||
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.2'
|
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.6.1'
|
testImplementation 'org.robolectric:robolectric:4.7.2'
|
||||||
testImplementation "androidx.test:runner:1.4.0"
|
testImplementation "androidx.test:runner:1.4.0"
|
||||||
testImplementation "androidx.test.ext:junit:1.1.3"
|
testImplementation "androidx.test.ext:junit:1.1.3"
|
||||||
testImplementation "androidx.test:core:1.4.0"
|
testImplementation "androidx.test:core:1.4.0"
|
||||||
|
BIN
app/key.p12.gpg
BIN
app/key.p12.gpg
Binary file not shown.
2316
app/schemas/io.github.wulkanowy.data.db.AppDatabase/40.json
Normal file
2316
app/schemas/io.github.wulkanowy.data.db.AppDatabase/40.json
Normal file
File diff suppressed because it is too large
Load Diff
2322
app/schemas/io.github.wulkanowy.data.db.AppDatabase/41.json
Normal file
2322
app/schemas/io.github.wulkanowy.data.db.AppDatabase/41.json
Normal file
File diff suppressed because it is too large
Load Diff
2396
app/schemas/io.github.wulkanowy.data.db.AppDatabase/42.json
Normal file
2396
app/schemas/io.github.wulkanowy.data.db.AppDatabase/42.json
Normal file
File diff suppressed because it is too large
Load Diff
2408
app/schemas/io.github.wulkanowy.data.db.AppDatabase/43.json
Normal file
2408
app/schemas/io.github.wulkanowy.data.db.AppDatabase/43.json
Normal file
File diff suppressed because it is too large
Load Diff
2414
app/schemas/io.github.wulkanowy.data.db.AppDatabase/44.json
Normal file
2414
app/schemas/io.github.wulkanowy.data.db.AppDatabase/44.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -9,6 +9,7 @@
|
|||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
|
||||||
|
|
||||||
<queries>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
@ -38,13 +39,14 @@
|
|||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="false"
|
android:supportsRtl="false"
|
||||||
android:theme="@style/WulkanowyTheme"
|
android:theme="@style/WulkanowyTheme"
|
||||||
android:usesCleartextTraffic="true"
|
|
||||||
tools:ignore="GoogleAppIndexingWarning,UnusedAttribute">
|
tools:ignore="GoogleAppIndexingWarning,UnusedAttribute">
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.modules.splash.SplashActivity"
|
android:name=".ui.modules.splash.SplashActivity"
|
||||||
|
android:exported="true"
|
||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
android:theme="@style/WulkanowyTheme.SplashScreen"
|
android:theme="@style/WulkanowyTheme.SplashScreen"
|
||||||
tools:ignore="LockedOrientationActivity">
|
tools:ignore="LockedOrientationActivity">
|
||||||
@ -74,6 +76,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".ui.modules.timetablewidget.TimetableWidgetConfigureActivity"
|
android:name=".ui.modules.timetablewidget.TimetableWidgetConfigureActivity"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
|
android:exported="true"
|
||||||
android:noHistory="true"
|
android:noHistory="true"
|
||||||
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
|
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@ -83,6 +86,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity"
|
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
|
android:exported="true"
|
||||||
android:noHistory="true"
|
android:noHistory="true"
|
||||||
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
|
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@ -93,6 +97,23 @@
|
|||||||
<service
|
<service
|
||||||
android:name=".services.widgets.TimetableWidgetService"
|
android:name=".services.widgets.TimetableWidgetService"
|
||||||
android:permission="android.permission.BIND_REMOTEVIEWS" />
|
android:permission="android.permission.BIND_REMOTEVIEWS" />
|
||||||
|
<service
|
||||||
|
android:name=".services.piggyback.VulcanNotificationListenerService"
|
||||||
|
android:exported="true"
|
||||||
|
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.service.notification.NotificationListenerService" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
<service
|
||||||
|
android:name=".services.messaging.AppMessagingService"
|
||||||
|
android:exported="false"
|
||||||
|
tools:ignore="MissingClass">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".ui.modules.timetablewidget.TimetableWidgetProvider"
|
android:name=".ui.modules.timetablewidget.TimetableWidgetProvider"
|
||||||
@ -107,6 +128,7 @@
|
|||||||
</receiver>
|
</receiver>
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetProvider"
|
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetProvider"
|
||||||
|
android:exported="true"
|
||||||
android:label="@string/lucky_number_title">
|
android:label="@string/lucky_number_title">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
@ -119,11 +141,9 @@
|
|||||||
<receiver android:name=".services.alarm.TimetableNotificationReceiver" />
|
<receiver android:name=".services.alarm.TimetableNotificationReceiver" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.work.impl.WorkManagerInitializer"
|
android:name="androidx.startup.InitializationProvider"
|
||||||
android:authorities="${applicationId}.workmanager-init"
|
android:authorities="${applicationId}.androidx-startup"
|
||||||
android:exported="false"
|
|
||||||
tools:node="remove" />
|
tools:node="remove" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="${applicationId}.fileprovider"
|
android:authorities="${applicationId}.fileprovider"
|
||||||
@ -134,44 +154,44 @@
|
|||||||
android:resource="@xml/provider_paths" />
|
android:resource="@xml/provider_paths" />
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
<meta-data
|
|
||||||
android:name="install_channel"
|
|
||||||
android:value="${install_channel}" />
|
|
||||||
|
|
||||||
<!-- workaround for https://github.com/firebase/firebase-android-sdk/issues/473 enabled:false -->
|
<!-- workaround for https://github.com/firebase/firebase-android-sdk/issues/473 enabled:false -->
|
||||||
<!-- https://firebase.googleblog.com/2017/03/take-control-of-your-firebase-init-on.html -->
|
<!-- https://firebase.googleblog.com/2017/03/take-control-of-your-firebase-init-on.html -->
|
||||||
<provider
|
<provider
|
||||||
android:name="com.google.firebase.provider.FirebaseInitProvider"
|
android:name="com.google.firebase.provider.FirebaseInitProvider"
|
||||||
android:authorities="${applicationId}.firebaseinitprovider"
|
android:authorities="${applicationId}.firebaseinitprovider"
|
||||||
android:enabled="${firebase_enabled}"
|
android:enabled="${firebase_enabled}"
|
||||||
android:exported="false" />
|
android:exported="false"
|
||||||
|
tools:ignore="MissingClass" />
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="install_channel"
|
||||||
|
android:value="${install_channel}" />
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="firebase_analytics_collection_enabled"
|
android:name="firebase_analytics_collection_enabled"
|
||||||
android:value="${firebase_enabled}" />
|
android:value="${firebase_enabled}" />
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="google_analytics_adid_collection_enabled"
|
android:name="google_analytics_adid_collection_enabled"
|
||||||
android:value="${firebase_enabled}" />
|
android:value="${firebase_enabled}" />
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="firebase_crashlytics_collection_enabled"
|
android:name="firebase_crashlytics_collection_enabled"
|
||||||
android:value="${firebase_enabled}" />
|
android:value="${firebase_enabled}" />
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="firebase_messaging_auto_init_enabled"
|
android:name="firebase_messaging_auto_init_enabled"
|
||||||
android:value="${firebase_enabled}" />
|
android:value="${firebase_enabled}" />
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="firebase_inapp_messaging_auto_data_collection_enabled"
|
android:name="firebase_inapp_messaging_auto_data_collection_enabled"
|
||||||
android:value="${firebase_enabled}" />
|
android:value="${firebase_enabled}" />
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.google.firebase.messaging.default_notification_icon"
|
android:name="com.google.firebase.messaging.default_notification_icon"
|
||||||
android:resource="@drawable/ic_stat_all" />
|
android:resource="@drawable/ic_stat_all" />
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.google.firebase.messaging.default_notification_channel_id"
|
android:name="com.google.firebase.messaging.default_notification_channel_id"
|
||||||
android:value="push_channel" />
|
android:value="push_channel" />
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.android.gms.ads.APPLICATION_ID"
|
||||||
|
android:value="${admob_project_id}" />
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.android.gms.ads.DELAY_APP_MEASUREMENT_INIT"
|
||||||
|
android:value="true" />
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
package io.github.wulkanowy
|
package io.github.wulkanowy
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.util.Log.DEBUG
|
import android.util.Log.DEBUG
|
||||||
import android.util.Log.INFO
|
import android.util.Log.INFO
|
||||||
import android.util.Log.VERBOSE
|
import android.util.Log.VERBOSE
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
import androidx.fragment.app.FragmentManager
|
|
||||||
import androidx.hilt.work.HiltWorkerFactory
|
import androidx.hilt.work.HiltWorkerFactory
|
||||||
import androidx.work.Configuration
|
import androidx.work.Configuration
|
||||||
import com.yariksoffice.lingver.Lingver
|
import com.yariksoffice.lingver.Lingver
|
||||||
@ -41,10 +39,8 @@ class WulkanowyApp : Application(), Configuration.Provider {
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var analyticsHelper: AnalyticsHelper
|
lateinit var analyticsHelper: AnalyticsHelper
|
||||||
|
|
||||||
@SuppressLint("UnsafeOptInUsageWarning")
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
FragmentManager.enableNewStateManager(false)
|
|
||||||
initializeAppLanguage()
|
initializeAppLanguage()
|
||||||
themeManager.applyDefaultTheme()
|
themeManager.applyDefaultTheme()
|
||||||
initLogging()
|
initLogging()
|
||||||
|
@ -2,62 +2,100 @@ package io.github.wulkanowy.data
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.res.AssetManager
|
|
||||||
import android.content.res.Resources
|
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.chuckerteam.chucker.api.ChuckerCollector
|
import com.chuckerteam.chucker.api.ChuckerCollector
|
||||||
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
||||||
import com.chuckerteam.chucker.api.RetentionManager
|
import com.chuckerteam.chucker.api.RetentionManager
|
||||||
import com.squareup.moshi.Moshi
|
|
||||||
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
|
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
|
||||||
|
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import io.github.wulkanowy.data.api.AdminMessageService
|
||||||
import io.github.wulkanowy.data.db.AppDatabase
|
import io.github.wulkanowy.data.db.AppDatabase
|
||||||
import io.github.wulkanowy.data.db.SharedPrefProvider
|
import io.github.wulkanowy.data.db.SharedPrefProvider
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.utils.AppInfo
|
import io.github.wulkanowy.utils.AppInfo
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import retrofit2.create
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
internal class RepositoryModule {
|
internal class DataModule {
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideSdk(chuckerCollector: ChuckerCollector, @ApplicationContext context: Context): Sdk {
|
fun provideSdk(chuckerInterceptor: ChuckerInterceptor) =
|
||||||
return Sdk().apply {
|
Sdk().apply {
|
||||||
androidVersion = android.os.Build.VERSION.RELEASE
|
androidVersion = android.os.Build.VERSION.RELEASE
|
||||||
buildTag = android.os.Build.MODEL
|
buildTag = android.os.Build.MODEL
|
||||||
setSimpleHttpLogger { Timber.d(it) }
|
setSimpleHttpLogger { Timber.d(it) }
|
||||||
|
|
||||||
// for debug only
|
// for debug only
|
||||||
addInterceptor(
|
addInterceptor(chuckerInterceptor, network = true)
|
||||||
ChuckerInterceptor.Builder(context)
|
|
||||||
.collector(chuckerCollector)
|
|
||||||
.alwaysReadResponseBody(true)
|
|
||||||
.build(), network = true
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideChuckerCollector(
|
fun provideChuckerCollector(
|
||||||
@ApplicationContext context: Context,
|
@ApplicationContext context: Context,
|
||||||
prefRepository: PreferencesRepository
|
prefRepository: PreferencesRepository
|
||||||
): ChuckerCollector {
|
) = ChuckerCollector(
|
||||||
return ChuckerCollector(
|
context = context,
|
||||||
context = context,
|
showNotification = prefRepository.isDebugNotificationEnable,
|
||||||
showNotification = prefRepository.isDebugNotificationEnable,
|
retentionPeriod = RetentionManager.Period.ONE_HOUR
|
||||||
retentionPeriod = RetentionManager.Period.ONE_HOUR
|
)
|
||||||
)
|
|
||||||
}
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideChuckerInterceptor(
|
||||||
|
@ApplicationContext context: Context,
|
||||||
|
chuckerCollector: ChuckerCollector
|
||||||
|
) = ChuckerInterceptor.Builder(context)
|
||||||
|
.collector(chuckerCollector)
|
||||||
|
.alwaysReadResponseBody(true)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideOkHttpClient(chuckerInterceptor: ChuckerInterceptor): OkHttpClient =
|
||||||
|
OkHttpClient.Builder()
|
||||||
|
.addNetworkInterceptor(chuckerInterceptor)
|
||||||
|
.addInterceptor(HttpLoggingInterceptor().apply {
|
||||||
|
level = HttpLoggingInterceptor.Level.BASIC
|
||||||
|
})
|
||||||
|
.connectTimeout(30, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(30, TimeUnit.SECONDS)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideRetrofit(
|
||||||
|
okHttpClient: OkHttpClient,
|
||||||
|
json: Json,
|
||||||
|
appInfo: AppInfo
|
||||||
|
): Retrofit = Retrofit.Builder()
|
||||||
|
.baseUrl(appInfo.messagesBaseUrl)
|
||||||
|
.client(okHttpClient)
|
||||||
|
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
|
||||||
|
.build()
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideAdminMessageService(retrofit: Retrofit): AdminMessageService = retrofit.create()
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
@ -67,14 +105,6 @@ internal class RepositoryModule {
|
|||||||
appInfo: AppInfo
|
appInfo: AppInfo
|
||||||
) = AppDatabase.newInstance(context, sharedPrefProvider, appInfo)
|
) = AppDatabase.newInstance(context, sharedPrefProvider, appInfo)
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@Provides
|
|
||||||
fun provideResources(@ApplicationContext context: Context): Resources = context.resources
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@Provides
|
|
||||||
fun provideAssets(@ApplicationContext context: Context): AssetManager = context.assets
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences =
|
fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences =
|
||||||
@ -88,7 +118,9 @@ internal class RepositoryModule {
|
|||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideMoshi() = Moshi.Builder().build()
|
fun provideJson() = Json {
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
}
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
@ -202,4 +234,12 @@ internal class RepositoryModule {
|
|||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideSchoolAnnouncementDao(database: AppDatabase) = database.schoolAnnouncementDao
|
fun provideSchoolAnnouncementDao(database: AppDatabase) = database.schoolAnnouncementDao
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideNotificationDao(database: AppDatabase) = database.notificationDao
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideAdminMessageDao(database: AppDatabase) = database.adminMessagesDao
|
||||||
}
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package io.github.wulkanowy.data.api
|
||||||
|
|
||||||
|
import io.github.wulkanowy.data.db.entities.AdminMessage
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
interface AdminMessageService {
|
||||||
|
|
||||||
|
@GET("/v1.json")
|
||||||
|
suspend fun getAdminMessages(): List<AdminMessage>
|
||||||
|
}
|
@ -6,11 +6,11 @@ import androidx.room.Room
|
|||||||
import androidx.room.RoomDatabase
|
import androidx.room.RoomDatabase
|
||||||
import androidx.room.RoomDatabase.JournalMode.TRUNCATE
|
import androidx.room.RoomDatabase.JournalMode.TRUNCATE
|
||||||
import androidx.room.TypeConverters
|
import androidx.room.TypeConverters
|
||||||
|
import io.github.wulkanowy.data.db.dao.AdminMessageDao
|
||||||
import io.github.wulkanowy.data.db.dao.AttendanceDao
|
import io.github.wulkanowy.data.db.dao.AttendanceDao
|
||||||
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
|
import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao
|
||||||
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
|
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
|
||||||
import io.github.wulkanowy.data.db.dao.ConferenceDao
|
import io.github.wulkanowy.data.db.dao.ConferenceDao
|
||||||
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
|
|
||||||
import io.github.wulkanowy.data.db.dao.ExamDao
|
import io.github.wulkanowy.data.db.dao.ExamDao
|
||||||
import io.github.wulkanowy.data.db.dao.GradeDao
|
import io.github.wulkanowy.data.db.dao.GradeDao
|
||||||
import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao
|
import io.github.wulkanowy.data.db.dao.GradePartialStatisticsDao
|
||||||
@ -23,8 +23,10 @@ import io.github.wulkanowy.data.db.dao.MessageAttachmentDao
|
|||||||
import io.github.wulkanowy.data.db.dao.MessagesDao
|
import io.github.wulkanowy.data.db.dao.MessagesDao
|
||||||
import io.github.wulkanowy.data.db.dao.MobileDeviceDao
|
import io.github.wulkanowy.data.db.dao.MobileDeviceDao
|
||||||
import io.github.wulkanowy.data.db.dao.NoteDao
|
import io.github.wulkanowy.data.db.dao.NoteDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.NotificationDao
|
||||||
import io.github.wulkanowy.data.db.dao.RecipientDao
|
import io.github.wulkanowy.data.db.dao.RecipientDao
|
||||||
import io.github.wulkanowy.data.db.dao.ReportingUnitDao
|
import io.github.wulkanowy.data.db.dao.ReportingUnitDao
|
||||||
|
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
|
||||||
import io.github.wulkanowy.data.db.dao.SchoolDao
|
import io.github.wulkanowy.data.db.dao.SchoolDao
|
||||||
import io.github.wulkanowy.data.db.dao.SemesterDao
|
import io.github.wulkanowy.data.db.dao.SemesterDao
|
||||||
import io.github.wulkanowy.data.db.dao.StudentDao
|
import io.github.wulkanowy.data.db.dao.StudentDao
|
||||||
@ -34,11 +36,11 @@ import io.github.wulkanowy.data.db.dao.TeacherDao
|
|||||||
import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao
|
import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao
|
||||||
import io.github.wulkanowy.data.db.dao.TimetableDao
|
import io.github.wulkanowy.data.db.dao.TimetableDao
|
||||||
import io.github.wulkanowy.data.db.dao.TimetableHeaderDao
|
import io.github.wulkanowy.data.db.dao.TimetableHeaderDao
|
||||||
|
import io.github.wulkanowy.data.db.entities.AdminMessage
|
||||||
import io.github.wulkanowy.data.db.entities.Attendance
|
import io.github.wulkanowy.data.db.entities.Attendance
|
||||||
import io.github.wulkanowy.data.db.entities.AttendanceSummary
|
import io.github.wulkanowy.data.db.entities.AttendanceSummary
|
||||||
import io.github.wulkanowy.data.db.entities.CompletedLesson
|
import io.github.wulkanowy.data.db.entities.CompletedLesson
|
||||||
import io.github.wulkanowy.data.db.entities.Conference
|
import io.github.wulkanowy.data.db.entities.Conference
|
||||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
|
||||||
import io.github.wulkanowy.data.db.entities.Exam
|
import io.github.wulkanowy.data.db.entities.Exam
|
||||||
import io.github.wulkanowy.data.db.entities.Grade
|
import io.github.wulkanowy.data.db.entities.Grade
|
||||||
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
|
import io.github.wulkanowy.data.db.entities.GradePartialStatistics
|
||||||
@ -51,9 +53,11 @@ import io.github.wulkanowy.data.db.entities.Message
|
|||||||
import io.github.wulkanowy.data.db.entities.MessageAttachment
|
import io.github.wulkanowy.data.db.entities.MessageAttachment
|
||||||
import io.github.wulkanowy.data.db.entities.MobileDevice
|
import io.github.wulkanowy.data.db.entities.MobileDevice
|
||||||
import io.github.wulkanowy.data.db.entities.Note
|
import io.github.wulkanowy.data.db.entities.Note
|
||||||
|
import io.github.wulkanowy.data.db.entities.Notification
|
||||||
import io.github.wulkanowy.data.db.entities.Recipient
|
import io.github.wulkanowy.data.db.entities.Recipient
|
||||||
import io.github.wulkanowy.data.db.entities.ReportingUnit
|
import io.github.wulkanowy.data.db.entities.ReportingUnit
|
||||||
import io.github.wulkanowy.data.db.entities.School
|
import io.github.wulkanowy.data.db.entities.School
|
||||||
|
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.db.entities.StudentInfo
|
import io.github.wulkanowy.data.db.entities.StudentInfo
|
||||||
@ -95,6 +99,11 @@ import io.github.wulkanowy.data.db.migrations.Migration37
|
|||||||
import io.github.wulkanowy.data.db.migrations.Migration38
|
import io.github.wulkanowy.data.db.migrations.Migration38
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration39
|
import io.github.wulkanowy.data.db.migrations.Migration39
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration4
|
import io.github.wulkanowy.data.db.migrations.Migration4
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration40
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration41
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration42
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration43
|
||||||
|
import io.github.wulkanowy.data.db.migrations.Migration44
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration5
|
import io.github.wulkanowy.data.db.migrations.Migration5
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration6
|
import io.github.wulkanowy.data.db.migrations.Migration6
|
||||||
import io.github.wulkanowy.data.db.migrations.Migration7
|
import io.github.wulkanowy.data.db.migrations.Migration7
|
||||||
@ -134,6 +143,8 @@ import javax.inject.Singleton
|
|||||||
StudentInfo::class,
|
StudentInfo::class,
|
||||||
TimetableHeader::class,
|
TimetableHeader::class,
|
||||||
SchoolAnnouncement::class,
|
SchoolAnnouncement::class,
|
||||||
|
Notification::class,
|
||||||
|
AdminMessage::class
|
||||||
],
|
],
|
||||||
version = AppDatabase.VERSION_SCHEMA,
|
version = AppDatabase.VERSION_SCHEMA,
|
||||||
exportSchema = true
|
exportSchema = true
|
||||||
@ -142,7 +153,7 @@ import javax.inject.Singleton
|
|||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val VERSION_SCHEMA = 39
|
const val VERSION_SCHEMA = 44
|
||||||
|
|
||||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
||||||
Migration2(),
|
Migration2(),
|
||||||
@ -183,6 +194,11 @@ abstract class AppDatabase : RoomDatabase() {
|
|||||||
Migration37(),
|
Migration37(),
|
||||||
Migration38(),
|
Migration38(),
|
||||||
Migration39(),
|
Migration39(),
|
||||||
|
Migration40(),
|
||||||
|
Migration41(sharedPrefProvider),
|
||||||
|
Migration42(),
|
||||||
|
Migration43(),
|
||||||
|
Migration44()
|
||||||
)
|
)
|
||||||
|
|
||||||
fun newInstance(
|
fun newInstance(
|
||||||
@ -252,4 +268,8 @@ abstract class AppDatabase : RoomDatabase() {
|
|||||||
abstract val timetableHeaderDao: TimetableHeaderDao
|
abstract val timetableHeaderDao: TimetableHeaderDao
|
||||||
|
|
||||||
abstract val schoolAnnouncementDao: SchoolAnnouncementDao
|
abstract val schoolAnnouncementDao: SchoolAnnouncementDao
|
||||||
|
|
||||||
|
abstract val notificationDao: NotificationDao
|
||||||
|
|
||||||
|
abstract val adminMessagesDao: AdminMessageDao
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
package io.github.wulkanowy.data.db
|
package io.github.wulkanowy.data.db
|
||||||
|
|
||||||
import androidx.room.TypeConverter
|
import androidx.room.TypeConverter
|
||||||
import com.squareup.moshi.Moshi
|
import kotlinx.serialization.SerializationException
|
||||||
import com.squareup.moshi.Types
|
import kotlinx.serialization.decodeFromString
|
||||||
import io.github.wulkanowy.data.db.adapters.PairAdapterFactory
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
@ -13,15 +14,7 @@ import java.util.Date
|
|||||||
|
|
||||||
class Converters {
|
class Converters {
|
||||||
|
|
||||||
private val moshi by lazy { Moshi.Builder().add(PairAdapterFactory).build() }
|
private val json = Json
|
||||||
|
|
||||||
private val integerListAdapter by lazy {
|
|
||||||
moshi.adapter<List<Int>>(Types.newParameterizedType(List::class.java, Integer::class.java))
|
|
||||||
}
|
|
||||||
|
|
||||||
private val stringListPairAdapter by lazy {
|
|
||||||
moshi.adapter<List<Pair<String, String>>>(Types.newParameterizedType(List::class.java, Pair::class.java, String::class.java, String::class.java))
|
|
||||||
}
|
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun timestampToDate(value: Long?): LocalDate? = value?.run {
|
fun timestampToDate(value: Long?): LocalDate? = value?.run {
|
||||||
@ -51,21 +44,25 @@ class Converters {
|
|||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun intListToJson(list: List<Int>): String {
|
fun intListToJson(list: List<Int>): String {
|
||||||
return integerListAdapter.toJson(list)
|
return json.encodeToString(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun jsonToIntList(value: String): List<Int> {
|
fun jsonToIntList(value: String): List<Int> {
|
||||||
return integerListAdapter.fromJson(value).orEmpty()
|
return json.decodeFromString(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun stringPairListToJson(list: List<Pair<String, String>>): String {
|
fun stringPairListToJson(list: List<Pair<String, String>>): String {
|
||||||
return stringListPairAdapter.toJson(list)
|
return json.encodeToString(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun jsonToStringPairList(value: String): List<Pair<String, String>> {
|
fun jsonToStringPairList(value: String): List<Pair<String, String>> {
|
||||||
return stringListPairAdapter.fromJson(value).orEmpty()
|
return try {
|
||||||
|
json.decodeFromString(value)
|
||||||
|
} catch (e: SerializationException) {
|
||||||
|
emptyList() // handle errors from old gson Pair serialized data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,11 +22,14 @@ class SharedPrefProvider @Inject constructor(
|
|||||||
|
|
||||||
fun getString(key: String) = sharedPref.getString(key, null)
|
fun getString(key: String) = sharedPref.getString(key, null)
|
||||||
|
|
||||||
fun getString(key: String, defaultValue: String): String = sharedPref.getString(key, defaultValue) ?: defaultValue
|
fun getString(key: String, defaultValue: String): String =
|
||||||
|
sharedPref.getString(key, defaultValue) ?: defaultValue
|
||||||
|
|
||||||
fun getBoolean(key: String, defaultValue: Boolean): Boolean = sharedPref.getBoolean(key, defaultValue)
|
fun getBoolean(key: String, defaultValue: Boolean): Boolean =
|
||||||
|
sharedPref.getBoolean(key, defaultValue)
|
||||||
|
|
||||||
fun putBoolean(key: String, value: Boolean, sync: Boolean = false) = sharedPref.edit(sync) { putBoolean(key, value) }
|
fun putBoolean(key: String, value: Boolean, sync: Boolean = false) =
|
||||||
|
sharedPref.edit(sync) { putBoolean(key, value) }
|
||||||
|
|
||||||
fun putString(key: String, value: String?, sync: Boolean = false) {
|
fun putString(key: String, value: String?, sync: Boolean = false) {
|
||||||
sharedPref.edit(sync) { putString(key, value) }
|
sharedPref.edit(sync) { putString(key, value) }
|
||||||
|
@ -1,68 +0,0 @@
|
|||||||
package io.github.wulkanowy.data.db.adapters
|
|
||||||
|
|
||||||
import com.squareup.moshi.JsonAdapter
|
|
||||||
import com.squareup.moshi.JsonReader
|
|
||||||
import com.squareup.moshi.JsonWriter
|
|
||||||
import com.squareup.moshi.Moshi
|
|
||||||
import com.squareup.moshi.Types
|
|
||||||
import java.lang.reflect.ParameterizedType
|
|
||||||
import java.lang.reflect.Type
|
|
||||||
|
|
||||||
object PairAdapterFactory : JsonAdapter.Factory {
|
|
||||||
|
|
||||||
override fun create(type: Type, annotations: MutableSet<out Annotation>, moshi: Moshi): JsonAdapter<*>? {
|
|
||||||
if (type !is ParameterizedType || List::class.java != type.rawType) return null
|
|
||||||
if (type.actualTypeArguments[0] != Pair::class.java) return null
|
|
||||||
|
|
||||||
val listType = Types.newParameterizedType(List::class.java, Map::class.java, String::class.java)
|
|
||||||
val listAdapter = moshi.adapter<List<Map<String, String>>>(listType)
|
|
||||||
|
|
||||||
val mapType = Types.newParameterizedType(MutableMap::class.java, String::class.java, String::class.java)
|
|
||||||
val mapAdapter = moshi.adapter<Map<String, String>>(mapType)
|
|
||||||
|
|
||||||
return PairAdapter(listAdapter, mapAdapter)
|
|
||||||
}
|
|
||||||
|
|
||||||
private class PairAdapter(
|
|
||||||
private val listAdapter: JsonAdapter<List<Map<String, String>>>,
|
|
||||||
private val mapAdapter: JsonAdapter<Map<String, String>>,
|
|
||||||
) : JsonAdapter<List<Pair<String, String>>>() {
|
|
||||||
|
|
||||||
override fun toJson(writer: JsonWriter, value: List<Pair<String, String>>?) {
|
|
||||||
writer.beginArray()
|
|
||||||
value?.forEach {
|
|
||||||
writer.beginObject()
|
|
||||||
writer.name("first").value(it.first)
|
|
||||||
writer.name("second").value(it.second)
|
|
||||||
writer.endObject()
|
|
||||||
}
|
|
||||||
writer.endArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun fromJson(reader: JsonReader): List<Pair<String, String>>? {
|
|
||||||
return if (reader.peek() == JsonReader.Token.BEGIN_OBJECT) deserializeMoshiMap(reader)
|
|
||||||
else deserializeGsonPair(reader)
|
|
||||||
}
|
|
||||||
|
|
||||||
// for compatibility with 0.21.0
|
|
||||||
private fun deserializeMoshiMap(reader: JsonReader): List<Pair<String, String>>? {
|
|
||||||
val map = mapAdapter.fromJson(reader) ?: return null
|
|
||||||
|
|
||||||
return map.entries.map {
|
|
||||||
it.key to it.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun deserializeGsonPair(reader: JsonReader): List<Pair<String, String>>? {
|
|
||||||
val list = listAdapter.fromJson(reader) ?: return null
|
|
||||||
|
|
||||||
return list.map {
|
|
||||||
require(it.size == 2) {
|
|
||||||
"pair with more or less than two elements: $list"
|
|
||||||
}
|
|
||||||
|
|
||||||
it["first"].orEmpty() to it["second"].orEmpty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,25 @@
|
|||||||
|
package io.github.wulkanowy.data.db.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Transaction
|
||||||
|
import io.github.wulkanowy.data.db.entities.AdminMessage
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Dao
|
||||||
|
abstract class AdminMessageDao : BaseDao<AdminMessage> {
|
||||||
|
|
||||||
|
@Query("SELECT * FROM AdminMessages")
|
||||||
|
abstract fun loadAll(): Flow<List<AdminMessage>>
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open suspend fun removeOldAndSaveNew(
|
||||||
|
oldMessages: List<AdminMessage>,
|
||||||
|
newMessages: List<AdminMessage>
|
||||||
|
) {
|
||||||
|
deleteAll(oldMessages)
|
||||||
|
insertAll(newMessages)
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,11 @@ import javax.inject.Singleton
|
|||||||
@Dao
|
@Dao
|
||||||
interface AttendanceDao : BaseDao<Attendance> {
|
interface AttendanceDao : BaseDao<Attendance> {
|
||||||
|
|
||||||
@Query("SELECT * FROM Attendance WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
|
@Query("SELECT * FROM Attendance WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :start AND date <= :end")
|
||||||
fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow<List<Attendance>>
|
fun loadAll(
|
||||||
|
diaryId: Int,
|
||||||
|
studentId: Int,
|
||||||
|
start: LocalDate,
|
||||||
|
end: LocalDate
|
||||||
|
): Flow<List<Attendance>>
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
package io.github.wulkanowy.data.db.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Query
|
||||||
|
import io.github.wulkanowy.data.db.entities.Notification
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Dao
|
||||||
|
interface NotificationDao : BaseDao<Notification> {
|
||||||
|
|
||||||
|
@Query("SELECT * FROM Notifications WHERE student_id = :studentId OR student_id = -1")
|
||||||
|
fun loadAll(studentId: Long): Flow<List<Notification>>
|
||||||
|
}
|
@ -14,33 +14,39 @@ import javax.inject.Singleton
|
|||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Dao
|
@Dao
|
||||||
interface StudentDao {
|
abstract class StudentDao {
|
||||||
|
|
||||||
@Insert(onConflict = ABORT)
|
@Insert(onConflict = ABORT)
|
||||||
suspend fun insertAll(student: List<Student>): List<Long>
|
abstract suspend fun insertAll(student: List<Student>): List<Long>
|
||||||
|
|
||||||
@Delete
|
@Delete
|
||||||
suspend fun delete(student: Student)
|
abstract suspend fun delete(student: Student)
|
||||||
|
|
||||||
@Update(entity = Student::class)
|
@Update(entity = Student::class)
|
||||||
suspend fun update(studentNickAndAvatar: StudentNickAndAvatar)
|
abstract suspend fun update(studentNickAndAvatar: StudentNickAndAvatar)
|
||||||
|
|
||||||
@Query("SELECT * FROM Students WHERE is_current = 1")
|
@Query("SELECT * FROM Students WHERE is_current = 1")
|
||||||
suspend fun loadCurrent(): Student?
|
abstract suspend fun loadCurrent(): Student?
|
||||||
|
|
||||||
@Query("SELECT * FROM Students WHERE id = :id")
|
@Query("SELECT * FROM Students WHERE id = :id")
|
||||||
suspend fun loadById(id: Long): Student?
|
abstract suspend fun loadById(id: Long): Student?
|
||||||
|
|
||||||
@Query("SELECT * FROM Students")
|
@Query("SELECT * FROM Students")
|
||||||
suspend fun loadAll(): List<Student>
|
abstract suspend fun loadAll(): List<Student>
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
@Query("SELECT * FROM Students")
|
@Query("SELECT * FROM Students")
|
||||||
suspend fun loadStudentsWithSemesters(): List<StudentWithSemesters>
|
abstract suspend fun loadStudentsWithSemesters(): List<StudentWithSemesters>
|
||||||
|
|
||||||
@Query("UPDATE Students SET is_current = 1 WHERE id = :id")
|
@Query("UPDATE Students SET is_current = 1 WHERE id = :id")
|
||||||
suspend fun updateCurrent(id: Long)
|
abstract suspend fun updateCurrent(id: Long)
|
||||||
|
|
||||||
@Query("UPDATE Students SET is_current = 0")
|
@Query("UPDATE Students SET is_current = 0")
|
||||||
suspend fun resetCurrent()
|
abstract suspend fun resetCurrent()
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open suspend fun switchCurrent(id: Long) {
|
||||||
|
resetCurrent()
|
||||||
|
updateCurrent(id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
package io.github.wulkanowy.data.db.entities
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@Entity(tableName = "AdminMessages")
|
||||||
|
data class AdminMessage(
|
||||||
|
|
||||||
|
@PrimaryKey
|
||||||
|
val id: Int,
|
||||||
|
|
||||||
|
val title: String,
|
||||||
|
|
||||||
|
val content: String,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "version_name")
|
||||||
|
val versionMin: Int? = null,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "version_max")
|
||||||
|
val versionMax: Int? = null,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "target_register_host")
|
||||||
|
val targetRegisterHost: String? = null,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "target_flavor")
|
||||||
|
val targetFlavor: String? = null,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "destination_url")
|
||||||
|
val destinationUrl: String? = null,
|
||||||
|
|
||||||
|
val priority: String,
|
||||||
|
|
||||||
|
val type: String,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "is_dismissible")
|
||||||
|
val isDismissible: Boolean = false
|
||||||
|
)
|
@ -47,4 +47,7 @@ data class Attendance(
|
|||||||
|
|
||||||
@PrimaryKey(autoGenerate = true)
|
@PrimaryKey(autoGenerate = true)
|
||||||
var id: Long = 0
|
var id: Long = 0
|
||||||
|
|
||||||
|
@ColumnInfo(name = "is_notified")
|
||||||
|
var isNotified: Boolean = true
|
||||||
}
|
}
|
||||||
|
@ -40,4 +40,7 @@ data class Homework(
|
|||||||
|
|
||||||
@ColumnInfo(name = "is_notified")
|
@ColumnInfo(name = "is_notified")
|
||||||
var isNotified: Boolean = true
|
var isNotified: Boolean = true
|
||||||
|
|
||||||
|
@ColumnInfo(name = "is_added_by_user")
|
||||||
|
var isAddedByUser: Boolean = false
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
package io.github.wulkanowy.data.db.entities
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import io.github.wulkanowy.services.sync.notifications.NotificationType
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
@Entity(tableName = "Notifications")
|
||||||
|
data class Notification(
|
||||||
|
|
||||||
|
@ColumnInfo(name = "student_id")
|
||||||
|
val studentId: Long,
|
||||||
|
|
||||||
|
val title: String,
|
||||||
|
|
||||||
|
val content: String,
|
||||||
|
|
||||||
|
val type: NotificationType,
|
||||||
|
|
||||||
|
val date: LocalDateTime,
|
||||||
|
|
||||||
|
val data: String? = null
|
||||||
|
) {
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
var id: Long = 0
|
||||||
|
}
|
@ -3,10 +3,9 @@ package io.github.wulkanowy.data.db.entities
|
|||||||
import androidx.room.ColumnInfo
|
import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import com.squareup.moshi.JsonClass
|
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@kotlinx.serialization.Serializable
|
||||||
@Entity(tableName = "Recipients")
|
@Entity(tableName = "Recipients")
|
||||||
data class Recipient(
|
data class Recipient(
|
||||||
|
|
||||||
|
@ -50,4 +50,7 @@ data class Timetable(
|
|||||||
|
|
||||||
@PrimaryKey(autoGenerate = true)
|
@PrimaryKey(autoGenerate = true)
|
||||||
var id: Long = 0
|
var id: Long = 0
|
||||||
|
|
||||||
|
@ColumnInfo(name = "is_notified")
|
||||||
|
var isNotified: Boolean = true
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
package io.github.wulkanowy.data.db.migrations
|
||||||
|
|
||||||
|
import androidx.room.migration.Migration
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
|
||||||
|
class Migration40 : Migration(39, 40) {
|
||||||
|
|
||||||
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
|
database.execSQL(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS `Notifications` (
|
||||||
|
`student_id` INTEGER NOT NULL,
|
||||||
|
`title` TEXT NOT NULL,
|
||||||
|
`content` TEXT NOT NULL,
|
||||||
|
`type` TEXT NOT NULL,
|
||||||
|
`date` INTEGER NOT NULL,
|
||||||
|
`data` TEXT,
|
||||||
|
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package io.github.wulkanowy.data.db.migrations
|
||||||
|
|
||||||
|
import androidx.room.migration.Migration
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
import io.github.wulkanowy.data.db.SharedPrefProvider
|
||||||
|
import io.github.wulkanowy.ui.modules.grade.GradeExpandMode
|
||||||
|
|
||||||
|
class Migration41(private val sharedPrefProvider: SharedPrefProvider) : Migration(40, 41) {
|
||||||
|
|
||||||
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
|
migrateSharedPreferences()
|
||||||
|
database.execSQL("ALTER TABLE Homework ADD COLUMN is_added_by_user INTEGER NOT NULL DEFAULT 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun migrateSharedPreferences() {
|
||||||
|
if (sharedPrefProvider.getBoolean("pref_key_expand_grade", false)) {
|
||||||
|
sharedPrefProvider.putString("pref_key_expand_grade_mode", GradeExpandMode.ALWAYS_EXPANDED.value)
|
||||||
|
}
|
||||||
|
sharedPrefProvider.delete("pref_key_expand_grade")
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package io.github.wulkanowy.data.db.migrations
|
||||||
|
|
||||||
|
import androidx.room.migration.Migration
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
|
||||||
|
class Migration42 : Migration(41, 42) {
|
||||||
|
|
||||||
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
|
database.execSQL(
|
||||||
|
"""CREATE TABLE IF NOT EXISTS `AdminMessages` (
|
||||||
|
`id` INTEGER NOT NULL,
|
||||||
|
`title` TEXT NOT NULL,
|
||||||
|
`content` TEXT NOT NULL,
|
||||||
|
`version_name` INTEGER,
|
||||||
|
`version_max` INTEGER,
|
||||||
|
`target_register_host` TEXT,
|
||||||
|
`target_flavor` TEXT,
|
||||||
|
`destination_url` TEXT,
|
||||||
|
`priority` TEXT NOT NULL,
|
||||||
|
`type` TEXT NOT NULL,
|
||||||
|
PRIMARY KEY(`id`))"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package io.github.wulkanowy.data.db.migrations
|
||||||
|
|
||||||
|
import androidx.room.migration.Migration
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
|
||||||
|
class Migration43 : Migration(42, 43) {
|
||||||
|
|
||||||
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
|
database.execSQL("ALTER TABLE Timetable ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1")
|
||||||
|
database.execSQL("ALTER TABLE Attendance ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1")
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package io.github.wulkanowy.data.db.migrations
|
||||||
|
|
||||||
|
import androidx.room.migration.Migration
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
|
||||||
|
class Migration44 : Migration(43, 44) {
|
||||||
|
|
||||||
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
|
database.execSQL("ALTER TABLE AdminMessages ADD COLUMN is_dismissible INTEGER NOT NULL DEFAULT 0")
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
package io.github.wulkanowy.data.pojos
|
package io.github.wulkanowy.data.pojos
|
||||||
|
|
||||||
import com.squareup.moshi.JsonClass
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@Serializable
|
||||||
class Contributor(
|
class Contributor(
|
||||||
val displayName: String,
|
val displayName: String,
|
||||||
val githubUsername: String
|
val githubUsername: String
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package io.github.wulkanowy.data.pojos
|
package io.github.wulkanowy.data.pojos
|
||||||
|
|
||||||
import com.squareup.moshi.JsonClass
|
|
||||||
import io.github.wulkanowy.ui.modules.message.send.RecipientChipItem
|
import io.github.wulkanowy.ui.modules.message.send.RecipientChipItem
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@Serializable
|
||||||
data class MessageDraft(
|
data class MessageDraft(
|
||||||
val recipients: List<RecipientChipItem>,
|
val recipients: List<RecipientChipItem>,
|
||||||
val subject: String,
|
val subject: String,
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
package io.github.wulkanowy.data.pojos
|
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes
|
|
||||||
import androidx.annotation.PluralsRes
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import io.github.wulkanowy.services.sync.notifications.NotificationType
|
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
|
||||||
|
|
||||||
sealed interface Notification {
|
|
||||||
val type: NotificationType
|
|
||||||
val startMenu: MainView.Section
|
|
||||||
val icon: Int
|
|
||||||
val titleStringRes: Int
|
|
||||||
val contentStringRes: Int
|
|
||||||
}
|
|
||||||
|
|
||||||
data class MultipleNotifications(
|
|
||||||
override val type: NotificationType,
|
|
||||||
override val startMenu: MainView.Section,
|
|
||||||
@DrawableRes override val icon: Int,
|
|
||||||
@PluralsRes override val titleStringRes: Int,
|
|
||||||
@PluralsRes override val contentStringRes: Int,
|
|
||||||
|
|
||||||
@PluralsRes val summaryStringRes: Int,
|
|
||||||
val lines: List<String>,
|
|
||||||
) : Notification
|
|
||||||
|
|
||||||
data class OneNotification(
|
|
||||||
override val type: NotificationType,
|
|
||||||
override val startMenu: MainView.Section,
|
|
||||||
@DrawableRes override val icon: Int,
|
|
||||||
@StringRes override val titleStringRes: Int,
|
|
||||||
@StringRes override val contentStringRes: Int,
|
|
||||||
|
|
||||||
val contentValues: List<String>,
|
|
||||||
) : Notification
|
|
@ -0,0 +1,19 @@
|
|||||||
|
package io.github.wulkanowy.data.pojos
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import io.github.wulkanowy.services.sync.notifications.NotificationType
|
||||||
|
|
||||||
|
data class NotificationData(
|
||||||
|
val intentToStart: Intent,
|
||||||
|
val title: String,
|
||||||
|
val content: String
|
||||||
|
)
|
||||||
|
|
||||||
|
data class GroupNotificationData(
|
||||||
|
val notificationDataList: List<NotificationData>,
|
||||||
|
val title: String,
|
||||||
|
val content: String,
|
||||||
|
val intentToStart: Intent,
|
||||||
|
val type: NotificationType
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,52 @@
|
|||||||
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
|
import io.github.wulkanowy.data.api.AdminMessageService
|
||||||
|
import io.github.wulkanowy.data.db.dao.AdminMessageDao
|
||||||
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
|
import io.github.wulkanowy.utils.AppInfo
|
||||||
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
|
import io.github.wulkanowy.utils.networkBoundResource
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class AdminMessageRepository @Inject constructor(
|
||||||
|
private val adminMessageService: AdminMessageService,
|
||||||
|
private val adminMessageDao: AdminMessageDao,
|
||||||
|
private val appInfo: AppInfo,
|
||||||
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
|
) {
|
||||||
|
private val saveFetchResultMutex = Mutex()
|
||||||
|
|
||||||
|
private val cacheKey = "admin_messages"
|
||||||
|
|
||||||
|
suspend fun getAdminMessages(student: Student, forceRefresh: Boolean) = networkBoundResource(
|
||||||
|
mutex = saveFetchResultMutex,
|
||||||
|
query = { adminMessageDao.loadAll() },
|
||||||
|
fetch = { adminMessageService.getAdminMessages() },
|
||||||
|
shouldFetch = {
|
||||||
|
refreshHelper.shouldBeRefreshed(cacheKey) || forceRefresh
|
||||||
|
},
|
||||||
|
saveFetchResult = { oldItems, newItems ->
|
||||||
|
adminMessageDao.removeOldAndSaveNew(oldItems, newItems)
|
||||||
|
refreshHelper.updateLastRefreshTimestamp(cacheKey)
|
||||||
|
},
|
||||||
|
showSavedOnLoading = false,
|
||||||
|
mapResult = { adminMessages ->
|
||||||
|
adminMessages.filter { adminMessage ->
|
||||||
|
val isCorrectRegister = adminMessage.targetRegisterHost?.let {
|
||||||
|
student.scrapperBaseUrl.contains(it, true)
|
||||||
|
} ?: true
|
||||||
|
val isCorrectFlavor =
|
||||||
|
adminMessage.targetFlavor?.equals(appInfo.buildFlavor, true) ?: true
|
||||||
|
val isCorrectMaxVersion =
|
||||||
|
adminMessage.versionMax?.let { it >= appInfo.versionCode } ?: true
|
||||||
|
val isCorrectMinVersion =
|
||||||
|
adminMessage.versionMin?.let { it <= appInfo.versionCode } ?: true
|
||||||
|
|
||||||
|
isCorrectRegister && isCorrectFlavor && isCorrectMaxVersion && isCorrectMinVersion
|
||||||
|
}.maxByOrNull { it.id }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
@ -1,25 +1,27 @@
|
|||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import android.content.res.AssetManager
|
import android.content.Context
|
||||||
import com.squareup.moshi.Moshi
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import com.squareup.moshi.Types
|
|
||||||
import io.github.wulkanowy.data.pojos.Contributor
|
import io.github.wulkanowy.data.pojos.Contributor
|
||||||
import io.github.wulkanowy.utils.DispatchersProvider
|
import io.github.wulkanowy.utils.DispatchersProvider
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.decodeFromStream
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class AppCreatorRepository @Inject constructor(
|
class AppCreatorRepository @Inject constructor(
|
||||||
private val assets: AssetManager,
|
@ApplicationContext private val context: Context,
|
||||||
private val dispatchers: DispatchersProvider
|
private val dispatchers: DispatchersProvider,
|
||||||
|
private val json: Json,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
suspend fun getAppCreators() = withContext(dispatchers.backgroundThread) {
|
suspend fun getAppCreators() = withContext(dispatchers.io) {
|
||||||
val moshi = Moshi.Builder().build()
|
val inputStream = context.assets.open("contributors.json").buffered()
|
||||||
val type = Types.newParameterizedType(List::class.java, Contributor::class.java)
|
json.decodeFromStream<List<Contributor>>(inputStream)
|
||||||
val adapter = moshi.adapter<List<Contributor>>(type)
|
|
||||||
adapter.fromJson(assets.open("contributors.json").bufferedReader().use { it.readText() })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import io.github.wulkanowy.utils.monday
|
|||||||
import io.github.wulkanowy.utils.networkBoundResource
|
import io.github.wulkanowy.utils.networkBoundResource
|
||||||
import io.github.wulkanowy.utils.sunday
|
import io.github.wulkanowy.utils.sunday
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
@ -32,10 +33,24 @@ class AttendanceRepository @Inject constructor(
|
|||||||
|
|
||||||
private val cacheKey = "attendance"
|
private val cacheKey = "attendance"
|
||||||
|
|
||||||
fun getAttendance(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
|
fun getAttendance(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
start: LocalDate,
|
||||||
|
end: LocalDate,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
notify: Boolean = false,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
shouldFetch = {
|
||||||
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday) },
|
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||||
|
key = getRefreshKey(cacheKey, semester, start, end)
|
||||||
|
)
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
|
},
|
||||||
|
query = {
|
||||||
|
attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday)
|
||||||
|
},
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
.getAttendance(start.monday, end.sunday, semester.semesterId)
|
.getAttendance(start.monday, end.sunday, semester.semesterId)
|
||||||
@ -43,19 +58,39 @@ class AttendanceRepository @Inject constructor(
|
|||||||
},
|
},
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
attendanceDb.deleteAll(old uniqueSubtract new)
|
attendanceDb.deleteAll(old uniqueSubtract new)
|
||||||
attendanceDb.insertAll(new uniqueSubtract old)
|
val attendanceToAdd = (new uniqueSubtract old).map { newAttendance ->
|
||||||
|
newAttendance.apply { if (notify) isNotified = false }
|
||||||
|
}
|
||||||
|
attendanceDb.insertAll(attendanceToAdd)
|
||||||
|
|
||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
|
||||||
},
|
},
|
||||||
filterResult = { it.filter { item -> item.date in start..end } }
|
filterResult = { it.filter { item -> item.date in start..end } }
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun excuseForAbsence(student: Student, semester: Semester, absenceList: List<Attendance>, reason: String? = null) {
|
fun getAttendanceFromDatabase(
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).excuseForAbsence(absenceList.map { attendance ->
|
semester: Semester,
|
||||||
|
start: LocalDate,
|
||||||
|
end: LocalDate
|
||||||
|
): Flow<List<Attendance>> {
|
||||||
|
return attendanceDb.loadAll(semester.diaryId, semester.studentId, start, end)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateTimetable(timetable: List<Attendance>) {
|
||||||
|
return attendanceDb.updateAll(timetable)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun excuseForAbsence(
|
||||||
|
student: Student, semester: Semester,
|
||||||
|
absenceList: List<Attendance>, reason: String? = null
|
||||||
|
) {
|
||||||
|
val items = absenceList.map { attendance ->
|
||||||
Absent(
|
Absent(
|
||||||
date = LocalDateTime.of(attendance.date, LocalTime.of(0, 0)),
|
date = LocalDateTime.of(attendance.date, LocalTime.of(0, 0)),
|
||||||
timeId = attendance.timeId
|
timeId = attendance.timeId
|
||||||
)
|
)
|
||||||
}, reason)
|
}
|
||||||
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
|
.excuseForAbsence(items, reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,12 +29,12 @@ class AttendanceSummaryRepository @Inject constructor(
|
|||||||
student: Student,
|
student: Student,
|
||||||
semester: Semester,
|
semester: Semester,
|
||||||
subjectId: Int,
|
subjectId: Int,
|
||||||
forceRefresh: Boolean
|
forceRefresh: Boolean,
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
it.isEmpty() || forceRefresh
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||||
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
},
|
},
|
||||||
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) },
|
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
|
@ -28,10 +28,28 @@ class CompletedLessonsRepository @Inject constructor(
|
|||||||
|
|
||||||
private val cacheKey = "completed"
|
private val cacheKey = "completed"
|
||||||
|
|
||||||
fun getCompletedLessons(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
|
fun getCompletedLessons(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
start: LocalDate,
|
||||||
|
end: LocalDate,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
shouldFetch = {
|
||||||
query = { completedLessonsDb.loadAll(semester.studentId, semester.diaryId, start.monday, end.sunday) },
|
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||||
|
key = getRefreshKey(cacheKey, semester, start, end)
|
||||||
|
)
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
|
},
|
||||||
|
query = {
|
||||||
|
completedLessonsDb.loadAll(
|
||||||
|
studentId = semester.studentId,
|
||||||
|
diaryId = semester.diaryId,
|
||||||
|
from = start.monday,
|
||||||
|
end = end.sunday
|
||||||
|
)
|
||||||
|
},
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
.getCompletedLessons(start.monday, end.sunday)
|
.getCompletedLessons(start.monday, end.sunday)
|
||||||
|
@ -35,12 +35,12 @@ class ConferenceRepository @Inject constructor(
|
|||||||
semester: Semester,
|
semester: Semester,
|
||||||
forceRefresh: Boolean,
|
forceRefresh: Boolean,
|
||||||
notify: Boolean = false,
|
notify: Boolean = false,
|
||||||
startDate: LocalDateTime = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC)
|
startDate: LocalDateTime = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC),
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
it.isEmpty() || forceRefresh
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||||
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
},
|
},
|
||||||
query = {
|
query = {
|
||||||
conferenceDb.loadAll(semester.diaryId, student.studentId, startDate)
|
conferenceDb.loadAll(semester.diaryId, student.studentId, startDate)
|
||||||
|
@ -36,14 +36,14 @@ class ExamRepository @Inject constructor(
|
|||||||
start: LocalDate,
|
start: LocalDate,
|
||||||
end: LocalDate,
|
end: LocalDate,
|
||||||
forceRefresh: Boolean,
|
forceRefresh: Boolean,
|
||||||
notify: Boolean = false
|
notify: Boolean = false,
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
val isShouldBeRefreshed = refreshHelper.isShouldBeRefreshed(
|
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||||
key = getRefreshKey(cacheKey, semester, start, end)
|
key = getRefreshKey(cacheKey, semester, start, end)
|
||||||
)
|
)
|
||||||
it.isEmpty() || forceRefresh || isShouldBeRefreshed
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
},
|
},
|
||||||
query = {
|
query = {
|
||||||
examDb.loadAll(
|
examDb.loadAll(
|
||||||
|
@ -37,13 +37,12 @@ class GradeRepository @Inject constructor(
|
|||||||
student: Student,
|
student: Student,
|
||||||
semester: Semester,
|
semester: Semester,
|
||||||
forceRefresh: Boolean,
|
forceRefresh: Boolean,
|
||||||
notify: Boolean = false
|
notify: Boolean = false,
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { (details, summaries) ->
|
shouldFetch = { (details, summaries) ->
|
||||||
val isShouldBeRefreshed =
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||||
refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
details.isEmpty() || summaries.isEmpty() || forceRefresh || isExpired
|
||||||
details.isEmpty() || summaries.isEmpty() || forceRefresh || isShouldBeRefreshed
|
|
||||||
},
|
},
|
||||||
query = {
|
query = {
|
||||||
val detailsFlow = gradeDb.loadAll(semester.semesterId, semester.studentId)
|
val detailsFlow = gradeDb.loadAll(semester.semesterId, semester.studentId)
|
||||||
@ -71,8 +70,8 @@ class GradeRepository @Inject constructor(
|
|||||||
newDetails: List<Grade>,
|
newDetails: List<Grade>,
|
||||||
notify: Boolean
|
notify: Boolean
|
||||||
) {
|
) {
|
||||||
val notifyBreakDate =
|
val notifyBreakDate = oldGrades.maxByOrNull {it.date }
|
||||||
oldGrades.maxByOrNull { it.date }?.date ?: student.registrationDate.toLocalDate()
|
?.date ?: student.registrationDate.toLocalDate()
|
||||||
gradeDb.deleteAll(oldGrades uniqueSubtract newDetails)
|
gradeDb.deleteAll(oldGrades uniqueSubtract newDetails)
|
||||||
gradeDb.insertAll((newDetails uniqueSubtract oldGrades).onEach {
|
gradeDb.insertAll((newDetails uniqueSubtract oldGrades).onEach {
|
||||||
if (it.date >= notifyBreakDate) it.apply {
|
if (it.date >= notifyBreakDate) it.apply {
|
||||||
@ -89,8 +88,7 @@ class GradeRepository @Inject constructor(
|
|||||||
) {
|
) {
|
||||||
gradeSummaryDb.deleteAll(oldSummaries uniqueSubtract newSummary)
|
gradeSummaryDb.deleteAll(oldSummaries uniqueSubtract newSummary)
|
||||||
gradeSummaryDb.insertAll((newSummary uniqueSubtract oldSummaries).onEach { summary ->
|
gradeSummaryDb.insertAll((newSummary uniqueSubtract oldSummaries).onEach { summary ->
|
||||||
val oldSummary =
|
val oldSummary = oldSummaries.find { old -> old.subject == summary.subject }
|
||||||
oldSummaries.find { oldSummary -> oldSummary.subject == summary.subject }
|
|
||||||
summary.isPredictedGradeNotified = when {
|
summary.isPredictedGradeNotified = when {
|
||||||
summary.predictedGrade.isEmpty() -> true
|
summary.predictedGrade.isEmpty() -> true
|
||||||
notify && oldSummary?.predictedGrade != summary.predictedGrade -> false
|
notify && oldSummary?.predictedGrade != summary.predictedGrade -> false
|
||||||
|
@ -39,9 +39,19 @@ class GradeStatisticsRepository @Inject constructor(
|
|||||||
private val semesterCacheKey = "grade_stats_semester"
|
private val semesterCacheKey = "grade_stats_semester"
|
||||||
private val pointsCacheKey = "grade_stats_points"
|
private val pointsCacheKey = "grade_stats_points"
|
||||||
|
|
||||||
fun getGradesPartialStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
|
fun getGradesPartialStatistics(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
subjectName: String,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = partialMutex,
|
mutex = partialMutex,
|
||||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(partialCacheKey, semester)) },
|
shouldFetch = {
|
||||||
|
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||||
|
key = getRefreshKey(partialCacheKey, semester)
|
||||||
|
)
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
|
},
|
||||||
query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
query = { gradePartialStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
@ -76,9 +86,19 @@ class GradeStatisticsRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getGradesSemesterStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
|
fun getGradesSemesterStatistics(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
subjectName: String,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = semesterMutex,
|
mutex = semesterMutex,
|
||||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(semesterCacheKey, semester)) },
|
shouldFetch = {
|
||||||
|
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||||
|
key = getRefreshKey(semesterCacheKey, semester)
|
||||||
|
)
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
|
},
|
||||||
query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
query = { gradeSemesterStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
@ -94,10 +114,12 @@ class GradeStatisticsRepository @Inject constructor(
|
|||||||
val itemsWithAverage = items.map { item ->
|
val itemsWithAverage = items.map { item ->
|
||||||
item.copy().apply {
|
item.copy().apply {
|
||||||
val denominator = item.amounts.sum()
|
val denominator = item.amounts.sum()
|
||||||
average = if (denominator == 0) "" else (item.amounts.mapIndexed { gradeValue, amount ->
|
average = if (denominator == 0) "" else {
|
||||||
(gradeValue + 1) * amount
|
(item.amounts.mapIndexed { gradeValue, amount ->
|
||||||
}.sum().toDouble() / denominator).let {
|
(gradeValue + 1) * amount
|
||||||
"%.2f".format(Locale.FRANCE, it)
|
}.sum().toDouble() / denominator).let {
|
||||||
|
"%.2f".format(Locale.FRANCE, it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,7 +131,9 @@ class GradeStatisticsRepository @Inject constructor(
|
|||||||
amounts = itemsWithAverage.map { it.amounts }.sumGradeAmounts(),
|
amounts = itemsWithAverage.map { it.amounts }.sumGradeAmounts(),
|
||||||
studentGrade = 0
|
studentGrade = 0
|
||||||
).apply {
|
).apply {
|
||||||
average = itemsWithAverage.mapNotNull { it.average.replace(",", ".").toDoubleOrNull() }.average().let {
|
average = itemsWithAverage.mapNotNull {
|
||||||
|
it.average.replace(",", ".").toDoubleOrNull()
|
||||||
|
}.average().let {
|
||||||
"%.2f".format(Locale.FRANCE, it)
|
"%.2f".format(Locale.FRANCE, it)
|
||||||
}
|
}
|
||||||
}).reversed()
|
}).reversed()
|
||||||
@ -118,9 +142,17 @@ class GradeStatisticsRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getGradesPointsStatistics(student: Student, semester: Semester, subjectName: String, forceRefresh: Boolean) = networkBoundResource(
|
fun getGradesPointsStatistics(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
subjectName: String,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = pointsMutex,
|
mutex = pointsMutex,
|
||||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(pointsCacheKey, semester)) },
|
shouldFetch = {
|
||||||
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(pointsCacheKey, semester))
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
|
},
|
||||||
query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
query = { gradePointsStatisticsDb.loadAll(semester.semesterId, semester.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
|
@ -30,16 +30,19 @@ class HomeworkRepository @Inject constructor(
|
|||||||
private val cacheKey = "homework"
|
private val cacheKey = "homework"
|
||||||
|
|
||||||
fun getHomework(
|
fun getHomework(
|
||||||
student: Student, semester: Semester,
|
student: Student,
|
||||||
start: LocalDate, end: LocalDate,
|
semester: Semester,
|
||||||
forceRefresh: Boolean, notify: Boolean = false
|
start: LocalDate,
|
||||||
|
end: LocalDate,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
notify: Boolean = false,
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
val isShouldBeRefreshed = refreshHelper.isShouldBeRefreshed(
|
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||||
key = getRefreshKey(cacheKey, semester, start, end)
|
key = getRefreshKey(cacheKey, semester, start, end)
|
||||||
)
|
)
|
||||||
it.isEmpty() || forceRefresh || isShouldBeRefreshed
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
},
|
},
|
||||||
query = {
|
query = {
|
||||||
homeworkDb.loadAll(
|
homeworkDb.loadAll(
|
||||||
@ -58,8 +61,9 @@ class HomeworkRepository @Inject constructor(
|
|||||||
val homeWorkToSave = (new uniqueSubtract old).onEach {
|
val homeWorkToSave = (new uniqueSubtract old).onEach {
|
||||||
if (notify) it.isNotified = false
|
if (notify) it.isNotified = false
|
||||||
}
|
}
|
||||||
|
val filteredOld = old.filterNot { it.isAddedByUser }
|
||||||
|
|
||||||
homeworkDb.deleteAll(old uniqueSubtract new)
|
homeworkDb.deleteAll(filteredOld uniqueSubtract new)
|
||||||
homeworkDb.insertAll(homeWorkToSave)
|
homeworkDb.insertAll(homeWorkToSave)
|
||||||
|
|
||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end))
|
||||||
@ -76,4 +80,8 @@ class HomeworkRepository @Inject constructor(
|
|||||||
homeworkDb.loadAll(semester.semesterId, semester.studentId, start.monday, end.sunday)
|
homeworkDb.loadAll(semester.semesterId, semester.studentId, start.monday, end.sunday)
|
||||||
|
|
||||||
suspend fun updateHomework(homework: List<Homework>) = homeworkDb.updateAll(homework)
|
suspend fun updateHomework(homework: List<Homework>) = homeworkDb.updateAll(homework)
|
||||||
|
|
||||||
|
suspend fun saveHomework(homework: Homework) = homeworkDb.insertAll(listOf(homework))
|
||||||
|
|
||||||
|
suspend fun deleteHomework(homework: Homework) = homeworkDb.deleteAll(listOf(homework))
|
||||||
}
|
}
|
||||||
|
@ -15,24 +15,23 @@ class LoggerRepository @Inject constructor(
|
|||||||
|
|
||||||
suspend fun getLastLogLines() = getLastModified().readText().split("\n")
|
suspend fun getLastLogLines() = getLastModified().readText().split("\n")
|
||||||
|
|
||||||
suspend fun getLogFiles() = withContext(dispatchers.backgroundThread) {
|
suspend fun getLogFiles() = withContext(dispatchers.io) {
|
||||||
File(context.filesDir.absolutePath).listFiles(File::isFile)?.filter {
|
File(context.filesDir.absolutePath).listFiles(File::isFile)
|
||||||
it.name.endsWith(".log")
|
?.filter { it.name.endsWith(".log") }!!
|
||||||
}!!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getLastModified(): File {
|
private suspend fun getLastModified() = withContext(dispatchers.io) {
|
||||||
return withContext(dispatchers.backgroundThread) {
|
var lastModifiedTime = Long.MIN_VALUE
|
||||||
var lastModifiedTime = Long.MIN_VALUE
|
var chosenFile: File? = null
|
||||||
var chosenFile: File? = null
|
|
||||||
File(context.filesDir.absolutePath).listFiles(File::isFile)?.forEach { file ->
|
File(context.filesDir.absolutePath).listFiles(File::isFile)
|
||||||
|
?.forEach { file ->
|
||||||
if (file.lastModified() > lastModifiedTime) {
|
if (file.lastModified() > lastModifiedTime) {
|
||||||
lastModifiedTime = file.lastModified()
|
lastModifiedTime = file.lastModified()
|
||||||
chosenFile = file
|
chosenFile = file
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (chosenFile == null) throw FileNotFoundException("Log file not found")
|
|
||||||
chosenFile!!
|
chosenFile ?: throw FileNotFoundException("Log file not found")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,11 +23,17 @@ class LuckyNumberRepository @Inject constructor(
|
|||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
private val saveFetchResultMutex = Mutex()
|
||||||
|
|
||||||
fun getLuckyNumber(student: Student, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource(
|
fun getLuckyNumber(
|
||||||
|
student: Student,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
notify: Boolean = false,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { it == null || forceRefresh },
|
shouldFetch = { it == null || forceRefresh },
|
||||||
query = { luckyNumberDb.load(student.studentId, now()) },
|
query = { luckyNumberDb.load(student.studentId, now()) },
|
||||||
fetch = { sdk.init(student).getLuckyNumber(student.schoolShortName)?.mapToEntity(student) },
|
fetch = {
|
||||||
|
sdk.init(student).getLuckyNumber(student.schoolShortName)?.mapToEntity(student)
|
||||||
|
},
|
||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
if (new != old) {
|
if (new != old) {
|
||||||
old?.let { luckyNumberDb.deleteAll(listOfNotNull(it)) }
|
old?.let { luckyNumberDb.deleteAll(listOfNotNull(it)) }
|
||||||
@ -41,9 +47,11 @@ class LuckyNumberRepository @Inject constructor(
|
|||||||
fun getLuckyNumberHistory(student: Student, start: LocalDate, end: LocalDate) =
|
fun getLuckyNumberHistory(student: Student, start: LocalDate, end: LocalDate) =
|
||||||
luckyNumberDb.getAll(student.studentId, start, end)
|
luckyNumberDb.getAll(student.studentId, start, end)
|
||||||
|
|
||||||
suspend fun getNotNotifiedLuckyNumber(student: Student) = luckyNumberDb.load(student.studentId, now()).map {
|
suspend fun getNotNotifiedLuckyNumber(student: Student) =
|
||||||
if (it?.isNotified == false) it else null
|
luckyNumberDb.load(student.studentId, now()).map {
|
||||||
}.first()
|
if (it?.isNotified == false) it else null
|
||||||
|
}.first()
|
||||||
|
|
||||||
suspend fun updateLuckyNumber(luckyNumber: LuckyNumber?) = luckyNumberDb.updateAll(listOfNotNull(luckyNumber))
|
suspend fun updateLuckyNumber(luckyNumber: LuckyNumber?) =
|
||||||
|
luckyNumberDb.updateAll(listOfNotNull(luckyNumber))
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.squareup.moshi.Moshi
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.Resource
|
import io.github.wulkanowy.data.Resource
|
||||||
@ -18,7 +17,6 @@ import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
|
|||||||
import io.github.wulkanowy.data.mappers.mapFromEntities
|
import io.github.wulkanowy.data.mappers.mapFromEntities
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.data.pojos.MessageDraft
|
import io.github.wulkanowy.data.pojos.MessageDraft
|
||||||
import io.github.wulkanowy.data.pojos.MessageDraftJsonAdapter
|
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
import io.github.wulkanowy.sdk.pojo.Folder
|
import io.github.wulkanowy.sdk.pojo.Folder
|
||||||
import io.github.wulkanowy.sdk.pojo.SentMessage
|
import io.github.wulkanowy.sdk.pojo.SentMessage
|
||||||
@ -29,6 +27,9 @@ import io.github.wulkanowy.utils.networkBoundResource
|
|||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.time.LocalDateTime.now
|
import java.time.LocalDateTime.now
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -42,7 +43,7 @@ class MessageRepository @Inject constructor(
|
|||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
private val refreshHelper: AutoRefreshHelper,
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
private val sharedPrefProvider: SharedPrefProvider,
|
private val sharedPrefProvider: SharedPrefProvider,
|
||||||
private val moshi: Moshi,
|
private val json: Json,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
private val saveFetchResultMutex = Mutex()
|
||||||
@ -51,14 +52,18 @@ class MessageRepository @Inject constructor(
|
|||||||
|
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
fun getMessages(
|
fun getMessages(
|
||||||
student: Student, semester: Semester,
|
student: Student,
|
||||||
folder: MessageFolder, forceRefresh: Boolean, notify: Boolean = false
|
semester: Semester,
|
||||||
|
folder: MessageFolder,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
notify: Boolean = false,
|
||||||
): Flow<Resource<List<Message>>> = networkBoundResource(
|
): Flow<Resource<List<Message>>> = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(
|
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||||
getRefreshKey(cacheKey, student, folder)
|
key = getRefreshKey(cacheKey, student, folder)
|
||||||
)
|
)
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
},
|
},
|
||||||
query = { messagesDb.loadAll(student.id.toInt(), folder.id) },
|
query = { messagesDb.loadAll(student.id.toInt(), folder.id) },
|
||||||
fetch = {
|
fetch = {
|
||||||
@ -77,7 +82,8 @@ class MessageRepository @Inject constructor(
|
|||||||
)
|
)
|
||||||
|
|
||||||
private fun getMessagesWithReadByChange(
|
private fun getMessagesWithReadByChange(
|
||||||
old: List<Message>, new: List<Message>,
|
old: List<Message>,
|
||||||
|
new: List<Message>,
|
||||||
setNotified: Boolean
|
setNotified: Boolean
|
||||||
): List<Message> {
|
): List<Message> {
|
||||||
val oldMeta = old.map { Triple(it, it.readBy, it.unreadBy) }
|
val oldMeta = old.map { Triple(it, it.readBy, it.unreadBy) }
|
||||||
@ -96,7 +102,9 @@ class MessageRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getMessage(
|
fun getMessage(
|
||||||
student: Student, message: Message, markAsRead: Boolean = false
|
student: Student,
|
||||||
|
message: Message,
|
||||||
|
markAsRead: Boolean = false,
|
||||||
): Flow<Resource<MessageWithAttachment?>> = networkBoundResource(
|
): Flow<Resource<MessageWithAttachment?>> = networkBoundResource(
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
checkNotNull(it, { "This message no longer exist!" })
|
checkNotNull(it, { "This message no longer exist!" })
|
||||||
@ -135,8 +143,10 @@ class MessageRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun sendMessage(
|
suspend fun sendMessage(
|
||||||
student: Student, subject: String, content: String,
|
student: Student,
|
||||||
recipients: List<Recipient>
|
subject: String,
|
||||||
|
content: String,
|
||||||
|
recipients: List<Recipient>,
|
||||||
): SentMessage = sdk.init(student).sendMessage(
|
): SentMessage = sdk.init(student).sendMessage(
|
||||||
subject = subject,
|
subject = subject,
|
||||||
content = content,
|
content = content,
|
||||||
@ -159,9 +169,9 @@ class MessageRepository @Inject constructor(
|
|||||||
|
|
||||||
var draftMessage: MessageDraft?
|
var draftMessage: MessageDraft?
|
||||||
get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_send_draft))
|
get() = sharedPrefProvider.getString(context.getString(R.string.pref_key_message_send_draft))
|
||||||
?.let { MessageDraftJsonAdapter(moshi).fromJson(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_send_draft),
|
||||||
value?.let { MessageDraftJsonAdapter(moshi).toJson(it) }
|
value?.let { json.encodeToString(it) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -28,9 +28,16 @@ class MobileDeviceRepository @Inject constructor(
|
|||||||
|
|
||||||
private val cacheKey = "devices"
|
private val cacheKey = "devices"
|
||||||
|
|
||||||
fun getDevices(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource(
|
fun getDevices(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, student)) },
|
shouldFetch = {
|
||||||
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
|
},
|
||||||
query = { mobileDb.loadAll(student.userLoginId.takeIf { it != 0 } ?: student.studentId) },
|
query = { mobileDb.loadAll(student.userLoginId.takeIf { it != 0 } ?: student.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
|
@ -12,7 +12,6 @@ import io.github.wulkanowy.utils.init
|
|||||||
import io.github.wulkanowy.utils.networkBoundResource
|
import io.github.wulkanowy.utils.networkBoundResource
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
@ -28,9 +27,19 @@ class NoteRepository @Inject constructor(
|
|||||||
|
|
||||||
private val cacheKey = "note"
|
private val cacheKey = "note"
|
||||||
|
|
||||||
fun getNotes(student: Student, semester: Semester, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource(
|
fun getNotes(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
notify: Boolean = false,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester)) },
|
shouldFetch = {
|
||||||
|
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||||
|
getRefreshKey(cacheKey, semester)
|
||||||
|
)
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
|
},
|
||||||
query = { noteDb.loadAll(student.studentId) },
|
query = { noteDb.loadAll(student.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
|
import io.github.wulkanowy.data.db.dao.NotificationDao
|
||||||
|
import io.github.wulkanowy.data.db.entities.Notification
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class NotificationRepository @Inject constructor(
|
||||||
|
private val notificationDao: NotificationDao,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun getNotifications(studentId: Long) = notificationDao.loadAll(studentId)
|
||||||
|
|
||||||
|
suspend fun saveNotification(notification: Notification) =
|
||||||
|
notificationDao.insertAll(listOf(notification))
|
||||||
|
}
|
@ -5,21 +5,21 @@ import android.content.SharedPreferences
|
|||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
|
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
|
||||||
import com.fredporciuncula.flow.preferences.Preference
|
import com.fredporciuncula.flow.preferences.Preference
|
||||||
import com.squareup.moshi.JsonAdapter
|
|
||||||
import com.squareup.moshi.Moshi
|
|
||||||
import com.squareup.moshi.adapter
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.sdk.toLocalDate
|
import io.github.wulkanowy.sdk.toLocalDate
|
||||||
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
|
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
|
||||||
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
|
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
|
||||||
|
import io.github.wulkanowy.ui.modules.grade.GradeExpandMode
|
||||||
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode
|
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode
|
||||||
import io.github.wulkanowy.utils.toTimestamp
|
|
||||||
import io.github.wulkanowy.utils.toLocalDateTime
|
import io.github.wulkanowy.utils.toLocalDateTime
|
||||||
import io.github.wulkanowy.utils.toTimestamp
|
import io.github.wulkanowy.utils.toTimestamp
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -28,16 +28,12 @@ import javax.inject.Singleton
|
|||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@Singleton
|
@Singleton
|
||||||
class PreferencesRepository @Inject constructor(
|
class PreferencesRepository @Inject constructor(
|
||||||
|
@ApplicationContext val context: Context,
|
||||||
private val sharedPref: SharedPreferences,
|
private val sharedPref: SharedPreferences,
|
||||||
private val flowSharedPref: FlowSharedPreferences,
|
private val flowSharedPref: FlowSharedPreferences,
|
||||||
@ApplicationContext val context: Context,
|
private val json: Json,
|
||||||
moshi: Moshi
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@OptIn(ExperimentalStdlibApi::class)
|
|
||||||
private val dashboardItemsPositionAdapter: JsonAdapter<Map<DashboardItem.Type, Int>> =
|
|
||||||
moshi.adapter()
|
|
||||||
|
|
||||||
val startMenuIndex: Int
|
val startMenuIndex: Int
|
||||||
get() = getString(R.string.pref_key_start_menu, R.string.pref_default_startup).toInt()
|
get() = getString(R.string.pref_key_start_menu, R.string.pref_default_startup).toInt()
|
||||||
|
|
||||||
@ -61,8 +57,13 @@ class PreferencesRepository @Inject constructor(
|
|||||||
R.bool.pref_default_grade_average_force_calc
|
R.bool.pref_default_grade_average_force_calc
|
||||||
)
|
)
|
||||||
|
|
||||||
val isGradeExpandable: Boolean
|
val gradeExpandMode: GradeExpandMode
|
||||||
get() = !getBoolean(R.string.pref_key_expand_grade, R.bool.pref_default_expand_grade)
|
get() = GradeExpandMode.getByValue(
|
||||||
|
getString(
|
||||||
|
R.string.pref_key_expand_grade_mode,
|
||||||
|
R.string.pref_default_expand_grade_mode
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
val showAllSubjectsOnStatisticsList: Boolean
|
val showAllSubjectsOnStatisticsList: Boolean
|
||||||
get() = getBoolean(
|
get() = getBoolean(
|
||||||
@ -102,12 +103,31 @@ class PreferencesRepository @Inject constructor(
|
|||||||
|
|
||||||
val isUpcomingLessonsNotificationsEnableKey =
|
val isUpcomingLessonsNotificationsEnableKey =
|
||||||
context.getString(R.string.pref_key_notifications_upcoming_lessons_enable)
|
context.getString(R.string.pref_key_notifications_upcoming_lessons_enable)
|
||||||
val isUpcomingLessonsNotificationsEnable: Boolean
|
var isUpcomingLessonsNotificationsEnable: Boolean
|
||||||
|
set(value) {
|
||||||
|
sharedPref.edit { putBoolean(isUpcomingLessonsNotificationsEnableKey, value) }
|
||||||
|
}
|
||||||
get() = getBoolean(
|
get() = getBoolean(
|
||||||
isUpcomingLessonsNotificationsEnableKey,
|
isUpcomingLessonsNotificationsEnableKey,
|
||||||
R.bool.pref_default_notification_upcoming_lessons_enable
|
R.bool.pref_default_notification_upcoming_lessons_enable
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val isUpcomingLessonsNotificationsPersistentKey =
|
||||||
|
context.getString(R.string.pref_key_notifications_upcoming_lessons_persistent)
|
||||||
|
val isUpcomingLessonsNotificationsPersistent: Boolean
|
||||||
|
get() = getBoolean(
|
||||||
|
isUpcomingLessonsNotificationsPersistentKey,
|
||||||
|
R.bool.pref_default_notification_upcoming_lessons_persistent
|
||||||
|
)
|
||||||
|
|
||||||
|
val isNotificationPiggybackEnabledKey =
|
||||||
|
context.getString(R.string.pref_key_notifications_piggyback)
|
||||||
|
val isNotificationPiggybackEnabled: Boolean
|
||||||
|
get() = getBoolean(
|
||||||
|
R.string.pref_key_notifications_piggyback,
|
||||||
|
R.bool.pref_default_notification_piggyback
|
||||||
|
)
|
||||||
|
|
||||||
val isDebugNotificationEnableKey = context.getString(R.string.pref_key_notification_debug)
|
val isDebugNotificationEnableKey = context.getString(R.string.pref_key_notification_debug)
|
||||||
val isDebugNotificationEnable: Boolean
|
val isDebugNotificationEnable: Boolean
|
||||||
get() = getBoolean(isDebugNotificationEnableKey, R.bool.pref_default_notification_debug)
|
get() = getBoolean(isDebugNotificationEnableKey, R.bool.pref_default_notification_debug)
|
||||||
@ -176,22 +196,20 @@ class PreferencesRepository @Inject constructor(
|
|||||||
)
|
)
|
||||||
|
|
||||||
var lasSyncDate: LocalDateTime
|
var lasSyncDate: LocalDateTime
|
||||||
get() = getLong(
|
get() = getLong(R.string.pref_key_last_sync_date, R.string.pref_default_last_sync_date)
|
||||||
R.string.pref_key_last_sync_date,
|
.toLocalDateTime()
|
||||||
R.string.pref_default_last_sync_date
|
|
||||||
).toLocalDateTime()
|
|
||||||
set(value) = sharedPref.edit().putLong("last_sync_date", value.toTimestamp()).apply()
|
set(value) = sharedPref.edit().putLong("last_sync_date", value.toTimestamp()).apply()
|
||||||
|
|
||||||
var dashboardItemsPosition: Map<DashboardItem.Type, Int>?
|
var dashboardItemsPosition: Map<DashboardItem.Type, Int>?
|
||||||
get() {
|
get() {
|
||||||
val json = sharedPref.getString(PREF_KEY_DASHBOARD_ITEMS_POSITION, null) ?: return null
|
val value = sharedPref.getString(PREF_KEY_DASHBOARD_ITEMS_POSITION, null) ?: return null
|
||||||
|
|
||||||
return dashboardItemsPositionAdapter.fromJson(json)
|
return json.decodeFromString(value)
|
||||||
}
|
}
|
||||||
set(value) = sharedPref.edit {
|
set(value) = sharedPref.edit {
|
||||||
putString(
|
putString(
|
||||||
PREF_KEY_DASHBOARD_ITEMS_POSITION,
|
PREF_KEY_DASHBOARD_ITEMS_POSITION,
|
||||||
dashboardItemsPositionAdapter.toJson(value)
|
json.encodeToString(value)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,6 +218,7 @@ class PreferencesRepository @Inject constructor(
|
|||||||
.map { set ->
|
.map { set ->
|
||||||
set.map { DashboardItem.Tile.valueOf(it) }
|
set.map { DashboardItem.Tile.valueOf(it) }
|
||||||
.plus(DashboardItem.Tile.ACCOUNT)
|
.plus(DashboardItem.Tile.ACCOUNT)
|
||||||
|
.plus(DashboardItem.Tile.ADMIN_MESSAGE)
|
||||||
.toSet()
|
.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,6 +226,7 @@ class PreferencesRepository @Inject constructor(
|
|||||||
get() = selectedDashboardTilesPreference.get()
|
get() = selectedDashboardTilesPreference.get()
|
||||||
.map { DashboardItem.Tile.valueOf(it) }
|
.map { DashboardItem.Tile.valueOf(it) }
|
||||||
.plus(DashboardItem.Tile.ACCOUNT)
|
.plus(DashboardItem.Tile.ACCOUNT)
|
||||||
|
.plus(DashboardItem.Tile.ADMIN_MESSAGE)
|
||||||
.toSet()
|
.toSet()
|
||||||
set(value) {
|
set(value) {
|
||||||
val filteredValue = value.filterNot { it == DashboardItem.Tile.ACCOUNT }
|
val filteredValue = value.filterNot { it == DashboardItem.Tile.ACCOUNT }
|
||||||
@ -225,13 +245,23 @@ class PreferencesRepository @Inject constructor(
|
|||||||
return flowSharedPref.getStringSet(prefKey, defaultSet)
|
return flowSharedPref.getStringSet(prefKey, defaultSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var dismissedAdminMessageIds: List<Int>
|
||||||
|
get() = sharedPref.getStringSet(PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS, emptySet())
|
||||||
|
.orEmpty()
|
||||||
|
.map { it.toInt() }
|
||||||
|
set(value) = sharedPref.edit {
|
||||||
|
putStringSet(PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS, value.map { it.toString() }.toSet())
|
||||||
|
}
|
||||||
|
|
||||||
var inAppReviewCount: Int
|
var inAppReviewCount: Int
|
||||||
get() = sharedPref.getInt(PREF_KEY_IN_APP_REVIEW_COUNT, 0)
|
get() = sharedPref.getInt(PREF_KEY_IN_APP_REVIEW_COUNT, 0)
|
||||||
set(value) = sharedPref.edit().putInt(PREF_KEY_IN_APP_REVIEW_COUNT, value).apply()
|
set(value) = sharedPref.edit().putInt(PREF_KEY_IN_APP_REVIEW_COUNT, value).apply()
|
||||||
|
|
||||||
var inAppReviewDate: LocalDate?
|
var inAppReviewDate: LocalDate?
|
||||||
get() = sharedPref.getLong(PREF_KEY_IN_APP_REVIEW_DATE, 0).takeIf { it != 0L }?.toLocalDate()
|
get() = sharedPref.getLong(PREF_KEY_IN_APP_REVIEW_DATE, 0).takeIf { it != 0L }
|
||||||
set(value) = sharedPref.edit().putLong(PREF_KEY_IN_APP_REVIEW_DATE, value!!.toTimestamp()).apply()
|
?.toLocalDate()
|
||||||
|
set(value) = sharedPref.edit().putLong(PREF_KEY_IN_APP_REVIEW_DATE, value!!.toTimestamp())
|
||||||
|
.apply()
|
||||||
|
|
||||||
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)
|
||||||
@ -252,6 +282,9 @@ 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_DASHBOARD_ITEMS_POSITION = "dashboard_items_position"
|
private const val PREF_KEY_DASHBOARD_ITEMS_POSITION = "dashboard_items_position"
|
||||||
@ -261,5 +294,7 @@ class PreferencesRepository @Inject constructor(
|
|||||||
private const val PREF_KEY_IN_APP_REVIEW_DATE = "in_app_review_date"
|
private const val PREF_KEY_IN_APP_REVIEW_DATE = "in_app_review_date"
|
||||||
|
|
||||||
private const val PREF_KEY_IN_APP_REVIEW_DONE = "in_app_review_done"
|
private const val PREF_KEY_IN_APP_REVIEW_DONE = "in_app_review_done"
|
||||||
|
|
||||||
|
private const val PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS = "admin_message_dismissed_ids"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,8 @@ import io.github.wulkanowy.data.db.entities.ReportingUnit
|
|||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
|
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 javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -15,26 +17,34 @@ import javax.inject.Singleton
|
|||||||
@Singleton
|
@Singleton
|
||||||
class RecipientRepository @Inject constructor(
|
class RecipientRepository @Inject constructor(
|
||||||
private val recipientDb: RecipientDao,
|
private val recipientDb: RecipientDao,
|
||||||
private val sdk: Sdk
|
private val sdk: Sdk,
|
||||||
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
private val cacheKey = "recipient"
|
||||||
|
|
||||||
suspend fun refreshRecipients(student: Student, unit: ReportingUnit, role: Int) {
|
suspend fun refreshRecipients(student: Student, unit: ReportingUnit, role: Int) {
|
||||||
val new = sdk.init(student).getRecipients(unit.unitId, role).mapToEntities(unit.studentId)
|
val new = sdk.init(student).getRecipients(unit.unitId, role).mapToEntities(unit.studentId)
|
||||||
val old = recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
val old = recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
||||||
|
|
||||||
recipientDb.deleteAll(old uniqueSubtract new)
|
recipientDb.deleteAll(old uniqueSubtract new)
|
||||||
recipientDb.insertAll(new uniqueSubtract old)
|
recipientDb.insertAll(new uniqueSubtract old)
|
||||||
|
|
||||||
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getRecipients(student: Student, unit: ReportingUnit, role: Int): List<Recipient> {
|
suspend fun getRecipients(student: Student, unit: ReportingUnit, role: Int): List<Recipient> {
|
||||||
return recipientDb.loadAll(unit.studentId, unit.unitId, role).ifEmpty {
|
val cached = recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
||||||
refreshRecipients(student, unit, role)
|
|
||||||
|
|
||||||
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||||
|
return if (cached.isEmpty() || isExpired) {
|
||||||
|
refreshRecipients(student, unit, role)
|
||||||
recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
||||||
}
|
} else cached
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getMessageRecipients(student: Student, message: Message): List<Recipient> {
|
suspend fun getMessageRecipients(student: Student, message: Message): List<Recipient> {
|
||||||
return sdk.init(student).getMessageRecipients(message.messageId, message.senderId).mapToEntities(student.studentId)
|
return sdk.init(student).getMessageRecipients(message.messageId, message.senderId)
|
||||||
|
.mapToEntities(student.studentId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ class RecoverRepository @Inject constructor(private val sdk: Sdk) {
|
|||||||
return sdk.getPasswordResetCaptchaCode(host, symbol)
|
return sdk.getPasswordResetCaptchaCode(host, symbol)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun sendRecoverRequest(url: String, symbol: String, email: String, reCaptchaResponse: String): String {
|
suspend fun sendRecoverRequest(
|
||||||
return sdk.sendPasswordResetRequest(url, symbol, email, reCaptchaResponse)
|
url: String, symbol: String, email: String, reCaptchaResponse: String
|
||||||
}
|
): String = sdk.sendPasswordResetRequest(url, symbol, email, reCaptchaResponse)
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package io.github.wulkanowy.data.repositories
|
|||||||
|
|
||||||
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
|
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
|
||||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
@ -12,7 +11,6 @@ import io.github.wulkanowy.utils.init
|
|||||||
import io.github.wulkanowy.utils.networkBoundResource
|
import io.github.wulkanowy.utils.networkBoundResource
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
@ -30,17 +28,15 @@ class SchoolAnnouncementRepository @Inject constructor(
|
|||||||
|
|
||||||
fun getSchoolAnnouncements(
|
fun getSchoolAnnouncements(
|
||||||
student: Student,
|
student: Student,
|
||||||
forceRefresh: Boolean,
|
forceRefresh: Boolean, notify: Boolean = false
|
||||||
notify: Boolean = false
|
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = {
|
shouldFetch = {
|
||||||
it.isEmpty() || forceRefresh
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||||
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, student))
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
},
|
},
|
||||||
query = {
|
query = {
|
||||||
schoolAnnouncementDb.loadAll(
|
schoolAnnouncementDb.loadAll(student.studentId)
|
||||||
student.studentId)
|
|
||||||
},
|
},
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student)
|
sdk.init(student)
|
||||||
@ -57,9 +53,11 @@ class SchoolAnnouncementRepository @Inject constructor(
|
|||||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getSchoolAnnouncementFromDatabase(student: Student): Flow<List<SchoolAnnouncement>> {
|
fun getSchoolAnnouncementFromDatabase(student: Student): Flow<List<SchoolAnnouncement>> {
|
||||||
return schoolAnnouncementDb.loadAll(student.studentId)
|
return schoolAnnouncementDb.loadAll(student.studentId)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateSchoolAnnouncement(schoolAnnouncement: List<SchoolAnnouncement>) = schoolAnnouncementDb.updateAll(schoolAnnouncement)
|
suspend fun updateSchoolAnnouncement(schoolAnnouncement: List<SchoolAnnouncement>) =
|
||||||
|
schoolAnnouncementDb.updateAll(schoolAnnouncement)
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ import io.github.wulkanowy.data.db.entities.Semester
|
|||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntity
|
import io.github.wulkanowy.data.mappers.mapToEntity
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
import io.github.wulkanowy.utils.init
|
import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.networkBoundResource
|
import io.github.wulkanowy.utils.networkBoundResource
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
@ -14,29 +16,41 @@ import javax.inject.Singleton
|
|||||||
@Singleton
|
@Singleton
|
||||||
class SchoolRepository @Inject constructor(
|
class SchoolRepository @Inject constructor(
|
||||||
private val schoolDb: SchoolDao,
|
private val schoolDb: SchoolDao,
|
||||||
private val sdk: Sdk
|
private val sdk: Sdk,
|
||||||
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
private val saveFetchResultMutex = Mutex()
|
||||||
|
|
||||||
fun getSchoolInfo(student: Student, semester: Semester, forceRefresh: Boolean) =
|
private val cacheKey = "school_info"
|
||||||
networkBoundResource(
|
|
||||||
mutex = saveFetchResultMutex,
|
fun getSchoolInfo(
|
||||||
shouldFetch = { it == null || forceRefresh },
|
student: Student,
|
||||||
query = { schoolDb.load(semester.studentId, semester.classId) },
|
semester: Semester,
|
||||||
fetch = {
|
forceRefresh: Boolean,
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).getSchool()
|
) = networkBoundResource(
|
||||||
.mapToEntity(semester)
|
mutex = saveFetchResultMutex,
|
||||||
},
|
shouldFetch = {
|
||||||
saveFetchResult = { old, new ->
|
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||||
if (old != null && new != old) {
|
key = getRefreshKey(cacheKey, student)
|
||||||
with(schoolDb) {
|
)
|
||||||
deleteAll(listOf(old))
|
it == null || forceRefresh || isExpired
|
||||||
insertAll(listOf(new))
|
},
|
||||||
}
|
query = { schoolDb.load(semester.studentId, semester.classId) },
|
||||||
} else if (old == null) {
|
fetch = {
|
||||||
schoolDb.insertAll(listOf(new))
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).getSchool()
|
||||||
|
.mapToEntity(semester)
|
||||||
|
},
|
||||||
|
saveFetchResult = { old, new ->
|
||||||
|
if (old != null && new != old) {
|
||||||
|
with(schoolDb) {
|
||||||
|
deleteAll(listOf(old))
|
||||||
|
insertAll(listOf(new))
|
||||||
}
|
}
|
||||||
|
} else if (old == null) {
|
||||||
|
schoolDb.insertAll(listOf(new))
|
||||||
}
|
}
|
||||||
)
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ class SemesterRepository @Inject constructor(
|
|||||||
student: Student,
|
student: Student,
|
||||||
forceRefresh: Boolean = false,
|
forceRefresh: Boolean = false,
|
||||||
refreshOnNoCurrent: Boolean = false
|
refreshOnNoCurrent: Boolean = false
|
||||||
) = withContext(dispatchers.backgroundThread) {
|
) = withContext(dispatchers.io) {
|
||||||
val semesters = semesterDb.loadAll(student.studentId, student.classId)
|
val semesters = semesterDb.loadAll(student.studentId, student.classId)
|
||||||
|
|
||||||
if (isShouldFetch(student, semesters, forceRefresh, refreshOnNoCurrent)) {
|
if (isShouldFetch(student, semesters, forceRefresh, refreshOnNoCurrent)) {
|
||||||
@ -64,7 +64,7 @@ class SemesterRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false) =
|
suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false) =
|
||||||
withContext(dispatchers.backgroundThread) {
|
withContext(dispatchers.io) {
|
||||||
getSemesters(student, forceRefresh).getCurrentOrLast()
|
getSemesters(student, forceRefresh).getCurrentOrLast()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,24 +19,27 @@ class StudentInfoRepository @Inject constructor(
|
|||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
private val saveFetchResultMutex = Mutex()
|
||||||
|
|
||||||
fun getStudentInfo(student: Student, semester: Semester, forceRefresh: Boolean) =
|
fun getStudentInfo(
|
||||||
networkBoundResource(
|
student: Student,
|
||||||
mutex = saveFetchResultMutex,
|
semester: Semester,
|
||||||
shouldFetch = { it == null || forceRefresh },
|
forceRefresh: Boolean,
|
||||||
query = { studentInfoDao.loadStudentInfo(student.studentId) },
|
) = networkBoundResource(
|
||||||
fetch = {
|
mutex = saveFetchResultMutex,
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
shouldFetch = { it == null || forceRefresh },
|
||||||
.getStudentInfo().mapToEntity(semester)
|
query = { studentInfoDao.loadStudentInfo(student.studentId) },
|
||||||
},
|
fetch = {
|
||||||
saveFetchResult = { old, new ->
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
if (old != null && new != old) {
|
.getStudentInfo().mapToEntity(semester)
|
||||||
with(studentInfoDao) {
|
},
|
||||||
deleteAll(listOf(old))
|
saveFetchResult = { old, new ->
|
||||||
insertAll(listOf(new))
|
if (old != null && new != old) {
|
||||||
}
|
with(studentInfoDao) {
|
||||||
} else if (old == null) {
|
deleteAll(listOf(old))
|
||||||
studentInfoDao.insertAll(listOf(new))
|
insertAll(listOf(new))
|
||||||
}
|
}
|
||||||
|
} else if (old == null) {
|
||||||
|
studentInfoDao.insertAll(listOf(new))
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package io.github.wulkanowy.data.repositories
|
package io.github.wulkanowy.data.repositories
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.room.withTransaction
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import io.github.wulkanowy.data.db.AppDatabase
|
||||||
import io.github.wulkanowy.data.db.dao.SemesterDao
|
import io.github.wulkanowy.data.db.dao.SemesterDao
|
||||||
import io.github.wulkanowy.data.db.dao.StudentDao
|
import io.github.wulkanowy.data.db.dao.StudentDao
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
@ -25,7 +27,8 @@ class StudentRepository @Inject constructor(
|
|||||||
private val studentDb: StudentDao,
|
private val studentDb: StudentDao,
|
||||||
private val semesterDb: SemesterDao,
|
private val semesterDb: SemesterDao,
|
||||||
private val sdk: Sdk,
|
private val sdk: Sdk,
|
||||||
private val appInfo: AppInfo
|
private val appInfo: AppInfo,
|
||||||
|
private val appDatabase: AppDatabase
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun isStudentSaved() = getSavedStudents(false).isNotEmpty()
|
suspend fun isStudentSaved() = getSavedStudents(false).isNotEmpty()
|
||||||
@ -63,7 +66,7 @@ class StudentRepository @Inject constructor(
|
|||||||
.map {
|
.map {
|
||||||
it.apply {
|
it.apply {
|
||||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
||||||
student.password = withContext(dispatchers.backgroundThread) {
|
student.password = withContext(dispatchers.io) {
|
||||||
decrypt(student.password)
|
decrypt(student.password)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -74,7 +77,7 @@ class StudentRepository @Inject constructor(
|
|||||||
val student = studentDb.loadById(id) ?: throw NoCurrentStudentException()
|
val student = studentDb.loadById(id) ?: throw NoCurrentStudentException()
|
||||||
|
|
||||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
||||||
student.password = withContext(dispatchers.backgroundThread) {
|
student.password = withContext(dispatchers.io) {
|
||||||
decrypt(student.password)
|
decrypt(student.password)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -85,35 +88,40 @@ class StudentRepository @Inject constructor(
|
|||||||
val student = studentDb.loadCurrent() ?: throw NoCurrentStudentException()
|
val student = studentDb.loadCurrent() ?: throw NoCurrentStudentException()
|
||||||
|
|
||||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
||||||
student.password = withContext(dispatchers.backgroundThread) {
|
student.password = withContext(dispatchers.io) {
|
||||||
decrypt(student.password)
|
decrypt(student.password)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return student
|
return student
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun saveStudents(studentsWithSemesters: List<StudentWithSemesters>): List<Long> {
|
suspend fun saveStudents(studentsWithSemesters: List<StudentWithSemesters>) {
|
||||||
val semesters = studentsWithSemesters.flatMap { it.semesters }
|
val semesters = studentsWithSemesters.flatMap { it.semesters }
|
||||||
val students = studentsWithSemesters.map { it.student }
|
val students = studentsWithSemesters.map { it.student }
|
||||||
.map {
|
.map {
|
||||||
it.apply {
|
it.apply {
|
||||||
if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) {
|
if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) {
|
||||||
password = withContext(dispatchers.backgroundThread) {
|
password = withContext(dispatchers.io) {
|
||||||
encrypt(password, context)
|
encrypt(password, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.mapIndexed { index, student ->
|
||||||
|
if (index == 0) {
|
||||||
|
student.copy(isCurrent = true).apply { avatarColor = student.avatarColor }
|
||||||
|
} else student
|
||||||
|
}
|
||||||
|
|
||||||
semesterDb.insertSemesters(semesters)
|
appDatabase.withTransaction {
|
||||||
return studentDb.insertAll(students)
|
studentDb.resetCurrent()
|
||||||
|
semesterDb.insertSemesters(semesters)
|
||||||
|
studentDb.insertAll(students)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun switchStudent(studentWithSemesters: StudentWithSemesters) {
|
suspend fun switchStudent(studentWithSemesters: StudentWithSemesters) {
|
||||||
with(studentDb) {
|
studentDb.switchCurrent(studentWithSemesters.student.id)
|
||||||
resetCurrent()
|
|
||||||
updateCurrent(studentWithSemesters.student.id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun logoutStudent(student: Student) = studentDb.delete(student)
|
suspend fun logoutStudent(student: Student) = studentDb.delete(student)
|
||||||
|
@ -5,6 +5,8 @@ import io.github.wulkanowy.data.db.entities.Semester
|
|||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
import io.github.wulkanowy.utils.init
|
import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.networkBoundResource
|
import io.github.wulkanowy.utils.networkBoundResource
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
@ -15,14 +17,24 @@ import javax.inject.Singleton
|
|||||||
@Singleton
|
@Singleton
|
||||||
class SubjectRepository @Inject constructor(
|
class SubjectRepository @Inject constructor(
|
||||||
private val subjectDao: SubjectDao,
|
private val subjectDao: SubjectDao,
|
||||||
private val sdk: Sdk
|
private val sdk: Sdk,
|
||||||
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
private val saveFetchResultMutex = Mutex()
|
||||||
|
|
||||||
fun getSubjects(student: Student, semester: Semester, forceRefresh: Boolean = false) = networkBoundResource(
|
private val cacheKey = "subjects"
|
||||||
|
|
||||||
|
fun getSubjects(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
forceRefresh: Boolean = false,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { it.isEmpty() || forceRefresh },
|
shouldFetch = {
|
||||||
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
|
},
|
||||||
query = { subjectDao.loadAll(semester.diaryId, semester.studentId) },
|
query = { subjectDao.loadAll(semester.diaryId, semester.studentId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
@ -31,6 +43,8 @@ class SubjectRepository @Inject constructor(
|
|||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
subjectDao.deleteAll(old uniqueSubtract new)
|
subjectDao.deleteAll(old uniqueSubtract new)
|
||||||
subjectDao.insertAll(new uniqueSubtract old)
|
subjectDao.insertAll(new uniqueSubtract old)
|
||||||
|
|
||||||
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ import io.github.wulkanowy.data.db.entities.Semester
|
|||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||||
import io.github.wulkanowy.sdk.Sdk
|
import io.github.wulkanowy.sdk.Sdk
|
||||||
|
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||||
|
import io.github.wulkanowy.utils.getRefreshKey
|
||||||
import io.github.wulkanowy.utils.init
|
import io.github.wulkanowy.utils.init
|
||||||
import io.github.wulkanowy.utils.networkBoundResource
|
import io.github.wulkanowy.utils.networkBoundResource
|
||||||
import io.github.wulkanowy.utils.uniqueSubtract
|
import io.github.wulkanowy.utils.uniqueSubtract
|
||||||
@ -15,14 +17,24 @@ import javax.inject.Singleton
|
|||||||
@Singleton
|
@Singleton
|
||||||
class TeacherRepository @Inject constructor(
|
class TeacherRepository @Inject constructor(
|
||||||
private val teacherDb: TeacherDao,
|
private val teacherDb: TeacherDao,
|
||||||
private val sdk: Sdk
|
private val sdk: Sdk,
|
||||||
|
private val refreshHelper: AutoRefreshHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val saveFetchResultMutex = Mutex()
|
private val saveFetchResultMutex = Mutex()
|
||||||
|
|
||||||
fun getTeachers(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource(
|
private val cacheKey = "teachers"
|
||||||
|
|
||||||
|
fun getTeachers(
|
||||||
|
student: Student,
|
||||||
|
semester: Semester,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { it.isEmpty() || forceRefresh },
|
shouldFetch = {
|
||||||
|
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||||
|
it.isEmpty() || forceRefresh || isExpired
|
||||||
|
},
|
||||||
query = { teacherDb.loadAll(semester.studentId, semester.classId) },
|
query = { teacherDb.loadAll(semester.studentId, semester.classId) },
|
||||||
fetch = {
|
fetch = {
|
||||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||||
@ -32,6 +44,8 @@ class TeacherRepository @Inject constructor(
|
|||||||
saveFetchResult = { old, new ->
|
saveFetchResult = { old, new ->
|
||||||
teacherDb.deleteAll(old uniqueSubtract new)
|
teacherDb.deleteAll(old uniqueSubtract new)
|
||||||
teacherDb.insertAll(new uniqueSubtract old)
|
teacherDb.insertAll(new uniqueSubtract old)
|
||||||
|
|
||||||
|
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -41,18 +41,23 @@ class TimetableRepository @Inject constructor(
|
|||||||
private val cacheKey = "timetable"
|
private val cacheKey = "timetable"
|
||||||
|
|
||||||
fun getTimetable(
|
fun getTimetable(
|
||||||
student: Student, semester: Semester, start: LocalDate, end: LocalDate,
|
student: Student,
|
||||||
forceRefresh: Boolean, refreshAdditional: Boolean = false
|
semester: Semester,
|
||||||
|
start: LocalDate,
|
||||||
|
end: LocalDate,
|
||||||
|
forceRefresh: Boolean,
|
||||||
|
refreshAdditional: Boolean = false,
|
||||||
|
notify: Boolean = false
|
||||||
) = networkBoundResource(
|
) = networkBoundResource(
|
||||||
mutex = saveFetchResultMutex,
|
mutex = saveFetchResultMutex,
|
||||||
shouldFetch = { (timetable, additional, headers) ->
|
shouldFetch = { (timetable, additional, headers) ->
|
||||||
val refreshKey = getRefreshKey(cacheKey, semester, start, end)
|
val refreshKey = getRefreshKey(cacheKey, semester, start, end)
|
||||||
val isShouldRefresh = refreshHelper.isShouldBeRefreshed(refreshKey)
|
val isExpired = refreshHelper.shouldBeRefreshed(refreshKey)
|
||||||
val isRefreshAdditional = additional.isEmpty() && refreshAdditional
|
val isRefreshAdditional = additional.isEmpty() && refreshAdditional
|
||||||
|
|
||||||
val isNoData = timetable.isEmpty() || isRefreshAdditional || headers.isEmpty()
|
val isNoData = timetable.isEmpty() || isRefreshAdditional || headers.isEmpty()
|
||||||
|
|
||||||
isNoData || forceRefresh || isShouldRefresh
|
isNoData || forceRefresh || isExpired
|
||||||
},
|
},
|
||||||
query = { getFullTimetableFromDatabase(student, semester, start, end) },
|
query = { getFullTimetableFromDatabase(student, semester, start, end) },
|
||||||
fetch = {
|
fetch = {
|
||||||
@ -63,7 +68,7 @@ class TimetableRepository @Inject constructor(
|
|||||||
timetableFull.mapToEntities(semester)
|
timetableFull.mapToEntities(semester)
|
||||||
},
|
},
|
||||||
saveFetchResult = { timetableOld, timetableNew ->
|
saveFetchResult = { timetableOld, timetableNew ->
|
||||||
refreshTimetable(student, timetableOld.lessons, timetableNew.lessons)
|
refreshTimetable(student, timetableOld.lessons, timetableNew.lessons, notify)
|
||||||
refreshAdditional(timetableOld.additional, timetableNew.additional)
|
refreshAdditional(timetableOld.additional, timetableNew.additional)
|
||||||
refreshDayHeaders(timetableOld.headers, timetableNew.headers)
|
refreshDayHeaders(timetableOld.headers, timetableNew.headers)
|
||||||
|
|
||||||
@ -79,8 +84,10 @@ class TimetableRepository @Inject constructor(
|
|||||||
)
|
)
|
||||||
|
|
||||||
private fun getFullTimetableFromDatabase(
|
private fun getFullTimetableFromDatabase(
|
||||||
student: Student, semester: Semester,
|
student: Student,
|
||||||
start: LocalDate, end: LocalDate
|
semester: Semester,
|
||||||
|
start: LocalDate,
|
||||||
|
end: LocalDate,
|
||||||
): Flow<TimetableFull> {
|
): Flow<TimetableFull> {
|
||||||
val timetableFlow = timetableDb.loadAll(
|
val timetableFlow = timetableDb.loadAll(
|
||||||
diaryId = semester.diaryId,
|
diaryId = semester.diaryId,
|
||||||
@ -111,21 +118,27 @@ class TimetableRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getTimetableFromDatabase(
|
||||||
|
semester: Semester,
|
||||||
|
from: LocalDate,
|
||||||
|
end: LocalDate
|
||||||
|
): Flow<List<Timetable>> {
|
||||||
|
return timetableDb.loadAll(semester.diaryId, semester.studentId, from, end)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateTimetable(timetable: List<Timetable>) {
|
||||||
|
return timetableDb.updateAll(timetable)
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun refreshTimetable(
|
private suspend fun refreshTimetable(
|
||||||
student: Student,
|
student: Student,
|
||||||
lessonsOld: List<Timetable>, lessonsNew: List<Timetable>
|
lessonsOld: List<Timetable>,
|
||||||
|
lessonsNew: List<Timetable>,
|
||||||
|
notify: Boolean
|
||||||
) {
|
) {
|
||||||
val lessonsToRemove = lessonsOld uniqueSubtract lessonsNew
|
val lessonsToRemove = lessonsOld uniqueSubtract lessonsNew
|
||||||
val lessonsToAdd = (lessonsNew uniqueSubtract lessonsOld).map { new ->
|
val lessonsToAdd = (lessonsNew uniqueSubtract lessonsOld).map { new ->
|
||||||
val matchingOld = lessonsOld.singleOrNull { new.start == it.start }
|
new.apply { if (notify) isNotified = false }
|
||||||
if (matchingOld != null) {
|
|
||||||
val useOldTeacher = new.teacher.isEmpty() && !new.changes && !matchingOld.changes
|
|
||||||
new.copy(
|
|
||||||
room = if (new.room.isEmpty()) matchingOld.room else new.room,
|
|
||||||
teacher = if (useOldTeacher) matchingOld.teacher
|
|
||||||
else new.teacher
|
|
||||||
)
|
|
||||||
} else new
|
|
||||||
}
|
}
|
||||||
|
|
||||||
timetableDb.deleteAll(lessonsToRemove)
|
timetableDb.deleteAll(lessonsToRemove)
|
||||||
|
@ -15,6 +15,7 @@ import dagger.multibindings.IntoSet
|
|||||||
import io.github.wulkanowy.services.sync.channels.Channel
|
import io.github.wulkanowy.services.sync.channels.Channel
|
||||||
import io.github.wulkanowy.services.sync.channels.DebugChannel
|
import io.github.wulkanowy.services.sync.channels.DebugChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.LuckyNumberChannel
|
import io.github.wulkanowy.services.sync.channels.LuckyNumberChannel
|
||||||
|
import io.github.wulkanowy.services.sync.channels.NewAttendanceChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.NewConferencesChannel
|
import io.github.wulkanowy.services.sync.channels.NewConferencesChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.NewExamChannel
|
import io.github.wulkanowy.services.sync.channels.NewExamChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.NewGradesChannel
|
import io.github.wulkanowy.services.sync.channels.NewGradesChannel
|
||||||
@ -23,6 +24,7 @@ import io.github.wulkanowy.services.sync.channels.NewMessagesChannel
|
|||||||
import io.github.wulkanowy.services.sync.channels.NewNotesChannel
|
import io.github.wulkanowy.services.sync.channels.NewNotesChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.NewSchoolAnnouncementsChannel
|
import io.github.wulkanowy.services.sync.channels.NewSchoolAnnouncementsChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.PushChannel
|
import io.github.wulkanowy.services.sync.channels.PushChannel
|
||||||
|
import io.github.wulkanowy.services.sync.channels.TimetableChangeChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel
|
import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel
|
||||||
import io.github.wulkanowy.services.sync.works.AttendanceSummaryWork
|
import io.github.wulkanowy.services.sync.works.AttendanceSummaryWork
|
||||||
import io.github.wulkanowy.services.sync.works.AttendanceWork
|
import io.github.wulkanowy.services.sync.works.AttendanceWork
|
||||||
@ -167,4 +169,12 @@ abstract class ServicesModule {
|
|||||||
@Binds
|
@Binds
|
||||||
@IntoSet
|
@IntoSet
|
||||||
abstract fun provideUpcomingLessonsChannel(channel: UpcomingLessonsChannel): Channel
|
abstract fun provideUpcomingLessonsChannel(channel: UpcomingLessonsChannel): Channel
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoSet
|
||||||
|
abstract fun provideChangeTimetableChannel(channel: TimetableChangeChannel): Channel
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoSet
|
||||||
|
abstract fun provideNewAttendanceChannel(channel: NewAttendanceChannel): Channel
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package io.github.wulkanowy.services.alarm
|
package io.github.wulkanowy.services.alarm
|
||||||
|
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
@ -11,11 +10,13 @@ import androidx.core.app.NotificationManagerCompat
|
|||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.Status
|
import io.github.wulkanowy.data.Status
|
||||||
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
import io.github.wulkanowy.services.HiltBroadcastReceiver
|
import io.github.wulkanowy.services.HiltBroadcastReceiver
|
||||||
import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel.Companion.CHANNEL_ID
|
import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel.Companion.CHANNEL_ID
|
||||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
import io.github.wulkanowy.ui.modules.Destination
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.splash.SplashActivity
|
||||||
|
import io.github.wulkanowy.utils.PendingIntentCompat
|
||||||
import io.github.wulkanowy.utils.flowWithResource
|
import io.github.wulkanowy.utils.flowWithResource
|
||||||
import io.github.wulkanowy.utils.getCompatColor
|
import io.github.wulkanowy.utils.getCompatColor
|
||||||
import io.github.wulkanowy.utils.toLocalDateTime
|
import io.github.wulkanowy.utils.toLocalDateTime
|
||||||
@ -32,12 +33,15 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var studentRepository: StudentRepository
|
lateinit var studentRepository: StudentRepository
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var preferencesRepository: PreferencesRepository
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val NOTIFICATION_TYPE_CURRENT = 1
|
const val NOTIFICATION_TYPE_CURRENT = 1
|
||||||
const val NOTIFICATION_TYPE_UPCOMING = 2
|
const val NOTIFICATION_TYPE_UPCOMING = 2
|
||||||
const val NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION = 3
|
const val NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION = 3
|
||||||
|
|
||||||
const val NOTIFICATION_ID = "id"
|
const val NOTIFICATION_ID = 2137
|
||||||
|
|
||||||
const val STUDENT_NAME = "student_name"
|
const val STUDENT_NAME = "student_name"
|
||||||
const val STUDENT_ID = "student_id"
|
const val STUDENT_ID = "student_id"
|
||||||
@ -67,10 +71,10 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
|
|||||||
|
|
||||||
private fun prepareNotification(context: Context, intent: Intent) {
|
private fun prepareNotification(context: Context, intent: Intent) {
|
||||||
val type = intent.getIntExtra(LESSON_TYPE, 0)
|
val type = intent.getIntExtra(LESSON_TYPE, 0)
|
||||||
val notificationId = intent.getIntExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
|
val isPersistent = preferencesRepository.isUpcomingLessonsNotificationsPersistent
|
||||||
|
|
||||||
if (type == NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION) {
|
if (type == NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION) {
|
||||||
return NotificationManagerCompat.from(context).cancel(notificationId)
|
return NotificationManagerCompat.from(context).cancel(NOTIFICATION_ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
val studentId = intent.getIntExtra(STUDENT_ID, 0)
|
val studentId = intent.getIntExtra(STUDENT_ID, 0)
|
||||||
@ -87,33 +91,58 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
|
|||||||
|
|
||||||
Timber.d("TimetableNotification receive: type: $type, subject: $subject, start: ${start.toLocalDateTime()}, student: $studentId")
|
Timber.d("TimetableNotification receive: type: $type, subject: $subject, start: ${start.toLocalDateTime()}, student: $studentId")
|
||||||
|
|
||||||
showNotification(context, notificationId, studentName,
|
showNotification(
|
||||||
|
context, isPersistent, studentName,
|
||||||
if (type == NOTIFICATION_TYPE_CURRENT) end else start, end - start,
|
if (type == NOTIFICATION_TYPE_CURRENT) end else start, end - start,
|
||||||
context.getString(if (type == NOTIFICATION_TYPE_CURRENT) R.string.timetable_now else R.string.timetable_next, "($room) $subject".removePrefix("()")),
|
context.getString(
|
||||||
nextSubject?.let { context.getString(R.string.timetable_later, "($nextRoom) $nextSubject".removePrefix("()")) }
|
if (type == NOTIFICATION_TYPE_CURRENT) R.string.timetable_now else R.string.timetable_next,
|
||||||
|
"($room) $subject".removePrefix("()")
|
||||||
|
),
|
||||||
|
nextSubject?.let {
|
||||||
|
context.getString(
|
||||||
|
R.string.timetable_later,
|
||||||
|
"($nextRoom) $nextSubject".removePrefix("()")
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showNotification(context: Context, notificationId: Int, studentName: String?, countDown: Long, timeout: Long, title: String, next: String?) {
|
private fun showNotification(
|
||||||
NotificationManagerCompat.from(context).notify(notificationId, NotificationCompat.Builder(context, CHANNEL_ID)
|
context: Context,
|
||||||
.setContentTitle(title)
|
isPersistent: Boolean,
|
||||||
.setContentText(next)
|
studentName: String?,
|
||||||
.setAutoCancel(false)
|
countDown: Long,
|
||||||
.setOngoing(true)
|
timeout: Long,
|
||||||
.setWhen(countDown)
|
title: String,
|
||||||
.apply {
|
next: String?
|
||||||
if (Build.VERSION.SDK_INT >= N) setUsesChronometer(true)
|
) {
|
||||||
}
|
NotificationManagerCompat.from(context)
|
||||||
.setTimeoutAfter(timeout)
|
.notify(NOTIFICATION_ID, NotificationCompat.Builder(context, CHANNEL_ID)
|
||||||
.setSmallIcon(R.drawable.ic_stat_timetable)
|
.setContentTitle(title)
|
||||||
.setColor(context.getCompatColor(R.color.colorPrimary))
|
.setContentText(next)
|
||||||
.setStyle(NotificationCompat.InboxStyle().also {
|
.setAutoCancel(false)
|
||||||
it.setSummaryText(studentName)
|
.setWhen(countDown)
|
||||||
it.addLine(next)
|
.setOngoing(isPersistent)
|
||||||
})
|
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||||
.setContentIntent(PendingIntent.getActivity(context, MainView.Section.TIMETABLE.id,
|
.apply {
|
||||||
MainActivity.getStartIntent(context, MainView.Section.TIMETABLE, true), FLAG_UPDATE_CURRENT))
|
if (Build.VERSION.SDK_INT >= N) setUsesChronometer(true)
|
||||||
.build()
|
}
|
||||||
)
|
.setTimeoutAfter(timeout)
|
||||||
|
.setSmallIcon(R.drawable.ic_stat_timetable)
|
||||||
|
.setColor(context.getCompatColor(R.color.colorPrimary))
|
||||||
|
.setStyle(NotificationCompat.InboxStyle().also {
|
||||||
|
it.setSummaryText(studentName)
|
||||||
|
it.addLine(next)
|
||||||
|
})
|
||||||
|
.setContentIntent(
|
||||||
|
PendingIntent.getActivity(
|
||||||
|
context,
|
||||||
|
NOTIFICATION_ID,
|
||||||
|
SplashActivity.getStartIntent(context, Destination.Timetable()),
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,9 @@ package io.github.wulkanowy.services.alarm
|
|||||||
import android.app.AlarmManager
|
import android.app.AlarmManager
|
||||||
import android.app.AlarmManager.RTC_WAKEUP
|
import android.app.AlarmManager.RTC_WAKEUP
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
import androidx.core.app.AlarmManagerCompat
|
import androidx.core.app.AlarmManagerCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
@ -25,12 +25,13 @@ import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companio
|
|||||||
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.NOTIFICATION_TYPE_UPCOMING
|
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.NOTIFICATION_TYPE_UPCOMING
|
||||||
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_ID
|
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_ID
|
||||||
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_NAME
|
import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_NAME
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
|
||||||
import io.github.wulkanowy.utils.DispatchersProvider
|
import io.github.wulkanowy.utils.DispatchersProvider
|
||||||
|
import io.github.wulkanowy.utils.PendingIntentCompat
|
||||||
import io.github.wulkanowy.utils.nickOrName
|
import io.github.wulkanowy.utils.nickOrName
|
||||||
import io.github.wulkanowy.utils.toTimestamp
|
import io.github.wulkanowy.utils.toTimestamp
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.LocalDateTime.now
|
import java.time.LocalDateTime.now
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -53,14 +54,17 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
|||||||
|
|
||||||
suspend fun cancelScheduled(lessons: List<Timetable>, student: Student) {
|
suspend fun cancelScheduled(lessons: List<Timetable>, student: Student) {
|
||||||
val studentId = student.studentId
|
val studentId = student.studentId
|
||||||
withContext(dispatchersProvider.backgroundThread) {
|
withContext(dispatchersProvider.io) {
|
||||||
lessons.sortedBy { it.start }.forEachIndexed { index, lesson ->
|
lessons.sortedBy { it.start }.forEachIndexed { index, lesson ->
|
||||||
val upcomingTime = getUpcomingLessonTime(index, lessons, lesson)
|
val upcomingTime = getUpcomingLessonTime(index, lessons, lesson)
|
||||||
cancelScheduledTo(
|
cancelScheduledTo(
|
||||||
upcomingTime..lesson.start,
|
range = upcomingTime..lesson.start,
|
||||||
getRequestCode(upcomingTime, studentId)
|
requestCode = getRequestCode(upcomingTime, studentId)
|
||||||
|
)
|
||||||
|
cancelScheduledTo(
|
||||||
|
range = lesson.start..lesson.end,
|
||||||
|
requestCode = getRequestCode(lesson.start, studentId)
|
||||||
)
|
)
|
||||||
cancelScheduledTo(lesson.start..lesson.end, getRequestCode(lesson.start, studentId))
|
|
||||||
|
|
||||||
Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId")
|
Timber.d("TimetableNotification canceled: type 1 & 2, subject: ${lesson.subject}, start: ${lesson.start}, student: $studentId")
|
||||||
}
|
}
|
||||||
@ -69,20 +73,37 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
|||||||
|
|
||||||
private fun cancelScheduledTo(range: ClosedRange<LocalDateTime>, requestCode: Int) {
|
private fun cancelScheduledTo(range: ClosedRange<LocalDateTime>, requestCode: Int) {
|
||||||
if (now() in range) cancelNotification()
|
if (now() in range) cancelNotification()
|
||||||
|
|
||||||
alarmManager.cancel(
|
alarmManager.cancel(
|
||||||
PendingIntent.getBroadcast(context, requestCode, Intent(), FLAG_UPDATE_CURRENT)
|
PendingIntent.getBroadcast(
|
||||||
|
context,
|
||||||
|
requestCode,
|
||||||
|
Intent(),
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancelNotification() =
|
fun cancelNotification() =
|
||||||
NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id)
|
NotificationManagerCompat.from(context).cancel(NOTIFICATION_ID)
|
||||||
|
|
||||||
suspend fun scheduleNotifications(lessons: List<Timetable>, student: Student) {
|
suspend fun scheduleNotifications(lessons: List<Timetable>, student: Student) {
|
||||||
if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) {
|
if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) {
|
||||||
return cancelScheduled(lessons, student)
|
return cancelScheduled(lessons, student)
|
||||||
}
|
}
|
||||||
|
|
||||||
withContext(dispatchersProvider.backgroundThread) {
|
if (!canScheduleExactAlarms()) {
|
||||||
|
Timber.w("Exact alarms are disabled by user")
|
||||||
|
preferencesRepository.isUpcomingLessonsNotificationsEnable = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lessons.firstOrNull()?.date?.isAfter(LocalDate.now().plusDays(2)) == true) {
|
||||||
|
Timber.d("Timetable notification scheduling skipped - lessons are too far")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
withContext(dispatchersProvider.io) {
|
||||||
lessons.groupBy { it.date }
|
lessons.groupBy { it.date }
|
||||||
.map { it.value.sortedBy { lesson -> lesson.start } }
|
.map { it.value.sortedBy { lesson -> lesson.start } }
|
||||||
.map { it.filter { lesson -> lesson.isStudentPlan } }
|
.map { it.filter { lesson -> lesson.isStudentPlan } }
|
||||||
@ -96,26 +117,26 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
|||||||
|
|
||||||
if (lesson.start > now()) {
|
if (lesson.start > now()) {
|
||||||
scheduleBroadcast(
|
scheduleBroadcast(
|
||||||
intent,
|
intent = intent,
|
||||||
student.studentId,
|
studentId = student.studentId,
|
||||||
NOTIFICATION_TYPE_UPCOMING,
|
notificationType = NOTIFICATION_TYPE_UPCOMING,
|
||||||
getUpcomingLessonTime(index, active, lesson)
|
time = getUpcomingLessonTime(index, active, lesson)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lesson.end > now()) {
|
if (lesson.end > now()) {
|
||||||
scheduleBroadcast(
|
scheduleBroadcast(
|
||||||
intent,
|
intent = intent,
|
||||||
student.studentId,
|
studentId = student.studentId,
|
||||||
NOTIFICATION_TYPE_CURRENT,
|
notificationType = NOTIFICATION_TYPE_CURRENT,
|
||||||
lesson.start
|
time = lesson.start
|
||||||
)
|
)
|
||||||
if (active.lastIndex == index) {
|
if (active.lastIndex == index) {
|
||||||
scheduleBroadcast(
|
scheduleBroadcast(
|
||||||
intent,
|
intent = intent,
|
||||||
student.studentId,
|
studentId = student.studentId,
|
||||||
NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION,
|
notificationType = NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION,
|
||||||
lesson.end
|
time = lesson.end
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -143,17 +164,30 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
|||||||
notificationType: Int,
|
notificationType: Int,
|
||||||
time: LocalDateTime
|
time: LocalDateTime
|
||||||
) {
|
) {
|
||||||
AlarmManagerCompat.setExactAndAllowWhileIdle(
|
try {
|
||||||
alarmManager, RTC_WAKEUP, time.toTimestamp(),
|
AlarmManagerCompat.setExactAndAllowWhileIdle(
|
||||||
PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also {
|
alarmManager, RTC_WAKEUP, time.toTimestamp(),
|
||||||
it.putExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
|
PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also {
|
||||||
it.putExtra(LESSON_TYPE, notificationType)
|
it.putExtra(LESSON_TYPE, notificationType)
|
||||||
}, FLAG_UPDATE_CURRENT)
|
}, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE)
|
||||||
)
|
)
|
||||||
Timber.d(
|
Timber.d(
|
||||||
"TimetableNotification scheduled: type: $notificationType, subject: ${
|
"TimetableNotification scheduled: type: $notificationType, subject: ${
|
||||||
intent.getStringExtra(LESSON_TITLE)
|
intent.getStringExtra(LESSON_TITLE)
|
||||||
}, start: $time, student: $studentId"
|
}, start: $time, student: $studentId"
|
||||||
)
|
)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Timber.e(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun canScheduleExactAlarms(): Boolean {
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
try {
|
||||||
|
alarmManager.canScheduleExactAlarms()
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
package io.github.wulkanowy.services.piggyback
|
||||||
|
|
||||||
|
import android.service.notification.NotificationListenerService
|
||||||
|
import android.service.notification.StatusBarNotification
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
|
import io.github.wulkanowy.services.sync.SyncManager
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class VulcanNotificationListenerService : NotificationListenerService() {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var syncManager: SyncManager
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var preferenceRepository: PreferencesRepository
|
||||||
|
|
||||||
|
override fun onNotificationPosted(statusBarNotification: StatusBarNotification?) {
|
||||||
|
if (statusBarNotification?.packageName == "pl.edu.vulcan.hebe" && preferenceRepository.isNotificationPiggybackEnabled) {
|
||||||
|
syncManager.startOneTimeSyncWorker()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
package io.github.wulkanowy.services.shortcuts
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.core.content.pm.ShortcutInfoCompat
|
||||||
|
import androidx.core.content.pm.ShortcutManagerCompat
|
||||||
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import io.github.wulkanowy.R
|
||||||
|
import io.github.wulkanowy.ui.modules.Destination
|
||||||
|
import io.github.wulkanowy.ui.modules.splash.SplashActivity
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class ShortcutsHelper @Inject constructor(@ApplicationContext private val context: Context) {
|
||||||
|
|
||||||
|
private val destinations = mapOf(
|
||||||
|
"grade" to Destination.Grade,
|
||||||
|
"attendance" to Destination.Attendance,
|
||||||
|
"exam" to Destination.Exam,
|
||||||
|
"timetable" to Destination.Timetable()
|
||||||
|
)
|
||||||
|
|
||||||
|
init {
|
||||||
|
initializeShortcuts()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDestination(intent: Intent) =
|
||||||
|
destinations[intent.getStringExtra(EXTRA_SHORTCUT_DESTINATION_ID)]
|
||||||
|
|
||||||
|
private fun initializeShortcuts() {
|
||||||
|
val shortcutsInfo = listOf(
|
||||||
|
ShortcutInfoCompat.Builder(context, "grade_shortcut")
|
||||||
|
.setShortLabel(context.getString(R.string.grade_title))
|
||||||
|
.setLongLabel(context.getString(R.string.grade_title))
|
||||||
|
.setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_grade))
|
||||||
|
.setIntent(SplashActivity.getStartIntent(context)
|
||||||
|
.apply {
|
||||||
|
action = Intent.ACTION_VIEW
|
||||||
|
putExtra(EXTRA_SHORTCUT_DESTINATION_ID, "grade")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.build(),
|
||||||
|
|
||||||
|
ShortcutInfoCompat.Builder(context, "attendance_shortcut")
|
||||||
|
.setShortLabel(context.getString(R.string.attendance_title))
|
||||||
|
.setLongLabel(context.getString(R.string.attendance_title))
|
||||||
|
.setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_attendance))
|
||||||
|
.setIntent(SplashActivity.getStartIntent(context)
|
||||||
|
.apply {
|
||||||
|
action = Intent.ACTION_VIEW
|
||||||
|
putExtra(EXTRA_SHORTCUT_DESTINATION_ID, "attendance")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.build(),
|
||||||
|
|
||||||
|
ShortcutInfoCompat.Builder(context, "exam_shortcut")
|
||||||
|
.setShortLabel(context.getString(R.string.exam_title))
|
||||||
|
.setLongLabel(context.getString(R.string.exam_title))
|
||||||
|
.setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_exam))
|
||||||
|
.setIntent(SplashActivity.getStartIntent(context)
|
||||||
|
.apply {
|
||||||
|
action = Intent.ACTION_VIEW
|
||||||
|
putExtra(EXTRA_SHORTCUT_DESTINATION_ID, "exam")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.build(),
|
||||||
|
|
||||||
|
ShortcutInfoCompat.Builder(context, "timetable_shortcut")
|
||||||
|
.setShortLabel(context.getString(R.string.timetable_title))
|
||||||
|
.setLongLabel(context.getString(R.string.timetable_title))
|
||||||
|
.setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_timetable))
|
||||||
|
.setIntent(SplashActivity.getStartIntent(context)
|
||||||
|
.apply {
|
||||||
|
action = Intent.ACTION_VIEW
|
||||||
|
putExtra(EXTRA_SHORTCUT_DESTINATION_ID, "timetable")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
|
||||||
|
shortcutsInfo.forEach { ShortcutManagerCompat.pushDynamicShortcut(context, it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
|
||||||
|
private const val EXTRA_SHORTCUT_DESTINATION_ID = "shortcut_destination_id"
|
||||||
|
}
|
||||||
|
}
|
@ -57,18 +57,24 @@ class SyncManager @Inject constructor(
|
|||||||
|
|
||||||
fun startPeriodicSyncWorker(restart: Boolean = false) {
|
fun startPeriodicSyncWorker(restart: Boolean = false) {
|
||||||
if (preferencesRepository.isServiceEnabled && !now().isHolidays) {
|
if (preferencesRepository.isServiceEnabled && !now().isHolidays) {
|
||||||
workManager.enqueueUniquePeriodicWork(SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP,
|
val serviceInterval = preferencesRepository.servicesInterval
|
||||||
PeriodicWorkRequestBuilder<SyncWorker>(preferencesRepository.servicesInterval, MINUTES)
|
|
||||||
|
workManager.enqueueUniquePeriodicWork(
|
||||||
|
SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP,
|
||||||
|
PeriodicWorkRequestBuilder<SyncWorker>(serviceInterval, MINUTES)
|
||||||
.setInitialDelay(10, MINUTES)
|
.setInitialDelay(10, MINUTES)
|
||||||
.setBackoffCriteria(EXPONENTIAL, 30, MINUTES)
|
.setBackoffCriteria(EXPONENTIAL, 30, MINUTES)
|
||||||
.setConstraints(Constraints.Builder()
|
.setConstraints(
|
||||||
.setRequiredNetworkType(if (preferencesRepository.isServicesOnlyWifi) UNMETERED else CONNECTED)
|
Constraints.Builder()
|
||||||
.build())
|
.setRequiredNetworkType(if (preferencesRepository.isServicesOnlyWifi) UNMETERED else CONNECTED)
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startOneTimeSyncWorker(): Flow<WorkInfo> {
|
fun startOneTimeSyncWorker(): Flow<WorkInfo?> {
|
||||||
val work = OneTimeWorkRequestBuilder<SyncWorker>()
|
val work = OneTimeWorkRequestBuilder<SyncWorker>()
|
||||||
.setInputData(
|
.setInputData(
|
||||||
Data.Builder()
|
Data.Builder()
|
||||||
@ -77,7 +83,11 @@ class SyncManager @Inject constructor(
|
|||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
workManager.enqueueUniqueWork("${SyncWorker::class.java.simpleName}_one_time", ExistingWorkPolicy.REPLACE, work)
|
workManager.enqueueUniqueWork(
|
||||||
|
"${SyncWorker::class.java.simpleName}_one_time",
|
||||||
|
ExistingWorkPolicy.REPLACE,
|
||||||
|
work
|
||||||
|
)
|
||||||
|
|
||||||
return workManager.getWorkInfoByIdLiveData(work.id).asFlow()
|
return workManager.getWorkInfoByIdLiveData(work.id).asFlow()
|
||||||
}
|
}
|
||||||
|
@ -19,11 +19,11 @@ import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException
|
|||||||
import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException
|
import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException
|
||||||
import io.github.wulkanowy.services.sync.channels.DebugChannel
|
import io.github.wulkanowy.services.sync.channels.DebugChannel
|
||||||
import io.github.wulkanowy.services.sync.works.Work
|
import io.github.wulkanowy.services.sync.works.Work
|
||||||
|
import io.github.wulkanowy.utils.DispatchersProvider
|
||||||
import io.github.wulkanowy.utils.getCompatColor
|
import io.github.wulkanowy.utils.getCompatColor
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.ZoneId
|
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
@HiltWorker
|
@HiltWorker
|
||||||
@ -34,13 +34,14 @@ class SyncWorker @AssistedInject constructor(
|
|||||||
private val semesterRepository: SemesterRepository,
|
private val semesterRepository: SemesterRepository,
|
||||||
private val works: Set<@JvmSuppressWildcards Work>,
|
private val works: Set<@JvmSuppressWildcards Work>,
|
||||||
private val preferencesRepository: PreferencesRepository,
|
private val preferencesRepository: PreferencesRepository,
|
||||||
private val notificationManager: NotificationManagerCompat
|
private val notificationManager: NotificationManagerCompat,
|
||||||
|
private val dispatchersProvider: DispatchersProvider
|
||||||
) : CoroutineWorker(appContext, workerParameters) {
|
) : CoroutineWorker(appContext, workerParameters) {
|
||||||
|
|
||||||
override suspend fun doWork() = coroutineScope {
|
override suspend fun doWork() = withContext(dispatchersProvider.io) {
|
||||||
Timber.i("SyncWorker is starting")
|
Timber.i("SyncWorker is starting")
|
||||||
|
|
||||||
if (!studentRepository.isCurrentStudentSet()) return@coroutineScope Result.failure()
|
if (!studentRepository.isCurrentStudentSet()) return@withContext Result.failure()
|
||||||
|
|
||||||
val student = studentRepository.getCurrentStudent()
|
val student = studentRepository.getCurrentStudent()
|
||||||
val semester = semesterRepository.getCurrentSemester(student, true)
|
val semester = semesterRepository.getCurrentSemester(student, true)
|
||||||
@ -50,12 +51,12 @@ class SyncWorker @AssistedInject constructor(
|
|||||||
Timber.i("${work::class.java.simpleName} is starting")
|
Timber.i("${work::class.java.simpleName} is starting")
|
||||||
work.doWork(student, semester)
|
work.doWork(student, semester)
|
||||||
Timber.i("${work::class.java.simpleName} result: Success")
|
Timber.i("${work::class.java.simpleName} result: Success")
|
||||||
preferencesRepository.lasSyncDate = LocalDateTime.now(ZoneId.systemDefault())
|
|
||||||
null
|
null
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Timber.w("${work::class.java.simpleName} result: An exception ${e.message} occurred")
|
Timber.w("${work::class.java.simpleName} result: An exception ${e.message} occurred")
|
||||||
if (e is FeatureDisabledException || e is FeatureNotAvailableException) null
|
if (e is FeatureDisabledException || e is FeatureNotAvailableException) {
|
||||||
else {
|
null
|
||||||
|
} else {
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
e
|
e
|
||||||
}
|
}
|
||||||
@ -70,13 +71,16 @@ class SyncWorker @AssistedInject constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
exceptions.isNotEmpty() -> Result.retry()
|
exceptions.isNotEmpty() -> Result.retry()
|
||||||
else -> Result.success()
|
else -> {
|
||||||
|
preferencesRepository.lasSyncDate = LocalDateTime.now()
|
||||||
|
Result.success()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preferencesRepository.isDebugNotificationEnable) notify(result)
|
if (preferencesRepository.isDebugNotificationEnable) notify(result)
|
||||||
Timber.i("SyncWorker result: $result")
|
Timber.i("SyncWorker result: $result")
|
||||||
|
|
||||||
result
|
return@withContext result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun notify(result: Result) {
|
private fun notify(result: Result) {
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
package io.github.wulkanowy.services.sync.channels
|
||||||
|
|
||||||
|
import android.annotation.TargetApi
|
||||||
|
import android.app.Notification
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import io.github.wulkanowy.R
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@TargetApi(26)
|
||||||
|
class NewAttendanceChannel @Inject constructor(
|
||||||
|
private val notificationManager: NotificationManagerCompat,
|
||||||
|
@ApplicationContext private val context: Context
|
||||||
|
) : Channel {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val CHANNEL_ID = "new_attendance_channel"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun create() {
|
||||||
|
notificationManager.createNotificationChannel(
|
||||||
|
NotificationChannel(
|
||||||
|
CHANNEL_ID,
|
||||||
|
context.getString(R.string.channel_new_attendance),
|
||||||
|
NotificationManager.IMPORTANCE_HIGH
|
||||||
|
)
|
||||||
|
.apply {
|
||||||
|
enableLights(true)
|
||||||
|
enableVibration(true)
|
||||||
|
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package io.github.wulkanowy.services.sync.channels
|
||||||
|
|
||||||
|
import android.annotation.TargetApi
|
||||||
|
import android.app.Notification
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import io.github.wulkanowy.R
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@TargetApi(26)
|
||||||
|
class TimetableChangeChannel @Inject constructor(
|
||||||
|
private val notificationManager: NotificationManagerCompat,
|
||||||
|
@ApplicationContext private val context: Context
|
||||||
|
) : Channel {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val CHANNEL_ID = "change_timetable_channel"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun create() {
|
||||||
|
notificationManager.createNotificationChannel(
|
||||||
|
NotificationChannel(
|
||||||
|
CHANNEL_ID,
|
||||||
|
context.getString(R.string.channel_change_timetable),
|
||||||
|
NotificationManager.IMPORTANCE_HIGH
|
||||||
|
)
|
||||||
|
.apply {
|
||||||
|
enableLights(true)
|
||||||
|
enableVibration(true)
|
||||||
|
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,180 @@
|
|||||||
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import io.github.wulkanowy.R
|
||||||
|
import io.github.wulkanowy.data.db.entities.Notification
|
||||||
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
|
import io.github.wulkanowy.data.pojos.GroupNotificationData
|
||||||
|
import io.github.wulkanowy.data.pojos.NotificationData
|
||||||
|
import io.github.wulkanowy.data.repositories.NotificationRepository
|
||||||
|
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||||
|
import io.github.wulkanowy.utils.PendingIntentCompat
|
||||||
|
import io.github.wulkanowy.utils.getCompatBitmap
|
||||||
|
import io.github.wulkanowy.utils.getCompatColor
|
||||||
|
import io.github.wulkanowy.utils.nickOrName
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
class AppNotificationManager @Inject constructor(
|
||||||
|
private val notificationManager: NotificationManagerCompat,
|
||||||
|
@ApplicationContext private val context: Context,
|
||||||
|
private val studentRepository: StudentRepository,
|
||||||
|
private val notificationRepository: NotificationRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
|
suspend fun sendSingleNotification(
|
||||||
|
notificationData: NotificationData,
|
||||||
|
notificationType: NotificationType,
|
||||||
|
student: Student
|
||||||
|
) {
|
||||||
|
val notification = NotificationCompat.Builder(context, notificationType.channel)
|
||||||
|
.setLargeIcon(context.getCompatBitmap(notificationType.icon, R.color.colorPrimary))
|
||||||
|
.setSmallIcon(R.drawable.ic_stat_all)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
.setColor(context.getCompatColor(R.color.colorPrimary))
|
||||||
|
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
|
||||||
|
.setContentIntent(
|
||||||
|
PendingIntent.getActivity(
|
||||||
|
context,
|
||||||
|
Random.nextInt(),
|
||||||
|
notificationData.intentToStart,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.setContentTitle(notificationData.title)
|
||||||
|
.setContentText(notificationData.content)
|
||||||
|
.setStyle(
|
||||||
|
NotificationCompat.BigTextStyle()
|
||||||
|
.bigText(notificationData.content)
|
||||||
|
.also { builder ->
|
||||||
|
if (shouldShowStudentName()) {
|
||||||
|
builder.setSummaryText(student.nickOrName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
notificationManager.notify(Random.nextInt(), notification)
|
||||||
|
saveNotification(notificationData, notificationType, student)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
|
suspend fun sendMultipleNotifications(
|
||||||
|
groupNotificationData: GroupNotificationData,
|
||||||
|
student: Student
|
||||||
|
) {
|
||||||
|
val notificationType = groupNotificationData.type
|
||||||
|
val groupType = notificationType.group ?: return
|
||||||
|
val group = "${groupType}_${student.id}"
|
||||||
|
|
||||||
|
sendSummaryNotification(groupNotificationData, group, student)
|
||||||
|
|
||||||
|
groupNotificationData.notificationDataList.forEach { notificationData ->
|
||||||
|
val notification = NotificationCompat.Builder(context, notificationType.channel)
|
||||||
|
.setLargeIcon(context.getCompatBitmap(notificationType.icon, R.color.colorPrimary))
|
||||||
|
.setSmallIcon(R.drawable.ic_stat_all)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
.setColor(context.getCompatColor(R.color.colorPrimary))
|
||||||
|
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
|
||||||
|
.setContentIntent(
|
||||||
|
PendingIntent.getActivity(
|
||||||
|
context,
|
||||||
|
Random.nextInt(),
|
||||||
|
notificationData.intentToStart,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.setContentTitle(notificationData.title)
|
||||||
|
.setContentText(notificationData.content)
|
||||||
|
.setStyle(
|
||||||
|
NotificationCompat.BigTextStyle()
|
||||||
|
.bigText(notificationData.content)
|
||||||
|
.also { builder ->
|
||||||
|
if (shouldShowStudentName()) {
|
||||||
|
builder.setSummaryText(student.nickOrName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.setGroup(group)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
notificationManager.notify(Random.nextInt(), notification)
|
||||||
|
saveNotification(notificationData, groupNotificationData.type, student)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun sendSummaryNotification(
|
||||||
|
groupNotificationData: GroupNotificationData,
|
||||||
|
group: String,
|
||||||
|
student: Student
|
||||||
|
) {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return
|
||||||
|
|
||||||
|
val summaryNotification =
|
||||||
|
NotificationCompat.Builder(context, groupNotificationData.type.channel)
|
||||||
|
.setContentTitle(groupNotificationData.title)
|
||||||
|
.setContentText(groupNotificationData.content)
|
||||||
|
.setSmallIcon(groupNotificationData.type.icon)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
.setColor(context.getCompatColor(R.color.colorPrimary))
|
||||||
|
.setStyle(
|
||||||
|
NotificationCompat.InboxStyle()
|
||||||
|
.also { builder ->
|
||||||
|
if (shouldShowStudentName()) {
|
||||||
|
builder.setSummaryText(student.nickOrName)
|
||||||
|
}
|
||||||
|
groupNotificationData.notificationDataList.forEach {
|
||||||
|
builder.addLine(it.content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.setContentIntent(
|
||||||
|
PendingIntent.getActivity(
|
||||||
|
context,
|
||||||
|
Random.nextInt(),
|
||||||
|
groupNotificationData.intentToStart,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.setLocalOnly(true)
|
||||||
|
.setGroup(group)
|
||||||
|
.setGroupSummary(true)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val groupId = student.id * 100 + groupNotificationData.type.ordinal
|
||||||
|
notificationManager.notify(groupId.toInt(), summaryNotification)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun saveNotification(
|
||||||
|
notificationData: NotificationData,
|
||||||
|
notificationType: NotificationType,
|
||||||
|
student: Student
|
||||||
|
) {
|
||||||
|
val notificationEntity = Notification(
|
||||||
|
studentId = student.id,
|
||||||
|
title = notificationData.title,
|
||||||
|
content = notificationData.content,
|
||||||
|
type = notificationType,
|
||||||
|
date = LocalDateTime.now()
|
||||||
|
)
|
||||||
|
|
||||||
|
notificationRepository.saveNotification(notificationEntity)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun shouldShowStudentName(): Boolean =
|
||||||
|
studentRepository.getSavedStudents(decryptPass = false).size > 1
|
||||||
|
}
|
@ -1,102 +0,0 @@
|
|||||||
package io.github.wulkanowy.services.sync.notifications
|
|
||||||
|
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.annotation.PluralsRes
|
|
||||||
import androidx.core.app.NotificationCompat
|
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import io.github.wulkanowy.R
|
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
|
||||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
|
||||||
import io.github.wulkanowy.data.pojos.Notification
|
|
||||||
import io.github.wulkanowy.data.pojos.OneNotification
|
|
||||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
|
||||||
import io.github.wulkanowy.utils.getCompatBitmap
|
|
||||||
import io.github.wulkanowy.utils.getCompatColor
|
|
||||||
import io.github.wulkanowy.utils.nickOrName
|
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
abstract class BaseNotification(
|
|
||||||
private val context: Context,
|
|
||||||
private val notificationManager: NotificationManagerCompat,
|
|
||||||
) {
|
|
||||||
|
|
||||||
protected fun sendNotification(notification: Notification, student: Student) =
|
|
||||||
when (notification) {
|
|
||||||
is OneNotification -> sendOneNotification(notification, student)
|
|
||||||
is MultipleNotifications -> sendMultipleNotifications(notification, student)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sendOneNotification(notification: OneNotification, student: Student?) {
|
|
||||||
notificationManager.notify(
|
|
||||||
Random.nextInt(Int.MAX_VALUE),
|
|
||||||
getNotificationBuilder(notification).apply {
|
|
||||||
val content = context.getString(
|
|
||||||
notification.contentStringRes,
|
|
||||||
*notification.contentValues.toTypedArray()
|
|
||||||
)
|
|
||||||
setContentTitle(context.getString(notification.titleStringRes))
|
|
||||||
setContentText(content)
|
|
||||||
setStyle(
|
|
||||||
NotificationCompat.BigTextStyle()
|
|
||||||
.setSummaryText(student?.nickOrName)
|
|
||||||
.bigText(content)
|
|
||||||
)
|
|
||||||
}.build()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sendMultipleNotifications(notification: MultipleNotifications, student: Student) {
|
|
||||||
val group = notification.type.group + student.id
|
|
||||||
val groupId = student.id * 100 + notification.type.ordinal
|
|
||||||
|
|
||||||
notification.lines.forEach { item ->
|
|
||||||
notificationManager.notify(
|
|
||||||
Random.nextInt(Int.MAX_VALUE),
|
|
||||||
getNotificationBuilder(notification).apply {
|
|
||||||
setContentTitle(getQuantityString(notification.titleStringRes, 1))
|
|
||||||
setContentText(item)
|
|
||||||
setStyle(
|
|
||||||
NotificationCompat.BigTextStyle()
|
|
||||||
.setSummaryText(student.nickOrName)
|
|
||||||
.bigText(item)
|
|
||||||
)
|
|
||||||
setGroup(group)
|
|
||||||
}.build()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return
|
|
||||||
|
|
||||||
notificationManager.notify(
|
|
||||||
groupId.toInt(),
|
|
||||||
getNotificationBuilder(notification).apply {
|
|
||||||
setSmallIcon(notification.icon)
|
|
||||||
setGroup(group)
|
|
||||||
setStyle(NotificationCompat.InboxStyle().setSummaryText(student.nickOrName))
|
|
||||||
setGroupSummary(true)
|
|
||||||
}.build()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getNotificationBuilder(notification: Notification) = NotificationCompat
|
|
||||||
.Builder(context, notification.type.channel)
|
|
||||||
.setLargeIcon(context.getCompatBitmap(notification.icon, R.color.colorPrimary))
|
|
||||||
.setSmallIcon(R.drawable.ic_stat_all)
|
|
||||||
.setAutoCancel(true)
|
|
||||||
.setDefaults(NotificationCompat.DEFAULT_ALL)
|
|
||||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
|
||||||
.setColor(context.getCompatColor(R.color.colorPrimary))
|
|
||||||
.setContentIntent(
|
|
||||||
PendingIntent.getActivity(
|
|
||||||
context, notification.startMenu.id,
|
|
||||||
MainActivity.getStartIntent(context, notification.startMenu, true),
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun getQuantityString(@PluralsRes id: Int, value: Int): String {
|
|
||||||
return context.resources.getQuantityString(id, value, value)
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,124 @@
|
|||||||
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import io.github.wulkanowy.R
|
||||||
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
|
import io.github.wulkanowy.data.db.entities.Timetable
|
||||||
|
import io.github.wulkanowy.data.pojos.GroupNotificationData
|
||||||
|
import io.github.wulkanowy.data.pojos.NotificationData
|
||||||
|
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.toFormattedString
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class ChangeTimetableNotification @Inject constructor(
|
||||||
|
private val appNotificationManager: AppNotificationManager,
|
||||||
|
@ApplicationContext private val context: Context,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun notify(items: List<Timetable>, student: Student) {
|
||||||
|
val currentTime = LocalDateTime.now()
|
||||||
|
val changedLessons = items.filter { (it.canceled || it.changes) && it.start > currentTime }
|
||||||
|
val notificationDataList = changedLessons.groupBy { it.date }
|
||||||
|
.map { (date, lessons) ->
|
||||||
|
getNotificationContents(date, lessons).map {
|
||||||
|
NotificationData(
|
||||||
|
title = context.getPlural(
|
||||||
|
R.plurals.timetable_notify_new_items_title,
|
||||||
|
1
|
||||||
|
),
|
||||||
|
content = it,
|
||||||
|
intentToStart = SplashActivity.getStartIntent(
|
||||||
|
context = context,
|
||||||
|
destination = Destination.Timetable(date)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.flatten()
|
||||||
|
.ifEmpty { return }
|
||||||
|
|
||||||
|
val groupNotificationData = GroupNotificationData(
|
||||||
|
notificationDataList = notificationDataList,
|
||||||
|
title = context.getPlural(
|
||||||
|
R.plurals.timetable_notify_new_items_title,
|
||||||
|
changedLessons.size
|
||||||
|
),
|
||||||
|
content = context.getPlural(
|
||||||
|
R.plurals.timetable_notify_new_items_group,
|
||||||
|
changedLessons.size,
|
||||||
|
changedLessons.size
|
||||||
|
),
|
||||||
|
intentToStart = SplashActivity.getStartIntent(context, Destination.Timetable()),
|
||||||
|
type = NotificationType.CHANGE_TIMETABLE
|
||||||
|
)
|
||||||
|
|
||||||
|
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getNotificationContents(date: LocalDate, lessons: List<Timetable>): List<String> {
|
||||||
|
val formattedDate = date.toFormattedString("EEE dd.MM")
|
||||||
|
|
||||||
|
return if (lessons.size > 2) {
|
||||||
|
listOf(
|
||||||
|
context.getPlural(
|
||||||
|
R.plurals.timetable_notify_new_items,
|
||||||
|
lessons.size,
|
||||||
|
formattedDate,
|
||||||
|
lessons.size,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
lessons.map {
|
||||||
|
buildString {
|
||||||
|
append(
|
||||||
|
context.getString(
|
||||||
|
R.string.timetable_notify_lesson,
|
||||||
|
formattedDate,
|
||||||
|
it.number,
|
||||||
|
it.subject
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (it.roomOld.isNotBlank()) {
|
||||||
|
appendLine()
|
||||||
|
append(
|
||||||
|
context.getString(
|
||||||
|
R.string.timetable_notify_change_room,
|
||||||
|
it.roomOld,
|
||||||
|
it.room
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (it.teacherOld.isNotBlank() && it.teacher != it.teacherOld) {
|
||||||
|
appendLine()
|
||||||
|
append(
|
||||||
|
context.getString(
|
||||||
|
R.string.timetable_notify_change_teacher,
|
||||||
|
it.teacherOld,
|
||||||
|
it.teacher
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (it.subjectOld.isNotBlank()) {
|
||||||
|
appendLine()
|
||||||
|
append(
|
||||||
|
context.getString(
|
||||||
|
R.string.timetable_notify_change_subject,
|
||||||
|
it.subjectOld,
|
||||||
|
it.subject
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (it.info.isNotBlank()) {
|
||||||
|
appendLine()
|
||||||
|
append(it.info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import io.github.wulkanowy.R
|
||||||
|
import io.github.wulkanowy.data.db.entities.Attendance
|
||||||
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
|
import io.github.wulkanowy.data.pojos.GroupNotificationData
|
||||||
|
import io.github.wulkanowy.data.pojos.NotificationData
|
||||||
|
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.getPlural
|
||||||
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class NewAttendanceNotification @Inject constructor(
|
||||||
|
private val appNotificationManager: AppNotificationManager,
|
||||||
|
@ApplicationContext private val context: Context
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun notify(items: List<Attendance>, student: Student) {
|
||||||
|
val lines = items.filterNot { it.presence || it.name == "UNKNOWN" }
|
||||||
|
.map {
|
||||||
|
val description = context.getString(it.descriptionRes)
|
||||||
|
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: $description"
|
||||||
|
}
|
||||||
|
.ifEmpty { return }
|
||||||
|
|
||||||
|
val notificationDataList = lines.map {
|
||||||
|
NotificationData(
|
||||||
|
title = context.getPlural(R.plurals.attendance_notify_new_items_title, 1),
|
||||||
|
content = it,
|
||||||
|
intentToStart = SplashActivity.getStartIntent(context, Destination.Attendance)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val groupNotificationData = GroupNotificationData(
|
||||||
|
notificationDataList = notificationDataList,
|
||||||
|
title = context.getPlural(
|
||||||
|
R.plurals.attendance_notify_new_items_title,
|
||||||
|
notificationDataList.size
|
||||||
|
),
|
||||||
|
content = context.getPlural(
|
||||||
|
R.plurals.attendance_notify_new_items,
|
||||||
|
notificationDataList.size,
|
||||||
|
notificationDataList.size
|
||||||
|
),
|
||||||
|
intentToStart = SplashActivity.getStartIntent(context, Destination.Attendance),
|
||||||
|
type = NotificationType.NEW_ATTENDANCE
|
||||||
|
)
|
||||||
|
|
||||||
|
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
|
||||||
|
}
|
||||||
|
}
|
@ -1,38 +1,52 @@
|
|||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Conference
|
import io.github.wulkanowy.data.db.entities.Conference
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
import io.github.wulkanowy.data.pojos.GroupNotificationData
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.data.pojos.NotificationData
|
||||||
|
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.toFormattedString
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewConferenceNotification @Inject constructor(
|
class NewConferenceNotification @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
private val appNotificationManager: AppNotificationManager,
|
||||||
notificationManager: NotificationManagerCompat,
|
@ApplicationContext private val context: Context
|
||||||
) : BaseNotification(context, notificationManager) {
|
) {
|
||||||
|
|
||||||
fun notify(items: List<Conference>, student: Student) {
|
suspend fun notify(items: List<Conference>, student: Student) {
|
||||||
val today = LocalDateTime.now()
|
val today = LocalDateTime.now()
|
||||||
val lines = items.filter { !it.date.isBefore(today) }.map {
|
val lines = items.filter { !it.date.isBefore(today) }
|
||||||
"${it.date.toFormattedString("dd.MM")} - ${it.title}: ${it.subject}"
|
.map {
|
||||||
}.ifEmpty { return }
|
"${it.date.toFormattedString("dd.MM")} - ${it.title}: ${it.subject}"
|
||||||
|
}
|
||||||
|
.ifEmpty { return }
|
||||||
|
|
||||||
val notification = MultipleNotifications(
|
val notificationDataList = lines.map {
|
||||||
type = NotificationType.NEW_CONFERENCE,
|
NotificationData(
|
||||||
icon = R.drawable.ic_more_conferences,
|
title = context.getPlural(R.plurals.conference_notify_new_item_title, 1),
|
||||||
titleStringRes = R.plurals.conference_notify_new_item_title,
|
content = it,
|
||||||
contentStringRes = R.plurals.conference_notify_new_items,
|
intentToStart = SplashActivity.getStartIntent(context, Destination.Conference)
|
||||||
summaryStringRes = R.plurals.conference_number_item,
|
)
|
||||||
startMenu = MainView.Section.CONFERENCE,
|
}
|
||||||
lines = lines
|
|
||||||
|
val groupNotificationData = GroupNotificationData(
|
||||||
|
notificationDataList = notificationDataList,
|
||||||
|
title = context.getPlural(R.plurals.conference_notify_new_item_title, lines.size),
|
||||||
|
content = context.getPlural(
|
||||||
|
R.plurals.conference_notify_new_items,
|
||||||
|
lines.size,
|
||||||
|
lines.size
|
||||||
|
),
|
||||||
|
intentToStart = SplashActivity.getStartIntent(context, Destination.Conference),
|
||||||
|
type = NotificationType.NEW_CONFERENCE
|
||||||
)
|
)
|
||||||
|
|
||||||
sendNotification(notification, student)
|
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,38 +1,52 @@
|
|||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Exam
|
import io.github.wulkanowy.data.db.entities.Exam
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
import io.github.wulkanowy.data.pojos.GroupNotificationData
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.data.pojos.NotificationData
|
||||||
|
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.toFormattedString
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewExamNotification @Inject constructor(
|
class NewExamNotification @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
private val appNotificationManager: AppNotificationManager,
|
||||||
notificationManager: NotificationManagerCompat,
|
@ApplicationContext private val context: Context
|
||||||
) : BaseNotification(context, notificationManager) {
|
) {
|
||||||
|
|
||||||
fun notify(items: List<Exam>, student: Student) {
|
suspend fun notify(items: List<Exam>, student: Student) {
|
||||||
val today = LocalDate.now()
|
val today = LocalDate.now()
|
||||||
val lines = items.filter { !it.date.isBefore(today) }.map {
|
val lines = items.filter { !it.date.isBefore(today) }
|
||||||
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.description}"
|
.map {
|
||||||
}.ifEmpty { return }
|
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.description}"
|
||||||
|
}
|
||||||
|
.ifEmpty { return }
|
||||||
|
|
||||||
val notification = MultipleNotifications(
|
val notificationDataList = lines.map {
|
||||||
type = NotificationType.NEW_EXAM,
|
NotificationData(
|
||||||
icon = R.drawable.ic_main_exam,
|
title = context.getPlural(R.plurals.exam_notify_new_item_title, 1),
|
||||||
titleStringRes = R.plurals.exam_notify_new_item_title,
|
content = it,
|
||||||
contentStringRes = R.plurals.exam_notify_new_item_content,
|
intentToStart = SplashActivity.getStartIntent(context, Destination.Exam),
|
||||||
summaryStringRes = R.plurals.exam_number_item,
|
)
|
||||||
startMenu = MainView.Section.EXAM,
|
}
|
||||||
lines = lines
|
|
||||||
|
val groupNotificationData = GroupNotificationData(
|
||||||
|
notificationDataList = notificationDataList,
|
||||||
|
title = context.getPlural(R.plurals.exam_notify_new_item_title, lines.size),
|
||||||
|
content = context.getPlural(
|
||||||
|
R.plurals.exam_notify_new_item_content,
|
||||||
|
lines.size,
|
||||||
|
lines.size
|
||||||
|
),
|
||||||
|
intentToStart = SplashActivity.getStartIntent(context, Destination.Exam),
|
||||||
|
type = NotificationType.NEW_EXAM
|
||||||
)
|
)
|
||||||
|
|
||||||
sendNotification(notification, student)
|
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,66 +1,88 @@
|
|||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Grade
|
import io.github.wulkanowy.data.db.entities.Grade
|
||||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
import io.github.wulkanowy.data.pojos.GroupNotificationData
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.data.pojos.NotificationData
|
||||||
|
import io.github.wulkanowy.ui.modules.Destination
|
||||||
|
import io.github.wulkanowy.ui.modules.splash.SplashActivity
|
||||||
|
import io.github.wulkanowy.utils.getPlural
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewGradeNotification @Inject constructor(
|
class NewGradeNotification @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
private val appNotificationManager: AppNotificationManager,
|
||||||
notificationManager: NotificationManagerCompat,
|
@ApplicationContext private val context: Context
|
||||||
) : BaseNotification(context, notificationManager) {
|
) {
|
||||||
|
|
||||||
fun notifyDetails(items: List<Grade>, student: Student) {
|
suspend fun notifyDetails(items: List<Grade>, student: Student) {
|
||||||
val notification = MultipleNotifications(
|
val notificationDataList = items.map {
|
||||||
type = NotificationType.NEW_GRADE_DETAILS,
|
NotificationData(
|
||||||
icon = R.drawable.ic_stat_grade,
|
title = context.getPlural(R.plurals.grade_new_items, 1),
|
||||||
titleStringRes = R.plurals.grade_new_items,
|
content = "${it.subject}: ${it.entry}",
|
||||||
contentStringRes = R.plurals.grade_notify_new_items,
|
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
|
||||||
summaryStringRes = R.plurals.grade_number_item,
|
)
|
||||||
startMenu = MainView.Section.GRADE,
|
}
|
||||||
lines = items.map {
|
|
||||||
"${it.subject}: ${it.entry}"
|
val groupNotificationData = GroupNotificationData(
|
||||||
}
|
notificationDataList = notificationDataList,
|
||||||
|
title = context.getPlural(R.plurals.grade_new_items, items.size),
|
||||||
|
content = context.getPlural(R.plurals.grade_notify_new_items, items.size, items.size),
|
||||||
|
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
|
||||||
|
type = NotificationType.NEW_GRADE_DETAILS
|
||||||
)
|
)
|
||||||
|
|
||||||
sendNotification(notification, student)
|
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun notifyPredicted(items: List<GradeSummary>, student: Student) {
|
suspend fun notifyPredicted(items: List<GradeSummary>, student: Student) {
|
||||||
val notification = MultipleNotifications(
|
val notificationDataList = items.map {
|
||||||
type = NotificationType.NEW_GRADE_PREDICTED,
|
NotificationData(
|
||||||
icon = R.drawable.ic_stat_grade,
|
title = context.getPlural(R.plurals.grade_new_items_predicted, 1),
|
||||||
titleStringRes = R.plurals.grade_new_items_predicted,
|
content = "${it.subject}: ${it.predictedGrade}",
|
||||||
contentStringRes = R.plurals.grade_notify_new_items_predicted,
|
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
|
||||||
summaryStringRes = R.plurals.grade_number_item,
|
)
|
||||||
startMenu = MainView.Section.GRADE,
|
}
|
||||||
lines = items.map {
|
|
||||||
"${it.subject}: ${it.predictedGrade}"
|
val groupNotificationData = GroupNotificationData(
|
||||||
}
|
notificationDataList = notificationDataList,
|
||||||
|
title = context.getPlural(R.plurals.grade_new_items_predicted, items.size),
|
||||||
|
content = context.getPlural(
|
||||||
|
R.plurals.grade_notify_new_items_predicted,
|
||||||
|
items.size,
|
||||||
|
items.size
|
||||||
|
),
|
||||||
|
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
|
||||||
|
type = NotificationType.NEW_GRADE_PREDICTED
|
||||||
)
|
)
|
||||||
|
|
||||||
sendNotification(notification, student)
|
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun notifyFinal(items: List<GradeSummary>, student: Student) {
|
suspend fun notifyFinal(items: List<GradeSummary>, student: Student) {
|
||||||
val notification = MultipleNotifications(
|
val notificationDataList = items.map {
|
||||||
type = NotificationType.NEW_GRADE_FINAL,
|
NotificationData(
|
||||||
icon = R.drawable.ic_stat_grade,
|
title = context.getPlural(R.plurals.grade_new_items_final, 1),
|
||||||
titleStringRes = R.plurals.grade_new_items_final,
|
content = "${it.subject}: ${it.finalGrade}",
|
||||||
contentStringRes = R.plurals.grade_notify_new_items_final,
|
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
|
||||||
summaryStringRes = R.plurals.grade_number_item,
|
)
|
||||||
startMenu = MainView.Section.GRADE,
|
}
|
||||||
lines = items.map {
|
|
||||||
"${it.subject}: ${it.finalGrade}"
|
val groupNotificationData = GroupNotificationData(
|
||||||
}
|
notificationDataList = notificationDataList,
|
||||||
|
title = context.getPlural(R.plurals.grade_new_items_final, items.size),
|
||||||
|
content = context.getPlural(
|
||||||
|
R.plurals.grade_notify_new_items_final,
|
||||||
|
items.size,
|
||||||
|
items.size
|
||||||
|
),
|
||||||
|
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
|
||||||
|
type = NotificationType.NEW_GRADE_FINAL
|
||||||
)
|
)
|
||||||
|
|
||||||
sendNotification(notification, student)
|
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,38 +1,52 @@
|
|||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Homework
|
import io.github.wulkanowy.data.db.entities.Homework
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
import io.github.wulkanowy.data.pojos.GroupNotificationData
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.data.pojos.NotificationData
|
||||||
|
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.toFormattedString
|
import io.github.wulkanowy.utils.toFormattedString
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewHomeworkNotification @Inject constructor(
|
class NewHomeworkNotification @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
private val appNotificationManager: AppNotificationManager,
|
||||||
notificationManager: NotificationManagerCompat,
|
@ApplicationContext private val context: Context
|
||||||
) : BaseNotification(context, notificationManager) {
|
) {
|
||||||
|
|
||||||
fun notify(items: List<Homework>, student: Student) {
|
suspend fun notify(items: List<Homework>, student: Student) {
|
||||||
val today = LocalDate.now()
|
val today = LocalDate.now()
|
||||||
val lines = items.filter { !it.date.isBefore(today) }.map {
|
val lines = items.filter { !it.date.isBefore(today) }
|
||||||
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.content}"
|
.map {
|
||||||
}.ifEmpty { return }
|
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.content}"
|
||||||
|
}
|
||||||
|
.ifEmpty { return }
|
||||||
|
|
||||||
val notification = MultipleNotifications(
|
val notificationDataList = lines.map {
|
||||||
|
NotificationData(
|
||||||
|
title = context.getPlural(R.plurals.homework_notify_new_item_title, 1),
|
||||||
|
content = it,
|
||||||
|
intentToStart = SplashActivity.getStartIntent(context, Destination.Homework),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val groupNotificationData = GroupNotificationData(
|
||||||
|
title = context.getPlural(R.plurals.homework_notify_new_item_title, lines.size),
|
||||||
|
content = context.getPlural(
|
||||||
|
R.plurals.homework_notify_new_item_content,
|
||||||
|
lines.size,
|
||||||
|
lines.size
|
||||||
|
),
|
||||||
|
intentToStart = SplashActivity.getStartIntent(context, Destination.Homework),
|
||||||
type = NotificationType.NEW_HOMEWORK,
|
type = NotificationType.NEW_HOMEWORK,
|
||||||
icon = R.drawable.ic_more_homework,
|
notificationDataList = notificationDataList
|
||||||
titleStringRes = R.plurals.homework_notify_new_item_title,
|
|
||||||
contentStringRes = R.plurals.homework_notify_new_item_content,
|
|
||||||
summaryStringRes = R.plurals.homework_number_item,
|
|
||||||
startMenu = MainView.Section.HOMEWORK,
|
|
||||||
lines = lines
|
|
||||||
)
|
)
|
||||||
|
|
||||||
sendNotification(notification, student)
|
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,34 @@
|
|||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.OneNotification
|
import io.github.wulkanowy.data.pojos.NotificationData
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.Destination
|
||||||
|
import io.github.wulkanowy.ui.modules.splash.SplashActivity
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewLuckyNumberNotification @Inject constructor(
|
class NewLuckyNumberNotification @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
private val appNotificationManager: AppNotificationManager,
|
||||||
notificationManager: NotificationManagerCompat,
|
@ApplicationContext private val context: Context
|
||||||
) : BaseNotification(context, notificationManager) {
|
) {
|
||||||
|
|
||||||
fun notify(item: LuckyNumber, student: Student) {
|
suspend fun notify(item: LuckyNumber, student: Student) {
|
||||||
val notification = OneNotification(
|
val notificationData = NotificationData(
|
||||||
type = NotificationType.NEW_LUCKY_NUMBER,
|
title = context.getString(R.string.lucky_number_notify_new_item_title),
|
||||||
icon = R.drawable.ic_stat_luckynumber,
|
content = context.getString(
|
||||||
titleStringRes = R.string.lucky_number_notify_new_item_title,
|
R.string.lucky_number_notify_new_item,
|
||||||
contentStringRes = R.string.lucky_number_notify_new_item,
|
item.luckyNumber.toString()
|
||||||
startMenu = MainView.Section.LUCKY_NUMBER,
|
),
|
||||||
contentValues = listOf(item.luckyNumber.toString())
|
intentToStart = SplashActivity.getStartIntent(context, Destination.LuckyNumber)
|
||||||
)
|
)
|
||||||
|
|
||||||
sendNotification(notification, student)
|
appNotificationManager.sendSingleNotification(
|
||||||
|
notificationData = notificationData,
|
||||||
|
notificationType = NotificationType.NEW_LUCKY_NUMBER,
|
||||||
|
student = student
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,33 +1,39 @@
|
|||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Message
|
import io.github.wulkanowy.data.db.entities.Message
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
import io.github.wulkanowy.data.pojos.GroupNotificationData
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.data.pojos.NotificationData
|
||||||
|
import io.github.wulkanowy.ui.modules.Destination
|
||||||
|
import io.github.wulkanowy.ui.modules.splash.SplashActivity
|
||||||
|
import io.github.wulkanowy.utils.getPlural
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewMessageNotification @Inject constructor(
|
class NewMessageNotification @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
private val appNotificationManager: AppNotificationManager,
|
||||||
notificationManager: NotificationManagerCompat,
|
@ApplicationContext private val context: Context
|
||||||
) : BaseNotification(context, notificationManager) {
|
) {
|
||||||
|
|
||||||
fun notify(items: List<Message>, student: Student) {
|
suspend fun notify(items: List<Message>, student: Student) {
|
||||||
val notification = MultipleNotifications(
|
val notificationDataList = items.map {
|
||||||
type = NotificationType.NEW_MESSAGE,
|
NotificationData(
|
||||||
icon = R.drawable.ic_stat_message,
|
title = context.getPlural(R.plurals.message_new_items, 1),
|
||||||
titleStringRes = R.plurals.message_new_items,
|
content = "${it.sender}: ${it.subject}",
|
||||||
contentStringRes = R.plurals.message_notify_new_items,
|
intentToStart = SplashActivity.getStartIntent(context, Destination.Message),
|
||||||
summaryStringRes = R.plurals.message_number_item,
|
)
|
||||||
startMenu = MainView.Section.MESSAGE,
|
}
|
||||||
lines = items.map {
|
|
||||||
"${it.sender}: ${it.subject}"
|
val groupNotificationData = GroupNotificationData(
|
||||||
}
|
notificationDataList = notificationDataList,
|
||||||
|
title = context.getPlural(R.plurals.message_new_items, items.size),
|
||||||
|
content = context.getPlural(R.plurals.message_notify_new_items, items.size, items.size),
|
||||||
|
intentToStart = SplashActivity.getStartIntent(context, Destination.Message),
|
||||||
|
type = NotificationType.NEW_MESSAGE
|
||||||
)
|
)
|
||||||
|
|
||||||
sendNotification(notification, student)
|
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,46 +1,46 @@
|
|||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.Note
|
import io.github.wulkanowy.data.db.entities.Note
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
import io.github.wulkanowy.data.pojos.GroupNotificationData
|
||||||
|
import io.github.wulkanowy.data.pojos.NotificationData
|
||||||
import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory
|
import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.ui.modules.Destination
|
||||||
|
import io.github.wulkanowy.ui.modules.splash.SplashActivity
|
||||||
|
import io.github.wulkanowy.utils.getPlural
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewNoteNotification @Inject constructor(
|
class NewNoteNotification @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
private val appNotificationManager: AppNotificationManager,
|
||||||
notificationManager: NotificationManagerCompat,
|
@ApplicationContext private val context: Context
|
||||||
) : BaseNotification(context, notificationManager) {
|
) {
|
||||||
|
|
||||||
fun notify(items: List<Note>, student: Student) {
|
suspend fun notify(items: List<Note>, student: Student) {
|
||||||
val notification = MultipleNotifications(
|
val notificationDataList = items.map {
|
||||||
type = NotificationType.NEW_NOTE,
|
val titleRes = when (NoteCategory.getByValue(it.categoryType)) {
|
||||||
icon = R.drawable.ic_stat_note,
|
|
||||||
titleStringRes = when (NoteCategory.getByValue(items.first().categoryType)) {
|
|
||||||
NoteCategory.POSITIVE -> R.plurals.praise_new_items
|
NoteCategory.POSITIVE -> R.plurals.praise_new_items
|
||||||
NoteCategory.NEUTRAL -> R.plurals.neutral_note_new_items
|
NoteCategory.NEUTRAL -> R.plurals.neutral_note_new_items
|
||||||
else -> R.plurals.note_new_items
|
else -> R.plurals.note_new_items
|
||||||
},
|
|
||||||
contentStringRes = when (NoteCategory.getByValue(items.first().categoryType)) {
|
|
||||||
NoteCategory.POSITIVE -> R.plurals.praise_notify_new_items
|
|
||||||
NoteCategory.NEUTRAL -> R.plurals.neutral_note_notify_new_items
|
|
||||||
else -> R.plurals.note_notify_new_items
|
|
||||||
},
|
|
||||||
summaryStringRes = when (NoteCategory.getByValue(items.first().categoryType)) {
|
|
||||||
NoteCategory.POSITIVE -> R.plurals.praise_number_item
|
|
||||||
NoteCategory.NEUTRAL -> R.plurals.neutral_note_number_item
|
|
||||||
else -> R.plurals.note_number_item
|
|
||||||
},
|
|
||||||
startMenu = MainView.Section.NOTE,
|
|
||||||
lines = items.map {
|
|
||||||
"${it.teacher}: ${it.category}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NotificationData(
|
||||||
|
title = context.getPlural(titleRes, 1),
|
||||||
|
content = "${it.teacher}: ${it.category}",
|
||||||
|
intentToStart = SplashActivity.getStartIntent(context, Destination.Note),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val groupNotificationData = GroupNotificationData(
|
||||||
|
notificationDataList = notificationDataList,
|
||||||
|
intentToStart = SplashActivity.getStartIntent(context, Destination.Note),
|
||||||
|
title = context.getPlural(R.plurals.note_new_items, items.size),
|
||||||
|
content = context.getPlural(R.plurals.note_notify_new_items, items.size, items.size),
|
||||||
|
type = NotificationType.NEW_NOTE
|
||||||
)
|
)
|
||||||
|
|
||||||
sendNotification(notification, student)
|
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,33 +1,54 @@
|
|||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import io.github.wulkanowy.R
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
import io.github.wulkanowy.data.pojos.GroupNotificationData
|
||||||
import io.github.wulkanowy.ui.modules.main.MainView
|
import io.github.wulkanowy.data.pojos.NotificationData
|
||||||
|
import io.github.wulkanowy.ui.modules.Destination
|
||||||
|
import io.github.wulkanowy.ui.modules.splash.SplashActivity
|
||||||
|
import io.github.wulkanowy.utils.getPlural
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NewSchoolAnnouncementNotification @Inject constructor(
|
class NewSchoolAnnouncementNotification @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
private val appNotificationManager: AppNotificationManager,
|
||||||
notificationManager: NotificationManagerCompat,
|
@ApplicationContext private val context: Context
|
||||||
) : BaseNotification(context, notificationManager) {
|
) {
|
||||||
|
|
||||||
fun notify(items: List<SchoolAnnouncement>, student: Student) {
|
suspend fun notify(items: List<SchoolAnnouncement>, student: Student) {
|
||||||
val notification = MultipleNotifications(
|
val notificationDataList = items.map {
|
||||||
|
NotificationData(
|
||||||
|
intentToStart = SplashActivity.getStartIntent(
|
||||||
|
context = context,
|
||||||
|
destination = Destination.SchoolAnnouncement
|
||||||
|
),
|
||||||
|
title = context.getPlural(
|
||||||
|
R.plurals.school_announcement_notify_new_item_title,
|
||||||
|
1
|
||||||
|
),
|
||||||
|
content = "${it.subject}: ${it.content}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val groupNotificationData = GroupNotificationData(
|
||||||
type = NotificationType.NEW_ANNOUNCEMENT,
|
type = NotificationType.NEW_ANNOUNCEMENT,
|
||||||
icon = R.drawable.ic_all_about,
|
intentToStart = SplashActivity.getStartIntent(
|
||||||
titleStringRes = R.plurals.school_announcement_notify_new_item_title,
|
context = context,
|
||||||
contentStringRes = R.plurals.school_announcement_notify_new_items,
|
destination = Destination.SchoolAnnouncement
|
||||||
summaryStringRes = R.plurals.school_announcement_number_item,
|
),
|
||||||
startMenu = MainView.Section.SCHOOL_ANNOUNCEMENT,
|
title = context.getPlural(
|
||||||
lines = items.map {
|
R.plurals.school_announcement_notify_new_item_title,
|
||||||
"${it.subject}: ${it.content}"
|
items.size
|
||||||
}
|
),
|
||||||
|
content = context.getPlural(
|
||||||
|
R.plurals.school_announcement_notify_new_items,
|
||||||
|
items.size,
|
||||||
|
items.size
|
||||||
|
),
|
||||||
|
notificationDataList = notificationDataList
|
||||||
)
|
)
|
||||||
|
|
||||||
sendNotification(notification, student)
|
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package io.github.wulkanowy.services.sync.notifications
|
package io.github.wulkanowy.services.sync.notifications
|
||||||
|
|
||||||
|
import io.github.wulkanowy.R
|
||||||
import io.github.wulkanowy.services.sync.channels.LuckyNumberChannel
|
import io.github.wulkanowy.services.sync.channels.LuckyNumberChannel
|
||||||
|
import io.github.wulkanowy.services.sync.channels.NewAttendanceChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.NewConferencesChannel
|
import io.github.wulkanowy.services.sync.channels.NewConferencesChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.NewExamChannel
|
import io.github.wulkanowy.services.sync.channels.NewExamChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.NewGradesChannel
|
import io.github.wulkanowy.services.sync.channels.NewGradesChannel
|
||||||
@ -8,16 +10,77 @@ import io.github.wulkanowy.services.sync.channels.NewHomeworkChannel
|
|||||||
import io.github.wulkanowy.services.sync.channels.NewMessagesChannel
|
import io.github.wulkanowy.services.sync.channels.NewMessagesChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.NewNotesChannel
|
import io.github.wulkanowy.services.sync.channels.NewNotesChannel
|
||||||
import io.github.wulkanowy.services.sync.channels.NewSchoolAnnouncementsChannel
|
import io.github.wulkanowy.services.sync.channels.NewSchoolAnnouncementsChannel
|
||||||
|
import io.github.wulkanowy.services.sync.channels.PushChannel
|
||||||
|
import io.github.wulkanowy.services.sync.channels.TimetableChangeChannel
|
||||||
|
|
||||||
enum class NotificationType(val group: String, val channel: String) {
|
enum class NotificationType(
|
||||||
NEW_CONFERENCE("new_conferences_group", NewConferencesChannel.CHANNEL_ID),
|
val group: String?,
|
||||||
NEW_EXAM("new_exam_group", NewExamChannel.CHANNEL_ID),
|
val channel: String,
|
||||||
NEW_GRADE_DETAILS("new_grade_details_group", NewGradesChannel.CHANNEL_ID),
|
val icon: Int
|
||||||
NEW_GRADE_PREDICTED("new_grade_predicted_group", NewGradesChannel.CHANNEL_ID),
|
) {
|
||||||
NEW_GRADE_FINAL("new_grade_final_group", NewGradesChannel.CHANNEL_ID),
|
NEW_CONFERENCE(
|
||||||
NEW_HOMEWORK("new_homework_group", NewHomeworkChannel.CHANNEL_ID),
|
group = "new_conferences_group",
|
||||||
NEW_LUCKY_NUMBER("lucky_number_group", LuckyNumberChannel.CHANNEL_ID),
|
channel = NewConferencesChannel.CHANNEL_ID,
|
||||||
NEW_MESSAGE("new_message_group", NewMessagesChannel.CHANNEL_ID),
|
icon = R.drawable.ic_more_conferences,
|
||||||
NEW_NOTE("new_notes_group", NewNotesChannel.CHANNEL_ID),
|
),
|
||||||
NEW_ANNOUNCEMENT("new_school_announcements_group", NewSchoolAnnouncementsChannel.CHANNEL_ID),
|
NEW_EXAM(
|
||||||
|
group = "new_exam_group",
|
||||||
|
channel = NewExamChannel.CHANNEL_ID,
|
||||||
|
icon = R.drawable.ic_main_exam
|
||||||
|
),
|
||||||
|
NEW_GRADE_DETAILS(
|
||||||
|
group = "new_grade_details_group",
|
||||||
|
channel = NewGradesChannel.CHANNEL_ID,
|
||||||
|
icon = R.drawable.ic_stat_grade,
|
||||||
|
),
|
||||||
|
NEW_GRADE_PREDICTED(
|
||||||
|
group = "new_grade_predicted_group",
|
||||||
|
channel = NewGradesChannel.CHANNEL_ID,
|
||||||
|
icon = R.drawable.ic_stat_grade,
|
||||||
|
),
|
||||||
|
NEW_GRADE_FINAL(
|
||||||
|
group = "new_grade_final_group",
|
||||||
|
channel = NewGradesChannel.CHANNEL_ID,
|
||||||
|
icon = R.drawable.ic_stat_grade,
|
||||||
|
),
|
||||||
|
NEW_HOMEWORK(
|
||||||
|
group = "new_homework_group",
|
||||||
|
channel = NewHomeworkChannel.CHANNEL_ID,
|
||||||
|
icon = R.drawable.ic_more_homework,
|
||||||
|
),
|
||||||
|
NEW_LUCKY_NUMBER(
|
||||||
|
group = null,
|
||||||
|
channel = LuckyNumberChannel.CHANNEL_ID,
|
||||||
|
icon = R.drawable.ic_stat_luckynumber,
|
||||||
|
),
|
||||||
|
NEW_MESSAGE(
|
||||||
|
group = "new_message_group",
|
||||||
|
channel = NewMessagesChannel.CHANNEL_ID,
|
||||||
|
icon = R.drawable.ic_stat_message,
|
||||||
|
),
|
||||||
|
NEW_NOTE(
|
||||||
|
group = "new_notes_group",
|
||||||
|
channel = NewNotesChannel.CHANNEL_ID,
|
||||||
|
icon = R.drawable.ic_stat_note
|
||||||
|
),
|
||||||
|
NEW_ANNOUNCEMENT(
|
||||||
|
group = "new_school_announcements_group",
|
||||||
|
channel = NewSchoolAnnouncementsChannel.CHANNEL_ID,
|
||||||
|
icon = R.drawable.ic_all_about
|
||||||
|
),
|
||||||
|
CHANGE_TIMETABLE(
|
||||||
|
group = "change_timetable_group",
|
||||||
|
channel = TimetableChangeChannel.CHANNEL_ID,
|
||||||
|
icon = R.drawable.ic_main_timetable
|
||||||
|
),
|
||||||
|
NEW_ATTENDANCE(
|
||||||
|
group = "new_attendance_group",
|
||||||
|
channel = NewAttendanceChannel.CHANNEL_ID,
|
||||||
|
icon = R.drawable.ic_main_attendance
|
||||||
|
),
|
||||||
|
PUSH(
|
||||||
|
group = null,
|
||||||
|
channel = PushChannel.CHANNEL_ID,
|
||||||
|
icon = R.drawable.ic_stat_all
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -3,15 +3,40 @@ package io.github.wulkanowy.services.sync.works
|
|||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
import io.github.wulkanowy.data.repositories.AttendanceRepository
|
import io.github.wulkanowy.data.repositories.AttendanceRepository
|
||||||
import io.github.wulkanowy.utils.monday
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.utils.sunday
|
import io.github.wulkanowy.services.sync.notifications.NewAttendanceNotification
|
||||||
|
import io.github.wulkanowy.utils.previousOrSameSchoolDay
|
||||||
import io.github.wulkanowy.utils.waitForResult
|
import io.github.wulkanowy.utils.waitForResult
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import java.time.LocalDate.now
|
import java.time.LocalDate.now
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AttendanceWork @Inject constructor(private val attendanceRepository: AttendanceRepository) : Work {
|
class AttendanceWork @Inject constructor(
|
||||||
|
private val attendanceRepository: AttendanceRepository,
|
||||||
|
private val newAttendanceNotification: NewAttendanceNotification,
|
||||||
|
private val preferencesRepository: PreferencesRepository
|
||||||
|
) : Work {
|
||||||
|
|
||||||
override suspend fun doWork(student: Student, semester: Semester) {
|
override suspend fun doWork(student: Student, semester: Semester) {
|
||||||
attendanceRepository.getAttendance(student, semester, now().monday, now().sunday, true).waitForResult()
|
attendanceRepository.getAttendance(
|
||||||
|
student = student,
|
||||||
|
semester = semester,
|
||||||
|
start = now().previousOrSameSchoolDay,
|
||||||
|
end = now().previousOrSameSchoolDay,
|
||||||
|
forceRefresh = true,
|
||||||
|
notify = preferencesRepository.isNotificationsEnable
|
||||||
|
)
|
||||||
|
.waitForResult()
|
||||||
|
|
||||||
|
attendanceRepository.getAttendanceFromDatabase(semester, now().minusDays(7), now())
|
||||||
|
.first()
|
||||||
|
.filterNot { it.isNotified }
|
||||||
|
.let {
|
||||||
|
if (it.isNotEmpty()) newAttendanceNotification.notify(it, student)
|
||||||
|
|
||||||
|
attendanceRepository.updateTimetable(it.onEach { attendance ->
|
||||||
|
attendance.isNotified = true
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,7 @@ import io.github.wulkanowy.data.db.entities.Student
|
|||||||
import io.github.wulkanowy.data.repositories.HomeworkRepository
|
import io.github.wulkanowy.data.repositories.HomeworkRepository
|
||||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.services.sync.notifications.NewHomeworkNotification
|
import io.github.wulkanowy.services.sync.notifications.NewHomeworkNotification
|
||||||
import io.github.wulkanowy.utils.monday
|
import io.github.wulkanowy.utils.nextOrSameSchoolDay
|
||||||
import io.github.wulkanowy.utils.sunday
|
|
||||||
import io.github.wulkanowy.utils.waitForResult
|
import io.github.wulkanowy.utils.waitForResult
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import java.time.LocalDate.now
|
import java.time.LocalDate.now
|
||||||
@ -22,13 +21,13 @@ class HomeworkWork @Inject constructor(
|
|||||||
homeworkRepository.getHomework(
|
homeworkRepository.getHomework(
|
||||||
student = student,
|
student = student,
|
||||||
semester = semester,
|
semester = semester,
|
||||||
start = now().monday,
|
start = now().nextOrSameSchoolDay,
|
||||||
end = now().sunday,
|
end = now().nextOrSameSchoolDay,
|
||||||
forceRefresh = true,
|
forceRefresh = true,
|
||||||
notify = preferencesRepository.isNotificationsEnable
|
notify = preferencesRepository.isNotificationsEnable
|
||||||
).waitForResult()
|
).waitForResult()
|
||||||
|
|
||||||
homeworkRepository.getHomeworkFromDatabase(semester, now().monday, now().sunday).first()
|
homeworkRepository.getHomeworkFromDatabase(semester, now(), now().plusDays(7)).first()
|
||||||
.filter { !it.isNotified }.let {
|
.filter { !it.isNotified }.let {
|
||||||
if (it.isNotEmpty()) newHomeworkNotification.notify(it, student)
|
if (it.isNotEmpty()) newHomeworkNotification.notify(it, student)
|
||||||
|
|
||||||
|
@ -2,18 +2,41 @@ package io.github.wulkanowy.services.sync.works
|
|||||||
|
|
||||||
import io.github.wulkanowy.data.db.entities.Semester
|
import io.github.wulkanowy.data.db.entities.Semester
|
||||||
import io.github.wulkanowy.data.db.entities.Student
|
import io.github.wulkanowy.data.db.entities.Student
|
||||||
|
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||||
import io.github.wulkanowy.data.repositories.TimetableRepository
|
import io.github.wulkanowy.data.repositories.TimetableRepository
|
||||||
import io.github.wulkanowy.utils.monday
|
import io.github.wulkanowy.services.sync.notifications.ChangeTimetableNotification
|
||||||
import io.github.wulkanowy.utils.sunday
|
import io.github.wulkanowy.utils.nextOrSameSchoolDay
|
||||||
import io.github.wulkanowy.utils.waitForResult
|
import io.github.wulkanowy.utils.waitForResult
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import java.time.LocalDate.now
|
import java.time.LocalDate.now
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class TimetableWork @Inject constructor(
|
class TimetableWork @Inject constructor(
|
||||||
private val timetableRepository: TimetableRepository
|
private val timetableRepository: TimetableRepository,
|
||||||
|
private val changeTimetableNotification: ChangeTimetableNotification,
|
||||||
|
private val preferencesRepository: PreferencesRepository
|
||||||
) : Work {
|
) : Work {
|
||||||
|
|
||||||
override suspend fun doWork(student: Student, semester: Semester) {
|
override suspend fun doWork(student: Student, semester: Semester) {
|
||||||
timetableRepository.getTimetable(student, semester, now().monday, now().sunday, true).waitForResult()
|
timetableRepository.getTimetable(
|
||||||
|
student = student,
|
||||||
|
semester = semester,
|
||||||
|
start = now().nextOrSameSchoolDay,
|
||||||
|
end = now().nextOrSameSchoolDay,
|
||||||
|
forceRefresh = true,
|
||||||
|
notify = preferencesRepository.isNotificationsEnable
|
||||||
|
)
|
||||||
|
.waitForResult()
|
||||||
|
|
||||||
|
timetableRepository.getTimetableFromDatabase(semester, now(), now().plusDays(7))
|
||||||
|
.first()
|
||||||
|
.filterNot { it.isNotified }
|
||||||
|
.let {
|
||||||
|
if (it.isNotEmpty()) changeTimetableNotification.notify(it, student)
|
||||||
|
|
||||||
|
timetableRepository.updateTimetable(it.onEach { timetable ->
|
||||||
|
timetable.isNotified = true
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
package io.github.wulkanowy.ui.base
|
package io.github.wulkanowy.ui.base
|
||||||
|
|
||||||
import android.app.ActivityManager
|
import android.app.ActivityManager
|
||||||
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
|
|
||||||
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
|
||||||
import androidx.viewbinding.ViewBinding
|
import androidx.viewbinding.ViewBinding
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
|
import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
|
||||||
@ -40,7 +37,6 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
|||||||
themeManager.applyActivityTheme(this)
|
themeManager.applyActivityTheme(this)
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleLogger, true)
|
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleLogger, true)
|
||||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
setTaskDescription(
|
setTaskDescription(
|
||||||
@ -83,8 +79,8 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun openClearLoginView() {
|
override fun openClearLoginView() {
|
||||||
startActivity(LoginActivity.getStartIntent(this)
|
startActivity(LoginActivity.getStartIntent(this))
|
||||||
.apply { addFlags(FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_NEW_TASK) })
|
finishAffinity()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
@ -2,32 +2,33 @@ package io.github.wulkanowy.ui.base
|
|||||||
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import androidx.fragment.app.FragmentPagerAdapter
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
|
import com.google.android.material.tabs.TabLayout
|
||||||
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
|
|
||||||
//TODO Use ViewPager2
|
class BaseFragmentPagerAdapter(
|
||||||
class BaseFragmentPagerAdapter(private val fragmentManager: FragmentManager) :
|
private val fragmentManager: FragmentManager,
|
||||||
FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
private val pagesCount: Int,
|
||||||
|
lifecycle: Lifecycle,
|
||||||
|
) : FragmentStateAdapter(fragmentManager, lifecycle), TabLayoutMediator.TabConfigurationStrategy {
|
||||||
|
|
||||||
private val pages = mutableMapOf<Fragment, String?>()
|
lateinit var itemFactory: (position: Int) -> Fragment
|
||||||
|
|
||||||
|
var titleFactory: (position: Int) -> String? = { "" }
|
||||||
|
|
||||||
var containerId = 0
|
var containerId = 0
|
||||||
|
|
||||||
fun getFragmentInstance(position: Int): Fragment? {
|
fun getFragmentInstance(position: Int): Fragment? {
|
||||||
require(containerId != 0) { "Container id is 0" }
|
require(containerId != 0) { "Container id is 0" }
|
||||||
return fragmentManager.findFragmentByTag("android:switcher:$containerId:$position")
|
return fragmentManager.findFragmentByTag("f$position")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addFragments(fragments: List<Fragment>) {
|
override fun createFragment(position: Int): Fragment = itemFactory(position)
|
||||||
fragments.forEach { pages[it] = null }
|
|
||||||
|
override fun getItemCount() = pagesCount
|
||||||
|
|
||||||
|
override fun onConfigureTab(tab: TabLayout.Tab, position: Int) {
|
||||||
|
tab.text = titleFactory(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addFragmentsWithTitle(pages: Map<Fragment, String>) {
|
|
||||||
this.pages.putAll(pages)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItem(position: Int) = pages.keys.elementAt(position)
|
|
||||||
|
|
||||||
override fun getCount() = pages.size
|
|
||||||
|
|
||||||
override fun getPageTitle(position: Int) = pages.values.elementAt(position)
|
|
||||||
}
|
}
|
||||||
|
@ -6,29 +6,27 @@ import io.github.wulkanowy.utils.flowWithResource
|
|||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.cancelChildren
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import kotlin.coroutines.CoroutineContext
|
|
||||||
|
|
||||||
open class BasePresenter<T : BaseView>(
|
open class BasePresenter<T : BaseView>(
|
||||||
protected val errorHandler: ErrorHandler,
|
protected val errorHandler: ErrorHandler,
|
||||||
protected val studentRepository: StudentRepository
|
protected val studentRepository: StudentRepository
|
||||||
) : CoroutineScope {
|
) {
|
||||||
|
private val job = SupervisorJob()
|
||||||
|
|
||||||
private var job = Job()
|
protected val presenterScope = CoroutineScope(job + Dispatchers.Main)
|
||||||
|
|
||||||
private val jobs = mutableMapOf<String, Job>()
|
private val childrenJobs = mutableMapOf<String, Job>()
|
||||||
|
|
||||||
override val coroutineContext: CoroutineContext
|
|
||||||
get() = Dispatchers.Main + job
|
|
||||||
|
|
||||||
var view: T? = null
|
var view: T? = null
|
||||||
|
|
||||||
open fun onAttachView(view: T) {
|
open fun onAttachView(view: T) {
|
||||||
job = Job()
|
|
||||||
this.view = view
|
this.view = view
|
||||||
errorHandler.apply {
|
errorHandler.apply {
|
||||||
showErrorMessage = view::showError
|
showErrorMessage = view::showError
|
||||||
@ -64,22 +62,22 @@ open class BasePresenter<T : BaseView>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun <T> Flow<T>.launch(individualJobTag: String = "load"): Job {
|
fun <T> Flow<T>.launch(individualJobTag: String = "load"): Job {
|
||||||
jobs[individualJobTag]?.cancel()
|
childrenJobs[individualJobTag]?.cancel()
|
||||||
val job = catch { errorHandler.dispatch(it) }.launchIn(this@BasePresenter)
|
val job = catch { errorHandler.dispatch(it) }.launchIn(presenterScope)
|
||||||
jobs[individualJobTag] = job
|
childrenJobs[individualJobTag] = job
|
||||||
Timber.d("Job $individualJobTag launched in ${this@BasePresenter.javaClass.simpleName}: $job")
|
Timber.d("Job $individualJobTag launched in ${this@BasePresenter.javaClass.simpleName}: $job")
|
||||||
return job
|
return job
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancelJobs(vararg names: String) {
|
fun cancelJobs(vararg names: String) {
|
||||||
names.forEach {
|
names.forEach {
|
||||||
jobs[it]?.cancel()
|
childrenJobs[it]?.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun onDetachView() {
|
open fun onDetachView() {
|
||||||
view = null
|
job.cancelChildren()
|
||||||
job.cancel()
|
|
||||||
errorHandler.clear()
|
errorHandler.clear()
|
||||||
|
view = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ 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
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
|
import androidx.core.view.isGone
|
||||||
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.DialogErrorBinding
|
import io.github.wulkanowy.databinding.DialogErrorBinding
|
||||||
@ -24,8 +25,6 @@ import io.github.wulkanowy.utils.openEmailClient
|
|||||||
import io.github.wulkanowy.utils.openInternetBrowser
|
import io.github.wulkanowy.utils.openInternetBrowser
|
||||||
import okhttp3.internal.http2.StreamResetException
|
import okhttp3.internal.http2.StreamResetException
|
||||||
import java.io.InterruptedIOException
|
import java.io.InterruptedIOException
|
||||||
import java.io.PrintWriter
|
|
||||||
import java.io.StringWriter
|
|
||||||
import java.net.ConnectException
|
import java.net.ConnectException
|
||||||
import java.net.SocketTimeoutException
|
import java.net.SocketTimeoutException
|
||||||
import java.net.UnknownHostException
|
import java.net.UnknownHostException
|
||||||
@ -64,26 +63,26 @@ class ErrorDialog : BaseDialogFragment<DialogErrorBinding>() {
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
val stringWriter = StringWriter().apply {
|
val errorStacktrace = error.stackTraceToString()
|
||||||
error.printStackTrace(PrintWriter(this))
|
|
||||||
}
|
|
||||||
|
|
||||||
with(binding) {
|
with(binding) {
|
||||||
errorDialogContent.text = stringWriter.toString()
|
errorDialogContent.text = errorStacktrace.replace(": ${error.localizedMessage}", "")
|
||||||
with(errorDialogHorizontalScroll) {
|
with(errorDialogHorizontalScroll) {
|
||||||
post { fullScroll(HorizontalScrollView.FOCUS_LEFT) }
|
post { fullScroll(HorizontalScrollView.FOCUS_LEFT) }
|
||||||
}
|
}
|
||||||
errorDialogCopy.setOnClickListener {
|
errorDialogCopy.setOnClickListener {
|
||||||
val clip = ClipData.newPlainText("wulkanowy", stringWriter.toString())
|
val clip = ClipData.newPlainText("Error details", errorStacktrace)
|
||||||
activity?.getSystemService<ClipboardManager>()?.setPrimaryClip(clip)
|
activity?.getSystemService<ClipboardManager>()?.setPrimaryClip(clip)
|
||||||
|
|
||||||
Toast.makeText(context, R.string.all_copied, LENGTH_LONG).show()
|
Toast.makeText(context, R.string.all_copied, LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
errorDialogCancel.setOnClickListener { dismiss() }
|
errorDialogCancel.setOnClickListener { dismiss() }
|
||||||
errorDialogReport.setOnClickListener {
|
errorDialogReport.setOnClickListener {
|
||||||
openConfirmDialog { openEmailClient(stringWriter.toString()) }
|
openConfirmDialog { openEmailClient(errorStacktrace) }
|
||||||
}
|
}
|
||||||
errorDialogMessage.text = resources.getString(error)
|
errorDialogHumanizedMessage.text = resources.getString(error)
|
||||||
|
errorDialogErrorMessage.text = error.localizedMessage
|
||||||
|
errorDialogErrorMessage.isGone = error.localizedMessage.isNullOrBlank()
|
||||||
errorDialogReport.isEnabled = when (error) {
|
errorDialogReport.isEnabled = when (error) {
|
||||||
is UnknownHostException,
|
is UnknownHostException,
|
||||||
is InterruptedIOException,
|
is InterruptedIOException,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package io.github.wulkanowy.ui.base
|
package io.github.wulkanowy.ui.base
|
||||||
|
|
||||||
import android.content.res.Resources
|
import android.content.Context
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
|
import io.github.wulkanowy.data.exceptions.NoCurrentStudentException
|
||||||
import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException
|
import io.github.wulkanowy.sdk.scrapper.login.BadCredentialsException
|
||||||
import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException
|
import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException
|
||||||
@ -9,7 +10,7 @@ import io.github.wulkanowy.utils.security.ScramblerException
|
|||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
open class ErrorHandler @Inject constructor(protected val resources: Resources) {
|
open class ErrorHandler @Inject constructor(@ApplicationContext protected val context: Context) {
|
||||||
|
|
||||||
var showErrorMessage: (String, Throwable) -> Unit = { _, _ -> }
|
var showErrorMessage: (String, Throwable) -> Unit = { _, _ -> }
|
||||||
|
|
||||||
@ -25,7 +26,7 @@ open class ErrorHandler @Inject constructor(protected val resources: Resources)
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected open fun proceed(error: Throwable) {
|
protected open fun proceed(error: Throwable) {
|
||||||
showErrorMessage(resources.getString(error), error)
|
showErrorMessage(context.resources.getString(error), error)
|
||||||
when (error) {
|
when (error) {
|
||||||
is PasswordChangeRequiredException -> onPasswordChangeRequired(error.redirectUrl)
|
is PasswordChangeRequiredException -> onPasswordChangeRequired(error.redirectUrl)
|
||||||
is ScramblerException, is BadCredentialsException -> onSessionExpired()
|
is ScramblerException, is BadCredentialsException -> onSessionExpired()
|
||||||
|
@ -41,14 +41,15 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isThemeApplicable(activity: AppCompatActivity): Boolean {
|
private fun isThemeApplicable(activity: AppCompatActivity) =
|
||||||
return activity.packageManager
|
activity.packageManager
|
||||||
.getPackageInfo(activity.packageName, GET_ACTIVITIES)
|
.getPackageInfo(activity.packageName, GET_ACTIVITIES)
|
||||||
.activities.singleOrNull { it.name == activity::class.java.canonicalName }
|
.activities
|
||||||
?.theme.let {
|
.singleOrNull { it.name == activity::class.java.canonicalName }
|
||||||
|
?.theme
|
||||||
|
.let {
|
||||||
it == R.style.WulkanowyTheme_Black || it == R.style.WulkanowyTheme_NoActionBar
|
it == R.style.WulkanowyTheme_Black || it == R.style.WulkanowyTheme_NoActionBar
|
||||||
|| it == R.style.WulkanowyTheme_Login || it == R.style.WulkanowyTheme_Login_Black
|
|| it == R.style.WulkanowyTheme_Login || it == R.style.WulkanowyTheme_Login_Black
|
||||||
|| it == R.style.WulkanowyTheme_MessageSend || it == R.style.WulkanowyTheme_MessageSend_Black
|
|| it == R.style.WulkanowyTheme_MessageSend || it == R.style.WulkanowyTheme_MessageSend_Black
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
136
app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt
Normal file
136
app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
package io.github.wulkanowy.ui.modules
|
||||||
|
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.dashboard.DashboardFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.exam.ExamFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.grade.GradeFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.homework.HomeworkFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.message.MessageFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.more.MoreFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.note.NoteFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.schoolandteachers.school.SchoolFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment
|
||||||
|
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
|
||||||
|
import java.io.Serializable
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
|
sealed interface Destination : Serializable {
|
||||||
|
|
||||||
|
/*
|
||||||
|
Type in children classes have to be as getter to avoid null in enums
|
||||||
|
https://stackoverflow.com/questions/68866453/kotlin-enum-val-is-returning-null-despite-being-set-at-compile-time
|
||||||
|
*/
|
||||||
|
val type: Type
|
||||||
|
|
||||||
|
val fragment: Fragment
|
||||||
|
|
||||||
|
enum class Type(val defaultDestination: Destination) {
|
||||||
|
DASHBOARD(Dashboard),
|
||||||
|
GRADE(Grade),
|
||||||
|
ATTENDANCE(Attendance),
|
||||||
|
EXAM(Exam),
|
||||||
|
TIMETABLE(Timetable()),
|
||||||
|
HOMEWORK(Homework),
|
||||||
|
NOTE(Note),
|
||||||
|
CONFERENCE(Conference),
|
||||||
|
SCHOOL_ANNOUNCEMENT(SchoolAnnouncement),
|
||||||
|
SCHOOL(School),
|
||||||
|
LUCKY_NUMBER(More),
|
||||||
|
MORE(More),
|
||||||
|
MESSAGE(Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
object Dashboard : Destination {
|
||||||
|
|
||||||
|
override val type get() = Type.DASHBOARD
|
||||||
|
|
||||||
|
override val fragment get() = DashboardFragment.newInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
object Grade : Destination {
|
||||||
|
|
||||||
|
override val type get() = Type.GRADE
|
||||||
|
|
||||||
|
override val fragment get() = GradeFragment.newInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
object Attendance : Destination {
|
||||||
|
|
||||||
|
override val type get() = Type.ATTENDANCE
|
||||||
|
|
||||||
|
override val fragment get() = AttendanceFragment.newInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
object Exam : Destination {
|
||||||
|
|
||||||
|
override val type get() = Type.EXAM
|
||||||
|
|
||||||
|
override val fragment get() = ExamFragment.newInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Timetable(val date: LocalDate? = null) : Destination {
|
||||||
|
|
||||||
|
override val type get() = Type.TIMETABLE
|
||||||
|
|
||||||
|
override val fragment get() = TimetableFragment.newInstance(date)
|
||||||
|
}
|
||||||
|
|
||||||
|
object Homework : Destination {
|
||||||
|
|
||||||
|
override val type get() = Type.HOMEWORK
|
||||||
|
|
||||||
|
override val fragment get() = HomeworkFragment.newInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
object Note : Destination {
|
||||||
|
|
||||||
|
override val type get() = Type.NOTE
|
||||||
|
|
||||||
|
override val fragment get() = NoteFragment.newInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
object Conference : Destination {
|
||||||
|
|
||||||
|
override val type get() = Type.CONFERENCE
|
||||||
|
|
||||||
|
override val fragment get() = ConferenceFragment.newInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
object SchoolAnnouncement : Destination {
|
||||||
|
|
||||||
|
override val type get() = Type.SCHOOL_ANNOUNCEMENT
|
||||||
|
|
||||||
|
override val fragment get() = SchoolAnnouncementFragment.newInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
object School : Destination {
|
||||||
|
|
||||||
|
override val type get() = Type.SCHOOL
|
||||||
|
|
||||||
|
override val fragment get() = SchoolFragment.newInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
object LuckyNumber : Destination {
|
||||||
|
|
||||||
|
override val type get() = Type.LUCKY_NUMBER
|
||||||
|
|
||||||
|
override val fragment get() = LuckyNumberFragment.newInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
object More : Destination {
|
||||||
|
|
||||||
|
override val type get() = Type.MORE
|
||||||
|
|
||||||
|
override val fragment get() = MoreFragment.newInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
object Message : Destination {
|
||||||
|
|
||||||
|
override val type get() = Type.MESSAGE
|
||||||
|
|
||||||
|
override val fragment get() = MessageFragment.newInstance()
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user