1
0

Compare commits

...

60 Commits
1.4.0 ... 1.4.4

Author SHA1 Message Date
4e12eb1552 Merge branch 'release/1.4.4' 2021-12-19 22:25:52 +01:00
c846cc999f Version 1.4.4 2021-12-19 22:25:47 +01:00
47d430292c Update template of login issue email (#1724)
* Update sdk

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

View File

@ -35,6 +35,9 @@ jobs:
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }} PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }} PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
ANDROID_PUBLISHER_CREDENTIALS: ${{ secrets.ANDROID_PUBLISHER_CREDENTIALS }} 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; run: ./gradlew publishPlayReleaseApps -PenableFirebase --stacktrace;
deploy-app-gallery: deploy-app-gallery:
@ -69,4 +72,5 @@ jobs:
PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }} PLAY_STORE_PASSWORD: ${{ secrets.PLAY_STORE_PASSWORD }}
PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }} PLAY_KEY_ALIAS: ${{ secrets.PLAY_KEY_ALIAS }}
PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }} PLAY_KEY_PASSWORD: ${{ secrets.PLAY_KEY_PASSWORD }}
SET_BUILD_TIMESTAMP: ${{ secrets.SET_BUILD_TIMESTAMP }}
run: ./gradlew bundleHmsRelease --stacktrace && ./gradlew publishHuaweiAppGalleryHmsRelease --stacktrace run: ./gradlew bundleHmsRelease --stacktrace && ./gradlew publishHuaweiAppGalleryHmsRelease --stacktrace

78
README.cs.md Normal file
View File

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

74
README.de.md Normal file
View File

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

View File

@ -1,5 +1,11 @@
[Polska wersja README](README.md) [Polska wersja README](README.md)
[Deutsche Version von README](README.de.md)
[Česká verze README](README.cs.md)
[Slovenská verzia README](README.sk.md)
# Wulkanowy # Wulkanowy
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/wulkanowy/wulkanowy/Tests/develop?style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions) [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/wulkanowy/wulkanowy/Tests/develop?style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions)

View File

@ -1,5 +1,11 @@
[English version of README](README.en.md) [English version of README](README.en.md)
[Deutsche Version von README](README.de.md)
[Česká verze README](README.cs.md)
[Slovenská verzia README](README.sk.md)
# Wulkanowy # Wulkanowy
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/wulkanowy/wulkanowy/Tests/develop?style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions) [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/wulkanowy/wulkanowy/Tests/develop?style=flat-square)](https://github.com/wulkanowy/wulkanowy/actions)

78
README.sk.md Normal file
View File

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

View File

@ -22,8 +22,8 @@ android {
testApplicationId "io.github.tests.wulkanowy" testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 31 targetSdkVersion 31
versionCode 98 versionCode 102
versionName "1.4.0" versionName "1.4.4"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "app_name", "Wulkanowy" resValue "string", "app_name", "Wulkanowy"
@ -150,7 +150,7 @@ kapt {
play { play {
defaultToAppBundles = false defaultToAppBundles = false
track = 'beta' track = 'beta'
updatePriority = 1 updatePriority = 4
enabled.set(false) enabled.set(false)
} }
@ -165,16 +165,16 @@ huaweiPublish {
} }
ext { ext {
work_manager = "2.7.0" work_manager = "2.7.1"
android_hilt = "1.0.0" android_hilt = "1.0.0"
room = "2.3.0" room = "2.3.0"
chucker = "3.5.2" chucker = "3.5.2"
mockk = "1.12.0" mockk = "1.12.1"
coroutines = "1.5.2" coroutines = "1.5.2"
} }
dependencies { dependencies {
implementation "io.github.wulkanowy:sdk:1.4.0" implementation "io.github.wulkanowy:sdk:1.4.4"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
@ -184,15 +184,15 @@ dependencies {
implementation "androidx.core:core-ktx:1.7.0" implementation "androidx.core:core-ktx:1.7.0"
implementation 'androidx.core:core-splashscreen:1.0.0-alpha02' implementation 'androidx.core:core-splashscreen:1.0.0-alpha02'
implementation "androidx.activity:activity-ktx:1.4.0" implementation "androidx.activity:activity-ktx:1.4.0"
implementation "androidx.appcompat:appcompat:1.4.0-rc01" implementation "androidx.appcompat:appcompat:1.4.0"
implementation "androidx.fragment:fragment-ktx:1.4.0-rc01" implementation "androidx.fragment:fragment-ktx:1.4.0"
implementation "androidx.annotation:annotation:1.3.0" implementation "androidx.annotation:annotation:1.3.0"
implementation "androidx.preference:preference-ktx:1.1.1" implementation "androidx.preference:preference-ktx:1.1.1"
implementation "androidx.recyclerview:recyclerview:1.2.1" implementation "androidx.recyclerview:recyclerview:1.2.1"
implementation "androidx.viewpager:viewpager:1.0.0" implementation "androidx.viewpager2:viewpager2:1.1.0-beta01"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "androidx.constraintlayout:constraintlayout:2.1.1" implementation "androidx.constraintlayout:constraintlayout:2.1.2"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0" implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
implementation "com.google.android.material:material:1.4.0" implementation "com.google.android.material:material:1.4.0"
implementation "com.github.wulkanowy:material-chips-input:2.3.1" implementation "com.github.wulkanowy:material-chips-input:2.3.1"
@ -218,7 +218,7 @@ dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0" implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0"
implementation "com.squareup.okhttp3:logging-interceptor:4.9.2" implementation "com.squareup.okhttp3:logging-interceptor:4.9.3"
implementation "com.jakewharton.timber:timber:5.0.1" implementation "com.jakewharton.timber:timber:5.0.1"
implementation "at.favre.lib:slf4j-timber:1.0.1" implementation "at.favre.lib:slf4j-timber:1.0.1"
@ -227,18 +227,18 @@ dependencies {
implementation "io.coil-kt:coil:1.4.0" implementation "io.coil-kt:coil:1.4.0"
implementation "io.github.wulkanowy:AppKillerManager:3.0.0" implementation "io.github.wulkanowy:AppKillerManager:3.0.0"
implementation 'me.xdrop:fuzzywuzzy:1.3.1' implementation 'me.xdrop:fuzzywuzzy:1.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:29.0.0') playImplementation platform('com.google.firebase:firebase-bom:29.0.2')
playImplementation 'com.google.firebase:firebase-analytics-ktx' playImplementation 'com.google.firebase:firebase-analytics-ktx'
playImplementation 'com.google.firebase:firebase-messaging:' playImplementation 'com.google.firebase:firebase-messaging:'
playImplementation 'com.google.firebase:firebase-crashlytics:' playImplementation 'com.google.firebase:firebase-crashlytics:'
playImplementation 'com.google.android.play:core:1.10.2' playImplementation 'com.google.android.play:core:1.10.2'
playImplementation 'com.google.android.play:core-ktx:1.8.1' playImplementation 'com.google.android.play:core-ktx:1.8.1'
playImplementation 'com.google.android.gms:play-services-ads:20.4.0' playImplementation 'com.google.android.gms:play-services-ads:20.5.0'
hmsImplementation 'com.huawei.hms:hianalytics:6.3.0.303' hmsImplementation 'com.huawei.hms:hianalytics:6.3.2.300'
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.1.300' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.2.300'
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker" releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
@ -250,7 +250,7 @@ dependencies {
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines"
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
testImplementation 'org.robolectric:robolectric:4.7' testImplementation 'org.robolectric:robolectric:4.7.3'
testImplementation "androidx.test:runner:1.4.0" testImplementation "androidx.test:runner:1.4.0"
testImplementation "androidx.test.ext:junit:1.1.3" testImplementation "androidx.test.ext:junit:1.1.3"
testImplementation "androidx.test:core:1.4.0" testImplementation "androidx.test:core:1.4.0"

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -9,6 +9,7 @@
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
<queries> <queries>
<intent> <intent>

View File

@ -103,6 +103,7 @@ import io.github.wulkanowy.data.db.migrations.Migration40
import io.github.wulkanowy.data.db.migrations.Migration41 import io.github.wulkanowy.data.db.migrations.Migration41
import io.github.wulkanowy.data.db.migrations.Migration42 import io.github.wulkanowy.data.db.migrations.Migration42
import io.github.wulkanowy.data.db.migrations.Migration43 import io.github.wulkanowy.data.db.migrations.Migration43
import io.github.wulkanowy.data.db.migrations.Migration44
import io.github.wulkanowy.data.db.migrations.Migration5 import io.github.wulkanowy.data.db.migrations.Migration5
import io.github.wulkanowy.data.db.migrations.Migration6 import io.github.wulkanowy.data.db.migrations.Migration6
import io.github.wulkanowy.data.db.migrations.Migration7 import io.github.wulkanowy.data.db.migrations.Migration7
@ -152,7 +153,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
companion object { companion object {
const val VERSION_SCHEMA = 43 const val VERSION_SCHEMA = 44
fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf(
Migration2(), Migration2(),
@ -196,7 +197,8 @@ abstract class AppDatabase : RoomDatabase() {
Migration40(), Migration40(),
Migration41(sharedPrefProvider), Migration41(sharedPrefProvider),
Migration42(), Migration42(),
Migration43() Migration43(),
Migration44()
) )
fun newInstance( fun newInstance(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,11 +7,14 @@ import com.fredporciuncula.flow.preferences.FlowSharedPreferences
import com.fredporciuncula.flow.preferences.Preference import com.fredporciuncula.flow.preferences.Preference
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.enums.AppTheme
import io.github.wulkanowy.data.enums.GradeColorTheme
import io.github.wulkanowy.data.enums.GradeExpandMode
import io.github.wulkanowy.data.enums.GradeSortingMode
import io.github.wulkanowy.data.enums.TimetableMode
import io.github.wulkanowy.sdk.toLocalDate import io.github.wulkanowy.sdk.toLocalDate
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
import io.github.wulkanowy.ui.modules.grade.GradeAverageMode import io.github.wulkanowy.ui.modules.grade.GradeAverageMode
import io.github.wulkanowy.ui.modules.grade.GradeExpandMode
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode
import io.github.wulkanowy.utils.toLocalDateTime import io.github.wulkanowy.utils.toLocalDateTime
import io.github.wulkanowy.utils.toTimestamp import io.github.wulkanowy.utils.toTimestamp
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -20,8 +23,6 @@ import kotlinx.coroutines.flow.map
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.lang.ClassCastException
import java.lang.IllegalStateException
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalDateTime import java.time.LocalDateTime
import javax.inject.Inject import javax.inject.Inject
@ -74,14 +75,16 @@ class PreferencesRepository @Inject constructor(
) )
val appThemeKey = context.getString(R.string.pref_key_app_theme) val appThemeKey = context.getString(R.string.pref_key_app_theme)
val appTheme: String val appTheme: AppTheme
get() = getString(appThemeKey, R.string.pref_default_app_theme) get() = AppTheme.getByValue(getString(appThemeKey, R.string.pref_default_app_theme))
val gradeColorTheme: String val gradeColorTheme: GradeColorTheme
get() = getString( get() = GradeColorTheme.getByValue(
getString(
R.string.pref_key_grade_color_scheme, R.string.pref_key_grade_color_scheme,
R.string.pref_default_grade_color_scheme R.string.pref_default_grade_color_scheme
) )
)
val appLanguageKey = context.getString(R.string.pref_key_app_language) val appLanguageKey = context.getString(R.string.pref_key_app_language)
val appLanguage val appLanguage
@ -105,7 +108,10 @@ class PreferencesRepository @Inject constructor(
val isUpcomingLessonsNotificationsEnableKey = val isUpcomingLessonsNotificationsEnableKey =
context.getString(R.string.pref_key_notifications_upcoming_lessons_enable) context.getString(R.string.pref_key_notifications_upcoming_lessons_enable)
val isUpcomingLessonsNotificationsEnable: Boolean var isUpcomingLessonsNotificationsEnable: Boolean
set(value) {
sharedPref.edit { putBoolean(isUpcomingLessonsNotificationsEnableKey, value) }
}
get() = getBoolean( get() = getBoolean(
isUpcomingLessonsNotificationsEnableKey, isUpcomingLessonsNotificationsEnableKey,
R.bool.pref_default_notification_upcoming_lessons_enable R.bool.pref_default_notification_upcoming_lessons_enable
@ -155,11 +161,13 @@ class PreferencesRepository @Inject constructor(
R.bool.pref_default_timetable_show_groups R.bool.pref_default_timetable_show_groups
) )
val showWholeClassPlan: String val showWholeClassPlan: TimetableMode
get() = getString( get() = TimetableMode.getByValue(
getString(
R.string.pref_key_timetable_show_whole_class, R.string.pref_key_timetable_show_whole_class,
R.string.pref_default_timetable_show_whole_class R.string.pref_default_timetable_show_whole_class
) )
)
val gradeSortingMode: GradeSortingMode val gradeSortingMode: GradeSortingMode
get() = GradeSortingMode.getByValue( get() = GradeSortingMode.getByValue(
@ -244,6 +252,14 @@ class PreferencesRepository @Inject constructor(
return flowSharedPref.getStringSet(prefKey, defaultSet) return flowSharedPref.getStringSet(prefKey, defaultSet)
} }
var dismissedAdminMessageIds: List<Int>
get() = sharedPref.getStringSet(PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS, emptySet())
.orEmpty()
.map { it.toInt() }
set(value) = sharedPref.edit {
putStringSet(PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS, value.map { it.toString() }.toSet())
}
var inAppReviewCount: Int var inAppReviewCount: Int
get() = sharedPref.getInt(PREF_KEY_IN_APP_REVIEW_COUNT, 0) get() = sharedPref.getInt(PREF_KEY_IN_APP_REVIEW_COUNT, 0)
set(value) = sharedPref.edit().putInt(PREF_KEY_IN_APP_REVIEW_COUNT, value).apply() set(value) = sharedPref.edit().putInt(PREF_KEY_IN_APP_REVIEW_COUNT, value).apply()
@ -285,5 +301,7 @@ class PreferencesRepository @Inject constructor(
private const val PREF_KEY_IN_APP_REVIEW_DATE = "in_app_review_date" private const val PREF_KEY_IN_APP_REVIEW_DATE = "in_app_review_date"
private const val PREF_KEY_IN_APP_REVIEW_DONE = "in_app_review_done" private const val PREF_KEY_IN_APP_REVIEW_DONE = "in_app_review_done"
private const val PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS = "admin_message_dismissed_ids"
} }
} }

View File

@ -5,6 +5,7 @@ import android.app.AlarmManager.RTC_WAKEUP
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build
import androidx.core.app.AlarmManagerCompat import androidx.core.app.AlarmManagerCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
@ -91,6 +92,12 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
return cancelScheduled(lessons, student) return cancelScheduled(lessons, student)
} }
if (!canScheduleExactAlarms()) {
Timber.w("Exact alarms are disabled by user")
preferencesRepository.isUpcomingLessonsNotificationsEnable = false
return
}
if (lessons.firstOrNull()?.date?.isAfter(LocalDate.now().plusDays(2)) == true) { if (lessons.firstOrNull()?.date?.isAfter(LocalDate.now().plusDays(2)) == true) {
Timber.d("Timetable notification scheduling skipped - lessons are too far") Timber.d("Timetable notification scheduling skipped - lessons are too far")
return return
@ -169,8 +176,18 @@ class TimetableNotificationSchedulerHelper @Inject constructor(
intent.getStringExtra(LESSON_TITLE) intent.getStringExtra(LESSON_TITLE)
}, start: $time, student: $studentId" }, start: $time, student: $studentId"
) )
} catch (e: IllegalStateException) { } catch (e: Throwable) {
Timber.e(e) Timber.e(e)
} }
} }
fun canScheduleExactAlarms(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
try {
alarmManager.canScheduleExactAlarms()
} catch (e: Throwable) {
false
}
} else true
}
} }

