Compare commits
119 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 |
14
.github/workflows/deploy-store.yml
vendored
14
.github/workflows/deploy-store.yml
vendored
@ -28,15 +28,17 @@ jobs:
|
||||
SERVICES_ENCRYPT_KEY: ${{ secrets.SERVICES_ENCRYPT_KEY }}
|
||||
run: |
|
||||
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/google-services.json.gpg
|
||||
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg
|
||||
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg
|
||||
- name: Upload apk to google play
|
||||
env:
|
||||
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
||||
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
|
||||
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
|
||||
PLAY_SERVICE_ACCOUNT_EMAIL: ${{ secrets.PLAY_SERVICE_ACCOUNT_EMAIL }}
|
||||
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
||||
run: ./gradlew publishPlayRelease -PenableFirebase --stacktrace;
|
||||
ANDROID_PUBLISHER_CREDENTIALS: ${{ secrets.ANDROID_PUBLISHER_CREDENTIALS }}
|
||||
ADMOB_PROJECT_ID: ${{ secrets.ADMOB_PROJECT_ID }}
|
||||
SINGLE_SUPPORT_AD_ID: ${{ secrets.SINGLE_SUPPORT_AD_ID }}
|
||||
SET_BUILD_TIMESTAMP: ${{ secrets.SET_BUILD_TIMESTAMP }}
|
||||
run: ./gradlew publishPlayReleaseApps -PenableFirebase --stacktrace;
|
||||
|
||||
deploy-app-gallery:
|
||||
name: Deploy to AppGallery
|
||||
@ -60,7 +62,6 @@ jobs:
|
||||
SERVICES_ENCRYPT_KEY: ${{ secrets.SERVICES_ENCRYPT_KEY }}
|
||||
run: |
|
||||
gpg --yes --batch --passphrase=$SERVICES_ENCRYPT_KEY ./app/src/release/agconnect-services.json.gpg
|
||||
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/key.p12.gpg
|
||||
gpg --yes --batch --passphrase=$ENCRYPT_KEY ./app/upload-key.jks.gpg
|
||||
- name: Prepare credentials
|
||||
env:
|
||||
@ -68,7 +69,8 @@ jobs:
|
||||
run: echo $AGC_CREDENTIALS > ./app/src/release/agconnect-credentials.json
|
||||
- name: Build and publish HMS version
|
||||
env:
|
||||
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
||||
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
|
||||
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
|
||||
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
|
||||
SET_BUILD_TIMESTAMP: ${{ secrets.SET_BUILD_TIMESTAMP }}
|
||||
run: ./gradlew bundleHmsRelease --stacktrace && ./gradlew publishHuaweiAppGalleryHmsRelease --stacktrace
|
||||
|
2
LICENSE
2
LICENSE
@ -186,7 +186,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2019 Wulkanowy
|
||||
Copyright 2021 Wulkanowy
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (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)
|
||||
|
||||
[Deutsche Version von README](README.de.md)
|
||||
|
||||
[Česká verze README](README.cs.md)
|
||||
|
||||
[Slovenská verzia README](README.sk.md)
|
||||
|
||||
# Wulkanowy
|
||||
|
||||
[](https://github.com/wulkanowy/wulkanowy/actions)
|
||||
|
@ -1,5 +1,11 @@
|
||||
[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
|
||||
|
||||
[](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)
|
109
app/build.gradle
109
app/build.gradle
@ -1,5 +1,6 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlinx-serialization'
|
||||
apply plugin: 'kotlin-parcelize'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'dagger.hilt.android.plugin'
|
||||
@ -14,23 +15,22 @@ apply from: 'sonarqube.gradle'
|
||||
apply from: 'hooks.gradle'
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
compileSdkVersion 31
|
||||
|
||||
defaultConfig {
|
||||
applicationId "io.github.wulkanowy"
|
||||
testApplicationId "io.github.tests.wulkanowy"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
versionCode 96
|
||||
versionName "1.2.3"
|
||||
targetSdkVersion 31
|
||||
versionCode 100
|
||||
versionName "1.4.2"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
|
||||
resValue "string", "app_name", "Wulkanowy"
|
||||
buildConfigField "long", "BUILD_TIMESTAMP", String.valueOf(System.currentTimeMillis())
|
||||
|
||||
manifestPlaceholders = [
|
||||
firebase_enabled: project.hasProperty("enableFirebase")
|
||||
firebase_enabled: project.hasProperty("enableFirebase"),
|
||||
admob_project_id: ""
|
||||
]
|
||||
javaCompileOptions {
|
||||
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 {
|
||||
@ -62,12 +70,14 @@ android {
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.release
|
||||
buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
|
||||
}
|
||||
debug {
|
||||
resValue "string", "app_name", "Wulkanowy DEV " + defaultConfig.versionCode
|
||||
resValue "string", "app_name", "Wulkanowy DEV"
|
||||
applicationIdSuffix ".dev"
|
||||
versionNameSuffix "-dev"
|
||||
ext.enableCrashlytics = project.hasProperty("enableFirebase")
|
||||
buildConfigField "String", "MESSAGES_BASE_URL", "\"https://messages.wulkanowy.net.pl\""
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,30 +86,38 @@ android {
|
||||
productFlavors {
|
||||
hms {
|
||||
dimension "platform"
|
||||
manifestPlaceholders = [
|
||||
install_channel: "AppGallery"
|
||||
]
|
||||
manifestPlaceholders = [install_channel: "AppGallery"]
|
||||
}
|
||||
|
||||
play {
|
||||
dimension "platform"
|
||||
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 {
|
||||
dimension "platform"
|
||||
manifestPlaceholders = [
|
||||
install_channel: "F-Droid"
|
||||
]
|
||||
manifestPlaceholders = [install_channel: "F-Droid"]
|
||||
}
|
||||
}
|
||||
|
||||
playConfigs {
|
||||
play { enabled.set(true) }
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
|
||||
bundle {
|
||||
language {
|
||||
enableSplit = false
|
||||
}
|
||||
}
|
||||
|
||||
testOptions.unitTests {
|
||||
includeAndroidResources = true
|
||||
}
|
||||
@ -130,11 +148,10 @@ kapt {
|
||||
}
|
||||
|
||||
play {
|
||||
serviceAccountEmail = System.getenv("PLAY_SERVICE_ACCOUNT_EMAIL") ?: "jan@fakelog.cf"
|
||||
serviceAccountCredentials = file('key.p12')
|
||||
defaultToAppBundles = false
|
||||
track = 'production'
|
||||
updatePriority = 3
|
||||
track = 'beta'
|
||||
updatePriority = 1
|
||||
enabled.set(false)
|
||||
}
|
||||
|
||||
huaweiPublish {
|
||||
@ -148,33 +165,34 @@ huaweiPublish {
|
||||
}
|
||||
|
||||
ext {
|
||||
work_manager = "2.6.0"
|
||||
work_manager = "2.7.1"
|
||||
android_hilt = "1.0.0"
|
||||
room = "2.3.0"
|
||||
chucker = "3.5.2"
|
||||
mockk = "1.12.0"
|
||||
moshi = "1.12.0"
|
||||
mockk = "1.12.1"
|
||||
coroutines = "1.5.2"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "io.github.wulkanowy:sdk:1.2.3"
|
||||
implementation "io.github.wulkanowy:sdk:1.4.2"
|
||||
|
||||
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.activity:activity-ktx:1.3.1"
|
||||
implementation "androidx.appcompat:appcompat:1.3.1"
|
||||
implementation "androidx.appcompat:appcompat-resources:1.3.1"
|
||||
implementation "androidx.fragment:fragment-ktx:1.3.6"
|
||||
implementation "androidx.annotation:annotation:1.2.0"
|
||||
implementation "androidx.core:core-ktx:1.7.0"
|
||||
implementation 'androidx.core:core-splashscreen:1.0.0-alpha02'
|
||||
implementation "androidx.activity:activity-ktx:1.4.0"
|
||||
implementation "androidx.appcompat:appcompat:1.4.0"
|
||||
implementation "androidx.fragment:fragment-ktx:1.4.0"
|
||||
implementation "androidx.annotation:annotation:1.3.0"
|
||||
|
||||
implementation "androidx.preference:preference-ktx:1.1.1"
|
||||
implementation "androidx.recyclerview:recyclerview:1.2.1"
|
||||
implementation "androidx.viewpager:viewpager:1.0.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 "com.google.android.material:material:1.4.0"
|
||||
implementation "com.github.wulkanowy:material-chips-input:2.3.1"
|
||||
@ -184,7 +202,7 @@ dependencies {
|
||||
implementation "androidx.work:work-runtime-ktx:$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-ktx:$room"
|
||||
@ -198,40 +216,41 @@ dependencies {
|
||||
implementation 'com.github.ncapdevi:FragNav:3.3.0'
|
||||
implementation "com.github.YarikSOffice:lingver:1.3.0"
|
||||
|
||||
implementation "com.squareup.moshi:moshi:$moshi"
|
||||
implementation "com.squareup.moshi:moshi-adapters:$moshi"
|
||||
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi"
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0"
|
||||
implementation "com.squareup.okhttp3:logging-interceptor:4.9.2"
|
||||
|
||||
implementation "com.jakewharton.timber:timber:5.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 "io.coil-kt:coil:1.3.2"
|
||||
implementation "io.coil-kt:coil:1.4.0"
|
||||
implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
|
||||
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.1')
|
||||
playImplementation platform('com.google.firebase:firebase-bom:29.0.0')
|
||||
playImplementation 'com.google.firebase:firebase-analytics-ktx'
|
||||
playImplementation 'com.google.firebase:firebase-messaging:'
|
||||
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.gms:play-services-ads:20.4.0'
|
||||
|
||||
hmsImplementation 'com.huawei.hms:hianalytics:6.2.0.301'
|
||||
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.0.300'
|
||||
hmsImplementation 'com.huawei.hms:hianalytics:6.3.0.303'
|
||||
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.1.300'
|
||||
|
||||
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$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 "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.robolectric:robolectric:4.6.1'
|
||||
testImplementation 'org.robolectric:robolectric:4.7'
|
||||
testImplementation "androidx.test:runner:1.4.0"
|
||||
testImplementation "androidx.test.ext:junit:1.1.3"
|
||||
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.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
@ -38,13 +39,14 @@
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="false"
|
||||
android:theme="@style/WulkanowyTheme"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:ignore="GoogleAppIndexingWarning,UnusedAttribute">
|
||||
<activity
|
||||
android:name=".ui.modules.splash.SplashActivity"
|
||||
android:exported="true"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/WulkanowyTheme.SplashScreen"
|
||||
tools:ignore="LockedOrientationActivity">
|
||||
@ -74,6 +76,7 @@
|
||||
<activity
|
||||
android:name=".ui.modules.timetablewidget.TimetableWidgetConfigureActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:noHistory="true"
|
||||
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
|
||||
<intent-filter>
|
||||
@ -83,6 +86,7 @@
|
||||
<activity
|
||||
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetConfigureActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:noHistory="true"
|
||||
android:theme="@style/WulkanowyTheme.WidgetAccountSwitcher">
|
||||
<intent-filter>
|
||||
@ -93,6 +97,23 @@
|
||||
<service
|
||||
android:name=".services.widgets.TimetableWidgetService"
|
||||
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
|
||||
android:name=".ui.modules.timetablewidget.TimetableWidgetProvider"
|
||||
@ -107,6 +128,7 @@
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".ui.modules.luckynumberwidget.LuckyNumberWidgetProvider"
|
||||
android:exported="true"
|
||||
android:label="@string/lucky_number_title">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
@ -132,44 +154,44 @@
|
||||
android:resource="@xml/provider_paths" />
|
||||
</provider>
|
||||
|
||||
<meta-data
|
||||
android:name="install_channel"
|
||||
android:value="${install_channel}" />
|
||||
|
||||
<!-- workaround for https://github.com/firebase/firebase-android-sdk/issues/473 enabled:false -->
|
||||
<!-- https://firebase.googleblog.com/2017/03/take-control-of-your-firebase-init-on.html -->
|
||||
<provider
|
||||
android:name="com.google.firebase.provider.FirebaseInitProvider"
|
||||
android:authorities="${applicationId}.firebaseinitprovider"
|
||||
android:enabled="${firebase_enabled}"
|
||||
android:exported="false" />
|
||||
android:exported="false"
|
||||
tools:ignore="MissingClass" />
|
||||
|
||||
<meta-data
|
||||
android:name="install_channel"
|
||||
android:value="${install_channel}" />
|
||||
<meta-data
|
||||
android:name="firebase_analytics_collection_enabled"
|
||||
android:value="${firebase_enabled}" />
|
||||
|
||||
<meta-data
|
||||
android:name="google_analytics_adid_collection_enabled"
|
||||
android:value="${firebase_enabled}" />
|
||||
|
||||
<meta-data
|
||||
android:name="firebase_crashlytics_collection_enabled"
|
||||
android:value="${firebase_enabled}" />
|
||||
|
||||
<meta-data
|
||||
android:name="firebase_messaging_auto_init_enabled"
|
||||
android:value="${firebase_enabled}" />
|
||||
|
||||
<meta-data
|
||||
android:name="firebase_inapp_messaging_auto_data_collection_enabled"
|
||||
android:value="${firebase_enabled}" />
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.firebase.messaging.default_notification_icon"
|
||||
android:resource="@drawable/ic_stat_all" />
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.firebase.messaging.default_notification_channel_id"
|
||||
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>
|
||||
</manifest>
|
||||
|
@ -1,12 +1,10 @@
|
||||
package io.github.wulkanowy
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.util.Log.DEBUG
|
||||
import android.util.Log.INFO
|
||||
import android.util.Log.VERBOSE
|
||||
import android.webkit.WebView
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.hilt.work.HiltWorkerFactory
|
||||
import androidx.work.Configuration
|
||||
import com.yariksoffice.lingver.Lingver
|
||||
@ -41,10 +39,8 @@ class WulkanowyApp : Application(), Configuration.Provider {
|
||||
@Inject
|
||||
lateinit var analyticsHelper: AnalyticsHelper
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageWarning")
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
FragmentManager.enableNewStateManager(false)
|
||||
initializeAppLanguage()
|
||||
themeManager.applyDefaultTheme()
|
||||
initLogging()
|
||||
|
@ -2,62 +2,100 @@ package io.github.wulkanowy.data
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.AssetManager
|
||||
import android.content.res.Resources
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.chuckerteam.chucker.api.ChuckerCollector
|
||||
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
||||
import com.chuckerteam.chucker.api.RetentionManager
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.fredporciuncula.flow.preferences.FlowSharedPreferences
|
||||
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
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.SharedPrefProvider
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AppInfo
|
||||
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 java.util.concurrent.TimeUnit
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal class RepositoryModule {
|
||||
internal class DataModule {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideSdk(chuckerCollector: ChuckerCollector, @ApplicationContext context: Context): Sdk {
|
||||
return Sdk().apply {
|
||||
fun provideSdk(chuckerInterceptor: ChuckerInterceptor) =
|
||||
Sdk().apply {
|
||||
androidVersion = android.os.Build.VERSION.RELEASE
|
||||
buildTag = android.os.Build.MODEL
|
||||
setSimpleHttpLogger { Timber.d(it) }
|
||||
|
||||
// for debug only
|
||||
addInterceptor(
|
||||
ChuckerInterceptor.Builder(context)
|
||||
.collector(chuckerCollector)
|
||||
.alwaysReadResponseBody(true)
|
||||
.build(), network = true
|
||||
)
|
||||
addInterceptor(chuckerInterceptor, network = true)
|
||||
}
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideChuckerCollector(
|
||||
@ApplicationContext context: Context,
|
||||
prefRepository: PreferencesRepository
|
||||
): ChuckerCollector {
|
||||
return ChuckerCollector(
|
||||
context = context,
|
||||
showNotification = prefRepository.isDebugNotificationEnable,
|
||||
retentionPeriod = RetentionManager.Period.ONE_HOUR
|
||||
)
|
||||
}
|
||||
) = ChuckerCollector(
|
||||
context = context,
|
||||
showNotification = prefRepository.isDebugNotificationEnable,
|
||||
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
|
||||
@Provides
|
||||
@ -67,14 +105,6 @@ internal class RepositoryModule {
|
||||
appInfo: 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
|
||||
@Provides
|
||||
fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences =
|
||||
@ -88,7 +118,9 @@ internal class RepositoryModule {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideMoshi() = Moshi.Builder().build()
|
||||
fun provideJson() = Json {
|
||||
ignoreUnknownKeys = true
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@ -202,4 +234,12 @@ internal class RepositoryModule {
|
||||
@Singleton
|
||||
@Provides
|
||||
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.JournalMode.TRUNCATE
|
||||
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.AttendanceSummaryDao
|
||||
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
|
||||
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.GradeDao
|
||||
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.MobileDeviceDao
|
||||
import io.github.wulkanowy.data.db.dao.NoteDao
|
||||
import io.github.wulkanowy.data.db.dao.NotificationDao
|
||||
import io.github.wulkanowy.data.db.dao.RecipientDao
|
||||
import io.github.wulkanowy.data.db.dao.ReportingUnitDao
|
||||
import io.github.wulkanowy.data.db.dao.SchoolAnnouncementDao
|
||||
import io.github.wulkanowy.data.db.dao.SchoolDao
|
||||
import io.github.wulkanowy.data.db.dao.SemesterDao
|
||||
import io.github.wulkanowy.data.db.dao.StudentDao
|
||||
@ -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.TimetableDao
|
||||
import io.github.wulkanowy.data.db.dao.TimetableHeaderDao
|
||||
import io.github.wulkanowy.data.db.entities.AdminMessage
|
||||
import io.github.wulkanowy.data.db.entities.Attendance
|
||||
import io.github.wulkanowy.data.db.entities.AttendanceSummary
|
||||
import io.github.wulkanowy.data.db.entities.CompletedLesson
|
||||
import io.github.wulkanowy.data.db.entities.Conference
|
||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||
import io.github.wulkanowy.data.db.entities.Exam
|
||||
import io.github.wulkanowy.data.db.entities.Grade
|
||||
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.MobileDevice
|
||||
import io.github.wulkanowy.data.db.entities.Note
|
||||
import io.github.wulkanowy.data.db.entities.Notification
|
||||
import io.github.wulkanowy.data.db.entities.Recipient
|
||||
import io.github.wulkanowy.data.db.entities.ReportingUnit
|
||||
import io.github.wulkanowy.data.db.entities.School
|
||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentInfo
|
||||
@ -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.Migration39
|
||||
import io.github.wulkanowy.data.db.migrations.Migration4
|
||||
import io.github.wulkanowy.data.db.migrations.Migration40
|
||||
import io.github.wulkanowy.data.db.migrations.Migration41
|
||||
import io.github.wulkanowy.data.db.migrations.Migration42
|
||||
import io.github.wulkanowy.data.db.migrations.Migration43
|
||||
import io.github.wulkanowy.data.db.migrations.Migration44
|
||||
import io.github.wulkanowy.data.db.migrations.Migration5
|
||||
import io.github.wulkanowy.data.db.migrations.Migration6
|
||||
import io.github.wulkanowy.data.db.migrations.Migration7
|
||||
@ -134,6 +143,8 @@ import javax.inject.Singleton
|
||||
StudentInfo::class,
|
||||
TimetableHeader::class,
|
||||
SchoolAnnouncement::class,
|
||||
Notification::class,
|
||||
AdminMessage::class
|
||||
],
|
||||
version = AppDatabase.VERSION_SCHEMA,
|
||||
exportSchema = true
|
||||
@ -142,7 +153,7 @@ import javax.inject.Singleton
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
companion object {
|
||||
const val VERSION_SCHEMA = 39
|
||||
const val VERSION_SCHEMA = 44
|
||||
|
||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
|
||||
Migration2(),
|
||||
@ -183,6 +194,11 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
Migration37(),
|
||||
Migration38(),
|
||||
Migration39(),
|
||||
Migration40(),
|
||||
Migration41(sharedPrefProvider),
|
||||
Migration42(),
|
||||
Migration43(),
|
||||
Migration44()
|
||||
)
|
||||
|
||||
fun newInstance(
|
||||
@ -252,4 +268,8 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
abstract val timetableHeaderDao: TimetableHeaderDao
|
||||
|
||||
abstract val schoolAnnouncementDao: SchoolAnnouncementDao
|
||||
|
||||
abstract val notificationDao: NotificationDao
|
||||
|
||||
abstract val adminMessagesDao: AdminMessageDao
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
package io.github.wulkanowy.data.db
|
||||
|
||||
import androidx.room.TypeConverter
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import io.github.wulkanowy.data.db.adapters.PairAdapterFactory
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
@ -13,15 +14,7 @@ import java.util.Date
|
||||
|
||||
class Converters {
|
||||
|
||||
private val moshi by lazy { Moshi.Builder().add(PairAdapterFactory).build() }
|
||||
|
||||
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))
|
||||
}
|
||||
private val json = Json
|
||||
|
||||
@TypeConverter
|
||||
fun timestampToDate(value: Long?): LocalDate? = value?.run {
|
||||
@ -51,21 +44,25 @@ class Converters {
|
||||
|
||||
@TypeConverter
|
||||
fun intListToJson(list: List<Int>): String {
|
||||
return integerListAdapter.toJson(list)
|
||||
return json.encodeToString(list)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun jsonToIntList(value: String): List<Int> {
|
||||
return integerListAdapter.fromJson(value).orEmpty()
|
||||
return json.decodeFromString(value)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun stringPairListToJson(list: List<Pair<String, String>>): String {
|
||||
return stringListPairAdapter.toJson(list)
|
||||
return json.encodeToString(list)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
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, 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) {
|
||||
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
|
||||
interface AttendanceDao : BaseDao<Attendance> {
|
||||
|
||||
@Query("SELECT * FROM Attendance WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
|
||||
fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow<List<Attendance>>
|
||||
@Query("SELECT * FROM Attendance WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :start AND date <= :end")
|
||||
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>>
|
||||
}
|
@ -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)
|
||||
var id: Long = 0
|
||||
|
||||
@ColumnInfo(name = "is_notified")
|
||||
var isNotified: Boolean = true
|
||||
}
|
||||
|
@ -40,4 +40,7 @@ data class Homework(
|
||||
|
||||
@ColumnInfo(name = "is_notified")
|
||||
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.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.squareup.moshi.JsonClass
|
||||
import java.io.Serializable
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
@kotlinx.serialization.Serializable
|
||||
@Entity(tableName = "Recipients")
|
||||
data class Recipient(
|
||||
|
||||
|
@ -50,4 +50,7 @@ data class Timetable(
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
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
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
@Serializable
|
||||
class Contributor(
|
||||
val displayName: String,
|
||||
val githubUsername: String
|
||||
|
@ -1,9 +1,9 @@
|
||||
package io.github.wulkanowy.data.pojos
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
import io.github.wulkanowy.ui.modules.message.send.RecipientChipItem
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
@Serializable
|
||||
data class MessageDraft(
|
||||
val recipients: List<RecipientChipItem>,
|
||||
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
|
||||
|
||||
import android.content.res.AssetManager
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.data.pojos.Contributor
|
||||
import io.github.wulkanowy.utils.DispatchersProvider
|
||||
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.Singleton
|
||||
|
||||
@Singleton
|
||||
class AppCreatorRepository @Inject constructor(
|
||||
private val assets: AssetManager,
|
||||
private val dispatchers: DispatchersProvider
|
||||
@ApplicationContext private val context: Context,
|
||||
private val dispatchers: DispatchersProvider,
|
||||
private val json: Json,
|
||||
) {
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
suspend fun getAppCreators() = withContext(dispatchers.backgroundThread) {
|
||||
val moshi = Moshi.Builder().build()
|
||||
val type = Types.newParameterizedType(List::class.java, Contributor::class.java)
|
||||
val adapter = moshi.adapter<List<Contributor>>(type)
|
||||
adapter.fromJson(assets.open("contributors.json").bufferedReader().use { it.readText() })
|
||||
suspend fun getAppCreators() = withContext(dispatchers.io) {
|
||||
val inputStream = context.assets.open("contributors.json").buffered()
|
||||
json.decodeFromStream<List<Contributor>>(inputStream)
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
@ -32,10 +33,24 @@ class AttendanceRepository @Inject constructor(
|
||||
|
||||
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,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
||||
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, start.monday, end.sunday) },
|
||||
shouldFetch = {
|
||||
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 = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
.getAttendance(start.monday, end.sunday, semester.semesterId)
|
||||
@ -43,19 +58,39 @@ class AttendanceRepository @Inject constructor(
|
||||
},
|
||||
saveFetchResult = { old, 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))
|
||||
},
|
||||
filterResult = { it.filter { item -> item.date in start..end } }
|
||||
)
|
||||
|
||||
suspend fun excuseForAbsence(student: Student, semester: Semester, absenceList: List<Attendance>, reason: String? = null) {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear).excuseForAbsence(absenceList.map { attendance ->
|
||||
fun getAttendanceFromDatabase(
|
||||
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(
|
||||
date = LocalDateTime.of(attendance.date, LocalTime.of(0, 0)),
|
||||
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,
|
||||
semester: Semester,
|
||||
subjectId: Int,
|
||||
forceRefresh: Boolean
|
||||
forceRefresh: Boolean,
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
it.isEmpty() || forceRefresh
|
||||
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = { attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId) },
|
||||
fetch = {
|
||||
|
@ -28,10 +28,28 @@ class CompletedLessonsRepository @Inject constructor(
|
||||
|
||||
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,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester, start, end)) },
|
||||
query = { completedLessonsDb.loadAll(semester.studentId, semester.diaryId, start.monday, end.sunday) },
|
||||
shouldFetch = {
|
||||
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 = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
.getCompletedLessons(start.monday, end.sunday)
|
||||
|
@ -35,12 +35,12 @@ class ConferenceRepository @Inject constructor(
|
||||
semester: Semester,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false,
|
||||
startDate: LocalDateTime = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC)
|
||||
startDate: LocalDateTime = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC),
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
it.isEmpty() || forceRefresh
|
||||
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = {
|
||||
conferenceDb.loadAll(semester.diaryId, student.studentId, startDate)
|
||||
|
@ -36,14 +36,14 @@ class ExamRepository @Inject constructor(
|
||||
start: LocalDate,
|
||||
end: LocalDate,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false
|
||||
notify: Boolean = false,
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
val isShouldBeRefreshed = refreshHelper.isShouldBeRefreshed(
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
key = getRefreshKey(cacheKey, semester, start, end)
|
||||
)
|
||||
it.isEmpty() || forceRefresh || isShouldBeRefreshed
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = {
|
||||
examDb.loadAll(
|
||||
|
@ -37,13 +37,12 @@ class GradeRepository @Inject constructor(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false
|
||||
notify: Boolean = false,
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { (details, summaries) ->
|
||||
val isShouldBeRefreshed =
|
||||
refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
details.isEmpty() || summaries.isEmpty() || forceRefresh || isShouldBeRefreshed
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
details.isEmpty() || summaries.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = {
|
||||
val detailsFlow = gradeDb.loadAll(semester.semesterId, semester.studentId)
|
||||
@ -71,8 +70,8 @@ class GradeRepository @Inject constructor(
|
||||
newDetails: List<Grade>,
|
||||
notify: Boolean
|
||||
) {
|
||||
val notifyBreakDate =
|
||||
oldGrades.maxByOrNull { it.date }?.date ?: student.registrationDate.toLocalDate()
|
||||
val notifyBreakDate = oldGrades.maxByOrNull {it.date }
|
||||
?.date ?: student.registrationDate.toLocalDate()
|
||||
gradeDb.deleteAll(oldGrades uniqueSubtract newDetails)
|
||||
gradeDb.insertAll((newDetails uniqueSubtract oldGrades).onEach {
|
||||
if (it.date >= notifyBreakDate) it.apply {
|
||||
@ -89,8 +88,7 @@ class GradeRepository @Inject constructor(
|
||||
) {
|
||||
gradeSummaryDb.deleteAll(oldSummaries uniqueSubtract newSummary)
|
||||
gradeSummaryDb.insertAll((newSummary uniqueSubtract oldSummaries).onEach { summary ->
|
||||
val oldSummary =
|
||||
oldSummaries.find { oldSummary -> oldSummary.subject == summary.subject }
|
||||
val oldSummary = oldSummaries.find { old -> old.subject == summary.subject }
|
||||
summary.isPredictedGradeNotified = when {
|
||||
summary.predictedGrade.isEmpty() -> true
|
||||
notify && oldSummary?.predictedGrade != summary.predictedGrade -> false
|
||||
|
@ -39,9 +39,19 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
private val semesterCacheKey = "grade_stats_semester"
|
||||
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,
|
||||
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) },
|
||||
fetch = {
|
||||
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,
|
||||
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) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
@ -94,10 +114,12 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
val itemsWithAverage = items.map { item ->
|
||||
item.copy().apply {
|
||||
val denominator = item.amounts.sum()
|
||||
average = if (denominator == 0) "" else (item.amounts.mapIndexed { gradeValue, amount ->
|
||||
(gradeValue + 1) * amount
|
||||
}.sum().toDouble() / denominator).let {
|
||||
"%.2f".format(Locale.FRANCE, it)
|
||||
average = if (denominator == 0) "" else {
|
||||
(item.amounts.mapIndexed { gradeValue, amount ->
|
||||
(gradeValue + 1) * amount
|
||||
}.sum().toDouble() / denominator).let {
|
||||
"%.2f".format(Locale.FRANCE, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -109,7 +131,9 @@ class GradeStatisticsRepository @Inject constructor(
|
||||
amounts = itemsWithAverage.map { it.amounts }.sumGradeAmounts(),
|
||||
studentGrade = 0
|
||||
).apply {
|
||||
average = itemsWithAverage.mapNotNull { it.average.replace(",", ".").toDoubleOrNull() }.average().let {
|
||||
average = itemsWithAverage.mapNotNull {
|
||||
it.average.replace(",", ".").toDoubleOrNull()
|
||||
}.average().let {
|
||||
"%.2f".format(Locale.FRANCE, it)
|
||||
}
|
||||
}).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,
|
||||
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) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
|
@ -30,16 +30,19 @@ class HomeworkRepository @Inject constructor(
|
||||
private val cacheKey = "homework"
|
||||
|
||||
fun getHomework(
|
||||
student: Student, semester: Semester,
|
||||
start: LocalDate, end: LocalDate,
|
||||
forceRefresh: Boolean, notify: Boolean = false
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
start: LocalDate,
|
||||
end: LocalDate,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false,
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
val isShouldBeRefreshed = refreshHelper.isShouldBeRefreshed(
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
key = getRefreshKey(cacheKey, semester, start, end)
|
||||
)
|
||||
it.isEmpty() || forceRefresh || isShouldBeRefreshed
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = {
|
||||
homeworkDb.loadAll(
|
||||
@ -58,8 +61,9 @@ class HomeworkRepository @Inject constructor(
|
||||
val homeWorkToSave = (new uniqueSubtract old).onEach {
|
||||
if (notify) it.isNotified = false
|
||||
}
|
||||
val filteredOld = old.filterNot { it.isAddedByUser }
|
||||
|
||||
homeworkDb.deleteAll(old uniqueSubtract new)
|
||||
homeworkDb.deleteAll(filteredOld uniqueSubtract new)
|
||||
homeworkDb.insertAll(homeWorkToSave)
|
||||
|
||||
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)
|
||||
|
||||
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 getLogFiles() = withContext(dispatchers.backgroundThread) {
|
||||
File(context.filesDir.absolutePath).listFiles(File::isFile)?.filter {
|
||||
it.name.endsWith(".log")
|
||||
}!!
|
||||
suspend fun getLogFiles() = withContext(dispatchers.io) {
|
||||
File(context.filesDir.absolutePath).listFiles(File::isFile)
|
||||
?.filter { it.name.endsWith(".log") }!!
|
||||
}
|
||||
|
||||
private suspend fun getLastModified(): File {
|
||||
return withContext(dispatchers.backgroundThread) {
|
||||
var lastModifiedTime = Long.MIN_VALUE
|
||||
var chosenFile: File? = null
|
||||
File(context.filesDir.absolutePath).listFiles(File::isFile)?.forEach { file ->
|
||||
private suspend fun getLastModified() = withContext(dispatchers.io) {
|
||||
var lastModifiedTime = Long.MIN_VALUE
|
||||
var chosenFile: File? = null
|
||||
|
||||
File(context.filesDir.absolutePath).listFiles(File::isFile)
|
||||
?.forEach { file ->
|
||||
if (file.lastModified() > lastModifiedTime) {
|
||||
lastModifiedTime = file.lastModified()
|
||||
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()
|
||||
|
||||
fun getLuckyNumber(student: Student, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource(
|
||||
fun getLuckyNumber(
|
||||
student: Student,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false,
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it == null || forceRefresh },
|
||||
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 ->
|
||||
if (new != old) {
|
||||
old?.let { luckyNumberDb.deleteAll(listOfNotNull(it)) }
|
||||
@ -41,9 +47,11 @@ class LuckyNumberRepository @Inject constructor(
|
||||
fun getLuckyNumberHistory(student: Student, start: LocalDate, end: LocalDate) =
|
||||
luckyNumberDb.getAll(student.studentId, start, end)
|
||||
|
||||
suspend fun getNotNotifiedLuckyNumber(student: Student) = luckyNumberDb.load(student.studentId, now()).map {
|
||||
if (it?.isNotified == false) it else null
|
||||
}.first()
|
||||
suspend fun getNotNotifiedLuckyNumber(student: Student) =
|
||||
luckyNumberDb.load(student.studentId, now()).map {
|
||||
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
|
||||
|
||||
import android.content.Context
|
||||
import com.squareup.moshi.Moshi
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.Resource
|
||||
@ -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.mapToEntities
|
||||
import io.github.wulkanowy.data.pojos.MessageDraft
|
||||
import io.github.wulkanowy.data.pojos.MessageDraftJsonAdapter
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.sdk.pojo.Folder
|
||||
import io.github.wulkanowy.sdk.pojo.SentMessage
|
||||
@ -29,6 +27,9 @@ import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import timber.log.Timber
|
||||
import java.time.LocalDateTime.now
|
||||
import javax.inject.Inject
|
||||
@ -42,7 +43,7 @@ class MessageRepository @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
private val sharedPrefProvider: SharedPrefProvider,
|
||||
private val moshi: Moshi,
|
||||
private val json: Json,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
@ -51,14 +52,18 @@ class MessageRepository @Inject constructor(
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun getMessages(
|
||||
student: Student, semester: Semester,
|
||||
folder: MessageFolder, forceRefresh: Boolean, notify: Boolean = false
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
folder: MessageFolder,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false,
|
||||
): Flow<Resource<List<Message>>> = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
it.isEmpty() || forceRefresh || refreshHelper.isShouldBeRefreshed(
|
||||
getRefreshKey(cacheKey, student, folder)
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
key = getRefreshKey(cacheKey, student, folder)
|
||||
)
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = { messagesDb.loadAll(student.id.toInt(), folder.id) },
|
||||
fetch = {
|
||||
@ -77,7 +82,8 @@ class MessageRepository @Inject constructor(
|
||||
)
|
||||
|
||||
private fun getMessagesWithReadByChange(
|
||||
old: List<Message>, new: List<Message>,
|
||||
old: List<Message>,
|
||||
new: List<Message>,
|
||||
setNotified: Boolean
|
||||
): List<Message> {
|
||||
val oldMeta = old.map { Triple(it, it.readBy, it.unreadBy) }
|
||||
@ -96,7 +102,9 @@ class MessageRepository @Inject constructor(
|
||||
}
|
||||
|
||||
fun getMessage(
|
||||
student: Student, message: Message, markAsRead: Boolean = false
|
||||
student: Student,
|
||||
message: Message,
|
||||
markAsRead: Boolean = false,
|
||||
): Flow<Resource<MessageWithAttachment?>> = networkBoundResource(
|
||||
shouldFetch = {
|
||||
checkNotNull(it, { "This message no longer exist!" })
|
||||
@ -135,8 +143,10 @@ class MessageRepository @Inject constructor(
|
||||
}
|
||||
|
||||
suspend fun sendMessage(
|
||||
student: Student, subject: String, content: String,
|
||||
recipients: List<Recipient>
|
||||
student: Student,
|
||||
subject: String,
|
||||
content: String,
|
||||
recipients: List<Recipient>,
|
||||
): SentMessage = sdk.init(student).sendMessage(
|
||||
subject = subject,
|
||||
content = content,
|
||||
@ -159,9 +169,9 @@ class MessageRepository @Inject constructor(
|
||||
|
||||
var draftMessage: MessageDraft?
|
||||
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(
|
||||
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"
|
||||
|
||||
fun getDevices(student: Student, semester: Semester, forceRefresh: Boolean) = networkBoundResource(
|
||||
fun getDevices(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
forceRefresh: Boolean,
|
||||
) = networkBoundResource(
|
||||
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) },
|
||||
fetch = {
|
||||
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.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -28,9 +27,19 @@ class NoteRepository @Inject constructor(
|
||||
|
||||
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,
|
||||
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) },
|
||||
fetch = {
|
||||
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 com.fredporciuncula.flow.preferences.FlowSharedPreferences
|
||||
import com.fredporciuncula.flow.preferences.Preference
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.adapter
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.sdk.toLocalDate
|
||||
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeExpandMode
|
||||
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode
|
||||
import io.github.wulkanowy.utils.toTimestamp
|
||||
import io.github.wulkanowy.utils.toLocalDateTime
|
||||
import io.github.wulkanowy.utils.toTimestamp
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import javax.inject.Inject
|
||||
@ -28,16 +28,12 @@ import javax.inject.Singleton
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Singleton
|
||||
class PreferencesRepository @Inject constructor(
|
||||
@ApplicationContext val context: Context,
|
||||
private val sharedPref: SharedPreferences,
|
||||
private val flowSharedPref: FlowSharedPreferences,
|
||||
@ApplicationContext val context: Context,
|
||||
moshi: Moshi
|
||||
private val json: Json,
|
||||
) {
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
private val dashboardItemsPositionAdapter: JsonAdapter<Map<DashboardItem.Type, Int>> =
|
||||
moshi.adapter()
|
||||
|
||||
val startMenuIndex: Int
|
||||
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
|
||||
)
|
||||
|
||||
val isGradeExpandable: Boolean
|
||||
get() = !getBoolean(R.string.pref_key_expand_grade, R.bool.pref_default_expand_grade)
|
||||
val gradeExpandMode: GradeExpandMode
|
||||
get() = GradeExpandMode.getByValue(
|
||||
getString(
|
||||
R.string.pref_key_expand_grade_mode,
|
||||
R.string.pref_default_expand_grade_mode
|
||||
)
|
||||
)
|
||||
|
||||
val showAllSubjectsOnStatisticsList: Boolean
|
||||
get() = getBoolean(
|
||||
@ -102,12 +103,31 @@ class PreferencesRepository @Inject constructor(
|
||||
|
||||
val isUpcomingLessonsNotificationsEnableKey =
|
||||
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(
|
||||
isUpcomingLessonsNotificationsEnableKey,
|
||||
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 isDebugNotificationEnable: Boolean
|
||||
get() = getBoolean(isDebugNotificationEnableKey, R.bool.pref_default_notification_debug)
|
||||
@ -176,22 +196,20 @@ class PreferencesRepository @Inject constructor(
|
||||
)
|
||||
|
||||
var lasSyncDate: LocalDateTime
|
||||
get() = getLong(
|
||||
R.string.pref_key_last_sync_date,
|
||||
R.string.pref_default_last_sync_date
|
||||
).toLocalDateTime()
|
||||
get() = getLong(R.string.pref_key_last_sync_date, R.string.pref_default_last_sync_date)
|
||||
.toLocalDateTime()
|
||||
set(value) = sharedPref.edit().putLong("last_sync_date", value.toTimestamp()).apply()
|
||||
|
||||
var dashboardItemsPosition: Map<DashboardItem.Type, Int>?
|
||||
get() {
|
||||
val json = sharedPref.getString(PREF_KEY_DASHBOARD_ITEMS_POSITION, null) ?: return null
|
||||
val value = sharedPref.getString(PREF_KEY_DASHBOARD_ITEMS_POSITION, null) ?: return null
|
||||
|
||||
return dashboardItemsPositionAdapter.fromJson(json)
|
||||
return json.decodeFromString(value)
|
||||
}
|
||||
set(value) = sharedPref.edit {
|
||||
putString(
|
||||
PREF_KEY_DASHBOARD_ITEMS_POSITION,
|
||||
dashboardItemsPositionAdapter.toJson(value)
|
||||
json.encodeToString(value)
|
||||
)
|
||||
}
|
||||
|
||||
@ -200,6 +218,7 @@ class PreferencesRepository @Inject constructor(
|
||||
.map { set ->
|
||||
set.map { DashboardItem.Tile.valueOf(it) }
|
||||
.plus(DashboardItem.Tile.ACCOUNT)
|
||||
.plus(DashboardItem.Tile.ADMIN_MESSAGE)
|
||||
.toSet()
|
||||
}
|
||||
|
||||
@ -207,6 +226,7 @@ class PreferencesRepository @Inject constructor(
|
||||
get() = selectedDashboardTilesPreference.get()
|
||||
.map { DashboardItem.Tile.valueOf(it) }
|
||||
.plus(DashboardItem.Tile.ACCOUNT)
|
||||
.plus(DashboardItem.Tile.ADMIN_MESSAGE)
|
||||
.toSet()
|
||||
set(value) {
|
||||
val filteredValue = value.filterNot { it == DashboardItem.Tile.ACCOUNT }
|
||||
@ -225,13 +245,23 @@ class PreferencesRepository @Inject constructor(
|
||||
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
|
||||
get() = sharedPref.getInt(PREF_KEY_IN_APP_REVIEW_COUNT, 0)
|
||||
set(value) = sharedPref.edit().putInt(PREF_KEY_IN_APP_REVIEW_COUNT, value).apply()
|
||||
|
||||
var inAppReviewDate: LocalDate?
|
||||
get() = sharedPref.getLong(PREF_KEY_IN_APP_REVIEW_DATE, 0).takeIf { it != 0L }?.toLocalDate()
|
||||
set(value) = sharedPref.edit().putLong(PREF_KEY_IN_APP_REVIEW_DATE, value!!.toTimestamp()).apply()
|
||||
get() = sharedPref.getLong(PREF_KEY_IN_APP_REVIEW_DATE, 0).takeIf { it != 0L }
|
||||
?.toLocalDate()
|
||||
set(value) = sharedPref.edit().putLong(PREF_KEY_IN_APP_REVIEW_DATE, value!!.toTimestamp())
|
||||
.apply()
|
||||
|
||||
var isAppReviewDone: Boolean
|
||||
get() = sharedPref.getBoolean(PREF_KEY_IN_APP_REVIEW_DONE, false)
|
||||
@ -252,6 +282,9 @@ class PreferencesRepository @Inject constructor(
|
||||
private fun getBoolean(id: String, default: Int) =
|
||||
sharedPref.getBoolean(id, context.resources.getBoolean(default))
|
||||
|
||||
private fun getBoolean(id: Int, default: Boolean) =
|
||||
sharedPref.getBoolean(context.getString(id), default)
|
||||
|
||||
private companion object {
|
||||
|
||||
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_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.mappers.mapToEntities
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import javax.inject.Inject
|
||||
@ -15,26 +17,34 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class RecipientRepository @Inject constructor(
|
||||
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) {
|
||||
val new = sdk.init(student).getRecipients(unit.unitId, role).mapToEntities(unit.studentId)
|
||||
val old = recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
||||
|
||||
recipientDb.deleteAll(old uniqueSubtract new)
|
||||
recipientDb.insertAll(new uniqueSubtract old)
|
||||
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||
}
|
||||
|
||||
suspend fun getRecipients(student: Student, unit: ReportingUnit, role: Int): List<Recipient> {
|
||||
return recipientDb.loadAll(unit.studentId, unit.unitId, role).ifEmpty {
|
||||
refreshRecipients(student, unit, role)
|
||||
val cached = recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
||||
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||
return if (cached.isEmpty() || isExpired) {
|
||||
refreshRecipients(student, unit, role)
|
||||
recipientDb.loadAll(unit.studentId, unit.unitId, role)
|
||||
}
|
||||
} else cached
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
suspend fun sendRecoverRequest(url: String, symbol: String, email: String, reCaptchaResponse: String): String {
|
||||
return sdk.sendPasswordResetRequest(url, symbol, email, reCaptchaResponse)
|
||||
}
|
||||
suspend fun sendRecoverRequest(
|
||||
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.entities.SchoolAnnouncement
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
@ -12,7 +11,6 @@ import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -30,17 +28,15 @@ class SchoolAnnouncementRepository @Inject constructor(
|
||||
|
||||
fun getSchoolAnnouncements(
|
||||
student: Student,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false
|
||||
forceRefresh: Boolean, notify: Boolean = false
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
it.isEmpty() || forceRefresh
|
||||
|| refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, student))
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = {
|
||||
schoolAnnouncementDb.loadAll(
|
||||
student.studentId)
|
||||
schoolAnnouncementDb.loadAll(student.studentId)
|
||||
},
|
||||
fetch = {
|
||||
sdk.init(student)
|
||||
@ -57,9 +53,11 @@ class SchoolAnnouncementRepository @Inject constructor(
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, student))
|
||||
}
|
||||
)
|
||||
|
||||
fun getSchoolAnnouncementFromDatabase(student: Student): Flow<List<SchoolAnnouncement>> {
|
||||
return schoolAnnouncementDb.loadAll(student.studentId)
|
||||
}
|
||||
|
||||
suspend fun updateSchoolAnnouncement(schoolAnnouncement: List<SchoolAnnouncement>) = schoolAnnouncementDb.updateAll(schoolAnnouncement)
|
||||
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.mappers.mapToEntity
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
@ -14,29 +16,41 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class SchoolRepository @Inject constructor(
|
||||
private val schoolDb: SchoolDao,
|
||||
private val sdk: Sdk
|
||||
private val sdk: Sdk,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
fun getSchoolInfo(student: Student, semester: Semester, forceRefresh: Boolean) =
|
||||
networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it == null || forceRefresh },
|
||||
query = { schoolDb.load(semester.studentId, semester.classId) },
|
||||
fetch = {
|
||||
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))
|
||||
private val cacheKey = "school_info"
|
||||
|
||||
fun getSchoolInfo(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
forceRefresh: Boolean,
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(
|
||||
key = getRefreshKey(cacheKey, student)
|
||||
)
|
||||
it == null || forceRefresh || isExpired
|
||||
},
|
||||
query = { schoolDb.load(semester.studentId, semester.classId) },
|
||||
fetch = {
|
||||
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,
|
||||
forceRefresh: Boolean = false,
|
||||
refreshOnNoCurrent: Boolean = false
|
||||
) = withContext(dispatchers.backgroundThread) {
|
||||
) = withContext(dispatchers.io) {
|
||||
val semesters = semesterDb.loadAll(student.studentId, student.classId)
|
||||
|
||||
if (isShouldFetch(student, semesters, forceRefresh, refreshOnNoCurrent)) {
|
||||
@ -64,7 +64,7 @@ class SemesterRepository @Inject constructor(
|
||||
}
|
||||
|
||||
suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false) =
|
||||
withContext(dispatchers.backgroundThread) {
|
||||
withContext(dispatchers.io) {
|
||||
getSemesters(student, forceRefresh).getCurrentOrLast()
|
||||
}
|
||||
}
|
||||
|
@ -19,24 +19,27 @@ class StudentInfoRepository @Inject constructor(
|
||||
|
||||
private val saveFetchResultMutex = Mutex()
|
||||
|
||||
fun getStudentInfo(student: Student, semester: Semester, forceRefresh: Boolean) =
|
||||
networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it == null || forceRefresh },
|
||||
query = { studentInfoDao.loadStudentInfo(student.studentId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
.getStudentInfo().mapToEntity(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
if (old != null && new != old) {
|
||||
with(studentInfoDao) {
|
||||
deleteAll(listOf(old))
|
||||
insertAll(listOf(new))
|
||||
}
|
||||
} else if (old == null) {
|
||||
studentInfoDao.insertAll(listOf(new))
|
||||
fun getStudentInfo(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
forceRefresh: Boolean,
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { it == null || forceRefresh },
|
||||
query = { studentInfoDao.loadStudentInfo(student.studentId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
.getStudentInfo().mapToEntity(semester)
|
||||
},
|
||||
saveFetchResult = { old, new ->
|
||||
if (old != null && new != old) {
|
||||
with(studentInfoDao) {
|
||||
deleteAll(listOf(old))
|
||||
insertAll(listOf(new))
|
||||
}
|
||||
} else if (old == null) {
|
||||
studentInfoDao.insertAll(listOf(new))
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ class StudentRepository @Inject constructor(
|
||||
.map {
|
||||
it.apply {
|
||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
||||
student.password = withContext(dispatchers.backgroundThread) {
|
||||
student.password = withContext(dispatchers.io) {
|
||||
decrypt(student.password)
|
||||
}
|
||||
}
|
||||
@ -77,7 +77,7 @@ class StudentRepository @Inject constructor(
|
||||
val student = studentDb.loadById(id) ?: throw NoCurrentStudentException()
|
||||
|
||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
||||
student.password = withContext(dispatchers.backgroundThread) {
|
||||
student.password = withContext(dispatchers.io) {
|
||||
decrypt(student.password)
|
||||
}
|
||||
}
|
||||
@ -88,7 +88,7 @@ class StudentRepository @Inject constructor(
|
||||
val student = studentDb.loadCurrent() ?: throw NoCurrentStudentException()
|
||||
|
||||
if (decryptPass && Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
||||
student.password = withContext(dispatchers.backgroundThread) {
|
||||
student.password = withContext(dispatchers.io) {
|
||||
decrypt(student.password)
|
||||
}
|
||||
}
|
||||
@ -101,7 +101,7 @@ class StudentRepository @Inject constructor(
|
||||
.map {
|
||||
it.apply {
|
||||
if (Sdk.Mode.valueOf(it.loginMode) != Sdk.Mode.API) {
|
||||
password = withContext(dispatchers.backgroundThread) {
|
||||
password = withContext(dispatchers.io) {
|
||||
encrypt(password, context)
|
||||
}
|
||||
}
|
||||
|
@ -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.mappers.mapToEntities
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
@ -15,14 +17,24 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class SubjectRepository @Inject constructor(
|
||||
private val subjectDao: SubjectDao,
|
||||
private val sdk: Sdk
|
||||
private val sdk: Sdk,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
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,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh },
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = { subjectDao.loadAll(semester.diaryId, semester.studentId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
@ -31,6 +43,8 @@ class SubjectRepository @Inject constructor(
|
||||
saveFetchResult = { old, new ->
|
||||
subjectDao.deleteAll(old uniqueSubtract new)
|
||||
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.mappers.mapToEntities
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.AutoRefreshHelper
|
||||
import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
@ -15,14 +17,24 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class TeacherRepository @Inject constructor(
|
||||
private val teacherDb: TeacherDao,
|
||||
private val sdk: Sdk
|
||||
private val sdk: Sdk,
|
||||
private val refreshHelper: AutoRefreshHelper,
|
||||
) {
|
||||
|
||||
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,
|
||||
shouldFetch = { it.isEmpty() || forceRefresh },
|
||||
shouldFetch = {
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
it.isEmpty() || forceRefresh || isExpired
|
||||
},
|
||||
query = { teacherDb.loadAll(semester.studentId, semester.classId) },
|
||||
fetch = {
|
||||
sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
@ -32,6 +44,8 @@ class TeacherRepository @Inject constructor(
|
||||
saveFetchResult = { old, new ->
|
||||
teacherDb.deleteAll(old uniqueSubtract new)
|
||||
teacherDb.insertAll(new uniqueSubtract old)
|
||||
|
||||
refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -41,18 +41,23 @@ class TimetableRepository @Inject constructor(
|
||||
private val cacheKey = "timetable"
|
||||
|
||||
fun getTimetable(
|
||||
student: Student, semester: Semester, start: LocalDate, end: LocalDate,
|
||||
forceRefresh: Boolean, refreshAdditional: Boolean = false
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
start: LocalDate,
|
||||
end: LocalDate,
|
||||
forceRefresh: Boolean,
|
||||
refreshAdditional: Boolean = false,
|
||||
notify: Boolean = false
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { (timetable, additional, headers) ->
|
||||
val refreshKey = getRefreshKey(cacheKey, semester, start, end)
|
||||
val isShouldRefresh = refreshHelper.isShouldBeRefreshed(refreshKey)
|
||||
val isExpired = refreshHelper.shouldBeRefreshed(refreshKey)
|
||||
val isRefreshAdditional = additional.isEmpty() && refreshAdditional
|
||||
|
||||
val isNoData = timetable.isEmpty() || isRefreshAdditional || headers.isEmpty()
|
||||
|
||||
isNoData || forceRefresh || isShouldRefresh
|
||||
isNoData || forceRefresh || isExpired
|
||||
},
|
||||
query = { getFullTimetableFromDatabase(student, semester, start, end) },
|
||||
fetch = {
|
||||
@ -63,7 +68,7 @@ class TimetableRepository @Inject constructor(
|
||||
timetableFull.mapToEntities(semester)
|
||||
},
|
||||
saveFetchResult = { timetableOld, timetableNew ->
|
||||
refreshTimetable(student, timetableOld.lessons, timetableNew.lessons)
|
||||
refreshTimetable(student, timetableOld.lessons, timetableNew.lessons, notify)
|
||||
refreshAdditional(timetableOld.additional, timetableNew.additional)
|
||||
refreshDayHeaders(timetableOld.headers, timetableNew.headers)
|
||||
|
||||
@ -79,8 +84,10 @@ class TimetableRepository @Inject constructor(
|
||||
)
|
||||
|
||||
private fun getFullTimetableFromDatabase(
|
||||
student: Student, semester: Semester,
|
||||
start: LocalDate, end: LocalDate
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
start: LocalDate,
|
||||
end: LocalDate,
|
||||
): Flow<TimetableFull> {
|
||||
val timetableFlow = timetableDb.loadAll(
|
||||
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(
|
||||
student: Student,
|
||||
lessonsOld: List<Timetable>, lessonsNew: List<Timetable>
|
||||
lessonsOld: List<Timetable>,
|
||||
lessonsNew: List<Timetable>,
|
||||
notify: Boolean
|
||||
) {
|
||||
val lessonsToRemove = lessonsOld uniqueSubtract lessonsNew
|
||||
val lessonsToAdd = (lessonsNew uniqueSubtract lessonsOld).map { new ->
|
||||
val matchingOld = lessonsOld.singleOrNull { new.start == it.start }
|
||||
if (matchingOld != null) {
|
||||
val useOldTeacher = new.teacher.isEmpty() && !new.changes && !matchingOld.changes
|
||||
new.copy(
|
||||
room = if (new.room.isEmpty()) matchingOld.room else new.room,
|
||||
teacher = if (useOldTeacher) matchingOld.teacher
|
||||
else new.teacher
|
||||
)
|
||||
} else new
|
||||
new.apply { if (notify) isNotified = false }
|
||||
}
|
||||
|
||||
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.DebugChannel
|
||||
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.NewExamChannel
|
||||
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.NewSchoolAnnouncementsChannel
|
||||
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.works.AttendanceSummaryWork
|
||||
import io.github.wulkanowy.services.sync.works.AttendanceWork
|
||||
@ -167,4 +169,12 @@ abstract class ServicesModule {
|
||||
@Binds
|
||||
@IntoSet
|
||||
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
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
@ -11,11 +10,13 @@ import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.Status
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.services.HiltBroadcastReceiver
|
||||
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.main.MainView
|
||||
import io.github.wulkanowy.ui.modules.Destination
|
||||
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.getCompatColor
|
||||
import io.github.wulkanowy.utils.toLocalDateTime
|
||||
@ -32,12 +33,15 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
|
||||
@Inject
|
||||
lateinit var studentRepository: StudentRepository
|
||||
|
||||
@Inject
|
||||
lateinit var preferencesRepository: PreferencesRepository
|
||||
|
||||
companion object {
|
||||
const val NOTIFICATION_TYPE_CURRENT = 1
|
||||
const val NOTIFICATION_TYPE_UPCOMING = 2
|
||||
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_ID = "student_id"
|
||||
@ -67,10 +71,10 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() {
|
||||
|
||||
private fun prepareNotification(context: Context, intent: Intent) {
|
||||
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) {
|
||||
return NotificationManagerCompat.from(context).cancel(notificationId)
|
||||
return NotificationManagerCompat.from(context).cancel(NOTIFICATION_ID)
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
showNotification(context, notificationId, studentName,
|
||||
showNotification(
|
||||
context, isPersistent, studentName,
|
||||
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("()")),
|
||||
nextSubject?.let { context.getString(R.string.timetable_later, "($nextRoom) $nextSubject".removePrefix("()")) }
|
||||
context.getString(
|
||||
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?) {
|
||||
NotificationManagerCompat.from(context).notify(notificationId, NotificationCompat.Builder(context, CHANNEL_ID)
|
||||
.setContentTitle(title)
|
||||
.setContentText(next)
|
||||
.setAutoCancel(false)
|
||||
.setOngoing(true)
|
||||
.setWhen(countDown)
|
||||
.apply {
|
||||
if (Build.VERSION.SDK_INT >= N) setUsesChronometer(true)
|
||||
}
|
||||
.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, MainView.Section.TIMETABLE.id,
|
||||
MainActivity.getStartIntent(context, MainView.Section.TIMETABLE, true), FLAG_UPDATE_CURRENT))
|
||||
.build()
|
||||
)
|
||||
private fun showNotification(
|
||||
context: Context,
|
||||
isPersistent: Boolean,
|
||||
studentName: String?,
|
||||
countDown: Long,
|
||||
timeout: Long,
|
||||
title: String,
|
||||
next: String?
|
||||
) {
|
||||
NotificationManagerCompat.from(context)
|
||||
.notify(NOTIFICATION_ID, NotificationCompat.Builder(context, CHANNEL_ID)
|
||||
.setContentTitle(title)
|
||||
.setContentText(next)
|
||||
.setAutoCancel(false)
|
||||
.setWhen(countDown)
|
||||
.setOngoing(isPersistent)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.apply {
|
||||
if (Build.VERSION.SDK_INT >= N) setUsesChronometer(true)
|
||||
}
|
||||
.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.RTC_WAKEUP
|
||||
import android.app.PendingIntent
|
||||
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.core.app.AlarmManagerCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
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.STUDENT_ID
|
||||
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.PendingIntentCompat
|
||||
import io.github.wulkanowy.utils.nickOrName
|
||||
import io.github.wulkanowy.utils.toTimestamp
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.time.LocalDateTime.now
|
||||
import javax.inject.Inject
|
||||
@ -53,14 +54,17 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||
|
||||
suspend fun cancelScheduled(lessons: List<Timetable>, student: Student) {
|
||||
val studentId = student.studentId
|
||||
withContext(dispatchersProvider.backgroundThread) {
|
||||
withContext(dispatchersProvider.io) {
|
||||
lessons.sortedBy { it.start }.forEachIndexed { index, lesson ->
|
||||
val upcomingTime = getUpcomingLessonTime(index, lessons, lesson)
|
||||
cancelScheduledTo(
|
||||
upcomingTime..lesson.start,
|
||||
getRequestCode(upcomingTime, studentId)
|
||||
range = upcomingTime..lesson.start,
|
||||
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")
|
||||
}
|
||||
@ -69,20 +73,37 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||
|
||||
private fun cancelScheduledTo(range: ClosedRange<LocalDateTime>, requestCode: Int) {
|
||||
if (now() in range) cancelNotification()
|
||||
|
||||
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() =
|
||||
NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id)
|
||||
NotificationManagerCompat.from(context).cancel(NOTIFICATION_ID)
|
||||
|
||||
suspend fun scheduleNotifications(lessons: List<Timetable>, student: Student) {
|
||||
if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) {
|
||||
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 }
|
||||
.map { it.value.sortedBy { lesson -> lesson.start } }
|
||||
.map { it.filter { lesson -> lesson.isStudentPlan } }
|
||||
@ -96,26 +117,26 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||
|
||||
if (lesson.start > now()) {
|
||||
scheduleBroadcast(
|
||||
intent,
|
||||
student.studentId,
|
||||
NOTIFICATION_TYPE_UPCOMING,
|
||||
getUpcomingLessonTime(index, active, lesson)
|
||||
intent = intent,
|
||||
studentId = student.studentId,
|
||||
notificationType = NOTIFICATION_TYPE_UPCOMING,
|
||||
time = getUpcomingLessonTime(index, active, lesson)
|
||||
)
|
||||
}
|
||||
|
||||
if (lesson.end > now()) {
|
||||
scheduleBroadcast(
|
||||
intent,
|
||||
student.studentId,
|
||||
NOTIFICATION_TYPE_CURRENT,
|
||||
lesson.start
|
||||
intent = intent,
|
||||
studentId = student.studentId,
|
||||
notificationType = NOTIFICATION_TYPE_CURRENT,
|
||||
time = lesson.start
|
||||
)
|
||||
if (active.lastIndex == index) {
|
||||
scheduleBroadcast(
|
||||
intent,
|
||||
student.studentId,
|
||||
NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION,
|
||||
lesson.end
|
||||
intent = intent,
|
||||
studentId = student.studentId,
|
||||
notificationType = NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION,
|
||||
time = lesson.end
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -143,17 +164,30 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
|
||||
notificationType: Int,
|
||||
time: LocalDateTime
|
||||
) {
|
||||
AlarmManagerCompat.setExactAndAllowWhileIdle(
|
||||
alarmManager, RTC_WAKEUP, time.toTimestamp(),
|
||||
PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also {
|
||||
it.putExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id)
|
||||
it.putExtra(LESSON_TYPE, notificationType)
|
||||
}, FLAG_UPDATE_CURRENT)
|
||||
)
|
||||
Timber.d(
|
||||
"TimetableNotification scheduled: type: $notificationType, subject: ${
|
||||
intent.getStringExtra(LESSON_TITLE)
|
||||
}, start: $time, student: $studentId"
|
||||
)
|
||||
try {
|
||||
AlarmManagerCompat.setExactAndAllowWhileIdle(
|
||||
alarmManager, RTC_WAKEUP, time.toTimestamp(),
|
||||
PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also {
|
||||
it.putExtra(LESSON_TYPE, notificationType)
|
||||
}, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE)
|
||||
)
|
||||
Timber.d(
|
||||
"TimetableNotification scheduled: type: $notificationType, subject: ${
|
||||
intent.getStringExtra(LESSON_TITLE)
|
||||
}, 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) {
|
||||
if (preferencesRepository.isServiceEnabled && !now().isHolidays) {
|
||||
workManager.enqueueUniquePeriodicWork(SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP,
|
||||
PeriodicWorkRequestBuilder<SyncWorker>(preferencesRepository.servicesInterval, MINUTES)
|
||||
val serviceInterval = preferencesRepository.servicesInterval
|
||||
|
||||
workManager.enqueueUniquePeriodicWork(
|
||||
SyncWorker::class.java.simpleName, if (restart) REPLACE else KEEP,
|
||||
PeriodicWorkRequestBuilder<SyncWorker>(serviceInterval, MINUTES)
|
||||
.setInitialDelay(10, MINUTES)
|
||||
.setBackoffCriteria(EXPONENTIAL, 30, MINUTES)
|
||||
.setConstraints(Constraints.Builder()
|
||||
.setRequiredNetworkType(if (preferencesRepository.isServicesOnlyWifi) UNMETERED else CONNECTED)
|
||||
.build())
|
||||
.build())
|
||||
.setConstraints(
|
||||
Constraints.Builder()
|
||||
.setRequiredNetworkType(if (preferencesRepository.isServicesOnlyWifi) UNMETERED else CONNECTED)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun startOneTimeSyncWorker(): Flow<WorkInfo> {
|
||||
fun startOneTimeSyncWorker(): Flow<WorkInfo?> {
|
||||
val work = OneTimeWorkRequestBuilder<SyncWorker>()
|
||||
.setInputData(
|
||||
Data.Builder()
|
||||
@ -77,7 +83,11 @@ class SyncManager @Inject constructor(
|
||||
)
|
||||
.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()
|
||||
}
|
||||
|
@ -19,11 +19,11 @@ import io.github.wulkanowy.sdk.exception.FeatureNotAvailableException
|
||||
import io.github.wulkanowy.sdk.scrapper.exception.FeatureDisabledException
|
||||
import io.github.wulkanowy.services.sync.channels.DebugChannel
|
||||
import io.github.wulkanowy.services.sync.works.Work
|
||||
import io.github.wulkanowy.utils.DispatchersProvider
|
||||
import io.github.wulkanowy.utils.getCompatColor
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import kotlin.random.Random
|
||||
|
||||
@HiltWorker
|
||||
@ -34,13 +34,14 @@ class SyncWorker @AssistedInject constructor(
|
||||
private val semesterRepository: SemesterRepository,
|
||||
private val works: Set<@JvmSuppressWildcards Work>,
|
||||
private val preferencesRepository: PreferencesRepository,
|
||||
private val notificationManager: NotificationManagerCompat
|
||||
private val notificationManager: NotificationManagerCompat,
|
||||
private val dispatchersProvider: DispatchersProvider
|
||||
) : CoroutineWorker(appContext, workerParameters) {
|
||||
|
||||
override suspend fun doWork() = coroutineScope {
|
||||
override suspend fun doWork() = withContext(dispatchersProvider.io) {
|
||||
Timber.i("SyncWorker is starting")
|
||||
|
||||
if (!studentRepository.isCurrentStudentSet()) return@coroutineScope Result.failure()
|
||||
if (!studentRepository.isCurrentStudentSet()) return@withContext Result.failure()
|
||||
|
||||
val student = studentRepository.getCurrentStudent()
|
||||
val semester = semesterRepository.getCurrentSemester(student, true)
|
||||
@ -50,12 +51,12 @@ class SyncWorker @AssistedInject constructor(
|
||||
Timber.i("${work::class.java.simpleName} is starting")
|
||||
work.doWork(student, semester)
|
||||
Timber.i("${work::class.java.simpleName} result: Success")
|
||||
preferencesRepository.lasSyncDate = LocalDateTime.now(ZoneId.systemDefault())
|
||||
null
|
||||
} catch (e: Throwable) {
|
||||
Timber.w("${work::class.java.simpleName} result: An exception ${e.message} occurred")
|
||||
if (e is FeatureDisabledException || e is FeatureNotAvailableException) null
|
||||
else {
|
||||
if (e is FeatureDisabledException || e is FeatureNotAvailableException) {
|
||||
null
|
||||
} else {
|
||||
Timber.e(e)
|
||||
e
|
||||
}
|
||||
@ -70,13 +71,16 @@ class SyncWorker @AssistedInject constructor(
|
||||
)
|
||||
}
|
||||
exceptions.isNotEmpty() -> Result.retry()
|
||||
else -> Result.success()
|
||||
else -> {
|
||||
preferencesRepository.lasSyncDate = LocalDateTime.now()
|
||||
Result.success()
|
||||
}
|
||||
}
|
||||
|
||||
if (preferencesRepository.isDebugNotificationEnable) notify(result)
|
||||
Timber.i("SyncWorker result: $result")
|
||||
|
||||
result
|
||||
return@withContext 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
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Conference
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.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.LocalDateTime
|
||||
import javax.inject.Inject
|
||||
|
||||
class NewConferenceNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager,
|
||||
@ApplicationContext private val context: Context
|
||||
) {
|
||||
|
||||
fun notify(items: List<Conference>, student: Student) {
|
||||
suspend fun notify(items: List<Conference>, student: Student) {
|
||||
val today = LocalDateTime.now()
|
||||
val lines = items.filter { !it.date.isBefore(today) }.map {
|
||||
"${it.date.toFormattedString("dd.MM")} - ${it.title}: ${it.subject}"
|
||||
}.ifEmpty { return }
|
||||
val lines = items.filter { !it.date.isBefore(today) }
|
||||
.map {
|
||||
"${it.date.toFormattedString("dd.MM")} - ${it.title}: ${it.subject}"
|
||||
}
|
||||
.ifEmpty { return }
|
||||
|
||||
val notification = MultipleNotifications(
|
||||
type = NotificationType.NEW_CONFERENCE,
|
||||
icon = R.drawable.ic_more_conferences,
|
||||
titleStringRes = R.plurals.conference_notify_new_item_title,
|
||||
contentStringRes = R.plurals.conference_notify_new_items,
|
||||
summaryStringRes = R.plurals.conference_number_item,
|
||||
startMenu = MainView.Section.CONFERENCE,
|
||||
lines = lines
|
||||
val notificationDataList = lines.map {
|
||||
NotificationData(
|
||||
title = context.getPlural(R.plurals.conference_notify_new_item_title, 1),
|
||||
content = it,
|
||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Conference)
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Exam
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.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 javax.inject.Inject
|
||||
|
||||
class NewExamNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager,
|
||||
@ApplicationContext private val context: Context
|
||||
) {
|
||||
|
||||
fun notify(items: List<Exam>, student: Student) {
|
||||
suspend fun notify(items: List<Exam>, student: Student) {
|
||||
val today = LocalDate.now()
|
||||
val lines = items.filter { !it.date.isBefore(today) }.map {
|
||||
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.description}"
|
||||
}.ifEmpty { return }
|
||||
val lines = items.filter { !it.date.isBefore(today) }
|
||||
.map {
|
||||
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.description}"
|
||||
}
|
||||
.ifEmpty { return }
|
||||
|
||||
val notification = MultipleNotifications(
|
||||
type = NotificationType.NEW_EXAM,
|
||||
icon = R.drawable.ic_main_exam,
|
||||
titleStringRes = R.plurals.exam_notify_new_item_title,
|
||||
contentStringRes = R.plurals.exam_notify_new_item_content,
|
||||
summaryStringRes = R.plurals.exam_number_item,
|
||||
startMenu = MainView.Section.EXAM,
|
||||
lines = lines
|
||||
val notificationDataList = lines.map {
|
||||
NotificationData(
|
||||
title = context.getPlural(R.plurals.exam_notify_new_item_title, 1),
|
||||
content = it,
|
||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Exam),
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Grade
|
||||
import io.github.wulkanowy.data.db.entities.GradeSummary
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import 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 javax.inject.Inject
|
||||
|
||||
class NewGradeNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager,
|
||||
@ApplicationContext private val context: Context
|
||||
) {
|
||||
|
||||
fun notifyDetails(items: List<Grade>, student: Student) {
|
||||
val notification = MultipleNotifications(
|
||||
type = NotificationType.NEW_GRADE_DETAILS,
|
||||
icon = R.drawable.ic_stat_grade,
|
||||
titleStringRes = R.plurals.grade_new_items,
|
||||
contentStringRes = R.plurals.grade_notify_new_items,
|
||||
summaryStringRes = R.plurals.grade_number_item,
|
||||
startMenu = MainView.Section.GRADE,
|
||||
lines = items.map {
|
||||
"${it.subject}: ${it.entry}"
|
||||
}
|
||||
suspend fun notifyDetails(items: List<Grade>, student: Student) {
|
||||
val notificationDataList = items.map {
|
||||
NotificationData(
|
||||
title = context.getPlural(R.plurals.grade_new_items, 1),
|
||||
content = "${it.subject}: ${it.entry}",
|
||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
|
||||
)
|
||||
}
|
||||
|
||||
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) {
|
||||
val notification = MultipleNotifications(
|
||||
type = NotificationType.NEW_GRADE_PREDICTED,
|
||||
icon = R.drawable.ic_stat_grade,
|
||||
titleStringRes = R.plurals.grade_new_items_predicted,
|
||||
contentStringRes = R.plurals.grade_notify_new_items_predicted,
|
||||
summaryStringRes = R.plurals.grade_number_item,
|
||||
startMenu = MainView.Section.GRADE,
|
||||
lines = items.map {
|
||||
"${it.subject}: ${it.predictedGrade}"
|
||||
}
|
||||
suspend fun notifyPredicted(items: List<GradeSummary>, student: Student) {
|
||||
val notificationDataList = items.map {
|
||||
NotificationData(
|
||||
title = context.getPlural(R.plurals.grade_new_items_predicted, 1),
|
||||
content = "${it.subject}: ${it.predictedGrade}",
|
||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
|
||||
)
|
||||
}
|
||||
|
||||
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) {
|
||||
val notification = MultipleNotifications(
|
||||
type = NotificationType.NEW_GRADE_FINAL,
|
||||
icon = R.drawable.ic_stat_grade,
|
||||
titleStringRes = R.plurals.grade_new_items_final,
|
||||
contentStringRes = R.plurals.grade_notify_new_items_final,
|
||||
summaryStringRes = R.plurals.grade_number_item,
|
||||
startMenu = MainView.Section.GRADE,
|
||||
lines = items.map {
|
||||
"${it.subject}: ${it.finalGrade}"
|
||||
}
|
||||
suspend fun notifyFinal(items: List<GradeSummary>, student: Student) {
|
||||
val notificationDataList = items.map {
|
||||
NotificationData(
|
||||
title = context.getPlural(R.plurals.grade_new_items_final, 1),
|
||||
content = "${it.subject}: ${it.finalGrade}",
|
||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Grade),
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Homework
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.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 javax.inject.Inject
|
||||
|
||||
class NewHomeworkNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager,
|
||||
@ApplicationContext private val context: Context
|
||||
) {
|
||||
|
||||
fun notify(items: List<Homework>, student: Student) {
|
||||
suspend fun notify(items: List<Homework>, student: Student) {
|
||||
val today = LocalDate.now()
|
||||
val lines = items.filter { !it.date.isBefore(today) }.map {
|
||||
"${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.content}"
|
||||
}.ifEmpty { return }
|
||||
val lines = items.filter { !it.date.isBefore(today) }
|
||||
.map {
|
||||
"${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,
|
||||
icon = R.drawable.ic_more_homework,
|
||||
titleStringRes = R.plurals.homework_notify_new_item_title,
|
||||
contentStringRes = R.plurals.homework_notify_new_item_content,
|
||||
summaryStringRes = R.plurals.homework_number_item,
|
||||
startMenu = MainView.Section.HOMEWORK,
|
||||
lines = lines
|
||||
notificationDataList = notificationDataList
|
||||
)
|
||||
|
||||
sendNotification(notification, student)
|
||||
appNotificationManager.sendMultipleNotifications(groupNotificationData, student)
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,34 @@
|
||||
package io.github.wulkanowy.services.sync.notifications
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.OneNotification
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.data.pojos.NotificationData
|
||||
import io.github.wulkanowy.ui.modules.Destination
|
||||
import io.github.wulkanowy.ui.modules.splash.SplashActivity
|
||||
import javax.inject.Inject
|
||||
|
||||
class NewLuckyNumberNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager,
|
||||
@ApplicationContext private val context: Context
|
||||
) {
|
||||
|
||||
fun notify(item: LuckyNumber, student: Student) {
|
||||
val notification = OneNotification(
|
||||
type = NotificationType.NEW_LUCKY_NUMBER,
|
||||
icon = R.drawable.ic_stat_luckynumber,
|
||||
titleStringRes = R.string.lucky_number_notify_new_item_title,
|
||||
contentStringRes = R.string.lucky_number_notify_new_item,
|
||||
startMenu = MainView.Section.LUCKY_NUMBER,
|
||||
contentValues = listOf(item.luckyNumber.toString())
|
||||
suspend fun notify(item: LuckyNumber, student: Student) {
|
||||
val notificationData = NotificationData(
|
||||
title = context.getString(R.string.lucky_number_notify_new_item_title),
|
||||
content = context.getString(
|
||||
R.string.lucky_number_notify_new_item,
|
||||
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
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Message
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import 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 javax.inject.Inject
|
||||
|
||||
class NewMessageNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager,
|
||||
@ApplicationContext private val context: Context
|
||||
) {
|
||||
|
||||
fun notify(items: List<Message>, student: Student) {
|
||||
val notification = MultipleNotifications(
|
||||
type = NotificationType.NEW_MESSAGE,
|
||||
icon = R.drawable.ic_stat_message,
|
||||
titleStringRes = R.plurals.message_new_items,
|
||||
contentStringRes = R.plurals.message_notify_new_items,
|
||||
summaryStringRes = R.plurals.message_number_item,
|
||||
startMenu = MainView.Section.MESSAGE,
|
||||
lines = items.map {
|
||||
"${it.sender}: ${it.subject}"
|
||||
}
|
||||
suspend fun notify(items: List<Message>, student: Student) {
|
||||
val notificationDataList = items.map {
|
||||
NotificationData(
|
||||
title = context.getPlural(R.plurals.message_new_items, 1),
|
||||
content = "${it.sender}: ${it.subject}",
|
||||
intentToStart = SplashActivity.getStartIntent(context, Destination.Message),
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Note
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.data.pojos.GroupNotificationData
|
||||
import io.github.wulkanowy.data.pojos.NotificationData
|
||||
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
|
||||
|
||||
class NewNoteNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager,
|
||||
@ApplicationContext private val context: Context
|
||||
) {
|
||||
|
||||
fun notify(items: List<Note>, student: Student) {
|
||||
val notification = MultipleNotifications(
|
||||
type = NotificationType.NEW_NOTE,
|
||||
icon = R.drawable.ic_stat_note,
|
||||
titleStringRes = when (NoteCategory.getByValue(items.first().categoryType)) {
|
||||
suspend fun notify(items: List<Note>, student: Student) {
|
||||
val notificationDataList = items.map {
|
||||
val titleRes = when (NoteCategory.getByValue(it.categoryType)) {
|
||||
NoteCategory.POSITIVE -> R.plurals.praise_new_items
|
||||
NoteCategory.NEUTRAL -> R.plurals.neutral_note_new_items
|
||||
else -> R.plurals.note_new_items
|
||||
},
|
||||
contentStringRes = when (NoteCategory.getByValue(items.first().categoryType)) {
|
||||
NoteCategory.POSITIVE -> R.plurals.praise_notify_new_items
|
||||
NoteCategory.NEUTRAL -> R.plurals.neutral_note_notify_new_items
|
||||
else -> R.plurals.note_notify_new_items
|
||||
},
|
||||
summaryStringRes = when (NoteCategory.getByValue(items.first().categoryType)) {
|
||||
NoteCategory.POSITIVE -> R.plurals.praise_number_item
|
||||
NoteCategory.NEUTRAL -> R.plurals.neutral_note_number_item
|
||||
else -> R.plurals.note_number_item
|
||||
},
|
||||
startMenu = MainView.Section.NOTE,
|
||||
lines = items.map {
|
||||
"${it.teacher}: ${it.category}"
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.pojos.MultipleNotifications
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import 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 javax.inject.Inject
|
||||
|
||||
class NewSchoolAnnouncementNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
) : BaseNotification(context, notificationManager) {
|
||||
private val appNotificationManager: AppNotificationManager,
|
||||
@ApplicationContext private val context: Context
|
||||
) {
|
||||
|
||||
fun notify(items: List<SchoolAnnouncement>, student: Student) {
|
||||
val notification = MultipleNotifications(
|
||||
suspend fun notify(items: List<SchoolAnnouncement>, student: Student) {
|
||||
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,
|
||||
icon = R.drawable.ic_all_about,
|
||||
titleStringRes = R.plurals.school_announcement_notify_new_item_title,
|
||||
contentStringRes = R.plurals.school_announcement_notify_new_items,
|
||||
summaryStringRes = R.plurals.school_announcement_number_item,
|
||||
startMenu = MainView.Section.SCHOOL_ANNOUNCEMENT,
|
||||
lines = items.map {
|
||||
"${it.subject}: ${it.content}"
|
||||
}
|
||||
intentToStart = SplashActivity.getStartIntent(
|
||||
context = context,
|
||||
destination = Destination.SchoolAnnouncement
|
||||
),
|
||||
title = context.getPlural(
|
||||
R.plurals.school_announcement_notify_new_item_title,
|
||||
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
|
||||
|
||||
import io.github.wulkanowy.R
|
||||
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.NewExamChannel
|
||||
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.NewNotesChannel
|
||||
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) {
|
||||
NEW_CONFERENCE("new_conferences_group", NewConferencesChannel.CHANNEL_ID),
|
||||
NEW_EXAM("new_exam_group", NewExamChannel.CHANNEL_ID),
|
||||
NEW_GRADE_DETAILS("new_grade_details_group", NewGradesChannel.CHANNEL_ID),
|
||||
NEW_GRADE_PREDICTED("new_grade_predicted_group", NewGradesChannel.CHANNEL_ID),
|
||||
NEW_GRADE_FINAL("new_grade_final_group", NewGradesChannel.CHANNEL_ID),
|
||||
NEW_HOMEWORK("new_homework_group", NewHomeworkChannel.CHANNEL_ID),
|
||||
NEW_LUCKY_NUMBER("lucky_number_group", LuckyNumberChannel.CHANNEL_ID),
|
||||
NEW_MESSAGE("new_message_group", NewMessagesChannel.CHANNEL_ID),
|
||||
NEW_NOTE("new_notes_group", NewNotesChannel.CHANNEL_ID),
|
||||
NEW_ANNOUNCEMENT("new_school_announcements_group", NewSchoolAnnouncementsChannel.CHANNEL_ID),
|
||||
enum class NotificationType(
|
||||
val group: String?,
|
||||
val channel: String,
|
||||
val icon: Int
|
||||
) {
|
||||
NEW_CONFERENCE(
|
||||
group = "new_conferences_group",
|
||||
channel = NewConferencesChannel.CHANNEL_ID,
|
||||
icon = R.drawable.ic_more_conferences,
|
||||
),
|
||||
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.Student
|
||||
import io.github.wulkanowy.data.repositories.AttendanceRepository
|
||||
import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.services.sync.notifications.NewAttendanceNotification
|
||||
import io.github.wulkanowy.utils.previousOrSameSchoolDay
|
||||
import io.github.wulkanowy.utils.waitForResult
|
||||
import kotlinx.coroutines.flow.first
|
||||
import java.time.LocalDate.now
|
||||
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) {
|
||||
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.PreferencesRepository
|
||||
import io.github.wulkanowy.services.sync.notifications.NewHomeworkNotification
|
||||
import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.nextOrSameSchoolDay
|
||||
import io.github.wulkanowy.utils.waitForResult
|
||||
import kotlinx.coroutines.flow.first
|
||||
import java.time.LocalDate.now
|
||||
@ -22,13 +21,13 @@ class HomeworkWork @Inject constructor(
|
||||
homeworkRepository.getHomework(
|
||||
student = student,
|
||||
semester = semester,
|
||||
start = now().monday,
|
||||
end = now().sunday,
|
||||
start = now().nextOrSameSchoolDay,
|
||||
end = now().nextOrSameSchoolDay,
|
||||
forceRefresh = true,
|
||||
notify = preferencesRepository.isNotificationsEnable
|
||||
).waitForResult()
|
||||
|
||||
homeworkRepository.getHomeworkFromDatabase(semester, now().monday, now().sunday).first()
|
||||
homeworkRepository.getHomeworkFromDatabase(semester, now(), now().plusDays(7)).first()
|
||||
.filter { !it.isNotified }.let {
|
||||
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.Student
|
||||
import io.github.wulkanowy.data.repositories.PreferencesRepository
|
||||
import io.github.wulkanowy.data.repositories.TimetableRepository
|
||||
import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.services.sync.notifications.ChangeTimetableNotification
|
||||
import io.github.wulkanowy.utils.nextOrSameSchoolDay
|
||||
import io.github.wulkanowy.utils.waitForResult
|
||||
import kotlinx.coroutines.flow.first
|
||||
import java.time.LocalDate.now
|
||||
import javax.inject.Inject
|
||||
|
||||
class TimetableWork @Inject constructor(
|
||||
private val timetableRepository: TimetableRepository
|
||||
private val timetableRepository: TimetableRepository,
|
||||
private val changeTimetableNotification: ChangeTimetableNotification,
|
||||
private val preferencesRepository: PreferencesRepository
|
||||
) : Work {
|
||||
|
||||
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
|
||||
|
||||
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.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
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)
|
||||
super.onCreate(savedInstanceState)
|
||||
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleLogger, true)
|
||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
setTaskDescription(
|
||||
@ -83,8 +79,8 @@ abstract class BaseActivity<T : BasePresenter<out BaseView>, VB : ViewBinding> :
|
||||
}
|
||||
|
||||
override fun openClearLoginView() {
|
||||
startActivity(LoginActivity.getStartIntent(this)
|
||||
.apply { addFlags(FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_NEW_TASK) })
|
||||
startActivity(LoginActivity.getStartIntent(this))
|
||||
finishAffinity()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
@ -2,32 +2,33 @@ package io.github.wulkanowy.ui.base
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
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(private val fragmentManager: FragmentManager) :
|
||||
FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
||||
class BaseFragmentPagerAdapter(
|
||||
private val fragmentManager: FragmentManager,
|
||||
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
|
||||
|
||||
fun getFragmentInstance(position: Int): Fragment? {
|
||||
require(containerId != 0) { "Container id is 0" }
|
||||
return fragmentManager.findFragmentByTag("android:switcher:$containerId:$position")
|
||||
return fragmentManager.findFragmentByTag("f$position")
|
||||
}
|
||||
|
||||
fun addFragments(fragments: List<Fragment>) {
|
||||
fragments.forEach { pages[it] = null }
|
||||
override fun createFragment(position: Int): Fragment = itemFactory(position)
|
||||
|
||||
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.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancelChildren
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import timber.log.Timber
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
open class BasePresenter<T : BaseView>(
|
||||
protected val errorHandler: ErrorHandler,
|
||||
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>()
|
||||
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = Dispatchers.Main + job
|
||||
private val childrenJobs = mutableMapOf<String, Job>()
|
||||
|
||||
var view: T? = null
|
||||
|
||||
open fun onAttachView(view: T) {
|
||||
job = Job()
|
||||
this.view = view
|
||||
errorHandler.apply {
|
||||
showErrorMessage = view::showError
|
||||
@ -64,22 +62,22 @@ open class BasePresenter<T : BaseView>(
|
||||
}
|
||||
|
||||
fun <T> Flow<T>.launch(individualJobTag: String = "load"): Job {
|
||||
jobs[individualJobTag]?.cancel()
|
||||
val job = catch { errorHandler.dispatch(it) }.launchIn(this@BasePresenter)
|
||||
jobs[individualJobTag] = job
|
||||
childrenJobs[individualJobTag]?.cancel()
|
||||
val job = catch { errorHandler.dispatch(it) }.launchIn(presenterScope)
|
||||
childrenJobs[individualJobTag] = job
|
||||
Timber.d("Job $individualJobTag launched in ${this@BasePresenter.javaClass.simpleName}: $job")
|
||||
return job
|
||||
}
|
||||
|
||||
fun cancelJobs(vararg names: String) {
|
||||
names.forEach {
|
||||
jobs[it]?.cancel()
|
||||
childrenJobs[it]?.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
open fun onDetachView() {
|
||||
view = null
|
||||
job.cancel()
|
||||
job.cancelChildren()
|
||||
errorHandler.clear()
|
||||
view = null
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import android.widget.Toast
|
||||
import android.widget.Toast.LENGTH_LONG
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.view.isGone
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.databinding.DialogErrorBinding
|
||||
@ -24,8 +25,6 @@ import io.github.wulkanowy.utils.openEmailClient
|
||||
import io.github.wulkanowy.utils.openInternetBrowser
|
||||
import okhttp3.internal.http2.StreamResetException
|
||||
import java.io.InterruptedIOException
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import java.net.ConnectException
|
||||
import java.net.SocketTimeoutException
|
||||
import java.net.UnknownHostException
|
||||
@ -64,26 +63,26 @@ class ErrorDialog : BaseDialogFragment<DialogErrorBinding>() {
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val stringWriter = StringWriter().apply {
|
||||
error.printStackTrace(PrintWriter(this))
|
||||
}
|
||||
val errorStacktrace = error.stackTraceToString()
|
||||
|
||||
with(binding) {
|
||||
errorDialogContent.text = stringWriter.toString()
|
||||
errorDialogContent.text = errorStacktrace.replace(": ${error.localizedMessage}", "")
|
||||
with(errorDialogHorizontalScroll) {
|
||||
post { fullScroll(HorizontalScrollView.FOCUS_LEFT) }
|
||||
}
|
||||
errorDialogCopy.setOnClickListener {
|
||||
val clip = ClipData.newPlainText("wulkanowy", stringWriter.toString())
|
||||
val clip = ClipData.newPlainText("Error details", errorStacktrace)
|
||||
activity?.getSystemService<ClipboardManager>()?.setPrimaryClip(clip)
|
||||
|
||||
Toast.makeText(context, R.string.all_copied, LENGTH_LONG).show()
|
||||
}
|
||||
errorDialogCancel.setOnClickListener { dismiss() }
|
||||
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) {
|
||||
is UnknownHostException,
|
||||
is InterruptedIOException,
|
||||
|
@ -1,6 +1,7 @@
|
||||
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.sdk.scrapper.login.BadCredentialsException
|
||||
import io.github.wulkanowy.sdk.scrapper.login.PasswordChangeRequiredException
|
||||
@ -9,7 +10,7 @@ import io.github.wulkanowy.utils.security.ScramblerException
|
||||
import timber.log.Timber
|
||||
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 = { _, _ -> }
|
||||
|
||||
@ -25,7 +26,7 @@ open class ErrorHandler @Inject constructor(protected val resources: Resources)
|
||||
}
|
||||
|
||||
protected open fun proceed(error: Throwable) {
|
||||
showErrorMessage(resources.getString(error), error)
|
||||
showErrorMessage(context.resources.getString(error), error)
|
||||
when (error) {
|
||||
is PasswordChangeRequiredException -> onPasswordChangeRequired(error.redirectUrl)
|
||||
is ScramblerException, is BadCredentialsException -> onSessionExpired()
|
||||
|
@ -41,14 +41,15 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer
|
||||
)
|
||||
}
|
||||
|
||||
private fun isThemeApplicable(activity: AppCompatActivity): Boolean {
|
||||
return activity.packageManager
|
||||
private fun isThemeApplicable(activity: AppCompatActivity) =
|
||||
activity.packageManager
|
||||
.getPackageInfo(activity.packageName, GET_ACTIVITIES)
|
||||
.activities.singleOrNull { it.name == activity::class.java.canonicalName }
|
||||
?.theme.let {
|
||||
.activities
|
||||
.singleOrNull { it.name == activity::class.java.canonicalName }
|
||||
?.theme
|
||||
.let {
|
||||
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_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()
|
||||
}
|
||||
}
|
@ -82,18 +82,20 @@ class AboutPresenter @Inject constructor(
|
||||
|
||||
private fun loadData() {
|
||||
view?.run {
|
||||
updateData(listOfNotNull(
|
||||
versionRes,
|
||||
creatorsRes,
|
||||
feedbackRes,
|
||||
faqRes,
|
||||
discordRes,
|
||||
facebookRes,
|
||||
twitterRes,
|
||||
homepageRes,
|
||||
licensesRes,
|
||||
privacyRes
|
||||
))
|
||||
updateData(
|
||||
listOfNotNull(
|
||||
versionRes,
|
||||
creatorsRes,
|
||||
feedbackRes,
|
||||
faqRes,
|
||||
discordRes,
|
||||
facebookRes,
|
||||
twitterRes,
|
||||
homepageRes,
|
||||
licensesRes,
|
||||
privacyRes
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user