View File

@ -74,7 +74,7 @@ class AppNotificationManager @Inject constructor(
student: Student student: Student
) { ) {
val notificationType = groupNotificationData.type val notificationType = groupNotificationData.type
val groupType = notificationType.group ?: return val groupType = notificationType.channel
val group = "${groupType}_${student.id}" val group = "${groupType}_${student.id}"
sendSummaryNotification(groupNotificationData, group, student) sendSummaryNotification(groupNotificationData, group, student)

View File

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

View File

@ -7,6 +7,7 @@ import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.enums.AppTheme
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
@ -20,7 +21,7 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer
fun applyActivityTheme(activity: AppCompatActivity) { fun applyActivityTheme(activity: AppCompatActivity) {
if (isThemeApplicable(activity)) { if (isThemeApplicable(activity)) {
applyDefaultTheme() applyDefaultTheme()
if (preferencesRepository.appTheme == "black") { if (preferencesRepository.appTheme == AppTheme.BLACK) {
when (activity) { when (activity) {
is MainActivity -> activity.setTheme(R.style.WulkanowyTheme_Black) is MainActivity -> activity.setTheme(R.style.WulkanowyTheme_Black)
is LoginActivity -> activity.setTheme(R.style.WulkanowyTheme_Login_Black) is LoginActivity -> activity.setTheme(R.style.WulkanowyTheme_Login_Black)
@ -32,11 +33,10 @@ class ThemeManager @Inject constructor(private val preferencesRepository: Prefer
fun applyDefaultTheme() { fun applyDefaultTheme() {
AppCompatDelegate.setDefaultNightMode( AppCompatDelegate.setDefaultNightMode(
when (val theme = preferencesRepository.appTheme) { when (preferencesRepository.appTheme) {
"light" -> MODE_NIGHT_NO AppTheme.LIGHT -> MODE_NIGHT_NO
"dark", "black" -> MODE_NIGHT_YES AppTheme.DARK, AppTheme.BLACK -> MODE_NIGHT_YES
"system" -> MODE_NIGHT_FOLLOW_SYSTEM AppTheme.SYSTEM -> MODE_NIGHT_FOLLOW_SYSTEM
else -> throw IllegalArgumentException("Wrong theme: $theme")
} }
) )
} }

View File

@ -16,9 +16,11 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.AdminMessage
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.db.entities.TimetableHeader import io.github.wulkanowy.data.db.entities.TimetableHeader
import io.github.wulkanowy.data.enums.GradeColorTheme
import io.github.wulkanowy.databinding.ItemDashboardAccountBinding import io.github.wulkanowy.databinding.ItemDashboardAccountBinding
import io.github.wulkanowy.databinding.ItemDashboardAdminMessageBinding import io.github.wulkanowy.databinding.ItemDashboardAdminMessageBinding
import io.github.wulkanowy.databinding.ItemDashboardAnnouncementsBinding import io.github.wulkanowy.databinding.ItemDashboardAnnouncementsBinding
@ -68,6 +70,8 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
var onAdminMessageClickListener: (String?) -> Unit = {} var onAdminMessageClickListener: (String?) -> Unit = {}
var onAdminMessageDismissClickListener: (AdminMessage) -> Unit = {}
val items = mutableListOf<DashboardItem>() val items = mutableListOf<DashboardItem>()
fun submitList(newItems: List<DashboardItem>) { fun submitList(newItems: List<DashboardItem>) {
@ -259,7 +263,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
val isLoading = item.isLoading val isLoading = item.isLoading
val dashboardGradesAdapter = gradesViewHolder.adapter.apply { val dashboardGradesAdapter = gradesViewHolder.adapter.apply {
this.items = subjectWithGrades.toList() this.items = subjectWithGrades.toList()
this.gradeTheme = gradeTheme.orEmpty() this.gradeColorTheme = gradeTheme ?: GradeColorTheme.VULCAN
} }
with(gradesViewHolder.binding) { with(gradesViewHolder.binding) {
@ -418,10 +422,12 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
isFirstTimeRangeVisible = true isFirstTimeRangeVisible = true
isFirstTimeVisible = false isFirstTimeVisible = false
} else { } else {
firstTimeText = context.resources.getQuantityString( firstTimeText = context.getString(
R.plurals.dashboard_timetable_first_lesson_time_in_minutes, R.string.timetable_time_until,
minutesToStartLesson.toInt(), context.getString(
minutesToStartLesson R.string.timetable_minutes,
minutesToStartLesson.toString()
)
) )
firstTimeRangeText = "" firstTimeRangeText = ""
@ -457,10 +463,12 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
0 0
} }
firstTimeText = context.resources.getQuantityString( firstTimeText = context.getString(
R.plurals.dashboard_timetable_first_lesson_time_more_minutes, R.string.timetable_time_left,
minutesToEndLesson.toInt(), context.getString(
minutesToEndLesson R.string.timetable_minutes,
minutesToEndLesson.toString()
)
) )
firstTimeRangeText = "" firstTimeRangeText = ""
@ -727,6 +735,10 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
dashboardAdminMessageItemDescription.text = item.content dashboardAdminMessageItemDescription.text = item.content
dashboardAdminMessageItemDescription.setTextColor(textColor) dashboardAdminMessageItemDescription.setTextColor(textColor)
dashboardAdminMessageItemIcon.setColorFilter(textColor) dashboardAdminMessageItemIcon.setColorFilter(textColor)
dashboardAdminMessageItemDismiss.isVisible = item.isDismissible
dashboardAdminMessageItemDismiss.setOnClickListener {
onAdminMessageDismissClickListener(item)
}
root.setCardBackgroundColor(backgroundColor?.let { ColorStateList.valueOf(it) }) root.setCardBackgroundColor(backgroundColor?.let { ColorStateList.valueOf(it) })
item.destinationUrl?.let { url -> item.destinationUrl?.let { url ->

View File

@ -100,6 +100,7 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
mainActivity.pushView(ConferenceFragment.newInstance()) mainActivity.pushView(ConferenceFragment.newInstance())
} }
onAdminMessageClickListener = presenter::onAdminMessageSelected onAdminMessageClickListener = presenter::onAdminMessageSelected
onAdminMessageDismissClickListener = presenter::onAdminMessageDismissed
registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {

View File

@ -4,6 +4,7 @@ import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.enums.GradeColorTheme
import io.github.wulkanowy.databinding.SubitemDashboardGradesBinding import io.github.wulkanowy.databinding.SubitemDashboardGradesBinding
import io.github.wulkanowy.databinding.SubitemDashboardSmallGradeBinding import io.github.wulkanowy.databinding.SubitemDashboardSmallGradeBinding
import io.github.wulkanowy.utils.getBackgroundColor import io.github.wulkanowy.utils.getBackgroundColor
@ -12,7 +13,7 @@ class DashboardGradesAdapter : RecyclerView.Adapter<DashboardGradesAdapter.ViewH
var items = listOf<Pair<String, List<Grade>>>() var items = listOf<Pair<String, List<Grade>>>()
var gradeTheme = "" lateinit var gradeColorTheme: GradeColorTheme
override fun getItemCount() = items.size override fun getItemCount() = items.size
@ -36,7 +37,7 @@ class DashboardGradesAdapter : RecyclerView.Adapter<DashboardGradesAdapter.ViewH
with(subitemBinding.dashboardSmallGradeSubitemValue) { with(subitemBinding.dashboardSmallGradeSubitemValue) {
text = it.entry text = it.entry
setBackgroundResource(it.getBackgroundColor(gradeTheme)) setBackgroundResource(it.getBackgroundColor(gradeColorTheme))
} }
dashboardGradesSubitemGradeContainer.addView(subitemBinding.root) dashboardGradesSubitemGradeContainer.addView(subitemBinding.root)

View File

@ -6,6 +6,7 @@ import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.enums.GradeColorTheme
import io.github.wulkanowy.data.pojos.TimetableFull import io.github.wulkanowy.data.pojos.TimetableFull
import io.github.wulkanowy.data.db.entities.Homework as EntitiesHomework import io.github.wulkanowy.data.db.entities.Homework as EntitiesHomework
@ -52,7 +53,7 @@ sealed class DashboardItem(val type: Type) {
data class Grades( data class Grades(
val subjectWithGrades: Map<String, List<Grade>>? = null, val subjectWithGrades: Map<String, List<Grade>>? = null,
val gradeTheme: String? = null, val gradeTheme: GradeColorTheme? = null,
override val error: Throwable? = null, override val error: Throwable? = null,
override val isLoading: Boolean = false override val isLoading: Boolean = false
) : DashboardItem(Type.GRADES) { ) : DashboardItem(Type.GRADES) {

View File

@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.modules.dashboard
import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.Resource
import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.db.entities.AdminMessage
import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.enums.MessageFolder import io.github.wulkanowy.data.enums.MessageFolder
@ -81,6 +82,12 @@ class DashboardPresenter @Inject constructor(
.launch("dashboard_pref") .launch("dashboard_pref")
} }
fun onAdminMessageDismissed(adminMessage: AdminMessage) {
preferencesRepository.dismissedAdminMessageIds += adminMessage.id
loadData(preferencesRepository.selectedDashboardTiles)
}
fun onDragAndDropEnd(list: List<DashboardItem>) { fun onDragAndDropEnd(list: List<DashboardItem>) {
with(dashboardItemLoadedList) { with(dashboardItemLoadedList) {
clear() clear()
@ -117,6 +124,7 @@ class DashboardPresenter @Inject constructor(
forceRefresh: Boolean forceRefresh: Boolean
) = dashboardTilesToLoad.filter { newItemToLoad -> ) = dashboardTilesToLoad.filter { newItemToLoad ->
dashboardLoadedTiles.none { it == newItemToLoad } || forceRefresh dashboardLoadedTiles.none { it == newItemToLoad } || forceRefresh
|| newItemToLoad == DashboardItem.Tile.ADMIN_MESSAGE
} }
private fun removeUnselectedTiles(tilesToLoad: List<DashboardItem.Tile>) { private fun removeUnselectedTiles(tilesToLoad: List<DashboardItem.Tile>) {
@ -575,6 +583,10 @@ class DashboardPresenter @Inject constructor(
private fun loadAdminMessage(student: Student, forceRefresh: Boolean) { private fun loadAdminMessage(student: Student, forceRefresh: Boolean) {
flowWithResourceIn { adminMessageRepository.getAdminMessages(student, forceRefresh) } flowWithResourceIn { adminMessageRepository.getAdminMessages(student, forceRefresh) }
.map {
val isDismissed = it.data?.id in preferencesRepository.dismissedAdminMessageIds
it.copy(data = it.data.takeUnless { isDismissed })
}
.onEach { .onEach {
when (it.status) { when (it.status) {
Status.LOADING -> { Status.LOADING -> {
@ -617,11 +629,16 @@ class DashboardPresenter @Inject constructor(
sortDashboardItems() sortDashboardItems()
if (dashboardItem is DashboardItem.AdminMessages && !dashboardItem.isDataLoaded) { if (dashboardItem is DashboardItem.AdminMessages) {
if (!dashboardItem.isDataLoaded) {
dashboardItemsToLoad = dashboardItemsToLoad - DashboardItem.Type.ADMIN_MESSAGE dashboardItemsToLoad = dashboardItemsToLoad - DashboardItem.Type.ADMIN_MESSAGE
dashboardTileLoadedList = dashboardTileLoadedList - DashboardItem.Tile.ADMIN_MESSAGE dashboardTileLoadedList = dashboardTileLoadedList - DashboardItem.Tile.ADMIN_MESSAGE
dashboardItemLoadedList.removeAll { it.type == DashboardItem.Type.ADMIN_MESSAGE } dashboardItemLoadedList.removeAll { it.type == DashboardItem.Type.ADMIN_MESSAGE }
} else {
dashboardItemsToLoad = dashboardItemsToLoad + DashboardItem.Type.ADMIN_MESSAGE
dashboardTileLoadedList = dashboardTileLoadedList + DashboardItem.Tile.ADMIN_MESSAGE
}
} }
if (forceRefresh) { if (forceRefresh) {

View File

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

View File

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

View File

@ -11,10 +11,11 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.NO_POSITION import androidx.recyclerview.widget.RecyclerView.NO_POSITION
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.enums.GradeColorTheme
import io.github.wulkanowy.data.enums.GradeExpandMode
import io.github.wulkanowy.databinding.HeaderGradeDetailsBinding import io.github.wulkanowy.databinding.HeaderGradeDetailsBinding
import io.github.wulkanowy.databinding.ItemGradeDetailsBinding import io.github.wulkanowy.databinding.ItemGradeDetailsBinding
import io.github.wulkanowy.ui.base.BaseExpandableAdapter import io.github.wulkanowy.ui.base.BaseExpandableAdapter
import io.github.wulkanowy.ui.modules.grade.GradeExpandMode
import io.github.wulkanowy.utils.getBackgroundColor import io.github.wulkanowy.utils.getBackgroundColor
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
import timber.log.Timber import timber.log.Timber
@ -33,7 +34,7 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<Recycler
var onClickListener: (Grade, position: Int) -> Unit = { _, _ -> } var onClickListener: (Grade, position: Int) -> Unit = { _, _ -> }
var colorTheme = "" lateinit var gradeColorTheme: GradeColorTheme
fun setDataItems(data: List<GradeDetailsItem>, expandMode: GradeExpandMode = this.expandMode) { fun setDataItems(data: List<GradeDetailsItem>, expandMode: GradeExpandMode = this.expandMode) {
headers = data.filter { it.viewType == ViewType.HEADER }.toMutableList() headers = data.filter { it.viewType == ViewType.HEADER }.toMutableList()
@ -202,7 +203,7 @@ class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<Recycler
with(holder.binding) { with(holder.binding) {
gradeItemValue.run { gradeItemValue.run {
text = grade.entry text = grade.entry
setBackgroundResource(grade.getBackgroundColor(colorTheme)) setBackgroundResource(grade.getBackgroundColor(gradeColorTheme))
} }
gradeItemDescription.text = when { gradeItemDescription.text = when {
grade.description.isNotBlank() -> grade.description grade.description.isNotBlank() -> grade.description

View File

@ -8,6 +8,7 @@ import android.view.ViewGroup
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.enums.GradeColorTheme
import io.github.wulkanowy.databinding.DialogGradeBinding import io.github.wulkanowy.databinding.DialogGradeBinding
import io.github.wulkanowy.utils.colorStringId import io.github.wulkanowy.utils.colorStringId
import io.github.wulkanowy.utils.getBackgroundColor import io.github.wulkanowy.utils.getBackgroundColor
@ -21,19 +22,19 @@ class GradeDetailsDialog : DialogFragment() {
private lateinit var grade: Grade private lateinit var grade: Grade
private lateinit var colorScheme: String private lateinit var gradeColorTheme: GradeColorTheme
companion object { companion object {
private const val ARGUMENT_KEY = "Item" private const val ARGUMENT_KEY = "Item"
private const val COLOR_SCHEME_KEY = "Scheme" private const val COLOR_THEME_KEY = "Theme"
fun newInstance(grade: Grade, colorScheme: String) = fun newInstance(grade: Grade, colorTheme: GradeColorTheme) =
GradeDetailsDialog().apply { GradeDetailsDialog().apply {
arguments = Bundle().apply { arguments = Bundle().apply {
putSerializable(ARGUMENT_KEY, grade) putSerializable(ARGUMENT_KEY, grade)
putString(COLOR_SCHEME_KEY, colorScheme) putSerializable(COLOR_THEME_KEY, colorTheme)
} }
} }
} }
@ -43,7 +44,7 @@ class GradeDetailsDialog : DialogFragment() {
setStyle(STYLE_NO_TITLE, 0) setStyle(STYLE_NO_TITLE, 0)
arguments?.run { arguments?.run {
grade = getSerializable(ARGUMENT_KEY) as Grade grade = getSerializable(ARGUMENT_KEY) as Grade
colorScheme = getString(COLOR_SCHEME_KEY) ?: "default" gradeColorTheme = getSerializable(COLOR_THEME_KEY) as GradeColorTheme
} }
} }
@ -76,7 +77,7 @@ class GradeDetailsDialog : DialogFragment() {
gradeDialogValue.run { gradeDialogValue.run {
text = grade.entry text = grade.entry
setBackgroundResource(grade.getBackgroundColor(colorScheme)) setBackgroundResource(grade.getBackgroundColor(gradeColorTheme))
} }
gradeDialogTeacherValue.text = if (grade.teacher.isBlank()) { gradeDialogTeacherValue.text = if (grade.teacher.isBlank()) {

View File

@ -12,7 +12,8 @@ import androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.ui.modules.grade.GradeExpandMode import io.github.wulkanowy.data.enums.GradeColorTheme
import io.github.wulkanowy.data.enums.GradeExpandMode
import io.github.wulkanowy.databinding.FragmentGradeDetailsBinding import io.github.wulkanowy.databinding.FragmentGradeDetailsBinding
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment
@ -80,9 +81,9 @@ class GradeDetailsFragment :
else false else false
} }
override fun updateData(data: List<GradeDetailsItem>, expandMode: GradeExpandMode, gradeColorTheme: String) { override fun updateData(data: List<GradeDetailsItem>, expandMode: GradeExpandMode, gradeColorTheme: GradeColorTheme) {
with(gradeDetailsAdapter) { with(gradeDetailsAdapter) {
colorTheme = gradeColorTheme this.gradeColorTheme = gradeColorTheme
setDataItems(data, expandMode) setDataItems(data, expandMode)
notifyDataSetChanged() notifyDataSetChanged()
} }
@ -143,8 +144,8 @@ class GradeDetailsFragment :
binding.gradeDetailsSwipe.isRefreshing = show binding.gradeDetailsSwipe.isRefreshing = show
} }
override fun showGradeDialog(grade: Grade, colorScheme: String) { override fun showGradeDialog(grade: Grade, colorTheme: GradeColorTheme) {
(activity as? MainActivity)?.showDialogFragment(GradeDetailsDialog.newInstance(grade, colorScheme)) (activity as? MainActivity)?.showDialogFragment(GradeDetailsDialog.newInstance(grade, colorTheme))
} }
override fun onParentLoadData(semesterId: Int, forceRefresh: Boolean) { override fun onParentLoadData(semesterId: Int, forceRefresh: Boolean) {

View File

@ -2,6 +2,9 @@ package io.github.wulkanowy.ui.modules.grade.details
import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.enums.GradeExpandMode
import io.github.wulkanowy.data.enums.GradeSortingMode.ALPHABETIC
import io.github.wulkanowy.data.enums.GradeSortingMode.DATE
import io.github.wulkanowy.data.repositories.GradeRepository import io.github.wulkanowy.data.repositories.GradeRepository
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.SemesterRepository
@ -9,9 +12,6 @@ import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider
import io.github.wulkanowy.ui.modules.grade.GradeExpandMode
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode.ALPHABETIC
import io.github.wulkanowy.ui.modules.grade.GradeSortingMode.DATE
import io.github.wulkanowy.ui.modules.grade.GradeSubject import io.github.wulkanowy.ui.modules.grade.GradeSubject
import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.afterLoading import io.github.wulkanowy.utils.afterLoading
@ -230,10 +230,14 @@ class GradeDetailsPresenter @Inject constructor(
gradesWithAverages.filter { it.grades.isNotEmpty() } gradesWithAverages.filter { it.grades.isNotEmpty() }
} else gradesWithAverages } else gradesWithAverages
} }
.let { .let { gradeSubjects ->
when (preferencesRepository.gradeSortingMode) { when (preferencesRepository.gradeSortingMode) {
DATE -> it.sortedByDescending { gradeDetailsWithAverage -> gradeDetailsWithAverage.grades.firstOrNull()?.date } DATE -> gradeSubjects.sortedByDescending { gradeDetailsWithAverage ->
ALPHABETIC -> it.sortedBy { gradeDetailsWithAverage -> gradeDetailsWithAverage.subject.lowercase() } gradeDetailsWithAverage.grades.maxByOrNull { it.date }?.date
}
ALPHABETIC -> gradeSubjects.sortedBy { gradeDetailsWithAverage ->
gradeDetailsWithAverage.subject.lowercase()
}
} }
} }
.map { (subject, average, points, _, grades) -> .map { (subject, average, points, _, grades) ->

View File

@ -1,7 +1,8 @@
package io.github.wulkanowy.ui.modules.grade.details package io.github.wulkanowy.ui.modules.grade.details
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.ui.modules.grade.GradeExpandMode import io.github.wulkanowy.data.enums.GradeColorTheme
import io.github.wulkanowy.data.enums.GradeExpandMode
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
interface GradeDetailsView : BaseView { interface GradeDetailsView : BaseView {
@ -10,7 +11,7 @@ interface GradeDetailsView : BaseView {
fun initView() fun initView()
fun updateData(data: List<GradeDetailsItem>, expandMode: GradeExpandMode, gradeColorTheme: String) fun updateData(data: List<GradeDetailsItem>, expandMode: GradeExpandMode, gradeColorTheme: GradeColorTheme)
fun updateItem(item: Grade, position: Int) fun updateItem(item: Grade, position: Int)
@ -22,7 +23,7 @@ interface GradeDetailsView : BaseView {
fun collapseAllItems() fun collapseAllItems()
fun showGradeDialog(grade: Grade, colorScheme: String) fun showGradeDialog(grade: Grade, colorTheme: GradeColorTheme)
fun showContent(show: Boolean) fun showContent(show: Boolean)

View File

@ -20,6 +20,7 @@ import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.GradePartialStatistics import io.github.wulkanowy.data.db.entities.GradePartialStatistics
import io.github.wulkanowy.data.db.entities.GradePointsStatistics import io.github.wulkanowy.data.db.entities.GradePointsStatistics
import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics import io.github.wulkanowy.data.db.entities.GradeSemesterStatistics
import io.github.wulkanowy.data.enums.GradeColorTheme
import io.github.wulkanowy.data.pojos.GradeStatisticsItem import io.github.wulkanowy.data.pojos.GradeStatisticsItem
import io.github.wulkanowy.databinding.ItemGradeStatisticsBarBinding import io.github.wulkanowy.databinding.ItemGradeStatisticsBarBinding
import io.github.wulkanowy.databinding.ItemGradeStatisticsHeaderBinding import io.github.wulkanowy.databinding.ItemGradeStatisticsHeaderBinding
@ -34,7 +35,7 @@ class GradeStatisticsAdapter @Inject constructor() :
var items = emptyList<GradeStatisticsItem>() var items = emptyList<GradeStatisticsItem>()
var theme: String = "vulcan" lateinit var gradeColorTheme: GradeColorTheme
var showAllSubjectsOnList: Boolean = false var showAllSubjectsOnList: Boolean = false
@ -156,8 +157,8 @@ class GradeStatisticsAdapter @Inject constructor() :
visibility = if (items.size == 1 || !showAllSubjectsOnList) GONE else VISIBLE visibility = if (items.size == 1 || !showAllSubjectsOnList) GONE else VISIBLE
} }
val gradeColors = when (theme) { val gradeColors = when (gradeColorTheme) {
"vulcan" -> vulcanGradeColors GradeColorTheme.VULCAN -> vulcanGradeColors
else -> materialGradeColors else -> materialGradeColors
} }

View File

@ -7,6 +7,7 @@ import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.enums.GradeColorTheme
import io.github.wulkanowy.data.pojos.GradeStatisticsItem import io.github.wulkanowy.data.pojos.GradeStatisticsItem
import io.github.wulkanowy.databinding.FragmentGradeStatisticsBinding import io.github.wulkanowy.databinding.FragmentGradeStatisticsBinding
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
@ -43,7 +44,7 @@ class GradeStatisticsFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding = FragmentGradeStatisticsBinding.bind(view) binding = FragmentGradeStatisticsBinding.bind(view)
messageContainer = binding.gradeStatisticsSwipe messageContainer = binding.gradeStatisticsRecycler
presenter.onAttachView( presenter.onAttachView(
this, this,
savedInstanceState?.getSerializable(SAVED_CHART_TYPE) as? GradeStatisticsItem.DataType savedInstanceState?.getSerializable(SAVED_CHART_TYPE) as? GradeStatisticsItem.DataType
@ -90,12 +91,12 @@ class GradeStatisticsFragment :
override fun updateData( override fun updateData(
newItems: List<GradeStatisticsItem>, newItems: List<GradeStatisticsItem>,
newTheme: String, newTheme: GradeColorTheme,
showAllSubjectsOnStatisticsList: Boolean showAllSubjectsOnStatisticsList: Boolean
) { ) {
with(statisticsAdapter) { with(statisticsAdapter) {
showAllSubjectsOnList = showAllSubjectsOnStatisticsList showAllSubjectsOnList = showAllSubjectsOnStatisticsList
theme = newTheme gradeColorTheme = newTheme
items = newItems items = newItems
notifyDataSetChanged() notifyDataSetChanged()
} }

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.grade.statistics package io.github.wulkanowy.ui.modules.grade.statistics
import io.github.wulkanowy.data.enums.GradeColorTheme
import io.github.wulkanowy.data.pojos.GradeStatisticsItem import io.github.wulkanowy.data.pojos.GradeStatisticsItem
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
@ -15,7 +16,7 @@ interface GradeStatisticsView : BaseView {
fun updateData( fun updateData(
newItems: List<GradeStatisticsItem>, newItems: List<GradeStatisticsItem>,
newTheme: String, newTheme: GradeColorTheme,
showAllSubjectsOnStatisticsList: Boolean showAllSubjectsOnStatisticsList: Boolean
) )

View File

@ -72,9 +72,9 @@ class LoginActivity : BaseActivity<LoginPresenter, ActivityLoginBinding>(), Logi
} }
with(binding.loginViewpager) { with(binding.loginViewpager) {
offscreenPageLimit = 2
adapter = pagerAdapter adapter = pagerAdapter
isUserInputEnabled = false isUserInputEnabled = false
offscreenPageLimit = 2
setOnSelectPageListener(presenter::onViewSelected) setOnSelectPageListener(presenter::onViewSelected)
} }

View File

@ -52,6 +52,8 @@ class LoginFormPresenter @Inject constructor(
clearHostError() clearHostError()
if (formHostValue.contains("fakelog")) { if (formHostValue.contains("fakelog")) {
setCredentials("jan@fakelog.cf", "jan123") setCredentials("jan@fakelog.cf", "jan123")
} else if (formUsernameValue == "jan@fakelog.cf" && formPassValue == "jan123") {
setCredentials("", "")
} }
updateUsernameLabel() updateUsernameLabel()
} }

View File

@ -50,7 +50,10 @@ class LoginSymbolPresenter @Inject constructor(
} }
fun attemptLogin(symbol: String) { fun attemptLogin(symbol: String) {
if (loginData == null) throw IllegalArgumentException("Login data is null") if (loginData == null) {
Timber.w("LoginSymbolPresenter - Login data is null")
return
}
if (symbol.isBlank()) { if (symbol.isBlank()) {
view?.setErrorSymbolRequire() view?.setErrorSymbolRequire()

View File

@ -56,7 +56,7 @@ class SendMessagePresenter @Inject constructor(
} }
message?.let { message?.let {
setSubject(when (reply) { setSubject(when (reply) {
true -> "RE: " true -> "Re: "
else -> "FW: " else -> "FW: "
} + message.subject) } + message.subject)
if (preferencesRepository.fillMessageContent || reply != true) { if (preferencesRepository.fillMessageContent || reply != true) {

View File

@ -74,7 +74,7 @@ class SchoolAnnouncementPresenter @Inject constructor(
Status.SUCCESS -> { Status.SUCCESS -> {
Timber.i("Loading School announcement result: Success") Timber.i("Loading School announcement result: Success")
view?.apply { view?.apply {
updateData(it.data!!.sortedByDescending { item -> item.date }) updateData(it.data!!)
showEmpty(it.data.isEmpty()) showEmpty(it.data.isEmpty())
showErrorView(false) showErrorView(false)
showContent(it.data.isNotEmpty()) showContent(it.data.isNotEmpty())

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.settings.notifications package io.github.wulkanowy.ui.modules.settings.notifications
import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.net.Uri import android.net.Uri
@ -50,10 +51,14 @@ class NotificationsFragment : PreferenceFragmentCompat(),
return appPackageName in packageNameList return appPackageName in packageNameList
} }
private val notificationSettingsContract = private val notificationSettingsPiggybackContract =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
presenter.onNotificationPiggybackPermissionResult()
}
presenter.onNotificationPermissionResult() private val notificationSettingsExactAlarmsContract =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
presenter.onNotificationExactAlarmPermissionResult()
} }
override fun initView(showDebugNotificationSwitch: Boolean) { override fun initView(showDebugNotificationSwitch: Boolean) {
@ -136,7 +141,7 @@ class NotificationsFragment : PreferenceFragmentCompat(),
.setTitle(R.string.pref_notify_fix_sync_issues) .setTitle(R.string.pref_notify_fix_sync_issues)
.setMessage(R.string.pref_notify_fix_sync_issues_message) .setMessage(R.string.pref_notify_fix_sync_issues_message)
.setNegativeButton(android.R.string.cancel) { _, _ -> } .setNegativeButton(android.R.string.cancel) { _, _ -> }
.setPositiveButton(R.string.pref_notify_fix_sync_issues_settings_button) { _, _ -> .setPositiveButton(R.string.pref_notify_open_system_settings) { _, _ ->
try { try {
AppKillerManager.doActionPowerSaving(requireContext()) AppKillerManager.doActionPowerSaving(requireContext())
AppKillerManager.doActionAutoStart(requireContext()) AppKillerManager.doActionAutoStart(requireContext())
@ -151,6 +156,7 @@ class NotificationsFragment : PreferenceFragmentCompat(),
.show() .show()
} }
@SuppressLint("InlinedApi")
override fun openSystemSettings() { override fun openSystemSettings() {
val intent = if (appInfo.systemVersion >= Build.VERSION_CODES.O) { val intent = if (appInfo.systemVersion >= Build.VERSION_CODES.O) {
Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply { Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply {
@ -172,8 +178,8 @@ class NotificationsFragment : PreferenceFragmentCompat(),
AlertDialog.Builder(requireContext()) AlertDialog.Builder(requireContext())
.setTitle(getString(R.string.pref_notification_piggyback_popup_title)) .setTitle(getString(R.string.pref_notification_piggyback_popup_title))
.setMessage(getString(R.string.pref_notification_piggyback_popup_description)) .setMessage(getString(R.string.pref_notification_piggyback_popup_description))
.setPositiveButton(getString(R.string.pref_notification_piggyback_popup_positive)) { _, _ -> .setPositiveButton(getString(R.string.pref_notification_go_to_settings)) { _, _ ->
notificationSettingsContract.launch(Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS")) notificationSettingsPiggybackContract.launch(Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"))
} }
.setNegativeButton(android.R.string.cancel) { _, _ -> .setNegativeButton(android.R.string.cancel) { _, _ ->
setNotificationPiggybackPreferenceChecked(false) setNotificationPiggybackPreferenceChecked(false)
@ -182,11 +188,30 @@ class NotificationsFragment : PreferenceFragmentCompat(),
.show() .show()
} }
override fun openNotificationExactAlarmSettings() {
AlertDialog.Builder(requireContext())
.setTitle(getString(R.string.pref_notification_exact_alarm_popup_title))
.setMessage(getString(R.string.pref_notification_exact_alarm_popup_descriptions))
.setPositiveButton(getString(R.string.pref_notification_go_to_settings)) { _, _ ->
notificationSettingsExactAlarmsContract.launch(Intent("android.settings.REQUEST_SCHEDULE_EXACT_ALARM"))
}
.setNegativeButton(android.R.string.cancel) { _, _ ->
setUpcomingLessonsNotificationPreferenceChecked(false)
}
.setOnDismissListener { setUpcomingLessonsNotificationPreferenceChecked(false) }
.show()
}
override fun setNotificationPiggybackPreferenceChecked(isChecked: Boolean) { override fun setNotificationPiggybackPreferenceChecked(isChecked: Boolean) {
findPreference<SwitchPreferenceCompat>(getString(R.string.pref_key_notifications_piggyback))?.isChecked = findPreference<SwitchPreferenceCompat>(getString(R.string.pref_key_notifications_piggyback))?.isChecked =
isChecked isChecked
} }
override fun setUpcomingLessonsNotificationPreferenceChecked(isChecked: Boolean) {
findPreference<SwitchPreferenceCompat>(getString(R.string.pref_key_notifications_upcoming_lessons_enable))?.isChecked =
isChecked
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this) preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(this)

View File

@ -45,6 +45,8 @@ class NotificationsPresenter @Inject constructor(
isUpcomingLessonsNotificationsEnableKey, isUpcomingLessonsNotificationsPersistentKey -> { isUpcomingLessonsNotificationsEnableKey, isUpcomingLessonsNotificationsPersistentKey -> {
if (!isUpcomingLessonsNotificationsEnable) { if (!isUpcomingLessonsNotificationsEnable) {
timetableNotificationHelper.cancelNotification() timetableNotificationHelper.cancelNotification()
} else if (!timetableNotificationHelper.canScheduleExactAlarms()) {
view?.openNotificationExactAlarmSettings()
} }
} }
isDebugNotificationEnableKey -> { isDebugNotificationEnableKey -> {
@ -68,12 +70,16 @@ class NotificationsPresenter @Inject constructor(
view?.openSystemSettings() view?.openSystemSettings()
} }
fun onNotificationPermissionResult() { fun onNotificationPiggybackPermissionResult() {
view?.run { view?.run {
setNotificationPiggybackPreferenceChecked(isNotificationPermissionGranted) setNotificationPiggybackPreferenceChecked(isNotificationPermissionGranted)
} }
} }
fun onNotificationExactAlarmPermissionResult() {
view?.setUpcomingLessonsNotificationPreferenceChecked(timetableNotificationHelper.canScheduleExactAlarms())
}
private fun checkNotificationPiggybackState() { private fun checkNotificationPiggybackState() {
if (preferencesRepository.isNotificationPiggybackEnabled) { if (preferencesRepository.isNotificationPiggybackEnabled) {
view?.run { view?.run {

View File

@ -16,5 +16,9 @@ interface NotificationsView : BaseView {
fun openNotificationPermissionDialog() fun openNotificationPermissionDialog()
fun openNotificationExactAlarmSettings()
fun setNotificationPiggybackPreferenceChecked(isChecked: Boolean) fun setNotificationPiggybackPreferenceChecked(isChecked: Boolean)
fun setUpcomingLessonsNotificationPreferenceChecked(isChecked: Boolean)
} }

View File

@ -12,6 +12,7 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.enums.TimetableMode
import io.github.wulkanowy.databinding.ItemTimetableBinding import io.github.wulkanowy.databinding.ItemTimetableBinding
import io.github.wulkanowy.databinding.ItemTimetableSmallBinding import io.github.wulkanowy.databinding.ItemTimetableSmallBinding
import io.github.wulkanowy.utils.getThemeAttrColor import io.github.wulkanowy.utils.getThemeAttrColor
@ -35,7 +36,7 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
var onClickListener: (Timetable) -> Unit = {} var onClickListener: (Timetable) -> Unit = {}
private var showWholeClassPlan: String = "no" private var showWholeClassPlan = TimetableMode.ONLY_CURRENT_GROUP
private var showGroupsInPlan: Boolean = false private var showGroupsInPlan: Boolean = false
@ -47,7 +48,7 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
fun submitList( fun submitList(
newTimetable: List<Timetable>, newTimetable: List<Timetable>,
showWholeClassPlan: String = this.showWholeClassPlan, showWholeClassPlan: TimetableMode = this.showWholeClassPlan,
showGroupsInPlan: Boolean = this.showGroupsInPlan, showGroupsInPlan: Boolean = this.showGroupsInPlan,
showTimers: Boolean = this.showTimers showTimers: Boolean = this.showTimers
) { ) {
@ -87,7 +88,7 @@ class TimetableAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
override fun getItemCount() = items.size override fun getItemCount() = items.size
override fun getItemViewType(position: Int) = when { override fun getItemViewType(position: Int) = when {
!items[position].isStudentPlan && showWholeClassPlan == "small" -> ViewType.ITEM_SMALL.ordinal !items[position].isStudentPlan && showWholeClassPlan == TimetableMode.SMALL_OTHER_GROUP -> ViewType.ITEM_SMALL.ordinal
else -> ViewType.ITEM_NORMAL.ordinal else -> ViewType.ITEM_NORMAL.ordinal
} }

View File

@ -14,6 +14,7 @@ import com.google.android.material.datepicker.MaterialDatePicker
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.enums.TimetableMode
import io.github.wulkanowy.databinding.FragmentTimetableBinding import io.github.wulkanowy.databinding.FragmentTimetableBinding
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
@ -115,7 +116,7 @@ class TimetableFragment : BaseFragment<FragmentTimetableBinding>(R.layout.fragme
override fun updateData( override fun updateData(
data: List<Timetable>, data: List<Timetable>,
showWholeClassPlanType: String, showWholeClassPlanType: TimetableMode,
showGroupsInPlanType: Boolean, showGroupsInPlanType: Boolean,
showTimetableTimers: Boolean showTimetableTimers: Boolean
) { ) {

View File

@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.modules.timetable
import android.annotation.SuppressLint import android.annotation.SuppressLint
import io.github.wulkanowy.data.Status import io.github.wulkanowy.data.Status
import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.enums.TimetableMode
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
@ -197,7 +198,7 @@ class TimetablePresenter @Inject constructor(
} }
private fun createItems(items: List<Timetable>) = items.filter { item -> private fun createItems(items: List<Timetable>) = items.filter { item ->
if (prefRepository.showWholeClassPlan == "no") item.isStudentPlan else true if (prefRepository.showWholeClassPlan == TimetableMode.ONLY_CURRENT_GROUP) item.isStudentPlan else true
}.sortedWith(compareBy({ item -> item.number }, { item -> !item.isStudentPlan })) }.sortedWith(compareBy({ item -> item.number }, { item -> !item.isStudentPlan }))
private fun showErrorViewOnError(message: String, error: Throwable) { private fun showErrorViewOnError(message: String, error: Throwable) {

View File

@ -1,6 +1,7 @@
package io.github.wulkanowy.ui.modules.timetable package io.github.wulkanowy.ui.modules.timetable
import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.enums.TimetableMode
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
import java.time.LocalDate import java.time.LocalDate
@ -12,7 +13,12 @@ interface TimetableView : BaseView {
fun initView() fun initView()
fun updateData(data: List<Timetable>, showWholeClassPlanType: String, showGroupsInPlanType: Boolean, showTimetableTimers: Boolean) fun updateData(
data: List<Timetable>,
showWholeClassPlanType: TimetableMode,
showGroupsInPlanType: Boolean,
showTimetableTimers: Boolean
)
fun updateNavigationDay(date: String) fun updateNavigationDay(date: String)

View File

@ -14,6 +14,7 @@ import android.widget.RemoteViewsService
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.enums.TimetableMode
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.data.repositories.SemesterRepository import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
@ -88,7 +89,7 @@ class TimetableWidgetFactory(
private fun getItemLayout(lesson: Timetable): Int { private fun getItemLayout(lesson: Timetable): Int {
return when { return when {
prefRepository.showWholeClassPlan == "small" && !lesson.isStudentPlan -> { prefRepository.showWholeClassPlan == TimetableMode.SMALL_OTHER_GROUP && !lesson.isStudentPlan -> {
if (savedCurrentTheme == 0L) R.layout.item_widget_timetable_small if (savedCurrentTheme == 0L) R.layout.item_widget_timetable_small
else R.layout.item_widget_timetable_small_dark else R.layout.item_widget_timetable_small_dark
} }
@ -109,7 +110,11 @@ class TimetableWidgetFactory(
timetableRepository.getTimetable(student, semester, date, date, false) timetableRepository.getTimetable(student, semester, date, date, false)
.toFirstResult().data?.lessons.orEmpty() .toFirstResult().data?.lessons.orEmpty()
.sortedWith(compareBy({ it.number }, { !it.isStudentPlan })) .sortedWith(compareBy({ it.number }, { !it.isStudentPlan }))
.filter { if (prefRepository.showWholeClassPlan == "no") it.isStudentPlan else true } .filter {
if (prefRepository.showWholeClassPlan == TimetableMode.ONLY_CURRENT_GROUP) {
it.isStudentPlan
} else true
}
} }
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "An error has occurred in timetable widget factory") Timber.e(e, "An error has occurred in timetable widget factory")
@ -124,8 +129,14 @@ class TimetableWidgetFactory(
return RemoteViews(context.packageName, getItemLayout(lesson)).apply { return RemoteViews(context.packageName, getItemLayout(lesson)).apply {
setTextViewText(R.id.timetableWidgetItemSubject, lesson.subject) setTextViewText(R.id.timetableWidgetItemSubject, lesson.subject)
setTextViewText(R.id.timetableWidgetItemNumber, lesson.number.toString()) setTextViewText(R.id.timetableWidgetItemNumber, lesson.number.toString())
setTextViewText(R.id.timetableWidgetItemTimeStart, lesson.start.toFormattedString("HH:mm")) setTextViewText(
setTextViewText(R.id.timetableWidgetItemTimeFinish, lesson.end.toFormattedString("HH:mm")) R.id.timetableWidgetItemTimeStart,
lesson.start.toFormattedString("HH:mm")
)
setTextViewText(
R.id.timetableWidgetItemTimeFinish,
lesson.end.toFormattedString("HH:mm")
)
updateDescription(this, lesson) updateDescription(this, lesson)
@ -156,11 +167,16 @@ class TimetableWidgetFactory(
private fun updateStylesCanceled(remoteViews: RemoteViews) { private fun updateStylesCanceled(remoteViews: RemoteViews) {
with(remoteViews) { with(remoteViews) {
setInt(R.id.timetableWidgetItemSubject, "setPaintFlags", setInt(
STRIKE_THRU_TEXT_FLAG or ANTI_ALIAS_FLAG) R.id.timetableWidgetItemSubject, "setPaintFlags",
STRIKE_THRU_TEXT_FLAG or ANTI_ALIAS_FLAG
)
setTextColor(R.id.timetableWidgetItemNumber, context.getCompatColor(primaryColor!!)) setTextColor(R.id.timetableWidgetItemNumber, context.getCompatColor(primaryColor!!))
setTextColor(R.id.timetableWidgetItemSubject, context.getCompatColor(primaryColor!!)) setTextColor(R.id.timetableWidgetItemSubject, context.getCompatColor(primaryColor!!))
setTextColor(R.id.timetableWidgetItemDescription, context.getCompatColor(primaryColor!!)) setTextColor(
R.id.timetableWidgetItemDescription,
context.getCompatColor(primaryColor!!)
)
} }
} }
@ -168,7 +184,10 @@ class TimetableWidgetFactory(
with(remoteViews) { with(remoteViews) {
setInt(R.id.timetableWidgetItemSubject, "setPaintFlags", ANTI_ALIAS_FLAG) setInt(R.id.timetableWidgetItemSubject, "setPaintFlags", ANTI_ALIAS_FLAG)
setTextColor(R.id.timetableWidgetItemSubject, context.getCompatColor(textColor!!)) setTextColor(R.id.timetableWidgetItemSubject, context.getCompatColor(textColor!!))
setTextColor(R.id.timetableWidgetItemDescription, context.getCompatColor(timetableChangeColor!!)) setTextColor(
R.id.timetableWidgetItemDescription,
context.getCompatColor(timetableChangeColor!!)
)
updateNotCanceledLessonNumberColor(this, lesson) updateNotCanceledLessonNumberColor(this, lesson)
updateNotCanceledSubjectColor(this, lesson) updateNotCanceledSubjectColor(this, lesson)
@ -180,37 +199,53 @@ class TimetableWidgetFactory(
} }
private fun updateNotCanceledLessonNumberColor(remoteViews: RemoteViews, lesson: Timetable) { private fun updateNotCanceledLessonNumberColor(remoteViews: RemoteViews, lesson: Timetable) {
remoteViews.setTextColor(R.id.timetableWidgetItemNumber, context.getCompatColor( remoteViews.setTextColor(
R.id.timetableWidgetItemNumber, context.getCompatColor(
if (lesson.changes || (lesson.info.isNotBlank() && !lesson.canceled)) timetableChangeColor!! if (lesson.changes || (lesson.info.isNotBlank() && !lesson.canceled)) timetableChangeColor!!
else textColor!! else textColor!!
)) )
)
} }
private fun updateNotCanceledSubjectColor(remoteViews: RemoteViews, lesson: Timetable) { private fun updateNotCanceledSubjectColor(remoteViews: RemoteViews, lesson: Timetable) {
remoteViews.setTextColor(R.id.timetableWidgetItemSubject, context.getCompatColor( remoteViews.setTextColor(
R.id.timetableWidgetItemSubject, context.getCompatColor(
if (lesson.subjectOld.isNotBlank() && lesson.subject != lesson.subjectOld) timetableChangeColor!! if (lesson.subjectOld.isNotBlank() && lesson.subject != lesson.subjectOld) timetableChangeColor!!
else textColor!! else textColor!!
)) )
)
} }
private fun updateNotCanceledRoom(remoteViews: RemoteViews, lesson: Timetable, teacherChange: Boolean) { private fun updateNotCanceledRoom(
remoteViews: RemoteViews,
lesson: Timetable,
teacherChange: Boolean
) {
with(remoteViews) { with(remoteViews) {
if (lesson.room.isNotBlank()) { if (lesson.room.isNotBlank()) {
setTextViewText(R.id.timetableWidgetItemRoom, setTextViewText(
R.id.timetableWidgetItemRoom,
if (teacherChange) lesson.room if (teacherChange) lesson.room
else "${context.getString(R.string.timetable_room)} ${lesson.room}" else "${context.getString(R.string.timetable_room)} ${lesson.room}"
) )
setTextColor(R.id.timetableWidgetItemRoom, context.getCompatColor( setTextColor(
R.id.timetableWidgetItemRoom, context.getCompatColor(
if (lesson.roomOld.isNotBlank() && lesson.room != lesson.roomOld) timetableChangeColor!! if (lesson.roomOld.isNotBlank() && lesson.room != lesson.roomOld) timetableChangeColor!!
else textColor!! else textColor!!
)) )
)
} else setTextViewText(R.id.timetableWidgetItemRoom, "") } else setTextViewText(R.id.timetableWidgetItemRoom, "")
} }
} }
private fun updateNotCanceledTeacher(remoteViews: RemoteViews, lesson: Timetable, teacherChange: Boolean) { private fun updateNotCanceledTeacher(
remoteViews.setTextViewText(R.id.timetableWidgetItemTeacher, remoteViews: RemoteViews,
lesson: Timetable,
teacherChange: Boolean
) {
remoteViews.setTextViewText(
R.id.timetableWidgetItemTeacher,
if (teacherChange) lesson.teacher if (teacherChange) lesson.teacher
else "" else ""
) )

View File

@ -3,6 +3,7 @@ package io.github.wulkanowy.utils
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.enums.GradeColorTheme
import io.github.wulkanowy.sdk.scrapper.grades.isGradeValid import io.github.wulkanowy.sdk.scrapper.grades.isGradeValid
fun List<Grade>.calcAverage(isOptionalArithmeticAverage: Boolean): Double { fun List<Grade>.calcAverage(isOptionalArithmeticAverage: Boolean): Double {
@ -37,28 +38,6 @@ fun List<GradeSummary>.calcFinalAverage(plusModifier: Double, minusModifier: Dou
.average() .average()
.let { if (it.isNaN()) 0.0 else it } .let { if (it.isNaN()) 0.0 else it }
fun Grade.getBackgroundColor(theme: String) = when (theme) {
"grade_color" -> getGradeColor()
"material" -> when (value.toInt()) {
6 -> R.color.grade_material_six
5 -> R.color.grade_material_five
4 -> R.color.grade_material_four
3 -> R.color.grade_material_three
2 -> R.color.grade_material_two
1 -> R.color.grade_material_one
else -> R.color.grade_material_default
}
else -> when (value.toInt()) {
6 -> R.color.grade_vulcan_six
5 -> R.color.grade_vulcan_five
4 -> R.color.grade_vulcan_four
3 -> R.color.grade_vulcan_three
2 -> R.color.grade_vulcan_two
1 -> R.color.grade_vulcan_one
else -> R.color.grade_vulcan_default
}
}
fun Grade.getGradeColor() = when (color) { fun Grade.getGradeColor() = when (color) {
"000000" -> R.color.grade_black "000000" -> R.color.grade_black
"F04C4C" -> R.color.grade_red "F04C4C" -> R.color.grade_red
@ -83,3 +62,25 @@ fun Grade.changeModifier(plusModifier: Double, minusModifier: Double) = when {
modifier < 0 -> copy(modifier = -minusModifier) modifier < 0 -> copy(modifier = -minusModifier)
else -> this else -> this
} }
fun Grade.getBackgroundColor(theme: GradeColorTheme) = when (theme) {
GradeColorTheme.GRADE_COLOR -> getGradeColor()
GradeColorTheme.MATERIAL -> when (value.toInt()) {
6 -> R.color.grade_material_six
5 -> R.color.grade_material_five
4 -> R.color.grade_material_four
3 -> R.color.grade_material_three
2 -> R.color.grade_material_two
1 -> R.color.grade_material_one
else -> R.color.grade_material_default
}
GradeColorTheme.VULCAN -> when (value.toInt()) {
6 -> R.color.grade_vulcan_six
5 -> R.color.grade_vulcan_five
4 -> R.color.grade_vulcan_four
3 -> R.color.grade_vulcan_three
2 -> R.color.grade_vulcan_two
1 -> R.color.grade_vulcan_one
else -> R.color.grade_vulcan_default
}
}

View File

@ -123,14 +123,6 @@ class FragmentLifecycleLogger @Inject constructor() :
Timber.d("${f::class.java.simpleName} VIEW DESTROYED") Timber.d("${f::class.java.simpleName} VIEW DESTROYED")
} }
override fun onFragmentActivityCreated(
fm: FragmentManager,
f: Fragment,
savedInstanceState: Bundle?
) {
Timber.d("${f::class.java.simpleName} ACTIVITY CREATED ${savedInstanceState.checkSavedState()}")
}
override fun onFragmentPaused(fm: FragmentManager, f: Fragment) { override fun onFragmentPaused(fm: FragmentManager, f: Fragment) {
Timber.d("${f::class.java.simpleName} PAUSED") Timber.d("${f::class.java.simpleName} PAUSED")
} }
@ -141,5 +133,5 @@ class FragmentLifecycleLogger @Inject constructor() :
} }
private fun Bundle?.checkSavedState() = private fun Bundle?.checkSavedState() =
if (this == null) "(STATE IS NULL)" else "(STATE IS NOT NULL)" if (this == null) "(STATE IS NULL)" else "(RESTORE STATE)"

View File

@ -1,10 +1,6 @@
Wersja 1.4.0 Wersja 1.4.4
- dodaliśmy możliwość dodawania własnych zadań domowych - naprawiliśmy logowanie do Gdańskiej Platformy Edukacyjnej
- dodaliśmy kafelek z wiadomościami od twórców - naprawiliśmy sortowanie ocen oraz ogłoszeń
- dodaliśmy dodatkowy tryb rozwijania szczegółów ocen
- dodaliśmy wsparcie dla Androida 12
- ulepszyliśmy powiadomienia na Mi Bandzie
- dokonaliśmy też kilka innych zmian i kosmetycznych poprawek poprawiających komfort używania aplikacji
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases

View File

@ -1,9 +1,14 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical"
tools:ignore="UselessParent">
<io.github.wulkanowy.ui.widgets.MaterialLinearLayout <io.github.wulkanowy.ui.widgets.MaterialLinearLayout
android:id="@+id/gradeStatisticsSubjectsContainer" android:id="@+id/gradeStatisticsSubjectsContainer"
@ -156,3 +161,4 @@
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout> </LinearLayout>
</FrameLayout>

View File

@ -33,8 +33,8 @@
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:textStyle="bold"
android:textSize="18sp" android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/dashboard_admin_message_item_icon" app:layout_constraintStart_toEndOf="@id/dashboard_admin_message_item_icon"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
@ -53,8 +53,23 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_admin_message_item_title" app:layout_constraintTop_toBottomOf="@id/dashboard_admin_message_item_title"
app:layout_constraintVertical_bias="0"
app:lineHeight="20dp" app:lineHeight="20dp"
tools:maxLines="5" tools:maxLines="5"
tools:text="@tools:sample/lorem/random" /> tools:text="@tools:sample/lorem/random" />
<com.google.android.material.button.MaterialButton
android:id="@+id/dashboard_admin_message_item_dismiss"
style="@style/Widget.MaterialComponents.Button.TextButton.Dialog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:text="@android:string/ok"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/dashboard_admin_message_item_description"
app:layout_constraintVertical_bias="0" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>

View File

@ -553,18 +553,6 @@
<string name="dashboard_timetable_first_lesson_title_soon">Brzy:</string> <string name="dashboard_timetable_first_lesson_title_soon">Brzy:</string>
<string name="dashboard_timetable_first_lesson_title_first">První:</string> <string name="dashboard_timetable_first_lesson_title_first">První:</string>
<string name="dashboard_timetable_first_lesson_title_now">Teď:</string> <string name="dashboard_timetable_first_lesson_title_now">Teď:</string>
<plurals name="dashboard_timetable_first_lesson_time_in_minutes">
<item quantity="one">za %1$d minutu</item>
<item quantity="few">za %1$d minuty</item>
<item quantity="many">za %1$d minut</item>
<item quantity="other">za %1$d minut</item>
</plurals>
<plurals name="dashboard_timetable_first_lesson_time_more_minutes">
<item quantity="one">ještě %1$d minutu</item>
<item quantity="few">ještě %1$d minuty</item>
<item quantity="many">ještě %1$d minut</item>
<item quantity="other">ještě %1$d minut</item>
</plurals>
<string name="dashboard_timetable_second_lesson_value_end">Konec lekcí</string> <string name="dashboard_timetable_second_lesson_value_end">Konec lekcí</string>
<string name="dashboard_timetable_second_lessons_title">Další:</string> <string name="dashboard_timetable_second_lessons_title">Další:</string>
<string name="dashboard_timetable_third_title">Později:</string> <string name="dashboard_timetable_third_title">Později:</string>
@ -678,13 +666,14 @@
<string name="pref_notify_open_system_settings">Otevřít systémová nastavení upozornění</string> <string name="pref_notify_open_system_settings">Otevřít systémová nastavení upozornění</string>
<string name="pref_notify_fix_sync_issues">Opravte problémy se synchronizací a upozorněním</string> <string name="pref_notify_fix_sync_issues">Opravte problémy se synchronizací a upozorněním</string>
<string name="pref_notify_fix_sync_issues_message">Vaše zařízení může mít problémy se synchronizací dat as upozorněními.\n\nChcete-li je opravit, přidejte Wulkanového do funkce Autostart a vypněte optimalizaci/úsporu baterie v nastavení systému telefonu.</string> <string name="pref_notify_fix_sync_issues_message">Vaše zařízení může mít problémy se synchronizací dat as upozorněními.\n\nChcete-li je opravit, přidejte Wulkanového do funkce Autostart a vypněte optimalizaci/úsporu baterie v nastavení systému telefonu.</string>
<string name="pref_notify_fix_sync_issues_settings_button">Přejít do nastavení</string>
<string name="pref_notify_debug_switch">Zobrazit upozornění o ladění</string> <string name="pref_notify_debug_switch">Zobrazit upozornění o ladění</string>
<string name="pref_notify_disabled_summary">Synchronizace je vypnutá</string> <string name="pref_notify_disabled_summary">Synchronizace je vypnutá</string>
<string name="pref_notify_notifications_piggyback">Zachytit upozornění oficiální aplikací</string> <string name="pref_notify_notifications_piggyback">Zachytit upozornění oficiální aplikací</string>
<string name="pref_notification_piggyback_popup_title">Zachytit upozornění</string> <string name="pref_notification_piggyback_popup_title">Zachytit upozornění</string>
<string name="pref_notification_piggyback_popup_description">S touto funkcí můžete získat náhradu push upozornění jako v oficiální aplikaci. Vše, co musíte udělat, je povolit Wulkanowému číst všechna vaše upozornění v nastaveních systému.\n\nJak to funguje?\nKdyž obdržíte oznámení v Deníčku VULCAN, Wulkanowy bude o tom informován (k tomu je to dodatečné povolení) a spustí synchronizaci, aby mohl zaslat vlastní upozornění.\n\nPOUZE PRO POKROČILÉ UŽIVATELE</string> <string name="pref_notification_piggyback_popup_description">S touto funkcí můžete získat náhradu push upozornění jako v oficiální aplikaci. Vše, co musíte udělat, je povolit Wulkanowému číst všechna vaše upozornění v nastaveních systému.\n\nJak to funguje?\nKdyž obdržíte oznámení v Deníčku VULCAN, Wulkanowy bude o tom informován (k tomu je to dodatečné povolení) a spustí synchronizaci, aby mohl zaslat vlastní upozornění.\n\nPOUZE PRO POKROČILÉ UŽIVATELE</string>
<string name="pref_notification_piggyback_popup_positive">Přejít do nastavení</string> <string name="pref_notification_exact_alarm_popup_title">Upozornění o nadcházející lekci</string>
<string name="pref_notification_exact_alarm_popup_descriptions">Musíte povolit Wulkanovému nastavit budíky a připomenutí v nastavení vašeho systému pro použití této funkce.</string>
<string name="pref_notification_go_to_settings">Přejít do nastavení</string>
<string name="pref_services_header">Synchronizace</string> <string name="pref_services_header">Synchronizace</string>
<string name="pref_services_switch">Automatická aktualizace</string> <string name="pref_services_switch">Automatická aktualizace</string>
<string name="pref_services_suspended">Pozastaveno na dovolené</string> <string name="pref_services_suspended">Pozastaveno na dovolené</string>
@ -706,6 +695,7 @@
<string name="pref_ads_privacy_agree">Souhlasím</string> <string name="pref_ads_privacy_agree">Souhlasím</string>
<string name="pref_ads_privacy_link">Ochrana osobních údajů</string> <string name="pref_ads_privacy_link">Ochrana osobních údajů</string>
<string name="pref_ads_loading">Reklama se načítá</string> <string name="pref_ads_loading">Reklama se načítá</string>
<string name="pref_ads_once_per_visit">Děkujeme za vaši podporu, vraťte se později pro více reklam</string>
<string name="pref_settings_advanced_title">Pokročilé</string> <string name="pref_settings_advanced_title">Pokročilé</string>
<string name="pref_settings_appearance_title">Vzhled a chování</string> <string name="pref_settings_appearance_title">Vzhled a chování</string>
<string name="pref_settings_notifications_title">Upozornění</string> <string name="pref_settings_notifications_title">Upozornění</string>

View File

@ -477,14 +477,6 @@
<string name="dashboard_timetable_first_lesson_title_soon">Bald:</string> <string name="dashboard_timetable_first_lesson_title_soon">Bald:</string>
<string name="dashboard_timetable_first_lesson_title_first">Erstens:</string> <string name="dashboard_timetable_first_lesson_title_first">Erstens:</string>
<string name="dashboard_timetable_first_lesson_title_now">Jetzt:</string> <string name="dashboard_timetable_first_lesson_title_now">Jetzt:</string>
<plurals name="dashboard_timetable_first_lesson_time_in_minutes">
<item quantity="one">in %1$d Minute</item>
<item quantity="other">in %1$d Minuten</item>
</plurals>
<plurals name="dashboard_timetable_first_lesson_time_more_minutes">
<item quantity="one">Noch %1$d Minute</item>
<item quantity="other">Noch %1$d Minuten</item>
</plurals>
<string name="dashboard_timetable_second_lesson_value_end">Ende der Lektion</string> <string name="dashboard_timetable_second_lesson_value_end">Ende der Lektion</string>
<string name="dashboard_timetable_second_lessons_title">Nächste:</string> <string name="dashboard_timetable_second_lessons_title">Nächste:</string>
<string name="dashboard_timetable_third_title">Später:</string> <string name="dashboard_timetable_third_title">Später:</string>
@ -588,13 +580,14 @@
<string name="pref_notify_open_system_settings">Systembenachrichtigungseinstellungen öffnen</string> <string name="pref_notify_open_system_settings">Systembenachrichtigungseinstellungen öffnen</string>
<string name="pref_notify_fix_sync_issues">Synchronisierungs- und Benachrichtigungsprobleme reparieren</string> <string name="pref_notify_fix_sync_issues">Synchronisierungs- und Benachrichtigungsprobleme reparieren</string>
<string name="pref_notify_fix_sync_issues_message">Ihr Gerät hat möglicherweise Probleme mit der Datensynchronisierung und Benachrichtigungen.\n\nUm diese zu reparieren, fügen Sie Wulkanowy zum Autostart hinzu und deaktivieren Sie die Batterieoptimierung in den Systemeinstellungen des Geräts.</string> <string name="pref_notify_fix_sync_issues_message">Ihr Gerät hat möglicherweise Probleme mit der Datensynchronisierung und Benachrichtigungen.\n\nUm diese zu reparieren, fügen Sie Wulkanowy zum Autostart hinzu und deaktivieren Sie die Batterieoptimierung in den Systemeinstellungen des Geräts.</string>
<string name="pref_notify_fix_sync_issues_settings_button">Gehe zu den Einstellungen</string>
<string name="pref_notify_debug_switch">Debug-Benachrichtigungen anzeigen</string> <string name="pref_notify_debug_switch">Debug-Benachrichtigungen anzeigen</string>
<string name="pref_notify_disabled_summary">Synchronisierung ist deaktiviert</string> <string name="pref_notify_disabled_summary">Synchronisierung ist deaktiviert</string>
<string name="pref_notify_notifications_piggyback">Offizielle App-Benachrichtigungen erfassen</string> <string name="pref_notify_notifications_piggyback">Offizielle App-Benachrichtigungen erfassen</string>
<string name="pref_notification_piggyback_popup_title">Benachrichtigungen erfassen</string> <string name="pref_notification_piggyback_popup_title">Benachrichtigungen erfassen</string>
<string name="pref_notification_piggyback_popup_description">With this feature you can gain a substitute of push notifications like in the official app. All you need to do is allow Wulkanowy to receive all notifications in your system settings.\n\nHow it works?\nWhen you get a notification in Dziennik VULCAN, Wulkanowy will be notified (that\'s what these extra permissions are for) and will trigger a sync so that can send its own notification.\n\nFOR ADVANCED USERS ONLY</string> <string name="pref_notification_piggyback_popup_description">With this feature you can gain a substitute of push notifications like in the official app. All you need to do is allow Wulkanowy to receive all notifications in your system settings.\n\nHow it works?\nWhen you get a notification in Dziennik VULCAN, Wulkanowy will be notified (that\'s what these extra permissions are for) and will trigger a sync so that can send its own notification.\n\nFOR ADVANCED USERS ONLY</string>
<string name="pref_notification_piggyback_popup_positive">Gehe zu Einstellungen</string> <string name="pref_notification_exact_alarm_popup_title">Upcoming lesson notifications</string>
<string name="pref_notification_exact_alarm_popup_descriptions">You must allow the Wulkanowy app to set alarms and reminders in your system settings to use this feature.</string>
<string name="pref_notification_go_to_settings">Go to settings</string>
<string name="pref_services_header">Synchronisierung</string> <string name="pref_services_header">Synchronisierung</string>
<string name="pref_services_switch">Automatische Aktualisierung</string> <string name="pref_services_switch">Automatische Aktualisierung</string>
<string name="pref_services_suspended">An Feiertagen suspendiert</string> <string name="pref_services_suspended">An Feiertagen suspendiert</string>
@ -616,6 +609,7 @@
<string name="pref_ads_privacy_agree">Agree</string> <string name="pref_ads_privacy_agree">Agree</string>
<string name="pref_ads_privacy_link">Privacy policy</string> <string name="pref_ads_privacy_link">Privacy policy</string>
<string name="pref_ads_loading">Ad is loading</string> <string name="pref_ads_loading">Ad is loading</string>
<string name="pref_ads_once_per_visit">Thank you for your support, come back later for more ads</string>
<string name="pref_settings_advanced_title">Erweitert</string> <string name="pref_settings_advanced_title">Erweitert</string>
<string name="pref_settings_appearance_title">Aussehen &amp; Verhalten</string> <string name="pref_settings_appearance_title">Aussehen &amp; Verhalten</string>
<string name="pref_settings_notifications_title">Benachrichtigungen</string> <string name="pref_settings_notifications_title">Benachrichtigungen</string>

View File

@ -553,18 +553,6 @@
<string name="dashboard_timetable_first_lesson_title_soon">Wkrótce:</string> <string name="dashboard_timetable_first_lesson_title_soon">Wkrótce:</string>
<string name="dashboard_timetable_first_lesson_title_first">Pierwsza:</string> <string name="dashboard_timetable_first_lesson_title_first">Pierwsza:</string>
<string name="dashboard_timetable_first_lesson_title_now">Teraz:</string> <string name="dashboard_timetable_first_lesson_title_now">Teraz:</string>
<plurals name="dashboard_timetable_first_lesson_time_in_minutes">
<item quantity="one">za %1$d minutę</item>
<item quantity="few">za %1$d minuty</item>
<item quantity="many">za %1$d minut</item>
<item quantity="other">za %1$d minut</item>
</plurals>
<plurals name="dashboard_timetable_first_lesson_time_more_minutes">
<item quantity="one">jeszcze %1$d minuta</item>
<item quantity="few">jeszcze %1$d minuty</item>
<item quantity="many">jeszcze %1$d minut</item>
<item quantity="other">jeszcze %1$d minut</item>
</plurals>
<string name="dashboard_timetable_second_lesson_value_end">Koniec lekcji</string> <string name="dashboard_timetable_second_lesson_value_end">Koniec lekcji</string>
<string name="dashboard_timetable_second_lessons_title">Następnie:</string> <string name="dashboard_timetable_second_lessons_title">Następnie:</string>
<string name="dashboard_timetable_third_title">Później:</string> <string name="dashboard_timetable_third_title">Później:</string>
@ -678,13 +666,14 @@
<string name="pref_notify_open_system_settings">Otwórz systemowe ustawienia powiadomień</string> <string name="pref_notify_open_system_settings">Otwórz systemowe ustawienia powiadomień</string>
<string name="pref_notify_fix_sync_issues">Napraw problemy z synchronizacją i powiadomieniami</string> <string name="pref_notify_fix_sync_issues">Napraw problemy z synchronizacją i powiadomieniami</string>
<string name="pref_notify_fix_sync_issues_message">Na twoim urządzeniu mogą występować problemy z synchronizacją danych i powiadomieniami.\n\nBy je naprawić, dodaj Wulkanowego do autostartu i wyłącz optymalizację/oszczędzanie baterii w ustawieniach systemowych telefonu.</string> <string name="pref_notify_fix_sync_issues_message">Na twoim urządzeniu mogą występować problemy z synchronizacją danych i powiadomieniami.\n\nBy je naprawić, dodaj Wulkanowego do autostartu i wyłącz optymalizację/oszczędzanie baterii w ustawieniach systemowych telefonu.</string>
<string name="pref_notify_fix_sync_issues_settings_button">Przejdź do ustawień</string>
<string name="pref_notify_debug_switch">Pokazuj powiadomienia debugowania</string> <string name="pref_notify_debug_switch">Pokazuj powiadomienia debugowania</string>
<string name="pref_notify_disabled_summary">Synchronizacja jest wyłączona</string> <string name="pref_notify_disabled_summary">Synchronizacja jest wyłączona</string>
<string name="pref_notify_notifications_piggyback">Przechwytywanie powiadomień oficjalnej aplikacji</string> <string name="pref_notify_notifications_piggyback">Przechwytywanie powiadomień oficjalnej aplikacji</string>
<string name="pref_notification_piggyback_popup_title">Przechwytywanie powiadomień</string> <string name="pref_notification_piggyback_popup_title">Przechwytywanie powiadomień</string>
<string name="pref_notification_piggyback_popup_description">Dzięki tej funkcji możesz uzyskać namiastkę powiadomień push, takich jak w oficjalnej aplikacji. Wszystko, co musisz zrobić, to zezwolić Wulkanowemu na odczytywanie wszystkich powiadomień w ustawieniach systemowych.\n\nJak to działa?\nKiedy otrzymasz powiadomienie w Dzienniczku VULCAN, Wulkanowy zostanie o tym powiadomiony (do tego jest to dodatkowe uprawnienie) i uruchomi synchronizację, aby mógł wysłać własne powiadomienie.\n\nWYŁĄCZNIE DLA ZAAWANSOWANYCH UŻYTKOWNIKÓW</string> <string name="pref_notification_piggyback_popup_description">Dzięki tej funkcji możesz uzyskać namiastkę powiadomień push, takich jak w oficjalnej aplikacji. Wszystko, co musisz zrobić, to zezwolić Wulkanowemu na odczytywanie wszystkich powiadomień w ustawieniach systemowych.\n\nJak to działa?\nKiedy otrzymasz powiadomienie w Dzienniczku VULCAN, Wulkanowy zostanie o tym powiadomiony (do tego jest to dodatkowe uprawnienie) i uruchomi synchronizację, aby mógł wysłać własne powiadomienie.\n\nWYŁĄCZNIE DLA ZAAWANSOWANYCH UŻYTKOWNIKÓW</string>
<string name="pref_notification_piggyback_popup_positive">Przejdź do ustawień</string> <string name="pref_notification_exact_alarm_popup_title">Powiadomienia o nadchodzących lekcjach</string>
<string name="pref_notification_exact_alarm_popup_descriptions">Musisz pozwolić Wulkanowemu na tworzenie alarmów i przypomnień w ustawieniach Twojego systemu, aby użyć tej funkcji.</string>
<string name="pref_notification_go_to_settings">Przejdź do ustawień</string>
<string name="pref_services_header">Synchronizacja</string> <string name="pref_services_header">Synchronizacja</string>
<string name="pref_services_switch">Automatyczna aktualizacja</string> <string name="pref_services_switch">Automatyczna aktualizacja</string>
<string name="pref_services_suspended">Zawieszona na wakacjach</string> <string name="pref_services_suspended">Zawieszona na wakacjach</string>
@ -706,6 +695,7 @@
<string name="pref_ads_privacy_agree">Akceptuję</string> <string name="pref_ads_privacy_agree">Akceptuję</string>
<string name="pref_ads_privacy_link">Polityka prywatności</string> <string name="pref_ads_privacy_link">Polityka prywatności</string>
<string name="pref_ads_loading">Ładowanie reklamy</string> <string name="pref_ads_loading">Ładowanie reklamy</string>
<string name="pref_ads_once_per_visit">Dziękujemy za wsparcie, wróć później po więcej reklam</string>
<string name="pref_settings_advanced_title">Zaawansowane</string> <string name="pref_settings_advanced_title">Zaawansowane</string>
<string name="pref_settings_appearance_title">Wygląd i zachowanie</string> <string name="pref_settings_appearance_title">Wygląd i zachowanie</string>
<string name="pref_settings_notifications_title">Powiadomienia</string> <string name="pref_settings_notifications_title">Powiadomienia</string>

View File

@ -553,18 +553,6 @@
<string name="dashboard_timetable_first_lesson_title_soon">Скоро:</string> <string name="dashboard_timetable_first_lesson_title_soon">Скоро:</string>
<string name="dashboard_timetable_first_lesson_title_first">Первый:</string> <string name="dashboard_timetable_first_lesson_title_first">Первый:</string>
<string name="dashboard_timetable_first_lesson_title_now">Сейчас:</string> <string name="dashboard_timetable_first_lesson_title_now">Сейчас:</string>
<plurals name="dashboard_timetable_first_lesson_time_in_minutes">
<item quantity="one">через %1$d минуту</item>
<item quantity="few">через %1$d минуту</item>
<item quantity="many">через %1$d минуту</item>
<item quantity="other">через %1$d минут</item>
</plurals>
<plurals name="dashboard_timetable_first_lesson_time_more_minutes">
<item quantity="one">Еще %1$d минута</item>
<item quantity="few">Еще %1$d минута</item>
<item quantity="many">Еще %1$d минута</item>
<item quantity="other">Ещё %1$d минут</item>
</plurals>
<string name="dashboard_timetable_second_lesson_value_end">Окончание уроков</string> <string name="dashboard_timetable_second_lesson_value_end">Окончание уроков</string>
<string name="dashboard_timetable_second_lessons_title">Далее:</string> <string name="dashboard_timetable_second_lessons_title">Далее:</string>
<string name="dashboard_timetable_third_title">Позднее:</string> <string name="dashboard_timetable_third_title">Позднее:</string>
@ -678,13 +666,14 @@
<string name="pref_notify_open_system_settings">Открыть настройки уведомлений системы</string> <string name="pref_notify_open_system_settings">Открыть настройки уведомлений системы</string>
<string name="pref_notify_fix_sync_issues">Исправить проблемы с синхронизацией и уведомлениями</string> <string name="pref_notify_fix_sync_issues">Исправить проблемы с синхронизацией и уведомлениями</string>
<string name="pref_notify_fix_sync_issues_message">На вашем устройстве могут быть проблемы с синхронизацией данных и уведомлениями.\n\nЧтобы их исправить, вам необходимо добавить Wulkanowy в авто-старт и выключить оптимизацию/экономию батареи в настройках устройства.</string> <string name="pref_notify_fix_sync_issues_message">На вашем устройстве могут быть проблемы с синхронизацией данных и уведомлениями.\n\nЧтобы их исправить, вам необходимо добавить Wulkanowy в авто-старт и выключить оптимизацию/экономию батареи в настройках устройства.</string>
<string name="pref_notify_fix_sync_issues_settings_button">Перейти в настройски</string>
<string name="pref_notify_debug_switch">Показывать дебаг-уведомления</string> <string name="pref_notify_debug_switch">Показывать дебаг-уведомления</string>
<string name="pref_notify_disabled_summary">Синхронизация отключена</string> <string name="pref_notify_disabled_summary">Синхронизация отключена</string>
<string name="pref_notify_notifications_piggyback">Записывать официальные уведомления</string> <string name="pref_notify_notifications_piggyback">Записывать официальные уведомления</string>
<string name="pref_notification_piggyback_popup_title">Показывать push-уведомления</string> <string name="pref_notification_piggyback_popup_title">Показывать push-уведомления</string>
<string name="pref_notification_piggyback_popup_description">С помощью этой функции вы можете получить замену push-уведомлений, как в официальном приложении. Все, что вам нужно сделать, это разрешить Wulkanowy получать все уведомления в настройках системы.\n\nКак это работает?\nКогда вы получаете уведомление в Dziennik VULCAN, Wulkanowy будет уведомлен (это требует дополнительных прав) и запустит синхронизацию, чтобы отправить свое уведомление.\n\nТОЛЬКО ДЛЯ ПОЛЬЗОВАТЕЛЯ</string> <string name="pref_notification_piggyback_popup_description">С помощью этой функции вы можете получить замену push-уведомлений, как в официальном приложении. Все, что вам нужно сделать, это разрешить Wulkanowy получать все уведомления в настройках системы.\n\nКак это работает?\nКогда вы получаете уведомление в Dziennik VULCAN, Wulkanowy будет уведомлен (это требует дополнительных прав) и запустит синхронизацию, чтобы отправить свое уведомление.\n\nТОЛЬКО ДЛЯ ПОЛЬЗОВАТЕЛЯ</string>
<string name="pref_notification_piggyback_popup_positive">Перейти к настройкам</string> <string name="pref_notification_exact_alarm_popup_title">Upcoming lesson notifications</string>
<string name="pref_notification_exact_alarm_popup_descriptions">You must allow the Wulkanowy app to set alarms and reminders in your system settings to use this feature.</string>
<string name="pref_notification_go_to_settings">Go to settings</string>
<string name="pref_services_header">Синхронизация</string> <string name="pref_services_header">Синхронизация</string>
<string name="pref_services_switch">Автоматическая синхронизация</string> <string name="pref_services_switch">Автоматическая синхронизация</string>
<string name="pref_services_suspended">Приостановить синхронизации во время каникул</string> <string name="pref_services_suspended">Приостановить синхронизации во время каникул</string>
@ -706,6 +695,7 @@
<string name="pref_ads_privacy_agree">Согласен</string> <string name="pref_ads_privacy_agree">Согласен</string>
<string name="pref_ads_privacy_link">Политика конфиденциальности</string> <string name="pref_ads_privacy_link">Политика конфиденциальности</string>
<string name="pref_ads_loading">Объявление загружается</string> <string name="pref_ads_loading">Объявление загружается</string>
<string name="pref_ads_once_per_visit">Thank you for your support, come back later for more ads</string>
<string name="pref_settings_advanced_title">Расширенные</string> <string name="pref_settings_advanced_title">Расширенные</string>
<string name="pref_settings_appearance_title">Внешний вид &amp; Поведение</string> <string name="pref_settings_appearance_title">Внешний вид &amp; Поведение</string>
<string name="pref_settings_notifications_title">Уведомления</string> <string name="pref_settings_notifications_title">Уведомления</string>

View File

@ -553,18 +553,6 @@
<string name="dashboard_timetable_first_lesson_title_soon">Čoskoro:</string> <string name="dashboard_timetable_first_lesson_title_soon">Čoskoro:</string>
<string name="dashboard_timetable_first_lesson_title_first">Prvá:</string> <string name="dashboard_timetable_first_lesson_title_first">Prvá:</string>
<string name="dashboard_timetable_first_lesson_title_now">Teraz:</string> <string name="dashboard_timetable_first_lesson_title_now">Teraz:</string>
<plurals name="dashboard_timetable_first_lesson_time_in_minutes">
<item quantity="one">za %1$d minútu</item>
<item quantity="few">za %1$d minúty</item>
<item quantity="many">za %1$d minút</item>
<item quantity="other">za %1$d minút</item>
</plurals>
<plurals name="dashboard_timetable_first_lesson_time_more_minutes">
<item quantity="one">ešte %1$d minútu</item>
<item quantity="few">ešte %1$d minúty</item>
<item quantity="many">ešte %1$d minút</item>
<item quantity="other">ešte %1$d minút</item>
</plurals>
<string name="dashboard_timetable_second_lesson_value_end">Koniec lekcií</string> <string name="dashboard_timetable_second_lesson_value_end">Koniec lekcií</string>
<string name="dashboard_timetable_second_lessons_title">Ďalej:</string> <string name="dashboard_timetable_second_lessons_title">Ďalej:</string>
<string name="dashboard_timetable_third_title">Neskôr:</string> <string name="dashboard_timetable_third_title">Neskôr:</string>
@ -678,13 +666,14 @@
<string name="pref_notify_open_system_settings">Otvoriť systémové nastavenia upozornení</string> <string name="pref_notify_open_system_settings">Otvoriť systémové nastavenia upozornení</string>
<string name="pref_notify_fix_sync_issues">Opravte problémy so synchronizáciou a upozornením</string> <string name="pref_notify_fix_sync_issues">Opravte problémy so synchronizáciou a upozornením</string>
<string name="pref_notify_fix_sync_issues_message">Vaše zariadenie môže mať problémy so synchronizáciou dát as upozorneniami.\n\nAk ich chcete opraviť, pridajte Wulkanového do funkcie Autostart a vypnite optimalizáciu/úsporu batérie v nastavení systému telefóne.</string> <string name="pref_notify_fix_sync_issues_message">Vaše zariadenie môže mať problémy so synchronizáciou dát as upozorneniami.\n\nAk ich chcete opraviť, pridajte Wulkanového do funkcie Autostart a vypnite optimalizáciu/úsporu batérie v nastavení systému telefóne.</string>
<string name="pref_notify_fix_sync_issues_settings_button">Prejsť do nastavení</string>
<string name="pref_notify_debug_switch">Zobraziť upozornenia o ladení</string> <string name="pref_notify_debug_switch">Zobraziť upozornenia o ladení</string>
<string name="pref_notify_disabled_summary">Synchronizácia je vypnutá</string> <string name="pref_notify_disabled_summary">Synchronizácia je vypnutá</string>
<string name="pref_notify_notifications_piggyback">Zachytiť upozornenia oficiálnej aplikácie</string> <string name="pref_notify_notifications_piggyback">Zachytiť upozornenia oficiálnej aplikácie</string>
<string name="pref_notification_piggyback_popup_title">Zachytiť upozornenia</string> <string name="pref_notification_piggyback_popup_title">Zachytiť upozornenia</string>
<string name="pref_notification_piggyback_popup_description">S touto funkciou môžete získať náhradu push upozornení ako v oficiálnej aplikácii. Všetko, čo musíte urobiť, je povoliť Wulkanowému čítať všetky vaše upozornenia v nastaveniach systému.\n\nAko to funguje?\nKeď dostanete oznámenie v Deníčku VULCAN, Wulkanowy bude o tom informovaný (k tomu je to dodatočné povolenie) a spustí synchronizáciu, aby mohol zaslať vlastné upozornenie.\n\nLEN PRE POKROČILÝCH POUŽĺVATEĹOV</string> <string name="pref_notification_piggyback_popup_description">S touto funkciou môžete získať náhradu push upozornení ako v oficiálnej aplikácii. Všetko, čo musíte urobiť, je povoliť Wulkanowému čítať všetky vaše upozornenia v nastaveniach systému.\n\nAko to funguje?\nKeď dostanete oznámenie v Deníčku VULCAN, Wulkanowy bude o tom informovaný (k tomu je to dodatočné povolenie) a spustí synchronizáciu, aby mohol zaslať vlastné upozornenie.\n\nLEN PRE POKROČILÝCH POUŽĺVATEĹOV</string>
<string name="pref_notification_piggyback_popup_positive">Prejsť do nastavení</string> <string name="pref_notification_exact_alarm_popup_title">Upozornenia o nadchádzajúcej lekciu</string>
<string name="pref_notification_exact_alarm_popup_descriptions">Musíte povoliť Wulkanovému nastaviť budíky a pripomenutie v nastavení vášho systému pre použitie tejto funkcie.</string>
<string name="pref_notification_go_to_settings">Prejsť do nastavení</string>
<string name="pref_services_header">Synchronizácia</string> <string name="pref_services_header">Synchronizácia</string>
<string name="pref_services_switch">Automatická aktualizácia</string> <string name="pref_services_switch">Automatická aktualizácia</string>
<string name="pref_services_suspended">Pozastavený počas dovolenky</string> <string name="pref_services_suspended">Pozastavený počas dovolenky</string>
@ -706,6 +695,7 @@
<string name="pref_ads_privacy_agree">Súhlasím</string> <string name="pref_ads_privacy_agree">Súhlasím</string>
<string name="pref_ads_privacy_link">Ochrana osobných údajov</string> <string name="pref_ads_privacy_link">Ochrana osobných údajov</string>
<string name="pref_ads_loading">Reklama sa načítava</string> <string name="pref_ads_loading">Reklama sa načítava</string>
<string name="pref_ads_once_per_visit">Ďakujeme za vašu podporu, vráťte sa neskôr pre viac reklám</string>
<string name="pref_settings_advanced_title">Pokročilé</string> <string name="pref_settings_advanced_title">Pokročilé</string>
<string name="pref_settings_appearance_title">Vzhľad a správanie</string> <string name="pref_settings_appearance_title">Vzhľad a správanie</string>
<string name="pref_settings_notifications_title">Upozornenia</string> <string name="pref_settings_notifications_title">Upozornenia</string>

View File

@ -553,18 +553,6 @@
<string name="dashboard_timetable_first_lesson_title_soon">Незабаром:</string> <string name="dashboard_timetable_first_lesson_title_soon">Незабаром:</string>
<string name="dashboard_timetable_first_lesson_title_first">Перше:</string> <string name="dashboard_timetable_first_lesson_title_first">Перше:</string>
<string name="dashboard_timetable_first_lesson_title_now">Зараз:</string> <string name="dashboard_timetable_first_lesson_title_now">Зараз:</string>
<plurals name="dashboard_timetable_first_lesson_time_in_minutes">
<item quantity="one">через %1$d хвилину</item>
<item quantity="few">через %1$d хвилину</item>
<item quantity="many">через %1$d хвилину</item>
<item quantity="other">через %1$d хвилин</item>
</plurals>
<plurals name="dashboard_timetable_first_lesson_time_more_minutes">
<item quantity="one">%1$d більше хвилини</item>
<item quantity="few">%1$d більше хвилини</item>
<item quantity="many">%1$d більше хвилини</item>
<item quantity="other">%1$d ще хвилин</item>
</plurals>
<string name="dashboard_timetable_second_lesson_value_end">Кінець уроків</string> <string name="dashboard_timetable_second_lesson_value_end">Кінець уроків</string>
<string name="dashboard_timetable_second_lessons_title">Далі:</string> <string name="dashboard_timetable_second_lessons_title">Далі:</string>
<string name="dashboard_timetable_third_title">Пізніше :</string> <string name="dashboard_timetable_third_title">Пізніше :</string>
@ -678,13 +666,14 @@
<string name="pref_notify_open_system_settings">Відкрити налаштування сповіщень системи</string> <string name="pref_notify_open_system_settings">Відкрити налаштування сповіщень системи</string>
<string name="pref_notify_fix_sync_issues">Виправити помилки з синхронізацією і повідомленнями</string> <string name="pref_notify_fix_sync_issues">Виправити помилки з синхронізацією і повідомленнями</string>
<string name="pref_notify_fix_sync_issues_message">На вашому пристрої можуть бути помилки з синхронізацією і повідомленнями\n\nЩоб виправити іх, вам необхідно додати Wulkanowy в авто-старт и вимкнути оптимізацію/экономію батареї в налаштуваннях пристрою.</string> <string name="pref_notify_fix_sync_issues_message">На вашому пристрої можуть бути помилки з синхронізацією і повідомленнями\n\nЩоб виправити іх, вам необхідно додати Wulkanowy в авто-старт и вимкнути оптимізацію/экономію батареї в налаштуваннях пристрою.</string>
<string name="pref_notify_fix_sync_issues_settings_button">Перейти до налаштувань</string>
<string name="pref_notify_debug_switch">Показувати дебаг-повідомлення</string> <string name="pref_notify_debug_switch">Показувати дебаг-повідомлення</string>
<string name="pref_notify_disabled_summary">Синхронізація вимкнена</string> <string name="pref_notify_disabled_summary">Синхронізація вимкнена</string>
<string name="pref_notify_notifications_piggyback">Захоплювати офіційні сповіщення програм</string> <string name="pref_notify_notifications_piggyback">Захоплювати офіційні сповіщення програм</string>
<string name="pref_notification_piggyback_popup_title">Показувати push-повідомлення</string> <string name="pref_notification_piggyback_popup_title">Показувати push-повідомлення</string>
<string name="pref_notification_piggyback_popup_description">За допомогою цієї функції ви можете отримати заміну push -повідомлень, як у офіційному додатку. Все, що вам потрібно зробити, це дозволити Wulkanowy отримувати всі сповіщення у налаштуваннях вашої системи. \ N \ nЯк це працює? \ NКоли ви отримаєте сповіщення у Dziennik VULCAN, Wulkanowy отримає сповіщення (для цього призначені ці додаткові дозволи) і запустить синхронізація, яка може надсилати власне сповіщення. \ n \ n ТІЛЬКИ ДЛЯ РОЗШИРЕНИХ КОРИСТУВАЧІВ</string> <string name="pref_notification_piggyback_popup_description">За допомогою цієї функції ви можете отримати заміну push -повідомлень, як у офіційному додатку. Все, що вам потрібно зробити, це дозволити Wulkanowy отримувати всі сповіщення у налаштуваннях вашої системи. \ N \ nЯк це працює? \ NКоли ви отримаєте сповіщення у Dziennik VULCAN, Wulkanowy отримає сповіщення (для цього призначені ці додаткові дозволи) і запустить синхронізація, яка може надсилати власне сповіщення. \ n \ n ТІЛЬКИ ДЛЯ РОЗШИРЕНИХ КОРИСТУВАЧІВ</string>
<string name="pref_notification_piggyback_popup_positive">Перейти до налаштувань</string> <string name="pref_notification_exact_alarm_popup_title">Upcoming lesson notifications</string>
<string name="pref_notification_exact_alarm_popup_descriptions">You must allow the Wulkanowy app to set alarms and reminders in your system settings to use this feature.</string>
<string name="pref_notification_go_to_settings">Go to settings</string>
<string name="pref_services_header">Синхронізація</string> <string name="pref_services_header">Синхронізація</string>
<string name="pref_services_switch">Автоматична синхронізація</string> <string name="pref_services_switch">Автоматична синхронізація</string>
<string name="pref_services_suspended">Призупинено на час канікул</string> <string name="pref_services_suspended">Призупинено на час канікул</string>
@ -706,6 +695,7 @@
<string name="pref_ads_privacy_agree">Погоджуюсь</string> <string name="pref_ads_privacy_agree">Погоджуюсь</string>
<string name="pref_ads_privacy_link">Політика конфіденційності</string> <string name="pref_ads_privacy_link">Політика конфіденційності</string>
<string name="pref_ads_loading">Реклама завантажується</string> <string name="pref_ads_loading">Реклама завантажується</string>
<string name="pref_ads_once_per_visit">Thank you for your support, come back later for more ads</string>
<string name="pref_settings_advanced_title">Додатково</string> <string name="pref_settings_advanced_title">Додатково</string>
<string name="pref_settings_appearance_title">Вигляд &amp; Поведінка</string> <string name="pref_settings_appearance_title">Вигляд &amp; Поведінка</string>
<string name="pref_settings_notifications_title">Повідомлення</string> <string name="pref_settings_notifications_title">Повідомлення</string>

View File

@ -71,7 +71,7 @@
<string name="login_contact_discord">Discord</string> <string name="login_contact_discord">Discord</string>
<string name="login_email_intent_title">Send email</string> <string name="login_email_intent_title">Send email</string>
<string name="login_email_subject" translatable="false">Zgłoszenie: Problemy z logowaniem</string> <string name="login_email_subject" translatable="false">Zgłoszenie: Problemy z logowaniem</string>
<string name="login_email_text" translatable="false">Informacje o aplikacji:\n\nUrządzenie: %1$s\nWersja SDK: %2$s\nWersja aplikacji: %3$s\nDodatkowe informacje: %4$s\nOstatni błąd: %5$s\n\nPełna nazwa szkoły i klasa ucznia: </string> <string name="login_email_text" translatable="false">Informacje o aplikacji:\n\nUrządzenie: %1$s\nWersja SDK: %2$s\nWersja aplikacji: %3$s\nDodatkowe informacje: %4$s\nOstatni błąd: %5$s\n\nNazwa szkoły wraz z miejscowością i numer klasy: </string>
<string name="login_recover_warning">Make sure you select the correct UONET+ register variation!</string> <string name="login_recover_warning">Make sure you select the correct UONET+ register variation!</string>
<string name="login_recover_button">I forgot my password</string> <string name="login_recover_button">I forgot my password</string>
<string name="login_recover_title">Recover your account</string> <string name="login_recover_title">Recover your account</string>
@ -247,8 +247,8 @@
<item quantity="other">New exams</item> <item quantity="other">New exams</item>
</plurals> </plurals>
<plurals name="exam_notify_new_item_content"> <plurals name="exam_notify_new_item_content">
<item quantity="one">You received %d new exam</item> <item quantity="one">%d new exam</item>
<item quantity="other">You received %d new exams</item> <item quantity="other">%d new exams</item>
</plurals> </plurals>
<plurals name="exam_number_item"> <plurals name="exam_number_item">
<item quantity="one">%d exam</item> <item quantity="one">%d exam</item>
@ -541,14 +541,6 @@
<string name="dashboard_timetable_first_lesson_title_soon">Soon:</string> <string name="dashboard_timetable_first_lesson_title_soon">Soon:</string>
<string name="dashboard_timetable_first_lesson_title_first">First:</string> <string name="dashboard_timetable_first_lesson_title_first">First:</string>
<string name="dashboard_timetable_first_lesson_title_now">Now:</string> <string name="dashboard_timetable_first_lesson_title_now">Now:</string>
<plurals name="dashboard_timetable_first_lesson_time_in_minutes">
<item quantity="one">in %1$d minute</item>
<item quantity="other">in %1$d minutes</item>
</plurals>
<plurals name="dashboard_timetable_first_lesson_time_more_minutes">
<item quantity="one">%1$d more minute</item>
<item quantity="other">%1$d more minutes</item>
</plurals>
<string name="dashboard_timetable_second_lesson_value_end">End of lessons</string> <string name="dashboard_timetable_second_lesson_value_end">End of lessons</string>
<string name="dashboard_timetable_second_lessons_title">Next:</string> <string name="dashboard_timetable_second_lessons_title">Next:</string>
<string name="dashboard_timetable_third_title">Later:</string> <string name="dashboard_timetable_third_title">Later:</string>
@ -667,13 +659,14 @@
<string name="pref_notify_open_system_settings">Open system notification settings</string> <string name="pref_notify_open_system_settings">Open system notification settings</string>
<string name="pref_notify_fix_sync_issues">Fix synchronization &amp; notifications issues</string> <string name="pref_notify_fix_sync_issues">Fix synchronization &amp; notifications issues</string>
<string name="pref_notify_fix_sync_issues_message">Your device may have data synchronization issues and with notifications.\n\nTo fix them, you need to add Wulkanowy to the autostart and turn off battery optimization/saving in the phone settings.</string> <string name="pref_notify_fix_sync_issues_message">Your device may have data synchronization issues and with notifications.\n\nTo fix them, you need to add Wulkanowy to the autostart and turn off battery optimization/saving in the phone settings.</string>
<string name="pref_notify_fix_sync_issues_settings_button">Go to settings</string>
<string name="pref_notify_debug_switch">Show debug notifications</string> <string name="pref_notify_debug_switch">Show debug notifications</string>
<string name="pref_notify_disabled_summary">Synchronization is disabled</string> <string name="pref_notify_disabled_summary">Synchronization is disabled</string>
<string name="pref_notify_notifications_piggyback">Capture official app notifications</string> <string name="pref_notify_notifications_piggyback">Capture official app notifications</string>
<string name="pref_notification_piggyback_popup_title">Capture notifications</string> <string name="pref_notification_piggyback_popup_title">Capture notifications</string>
<string name="pref_notification_piggyback_popup_description">With this feature you can gain a substitute of push notifications like in the official app. All you need to do is allow Wulkanowy to receive all notifications in your system settings.\n\nHow it works?\nWhen you get a notification in Dziennik VULCAN, Wulkanowy will be notified (that\'s what these extra permissions are for) and will trigger a sync so that can send its own notification.\n\nFOR ADVANCED USERS ONLY</string> <string name="pref_notification_piggyback_popup_description">With this feature you can gain a substitute of push notifications like in the official app. All you need to do is allow Wulkanowy to receive all notifications in your system settings.\n\nHow it works?\nWhen you get a notification in Dziennik VULCAN, Wulkanowy will be notified (that\'s what these extra permissions are for) and will trigger a sync so that can send its own notification.\n\nFOR ADVANCED USERS ONLY</string>
<string name="pref_notification_piggyback_popup_positive">Go to settings</string> <string name="pref_notification_exact_alarm_popup_title">Upcoming lesson notifications</string>
<string name="pref_notification_exact_alarm_popup_descriptions">You must allow the Wulkanowy app to set alarms and reminders in your system settings to use this feature.</string>
<string name="pref_notification_go_to_settings">Go to settings</string>
<string name="pref_services_header">Synchronization</string> <string name="pref_services_header">Synchronization</string>
<string name="pref_services_switch">Automatic update</string> <string name="pref_services_switch">Automatic update</string>
@ -698,6 +691,7 @@
<string name="pref_ads_privacy_agree">Agree</string> <string name="pref_ads_privacy_agree">Agree</string>
<string name="pref_ads_privacy_link">Privacy policy</string> <string name="pref_ads_privacy_link">Privacy policy</string>
<string name="pref_ads_loading">Ad is loading</string> <string name="pref_ads_loading">Ad is loading</string>
<string name="pref_ads_once_per_visit">Thank you for your support, come back later for more ads</string>
<string name="pref_settings_advanced_title">Advanced</string> <string name="pref_settings_advanced_title">Advanced</string>
<string name="pref_settings_appearance_title">Appearance &amp; Behavior</string> <string name="pref_settings_appearance_title">Appearance &amp; Behavior</string>

View File

@ -68,6 +68,13 @@ class AdsFragment : PreferenceFragmentCompat(), MainView.TitledView, AdsView {
} }
} }
override fun showWatchAdOncePerVisit(show: Boolean) {
findPreference<Preference>(getString(R.string.pref_key_ads_single_support))?.run {
isEnabled = !show
summary = if (show) getString(R.string.pref_ads_once_per_visit) else null
}
}
override fun showError(text: String, error: Throwable) { override fun showError(text: String, error: Throwable) {
(activity as? BaseActivity<*, *>)?.showError(text, error) (activity as? BaseActivity<*, *>)?.showError(text, error)
} }

View File

@ -35,7 +35,10 @@ class AdsPresenter @Inject constructor(
.onFailure(errorHandler::dispatch) .onFailure(errorHandler::dispatch)
.onSuccess { it?.let { view?.showAd(it) } } .onSuccess { it?.let { view?.showAd(it) } }
view?.showLoadingSupportAd(false) view?.run {
showLoadingSupportAd(false)
showWatchAdOncePerVisit(true)
}
} }
} }
} }

View File

@ -14,4 +14,6 @@ interface AdsView : BaseView {
fun openPrivacyPolicy() fun openPrivacyPolicy()
fun showLoadingSupportAd(show: Boolean) fun showLoadingSupportAd(show: Boolean)
fun showWatchAdOncePerVisit(show: Boolean)
} }

View File

@ -1,6 +1,8 @@
package io.github.wulkanowy.utils package io.github.wulkanowy.utils
import android.content.Context import android.content.Context
import android.os.Bundle
import com.google.ads.mediation.admob.AdMobAdapter
import com.google.android.gms.ads.AdRequest import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.LoadAdError import com.google.android.gms.ads.LoadAdError
import com.google.android.gms.ads.MobileAds import com.google.android.gms.ads.MobileAds
@ -18,7 +20,10 @@ class AdsHelper @Inject constructor(@ApplicationContext private val context: Con
suspend fun getSupportAd(): RewardedInterstitialAd? { suspend fun getSupportAd(): RewardedInterstitialAd? {
MobileAds.initialize(context) MobileAds.initialize(context)
val adRequest = AdRequest.Builder().build() val extra = Bundle().apply { putString("npa", "1") }
val adRequest = AdRequest.Builder()
.addNetworkExtrasBundle(AdMobAdapter::class.java, extra)
.build()
return suspendCoroutine { return suspendCoroutine {
RewardedInterstitialAd.load( RewardedInterstitialAd.load(

View File

@ -6,7 +6,7 @@
<Preference <Preference
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
app:key="@string/pref_key_ads_single_support" app:key="@string/pref_key_ads_single_support"
app:singleLineTitle="true" app:singleLineTitle="false"
app:title="@string/pref_ads_support" /> app:title="@string/pref_ads_support" />
</PreferenceCategory> </PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

View File

@ -3,6 +3,7 @@ package io.github.wulkanowy.utils
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.data.enums.GradeColorTheme
import io.mockk.MockKAnnotations import io.mockk.MockKAnnotations
import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.MockK
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
@ -46,10 +47,25 @@ class GradeExtensionTest {
@Test @Test
fun getBackgroundColor() { fun getBackgroundColor() {
assertEquals(R.color.grade_material_five, createGrade(5.0).getBackgroundColor("material")) assertEquals(
assertEquals(R.color.grade_material_five, createGrade(5.5).getBackgroundColor("material")) R.color.grade_material_five, createGrade(5.0).getBackgroundColor(
assertEquals(R.color.grade_material_five, createGrade(5.9).getBackgroundColor("material")) GradeColorTheme.MATERIAL
assertEquals(R.color.grade_vulcan_five, createGrade(5.9).getBackgroundColor("whatever")) )
)
assertEquals(
R.color.grade_material_five, createGrade(5.5).getBackgroundColor(
GradeColorTheme.MATERIAL
)
)
assertEquals(
R.color.grade_material_five, createGrade(5.9).getBackgroundColor(
GradeColorTheme.MATERIAL
)
)
assertEquals(
R.color.grade_vulcan_five,
createGrade(5.9).getBackgroundColor(GradeColorTheme.VULCAN)
)
} }
@Test @Test

View File

@ -1,8 +1,8 @@
buildscript { buildscript {
ext { ext {
kotlin_version = '1.5.31' kotlin_version = '1.6.0'
about_libraries = '8.9.4' about_libraries = '8.9.4'
hilt_version = "2.40.1" hilt_version = "2.40.5"
} }
repositories { repositories {
mavenCentral() mavenCentral()
@ -13,11 +13,11 @@ buildscript {
dependencies { dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath 'com.android.tools.build:gradle:7.0.3' classpath 'com.android.tools.build:gradle:7.0.4'
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
classpath 'com.google.gms:google-services:4.3.10' classpath 'com.google.gms:google-services:4.3.10'
classpath 'com.huawei.agconnect:agcp:1.6.1.300' classpath 'com.huawei.agconnect:agcp:1.6.2.300'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.0' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.1'
classpath "com.github.triplet.gradle:play-publisher:3.6.0" classpath "com.github.triplet.gradle:play-publisher:3.6.0"
classpath "ru.cian:huawei-publish-gradle-plugin:1.3.0" classpath "ru.cian:huawei-publish-gradle-plugin:1.3.0"
classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3" classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3"