From 1bf07d736f07b3279e46b9d520549573bf6c5ad5 Mon Sep 17 00:00:00 2001 From: "B.O.S.S" Date: Wed, 2 Jun 2021 22:59:57 +0200 Subject: [PATCH 01/30] [API/Librus] Update client ID (#51) @BxOxSxS --- .../main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt index f06ef354..db53282e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt @@ -24,7 +24,7 @@ const val FAKE_LIBRUS_ACCOUNTS = "/synergia_accounts.php" val LIBRUS_USER_AGENT = "${SYSTEM_USER_AGENT}LibrusMobileApp" const val SYNERGIA_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/62.0" -const val LIBRUS_CLIENT_ID = "0RbsDOkV9tyKEQYzlLv5hs3DM1ukrynFI4p6C1Yc" +const val LIBRUS_CLIENT_ID = "VaItV6oRutdo8fnjJwysnTjVlvaswf52ZqmXsJGP" const val LIBRUS_REDIRECT_URL = "app://librus" const val LIBRUS_AUTHORIZE_URL = "https://portal.librus.pl/oauth2/authorize?client_id=$LIBRUS_CLIENT_ID&redirect_uri=$LIBRUS_REDIRECT_URL&response_type=code" const val LIBRUS_LOGIN_URL = "https://portal.librus.pl/rodzina/login/action" From 71ca51e813337ffdb3017e6dd2bfb909e67e33a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Sun, 6 Jun 2021 16:47:10 +0200 Subject: [PATCH 02/30] [Strings] Update copyright dates. --- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-en/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 431fd960..18190d60 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -856,7 +856,7 @@ Open-Source-Lizenzen Datenschutzrichtlinie E-Klassenbuch - © Kuba Szczodrzyński && Kacper Ziubryniewicz\nSeptember 2018 - Mai 2021 + © Kuba Szczodrzyński && Kacper Ziubryniewicz\nSeptember 2018 - Juni 2021 Klicken Sie hier, um nach Aktualisierungen zu suchen Aktualisierung Version diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index e519d272..d34932b7 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -858,7 +858,7 @@ Open-source licenses Privacy policy E-register - © Kuba Szczodrzyński && Kacper Ziubryniewicz\nSeptember 2018 - May 2021 + © Kuba Szczodrzyński && Kacper Ziubryniewicz\nSeptember 2018 - June 2021 Click to check for updates Update Version diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5aa206af..2043046f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -921,7 +921,7 @@ Licencje open-source Polityka prywatności E-dziennik - © Kuba Szczodrzyński && Kacper Ziubryniewicz\nwrzesień 2018 - maj 2021 + © Kuba Szczodrzyński && Kacper Ziubryniewicz\nwrzesień 2018 - czerwiec 2021 Kliknij, aby sprawdzić aktualizacje Aktualizacja Wersja From ae4405ef781e970ccc3530672dd2f869e50f63f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Sun, 6 Jun 2021 16:50:04 +0200 Subject: [PATCH 03/30] [4.8.1] Update build.gradle, signing and changelog. --- app/src/main/assets/pl-changelog.html | 12 +++--------- app/src/main/cpp/szkolny-signing.cpp | 2 +- .../data/api/szkolny/interceptor/Signing.kt | 2 +- build.gradle | 4 ++-- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/app/src/main/assets/pl-changelog.html b/app/src/main/assets/pl-changelog.html index 27c85f0f..5c110e87 100644 --- a/app/src/main/assets/pl-changelog.html +++ b/app/src/main/assets/pl-changelog.html @@ -1,13 +1,7 @@ -

Wersja 4.8, 2021-05-26

+

Wersja 4.8.1, 2021-06-06

    -
  • Dodano ikony dla powiadomień. @Luncenok
  • -
  • Terminarz: opcje konfiguracji, widok kompaktowy, grupowanie wydarzeń, znaczki nieprzeczytanych, nowe ikony i wiele innych usprawnień.
  • -
  • Wiadomości: usprawiono wyszukiwanie - zapisywanie szukanego tekstu po wejściu w wiadomość.
  • -
  • Wiadomości: dodano opcję konfiguracji podpisu przy wysyłaniu wiadomości.
  • -
  • Plan lekcji: dodano znacznik aktualnej pory dnia w planie lekcji.
  • -
  • Powiadomienia: dodano szczegółowy opis po rozwinięciu.
  • -
  • Wydarzenia: nowy rodzaj "lekcja online".
  • -
  • Naprawiono odbieranie nagrody w easter egg'u.
  • +
  • Poprawiono funkcje logowania. @BxOxSxS
  • +
  • MobiDziennik: naprawiono wysyłanie wiadomości (błąd "nie znaleziono wiadomości").


diff --git a/app/src/main/cpp/szkolny-signing.cpp b/app/src/main/cpp/szkolny-signing.cpp index 9af553cb..273bfd2f 100644 --- a/app/src/main/cpp/szkolny-signing.cpp +++ b/app/src/main/cpp/szkolny-signing.cpp @@ -9,7 +9,7 @@ /*secret password - removed for source code publication*/ static toys AES_IV[16] = { - 0x71, 0xcf, 0xdf, 0x13, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + 0x90, 0x44, 0x08, 0xb8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat); diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt index a6095449..92fecbb2 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt @@ -46,6 +46,6 @@ object Signing { /*fun provideKey(param1: String, param2: Long): ByteArray {*/ fun pleaseStopRightNow(param1: String, param2: Long): ByteArray { - return "$param1.MTIzNDU2Nzg5MDZ/2nExVD===.$param2".sha256() + return "$param1.MTIzNDU2Nzg5MDOlSskDmW===.$param2".sha256() } } diff --git a/build.gradle b/build.gradle index a2eb932b..be590e1e 100644 --- a/build.gradle +++ b/build.gradle @@ -5,8 +5,8 @@ buildscript { kotlin_version = '1.4.31' release = [ - versionName: "4.8", - versionCode: 4080099 + versionName: "4.8.1", + versionCode: 4080199 ] setup = [ From a4d604e146c015d99e6de4a9d8d3c0b91c73c4fa Mon Sep 17 00:00:00 2001 From: arin <67313390+6Arin9@users.noreply.github.com> Date: Fri, 11 Jun 2021 00:05:54 +0200 Subject: [PATCH 04/30] [API/Librus] Update JST Client ID (#53) @6Arin9 --- .../main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt index db53282e..cb0d5a06 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt @@ -43,7 +43,7 @@ const val LIBRUS_API_TOKEN_URL = "https://api.librus.pl/OAuth/Token" const val LIBRUS_API_TOKEN_JST_URL = "https://api.librus.pl/OAuth/TokenJST" const val LIBRUS_API_AUTHORIZATION = "Mjg6ODRmZGQzYTg3YjAzZDNlYTZmZmU3NzdiNThiMzMyYjE=" const val LIBRUS_API_SECRET_JST = "18b7c1ee08216f636a1b1a2440e68398" -const val LIBRUS_API_CLIENT_ID_JST = "49" +const val LIBRUS_API_CLIENT_ID_JST = "59" //const val LIBRUS_API_CLIENT_ID_JST_REFRESH = "42" const val LIBRUS_JST_DEMO_CODE = "68656A21" From 4bed62aa6f6690ce5433e30b42674c17f3a3e82a Mon Sep 17 00:00:00 2001 From: doteq Date: Fri, 11 Jun 2021 22:06:07 +0200 Subject: [PATCH 05/30] [UI] Fix timetable crash when syncing (#54) * Fix removeView * Use removeView() instead of removeAllViews() * Remove dayView from layout file --- .../edziennik/ui/modules/timetable/TimetableDayFragment.kt | 2 +- app/src/main/res/layout/timetable_day_fragment.xml | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt index 3fc9ce83..00ce872e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableDayFragment.kt @@ -151,7 +151,7 @@ class TimetableDayFragment : LazyFragment(), CoroutineScope { } b.scrollView.isVisible = true - b.dayFrame.removeView(b.dayView) + b.dayFrame.removeView(dayView) b.dayFrame.addView(dayView, 0) // Inflate a label view for each hour the day view will display diff --git a/app/src/main/res/layout/timetable_day_fragment.xml b/app/src/main/res/layout/timetable_day_fragment.xml index 5464482d..041f6e1f 100644 --- a/app/src/main/res/layout/timetable_day_fragment.xml +++ b/app/src/main/res/layout/timetable_day_fragment.xml @@ -36,12 +36,6 @@ android:layout_marginHorizontal="8dp" android:background="@color/md_red_500" tools:layout_marginTop="100dp" /> - - From 5a217aca015da97cecd47f364a0296be326e756f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Tue, 15 Jun 2021 18:13:18 +0200 Subject: [PATCH 06/30] [4.8.2] Update build.gradle, signing and changelog. --- app/src/main/assets/pl-changelog.html | 6 +++--- app/src/main/cpp/szkolny-signing.cpp | 2 +- .../edziennik/data/api/szkolny/interceptor/Signing.kt | 2 +- build.gradle | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main/assets/pl-changelog.html b/app/src/main/assets/pl-changelog.html index 5c110e87..fbc5bbd5 100644 --- a/app/src/main/assets/pl-changelog.html +++ b/app/src/main/assets/pl-changelog.html @@ -1,7 +1,7 @@ -

Wersja 4.8.1, 2021-06-06

+

Wersja 4.8.2, 2021-06-15

    -
  • Poprawiono funkcje logowania. @BxOxSxS
  • -
  • MobiDziennik: naprawiono wysyłanie wiadomości (błąd "nie znaleziono wiadomości").
  • +
  • Poprawiono funkcje logowania. @6Arin9
  • +
  • Naprawiono zatrzymanie aplikacji na ekranie planu lekcji. @doteq


diff --git a/app/src/main/cpp/szkolny-signing.cpp b/app/src/main/cpp/szkolny-signing.cpp index 273bfd2f..6c412f0b 100644 --- a/app/src/main/cpp/szkolny-signing.cpp +++ b/app/src/main/cpp/szkolny-signing.cpp @@ -9,7 +9,7 @@ /*secret password - removed for source code publication*/ static toys AES_IV[16] = { - 0x90, 0x44, 0x08, 0xb8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + 0x1d, 0xa7, 0x6e, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat); diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt index 92fecbb2..fa3f4128 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing.kt @@ -46,6 +46,6 @@ object Signing { /*fun provideKey(param1: String, param2: Long): ByteArray {*/ fun pleaseStopRightNow(param1: String, param2: Long): ByteArray { - return "$param1.MTIzNDU2Nzg5MDOlSskDmW===.$param2".sha256() + return "$param1.MTIzNDU2Nzg5MDn7mcwDD+===.$param2".sha256() } } diff --git a/build.gradle b/build.gradle index be590e1e..4d60cda8 100644 --- a/build.gradle +++ b/build.gradle @@ -5,8 +5,8 @@ buildscript { kotlin_version = '1.4.31' release = [ - versionName: "4.8.1", - versionCode: 4080199 + versionName: "4.8.2", + versionCode: 4080299 ] setup = [ From 288c80ea26b468b3f35d36a69baa4a815787748a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Thu, 2 Sep 2021 17:40:34 +0200 Subject: [PATCH 07/30] [Gradle] Update wrapper and AGP to match AS 2020.3.1. --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 4d60cda8..9214ceda 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ buildscript { jcenter() } dependencies { - classpath "com.android.tools.build:gradle:4.2.0-beta06" + classpath 'com.android.tools.build:gradle:7.0.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.google.gms:google-services:4.3.5' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.5.1' diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9bd0ad16..80cea62c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Wed Feb 17 14:04:38 CET 2021 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME From 96dbb0a05759c5498947f0c8c5c0953d52a5f9ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Thu, 2 Sep 2021 17:48:02 +0200 Subject: [PATCH 08/30] [Gradle] Update Kotlin to v1.5.20. --- .../mobidziennik/data/web/MobidziennikWebAttendance.kt | 2 +- .../edziennik/ui/modules/login/LoginProgressFragment.kt | 2 +- .../edziennik/ui/modules/timetable/TimetableFragment.kt | 4 ++-- build.gradle | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAttendance.kt index f23478b6..cbd797b1 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAttendance.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebAttendance.kt @@ -48,7 +48,7 @@ class MobidziennikWebAttendance(override val data: DataMobidziennik, //syncWeeks.clear() //syncWeeks += Date.fromY_m_d("2019-12-19") - syncWeeks.minBy { it.value }?.let { + syncWeeks.minByOrNull { it.value }?.let { data.toRemove.add(DataRemoveModel.Attendance.from(it)) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginProgressFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginProgressFragment.kt index 7550b08c..aaebbac0 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginProgressFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginProgressFragment.kt @@ -76,7 +76,7 @@ class LoginProgressFragment : Fragment(), CoroutineScope { val maxProfileId = max( app.db.profileDao().lastId ?: 0, - activity.profiles.maxBy { it.profile.id }?.profile?.id ?: 0 + activity.profiles.maxByOrNull { it.profile.id }?.profile?.id ?: 0 ) val loginType = args.getInt("loginType", -1) val loginMode = args.getInt("loginMode", 0) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableFragment.kt index 970daeec..85332fe1 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/TimetableFragment.kt @@ -120,8 +120,8 @@ class TimetableFragment : Fragment(), CoroutineScope { } val lessonRanges = app.db.lessonRangeDao().getAllNow(App.profileId) - startHour = lessonRanges.map { it.startTime.hour }.min() ?: DEFAULT_START_HOUR - endHour = lessonRanges.map { it.endTime.hour }.max()?.plus(1) ?: DEFAULT_END_HOUR + startHour = lessonRanges.map { it.startTime.hour }.minOrNull() ?: DEFAULT_START_HOUR + endHour = lessonRanges.map { it.endTime.hour }.maxOrNull()?.plus(1) ?: DEFAULT_END_HOUR } deferred.await() if (!isAdded) diff --git a/build.gradle b/build.gradle index 9214ceda..ac506325 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { ext { - kotlin_version = '1.4.31' + kotlin_version = '1.5.20' release = [ versionName: "4.8.2", From c88056ddb952bef0730abda96fe7ef5f3fce372c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Thu, 2 Sep 2021 17:49:01 +0200 Subject: [PATCH 09/30] [Gradle] Update library dependencies. --- app/build.gradle | 26 +++++++++++++------------- build.gradle | 4 ++-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index c0af6190..29a434d8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -120,25 +120,25 @@ dependencies { coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5" // Android Jetpack - implementation "androidx.appcompat:appcompat:1.2.0" + implementation "androidx.appcompat:appcompat:1.3.1" implementation "androidx.cardview:cardview:1.0.0" - implementation "androidx.constraintlayout:constraintlayout:2.0.4" - implementation "androidx.core:core-ktx:1.3.2" - implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.0" - implementation "androidx.navigation:navigation-fragment-ktx:2.3.4" - implementation "androidx.recyclerview:recyclerview:1.1.0" - implementation "androidx.room:room-runtime:2.2.6" - implementation "androidx.work:work-runtime-ktx:2.5.0" - kapt "androidx.room:room-compiler:2.2.6" + implementation "androidx.constraintlayout:constraintlayout:2.1.0" + implementation "androidx.core:core-ktx:1.6.0" + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + implementation "androidx.navigation:navigation-fragment-ktx:2.3.5" + implementation "androidx.recyclerview:recyclerview:1.2.1" + implementation "androidx.room:room-runtime:2.3.0" + implementation "androidx.work:work-runtime-ktx:2.6.0" + kapt "androidx.room:room-compiler:2.3.0" // Google design libs - implementation "com.google.android.material:material:1.3.0" + implementation "com.google.android.material:material:1.4.0" implementation "com.google.android:flexbox:2.0.1" // Play Services/Firebase - implementation "com.google.android.gms:play-services-wearable:17.0.0" - implementation "com.google.firebase:firebase-core:18.0.2" - implementation "com.google.firebase:firebase-crashlytics:17.4.0" + implementation "com.google.android.gms:play-services-wearable:17.1.0" + implementation "com.google.firebase:firebase-core:19.0.1" + implementation "com.google.firebase:firebase-crashlytics:18.2.1" implementation("com.google.firebase:firebase-messaging") { version { strictly "20.1.3" } } // OkHttp, Retrofit, Gson, Jsoup diff --git a/build.gradle b/build.gradle index ac506325..99ad2eca 100644 --- a/build.gradle +++ b/build.gradle @@ -23,8 +23,8 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:7.0.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'com.google.gms:google-services:4.3.5' - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.5.1' + classpath 'com.google.gms:google-services:4.3.10' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.7.1' } } From e2bf48d1b68e7ba8f0bc67ced9615a15184a7390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Thu, 2 Sep 2021 18:27:51 +0200 Subject: [PATCH 10/30] [Actions] Use JDK 11. --- .github/workflows/build-nightly-apk.yml | 8 +++++--- .github/workflows/build-release-aab-play.yml | 8 +++++--- .github/workflows/build-release-apk.yml | 8 +++++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-nightly-apk.yml b/.github/workflows/build-nightly-apk.yml index bca267be..c64dba12 100644 --- a/.github/workflows/build-nightly-apk.yml +++ b/.github/workflows/build-nightly-apk.yml @@ -51,10 +51,12 @@ jobs: androidHome: ${{ env.ANDROID_HOME }} androidSdkRoot: ${{ env.ANDROID_SDK_ROOT }} steps: - - name: Setup JDK 1.8 - uses: actions/setup-java@v1 + - name: Setup JDK 11 + uses: actions/setup-java@v2 with: - java-version: 1.8 + distribution: 'zulu' + java-version: '11' + cache: 'gradle' - name: Setup Android SDK uses: android-actions/setup-android@v2 - name: Clean build artifacts diff --git a/.github/workflows/build-release-aab-play.yml b/.github/workflows/build-release-aab-play.yml index ec0a7069..5f3026e6 100644 --- a/.github/workflows/build-release-aab-play.yml +++ b/.github/workflows/build-release-aab-play.yml @@ -43,10 +43,12 @@ jobs: androidHome: ${{ env.ANDROID_HOME }} androidSdkRoot: ${{ env.ANDROID_SDK_ROOT }} steps: - - name: Setup JDK 1.8 - uses: actions/setup-java@v1 + - name: Setup JDK 11 + uses: actions/setup-java@v2 with: - java-version: 1.8 + distribution: 'zulu' + java-version: '11' + cache: 'gradle' - name: Setup Android SDK uses: android-actions/setup-android@v2 - name: Clean build artifacts diff --git a/.github/workflows/build-release-apk.yml b/.github/workflows/build-release-apk.yml index 169704c1..4816ee73 100644 --- a/.github/workflows/build-release-apk.yml +++ b/.github/workflows/build-release-apk.yml @@ -43,10 +43,12 @@ jobs: androidHome: ${{ env.ANDROID_HOME }} androidSdkRoot: ${{ env.ANDROID_SDK_ROOT }} steps: - - name: Setup JDK 1.8 - uses: actions/setup-java@v1 + - name: Setup JDK 11 + uses: actions/setup-java@v2 with: - java-version: 1.8 + distribution: 'zulu' + java-version: '11' + cache: 'gradle' - name: Setup Android SDK uses: android-actions/setup-android@v2 - name: Clean build artifacts From 7b4effe8893414a5fbfa6757daa0f29f1c19588b Mon Sep 17 00:00:00 2001 From: doteq Date: Tue, 7 Sep 2021 22:11:29 +0200 Subject: [PATCH 11/30] [UI] Fix block timetable export. (#57) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use MediaStore on SDK level >= 29 for timetable export * Fix imports * Use subdirectory in the Photos folder * Use MediaStore for all API levels * Remove not-null assertion * Remove unnecessary outputStream close. * Use File constructor for directory path * Use File(File, String) constructor Co-authored-by: Kuba Szczodrzyński --- .../timetable/GenerateBlockTimetableDialog.kt | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/GenerateBlockTimetableDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/GenerateBlockTimetableDialog.kt index c93d5a6e..9eefffd0 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/GenerateBlockTimetableDialog.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/GenerateBlockTimetableDialog.kt @@ -4,11 +4,14 @@ package pl.szczodrzynski.edziennik.ui.dialogs.timetable +import android.content.ContentResolver +import android.content.ContentValues import android.content.Intent import android.graphics.* import android.net.Uri import android.os.Build import android.os.Environment +import android.provider.MediaStore import android.util.Log import android.view.View import android.view.View.MeasureSpec @@ -373,25 +376,31 @@ class GenerateBlockTimetableDialog( val today = Date.getToday().stringY_m_d val now = Time.getNow().stringH_M_S + val filename = "plan_lekcji_${app.profile.name}_${today}_${now}.png" + val resolver: ContentResolver = activity.applicationContext.contentResolver + val values = ContentValues() + values.put(MediaStore.MediaColumns.MIME_TYPE, "image/png") - val outputDir = Environment.getExternalStoragePublicDirectory("Szkolny.eu").apply { mkdirs() } - val outputFile = File(outputDir, "plan_lekcji_${app.profile.name}_${today}_${now}.png") + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + values.put(MediaStore.MediaColumns.DISPLAY_NAME, filename) + values.put(MediaStore.MediaColumns.RELATIVE_PATH, File(Environment.DIRECTORY_PICTURES, "Szkolny.eu").path) + } else { + val picturesDirectory = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "Szkolny.eu") + picturesDirectory.mkdirs() + values.put(MediaStore.MediaColumns.DATA, File(picturesDirectory, filename).path) + } try { - val fos = FileOutputStream(outputFile) - bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos) - fos.close() + val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values) ?: return@withContext null + resolver.openOutputStream(uri).use { + bitmap.compress(Bitmap.CompressFormat.PNG, 100, it) + } + uri } catch (e: Exception) { Log.e("SAVE_IMAGE", e.message, e) return@withContext null } - val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - FileProvider.getUriForFile(activity, app.packageName + ".provider", outputFile) - } else { - Uri.parse("file://" + outputFile.absolutePath) - } - uri } progressDialog.dismiss() From 452271e8c0a7b4ca6261aef2acb9eb2d36f54ba2 Mon Sep 17 00:00:00 2001 From: Tomasz F Date: Wed, 8 Sep 2021 19:11:14 +0200 Subject: [PATCH 12/30] [UI] Add list of contributors in Settings. (#15) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Contributors item in settings * Move contributors activity to settings package && actualize branch * Update AndroidManifest.xml * Getting contributors from github api * Cleaning code * Fetching data from szkolny api, displaying content, a lot of changes :D * Strings * Remove androidx legacy library * Revert manifest changes * Remove logging in SzkolnyApi * Fix app name spelling * Revert changes to dimens.xml * Refactor contributors code * Revert changes to dimens.xml Again * Revert changes to build.gradle * Revert changes to gradle-wrapper.properties * Revert changes to gradle.properties * Make user name nullable * Add caching, refactor plurals, add progress bar * Update contributors UI * Shorten activity name in manifest * Remove unneeded line break * Remove fragment_translators.xml Co-authored-by: Kuba Szczodrzyński --- app/build.gradle | 1 + app/src/main/AndroidManifest.xml | 1 + .../pl/szczodrzynski/edziennik/Extensions.kt | 2 + .../edziennik/data/api/szkolny/SzkolnyApi.kt | 14 ++- .../data/api/szkolny/SzkolnyService.kt | 3 + .../szkolny/response/ContributorsResponse.kt | 20 ++++ .../lazypager/FragmentLazyPagerAdapter.kt | 2 +- .../settings/cards/SettingsAboutCard.kt | 9 ++ .../contributors/ContributorsActivity.kt | 83 +++++++++++++++ .../contributors/ContributorsAdapter.kt | 63 +++++++++++ .../contributors/ContributorsFragment.kt | 47 ++++++++ .../main/res/layout/contributors_activity.xml | 100 ++++++++++++++++++ .../res/layout/contributors_list_fragment.xml | 10 ++ .../res/layout/contributors_list_item.xml | 49 +++++++++ app/src/main/res/values-de/strings.xml | 2 + app/src/main/res/values-en/plurals.xml | 11 +- app/src/main/res/values-en/strings.xml | 4 + app/src/main/res/values/plurals.xml | 11 ++ app/src/main/res/values/strings.xml | 6 ++ 19 files changed, 432 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/response/ContributorsResponse.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/contributors/ContributorsActivity.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/contributors/ContributorsAdapter.kt create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/contributors/ContributorsFragment.kt create mode 100644 app/src/main/res/layout/contributors_activity.xml create mode 100644 app/src/main/res/layout/contributors_list_fragment.xml create mode 100644 app/src/main/res/layout/contributors_list_item.xml diff --git a/app/build.gradle b/app/build.gradle index 29a434d8..827ae0d6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,7 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' +apply plugin: 'kotlin-parcelize' apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.firebase.crashlytics' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index af4b653a..cbe7bd48 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -146,6 +146,7 @@ android:configChanges="orientation|keyboardHidden" android:theme="@style/Base.Theme.AppCompat" /> + %d ocen + + + %d commit + %d commity + %d commit\'ów + + + %d tłumaczenie + %d tłumaczenia + %d tłumaczeń + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2043046f..b11631b5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1391,6 +1391,10 @@ Zobacz także Wejdź na stronę aplikacji Uzyskaj pomoc lub wesprzyj autorów + Twórcy aplikacji + Lista twórców Szkolnego + Współtwórcy + Tłumacze Kod źródłowy Pomóż w rozwoju aplikacji na GitHubie Nazwa profilu @@ -1457,4 +1461,6 @@ Ocena: %s (waga %s)\nPrzedmiot: %s\nKategoria: %s\nOpis: %s\nNauczyciel: %s Rodzaj: %s\nNauczyciel: %s\nTreść: %s Rodzaj: %s\nTermin: %s, %s\nNr lekcji: %s\nPrzedmiot: %s\nNauczyciel: %s\nTemat lekcji: %s + \@%s - %s + Najłatwiejszy sposób na korzystanie z e-dziennika. From 8f72e11d0c1a1314aad72532e0fd4ea3438f9117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Wed, 8 Sep 2021 22:48:21 +0200 Subject: [PATCH 13/30] [UI] Center "no data" text view. (#59) --- app/src/main/res/layout/attendance_list_fragment.xml | 1 + app/src/main/res/layout/attendance_summary_fragment.xml | 1 + app/src/main/res/layout/card_home_grades.xml | 1 + app/src/main/res/layout/card_home_template.xml | 1 + app/src/main/res/layout/grades_item_stats.xml | 1 + app/src/main/res/layout/grades_list_fragment.xml | 1 + app/src/main/res/layout/homework_list_fragment.xml | 1 + app/src/main/res/layout/messages_list_fragment.xml | 1 + app/src/main/res/layout/notifications_list_fragment.xml | 1 + app/src/main/res/layout/template_list_fragment.xml | 1 + app/src/main/res/layout/template_list_page_fragment.xml | 1 + 11 files changed, 11 insertions(+) diff --git a/app/src/main/res/layout/attendance_list_fragment.xml b/app/src/main/res/layout/attendance_list_fragment.xml index 36f54733..bfcbf645 100644 --- a/app/src/main/res/layout/attendance_list_fragment.xml +++ b/app/src/main/res/layout/attendance_list_fragment.xml @@ -24,6 +24,7 @@ android:layout_gravity="center" android:drawablePadding="16dp" android:fontFamily="sans-serif-light" + android:gravity="center" android:text="@string/attendances_no_data" android:textSize="24sp" android:visibility="gone" diff --git a/app/src/main/res/layout/attendance_summary_fragment.xml b/app/src/main/res/layout/attendance_summary_fragment.xml index e4d96e80..80b01ed4 100644 --- a/app/src/main/res/layout/attendance_summary_fragment.xml +++ b/app/src/main/res/layout/attendance_summary_fragment.xml @@ -152,6 +152,7 @@ android:layout_gravity="center" android:drawablePadding="16dp" android:fontFamily="sans-serif-light" + android:gravity="center" android:text="@string/attendances_no_data" android:textSize="24sp" android:visibility="gone" diff --git a/app/src/main/res/layout/card_home_grades.xml b/app/src/main/res/layout/card_home_grades.xml index faab735b..38ae61de 100644 --- a/app/src/main/res/layout/card_home_grades.xml +++ b/app/src/main/res/layout/card_home_grades.xml @@ -22,6 +22,7 @@ android:layout_gravity="center_horizontal" android:layout_margin="16dp" android:fontFamily="sans-serif-light" + android:gravity="center" android:text="@string/card_grades_no_data" android:textSize="16sp" /> diff --git a/app/src/main/res/layout/card_home_template.xml b/app/src/main/res/layout/card_home_template.xml index 8cea8889..1570c8b3 100644 --- a/app/src/main/res/layout/card_home_template.xml +++ b/app/src/main/res/layout/card_home_template.xml @@ -24,6 +24,7 @@ android:layout_gravity="center_horizontal" android:layout_margin="16dp" android:fontFamily="sans-serif-light" + android:gravity="center" android:text="@string/card_grades_no_data" android:textSize="16sp" /> diff --git a/app/src/main/res/layout/grades_item_stats.xml b/app/src/main/res/layout/grades_item_stats.xml index 8ffeab4f..8bddb7de 100644 --- a/app/src/main/res/layout/grades_item_stats.xml +++ b/app/src/main/res/layout/grades_item_stats.xml @@ -32,6 +32,7 @@ android:layout_gravity="center_horizontal" android:layout_margin="8dp" android:fontFamily="sans-serif-light" + android:gravity="center" android:text="@string/grades_stats_no_data" android:textSize="16sp" android:visibility="gone" diff --git a/app/src/main/res/layout/grades_list_fragment.xml b/app/src/main/res/layout/grades_list_fragment.xml index faf8264d..ec2d7a59 100644 --- a/app/src/main/res/layout/grades_list_fragment.xml +++ b/app/src/main/res/layout/grades_list_fragment.xml @@ -29,6 +29,7 @@ android:layout_gravity="center" android:drawablePadding="16dp" android:fontFamily="sans-serif-light" + android:gravity="center" android:text="@string/grades_no_data" android:textSize="24sp" android:visibility="gone" diff --git a/app/src/main/res/layout/homework_list_fragment.xml b/app/src/main/res/layout/homework_list_fragment.xml index d53b9d17..53949d24 100644 --- a/app/src/main/res/layout/homework_list_fragment.xml +++ b/app/src/main/res/layout/homework_list_fragment.xml @@ -24,6 +24,7 @@ android:layout_gravity="center" android:drawablePadding="16dp" android:fontFamily="sans-serif-light" + android:gravity="center" android:text="@string/homework_no_data" android:textSize="24sp" android:visibility="gone" diff --git a/app/src/main/res/layout/messages_list_fragment.xml b/app/src/main/res/layout/messages_list_fragment.xml index 0399af9b..78de5194 100644 --- a/app/src/main/res/layout/messages_list_fragment.xml +++ b/app/src/main/res/layout/messages_list_fragment.xml @@ -24,6 +24,7 @@ android:layout_gravity="center" android:drawablePadding="16dp" android:fontFamily="sans-serif-light" + android:gravity="center" android:text="@string/messages_no_data" android:textSize="24sp" android:visibility="gone" diff --git a/app/src/main/res/layout/notifications_list_fragment.xml b/app/src/main/res/layout/notifications_list_fragment.xml index a5c9aa90..e73d45d5 100644 --- a/app/src/main/res/layout/notifications_list_fragment.xml +++ b/app/src/main/res/layout/notifications_list_fragment.xml @@ -24,6 +24,7 @@ android:layout_gravity="center" android:drawablePadding="16dp" android:fontFamily="sans-serif-light" + android:gravity="center" android:text="@string/notifications_no_data" android:textSize="24sp" android:visibility="gone" diff --git a/app/src/main/res/layout/template_list_fragment.xml b/app/src/main/res/layout/template_list_fragment.xml index 79646608..19c7221c 100644 --- a/app/src/main/res/layout/template_list_fragment.xml +++ b/app/src/main/res/layout/template_list_fragment.xml @@ -29,6 +29,7 @@ android:layout_gravity="center" android:drawablePadding="16dp" android:fontFamily="sans-serif-light" + android:gravity="center" android:text="@string/grades_no_data" android:textSize="24sp" android:visibility="gone" diff --git a/app/src/main/res/layout/template_list_page_fragment.xml b/app/src/main/res/layout/template_list_page_fragment.xml index 09f8bfda..062b4c89 100644 --- a/app/src/main/res/layout/template_list_page_fragment.xml +++ b/app/src/main/res/layout/template_list_page_fragment.xml @@ -24,6 +24,7 @@ android:layout_gravity="center" android:drawablePadding="16dp" android:fontFamily="sans-serif-light" + android:gravity="center" android:text="@string/grades_no_data" android:textSize="24sp" android:visibility="gone" From ea9d801d0884e9aca50ba378ab1f3b99f32a3e3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Wed, 8 Sep 2021 22:48:35 +0200 Subject: [PATCH 14/30] [DB] Workaround missing event types after profile archiving. (#60) --- .../edziennik/ui/modules/agenda/AgendaFragment.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt index 8d228dcc..8607217a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt @@ -21,6 +21,7 @@ import kotlinx.coroutines.* import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.db.entity.EventType import pl.szczodrzynski.edziennik.data.db.entity.Metadata import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.databinding.FragmentAgendaCalendarBinding @@ -136,9 +137,22 @@ class AgendaFragment : Fragment(), CoroutineScope { } } + private suspend fun checkEventTypes() { + withContext(Dispatchers.Default) { + val eventTypes = app.db.eventTypeDao().getAllNow(app.profileId).map { + it.id + } + val defaultEventTypes = EventType.getTypeColorMap().keys + if (!eventTypes.containsAll(defaultEventTypes)) { + app.db.eventTypeDao().addDefaultTypes(activity, app.profileId) + } + } + } + private fun createDefaultAgendaView() { (b as? FragmentAgendaDefaultBinding)?.let { b -> launch { if (!isAdded) return@launch + checkEventTypes() delay(500) agendaDefault = AgendaFragmentDefault(activity, app, b) @@ -146,6 +160,7 @@ class AgendaFragment : Fragment(), CoroutineScope { }}} private fun createCalendarAgendaView() { (b as? FragmentAgendaCalendarBinding)?.let { b -> launch { + checkEventTypes() delay(300) val dayList = mutableListOf() From 8edc581f0b7ecbe5c2dafe81d019c6e31d4af839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Wed, 8 Sep 2021 22:48:48 +0200 Subject: [PATCH 15/30] [UI] Fix multiplicated day dialog in Agenda. (#61) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 827ae0d6..cc99abe8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -154,7 +154,7 @@ dependencies { // Szkolny.eu libraries/forks implementation "eu.szkolny:android-snowfall:1ca9ea2da3" - implementation "eu.szkolny:agendacalendarview:5431f03098" + implementation "eu.szkolny:agendacalendarview:ac0f3dcf42" implementation "eu.szkolny:cafebar:5bf0c618de" implementation "eu.szkolny.fslogin:lib:2.0.0" implementation "eu.szkolny:material-about-library:1d5ebaf47c" From c1062cd7ed83747f6c9f4a876ca5617a3e2ab823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Wed, 8 Sep 2021 22:49:00 +0200 Subject: [PATCH 16/30] [UI] Update drawer header background. (#62) --- app/src/main/res/drawable/header.png | Bin 62950 -> 0 bytes app/src/main/res/drawable/header.webp | Bin 0 -> 20798 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 app/src/main/res/drawable/header.png create mode 100644 app/src/main/res/drawable/header.webp diff --git a/app/src/main/res/drawable/header.png b/app/src/main/res/drawable/header.png deleted file mode 100644 index 365f66e9963f86211c9adb29e2c800ad1390fc6e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62950 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 diff --git a/app/src/main/res/drawable/header.webp b/app/src/main/res/drawable/header.webp new file mode 100644 index 0000000000000000000000000000000000000000..d112e2f36c17195d7140c5043a23566b55644840 GIT binary patch literal 20798 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 VJ*1qYcGFo81J_u-!HzNj00918v(x|p literal 0 HcmV?d00001 From 680a5dfea35018d2f762210fdafe6e94982443c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Wed, 8 Sep 2021 22:49:14 +0200 Subject: [PATCH 17/30] [Mobidziennik] Fix missing linebreaks when sending messages. (#63) --- .../modules/messages/compose/MessagesComposeFragment.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/compose/MessagesComposeFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/compose/MessagesComposeFragment.kt index 091dabef..33744a98 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/compose/MessagesComposeFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/messages/compose/MessagesComposeFragment.kt @@ -503,10 +503,14 @@ class MessagesComposeFragment : Fragment(), CoroutineScope { text.toString() } + textHtml = textHtml + .replace("", "") + .replace("", "") + .replace("p style=\"margin-top:0; margin-bottom:0;\"", "p") + if (app.profile.loginStoreType == LoginStore.LOGIN_TYPE_MOBIDZIENNIK) { textHtml = textHtml - .replace("p style=\"margin-top:0; margin-bottom:0;\"", "span") - .replace("

", "") + .replace("


", "

") .replace("", "") .replace("", "") .replace("", "") From cf4906f2f414c885e7a88c91b3012d92a17f1048 Mon Sep 17 00:00:00 2001 From: Czapla <56671347+Antoni-Czaplicki@users.noreply.github.com> Date: Thu, 9 Sep 2021 18:52:04 +0200 Subject: [PATCH 18/30] [API/Vulcan] Fix sending messages. (#64) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix sending message * Add checking for address name/hash when sending message. Co-authored-by: Kuba Szczodrzyński --- .../edziennik/data/api/Errors.kt | 1 + .../data/api/edziennik/vulcan/DataVulcan.kt | 10 +++++++++ .../vulcan/data/hebe/VulcanHebeMain.kt | 6 ++++++ .../vulcan/data/hebe/VulcanHebeSendMessage.kt | 21 +++++++++++++++++-- app/src/main/res/values/errors.xml | 2 ++ 5 files changed, 38 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt index 5738064c..eefc1d31 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt @@ -195,6 +195,7 @@ const val ERROR_VULCAN_HEBE_FIREBASE_ERROR = 362 const val ERROR_VULCAN_HEBE_CERTIFICATE_GONE = 363 const val ERROR_VULCAN_HEBE_SERVER_ERROR = 364 const val ERROR_VULCAN_HEBE_ENTITY_NOT_FOUND = 365 +const val ERROR_VULCAN_HEBE_MISSING_SENDER_ENTRY = 366 const val ERROR_VULCAN_API_DEPRECATED = 390 const val ERROR_LOGIN_EDUDZIENNIK_WEB_INVALID_LOGIN = 501 diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/DataVulcan.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/DataVulcan.kt index 55185745..e3516ad4 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/DataVulcan.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/DataVulcan.kt @@ -222,6 +222,16 @@ class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app get() { mHebeContext = mHebeContext ?: profile?.getStudentData("hebeContext", null); return mHebeContext } set(value) { profile?.putStudentData("hebeContext", value) ?: return; mHebeContext = value } + private var mSenderAddressHash: String? = null + var senderAddressHash: String? + get() { mSenderAddressHash = mSenderAddressHash ?: profile?.getStudentData("senderAddressHash", null); return mSenderAddressHash } + set(value) { profile?.putStudentData("senderAddressHash", value) ?: return; mSenderAddressHash = value } + + private var mSenderAddressName: String? = null + var senderAddressName: String? + get() { mSenderAddressName = mSenderAddressName ?: profile?.getStudentData("senderAddressName", null); return mSenderAddressName } + set(value) { profile?.putStudentData("senderAddressName", value) ?: return; mSenderAddressName = value } + val apiUrl: String? get() { val url = when (apiToken[symbol]?.substring(0, 3)) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeMain.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeMain.kt index c017e3c9..5e2d31c0 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeMain.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeMain.kt @@ -97,6 +97,10 @@ class VulcanHebeMain( val studentSemesterId = period.getInt("Id") ?: return@forEach val studentSemesterNumber = period.getInt("Number") ?: return@forEach + val senderEntry = student.getJsonObject("SenderEntry") + val senderAddressName = senderEntry.getString("Address") + val senderAddressHash = senderEntry.getString("AddressHash") + val hebeContext = student.getString("Context") val isParent = login.getString("LoginRole").equals("opiekun", ignoreCase = true) @@ -143,6 +147,8 @@ class VulcanHebeMain( studentData["schoolSymbol"] = schoolSymbol studentData["schoolShort"] = schoolShort studentData["schoolName"] = schoolCode + studentData["senderAddressName"] = senderAddressName + studentData["senderAddressHash"] = senderAddressHash studentData["hebeContext"] = hebeContext } dateSemester1Start?.let { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeSendMessage.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeSendMessage.kt index 8f5f5afa..c6a02ae7 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeSendMessage.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeSendMessage.kt @@ -7,6 +7,7 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.hebe import com.google.gson.JsonObject import org.greenrobot.eventbus.EventBus import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.ERROR_VULCAN_HEBE_MISSING_SENDER_ENTRY import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_MESSAGES_SEND import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe @@ -27,6 +28,22 @@ class VulcanHebeSendMessage( } init { + if (data.senderAddressName == null || data.senderAddressHash == null) { + VulcanHebeMain(data).getStudents(data.profile, null) { + if (data.senderAddressName == null || data.senderAddressHash == null) { + data.error(TAG, ERROR_VULCAN_HEBE_MISSING_SENDER_ENTRY) + } + else { + sendMessage() + } + } + } + else { + sendMessage() + } + } + + private fun sendMessage() { val recipientsArray = JsonArray() recipients.forEach { teacher -> recipientsArray += JsonObject( @@ -40,10 +57,10 @@ class VulcanHebeSendMessage( val senderName = (profile?.accountName ?: profile?.studentNameLong) ?.swapFirstLastName() ?: "" val sender = JsonObject( - "Address" to senderName, + "Address" to data.senderAddressName, "LoginId" to data.studentLoginId.toString(), "Initials" to senderName.getNameInitials(), - "AddressHash" to senderName.sha1Hex() + "AddressHash" to data.senderAddressHash ) apiPost( diff --git a/app/src/main/res/values/errors.xml b/app/src/main/res/values/errors.xml index d90af9ed..827227c5 100644 --- a/app/src/main/res/values/errors.xml +++ b/app/src/main/res/values/errors.xml @@ -165,6 +165,7 @@ ERROR_VULCAN_HEBE_CERTIFICATE_GONE ERROR_VULCAN_HEBE_SERVER_ERROR ERROR_VULCAN_HEBE_ENTITY_NOT_FOUND + ERROR_VULCAN_HEBE_MISSING_SENDER_ENTRY ERROR_VULCAN_API_DEPRECATED ERROR_LOGIN_EDUDZIENNIK_WEB_INVALID_LOGIN @@ -363,6 +364,7 @@ VULCAN®: urządzenie usunięte. Zaloguj się ponownie do dziennika. VULCAN®: błąd serwera. Dziennik może być przeciążony. VULCAN®: nie znaleziono bytu + Błąd wysyłania wiadomości - brak informacji o nadawcy. W związku z wygaszeniem aplikacji Dzienniczek+ przez firmę Vulcan, należy zalogować się ponownie.\n\nAby móc dalej korzystać z aplikacji Szkolny.eu, otwórz Ustawienia i wybierz opcję Dodaj nowego ucznia.\nNastępnie zaloguj się do dziennika Vulcan zgodnie z instrukcją.\n\nPrzepraszamy za niedogodności. Błędny email lub hasło From b31bf5c1abf4f2b3d07a5713dcefe059e26db66e Mon Sep 17 00:00:00 2001 From: Czapla <56671347+Antoni-Czaplicki@users.noreply.github.com> Date: Thu, 9 Sep 2021 23:13:39 +0200 Subject: [PATCH 19/30] [API/Vulcan] Fix missing timetable entries. (#67) --- .../api/edziennik/vulcan/data/hebe/VulcanHebeTimetable.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeTimetable.kt index 03a28a9e..ffbb0289 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeTimetable.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeTimetable.kt @@ -11,6 +11,7 @@ import pl.szczodrzynski.edziennik.data.api.VULCAN_HEBE_ENDPOINT_TIMETABLE_CHANGE import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_HEBE_TIMETABLE import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanHebe +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel import pl.szczodrzynski.edziennik.data.db.entity.Lesson import pl.szczodrzynski.edziennik.data.db.entity.Lesson.Companion.TYPE_CANCELLED import pl.szczodrzynski.edziennik.data.db.entity.Lesson.Companion.TYPE_CHANGE @@ -47,7 +48,7 @@ class VulcanHebeTimetable( ?: previousWeekStart val dateTo = dateFrom.clone().stepForward(0, 0, 13) - val lastSync = null + val lastSync = 0L apiGetList( TAG, @@ -106,6 +107,8 @@ class VulcanHebeTimetable( "Clearing lessons between ${dateFrom.stringY_m_d} and ${dateTo.stringY_m_d}" ) + data.toRemove.add(DataRemoveModel.Timetable.between(dateFrom, dateTo)) + data.lessonList.addAll(lessonList) data.setSyncNext(ENDPOINT_VULCAN_HEBE_TIMETABLE, SYNC_ALWAYS) From 9fdee6e0c74b9666e64faa4f6a9650e6fd83149c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Thu, 9 Sep 2021 23:14:07 +0200 Subject: [PATCH 20/30] [UI] Fix restoring header background dialog. (#65) --- .../edziennik/ui/modules/settings/cards/SettingsThemeCard.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/cards/SettingsThemeCard.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/cards/SettingsThemeCard.kt index a009279d..1018e92c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/cards/SettingsThemeCard.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/settings/cards/SettingsThemeCard.kt @@ -88,7 +88,7 @@ class SettingsThemeCard(util: SettingsUtil) : SettingsCard(util) { text = R.string.settings_theme_drawer_header_text, icon = CommunityMaterial.Icon2.cmd_image_outline ) { - if (app.config.ui.appBackground == null) { + if (app.config.ui.headerBackground == null) { setHeaderBackground() return@createActionItem } From dd6a2c0979c2f01a96bea39726d57d523a7a6f9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Thu, 9 Sep 2021 23:14:24 +0200 Subject: [PATCH 21/30] [API/Mobidziennik] Add Web timetable scrapper. (#66) * [API] Add utils for getting teacher, subject and team by name. * [API/Mobidziennik] Add Web timetable scrapper. * [API/Mobidziennik] Add missing Regexes. --- .../edziennik/data/api/Regexes.kt | 11 + .../edziennik/edudziennik/DataEdudziennik.kt | 31 -- .../data/web/EdudziennikWebExams.kt | 2 +- .../data/web/EdudziennikWebGrades.kt | 2 +- .../data/web/EdudziennikWebHomework.kt | 2 +- .../data/web/EdudziennikWebStart.kt | 2 +- .../data/web/EdudziennikWebTimetable.kt | 3 +- .../mobidziennik/MobidziennikFeatures.kt | 7 + .../mobidziennik/data/MobidziennikData.kt | 4 + .../data/web/MobidziennikWebTimetable.kt | 333 ++++++++++++++++++ .../api/edziennik/podlasie/DataPodlasie.kt | 35 -- .../data/api/PodlasieApiFinalGrades.kt | 2 +- .../podlasie/data/api/PodlasieApiGrades.kt | 2 +- .../podlasie/data/api/PodlasieApiMain.kt | 8 +- .../podlasie/data/api/PodlasieApiTimetable.kt | 11 +- .../edziennik/data/api/models/Data.kt | 105 ++++++ 16 files changed, 484 insertions(+), 76 deletions(-) create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebTimetable.kt diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt index 5d847e3f..2bbf1894 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt @@ -117,6 +117,17 @@ object Regexes { } + val MOBIDZIENNIK_TIMETABLE_TOP by lazy { + """
.+?
""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_TIMETABLE_CELL by lazy { + """
.+?style="(.+?)".+?title="(.+?)".+?>\s+(.+?)\s+
""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_TIMETABLE_LEFT by lazy { + """
.+?
""".toRegex(DOT_MATCHES_ALL) + } + + val IDZIENNIK_LOGIN_HIDDEN_FIELDS by lazy { """""".toRegex(DOT_MATCHES_ALL) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/DataEdudziennik.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/DataEdudziennik.kt index 40601249..49a9d4a0 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/DataEdudziennik.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/DataEdudziennik.kt @@ -111,37 +111,6 @@ class DataEdudziennik(app: App, profile: Profile?, loginStore: LoginStore) : Dat val courseStudentEndpoint: String get() = "Course/$studentId/" - fun getSubject(longId: String, name: String): Subject { - val id = longId.crc32() - return subjectList.singleOrNull { it.id == id } ?: run { - val subject = Subject(profileId, id, name, name) - subjectList.put(id, subject) - subject - } - } - - fun getTeacher(firstName: String, lastName: String, longId: String? = null): Teacher { - val name = "$firstName $lastName".fixName() - val id = name.crc32() - return teacherList.singleOrNull { it.id == id }?.also { - if (longId != null && it.loginId == null) it.loginId = longId - } ?: run { - val teacher = Teacher(profileId, id, firstName, lastName, longId) - teacherList.put(id, teacher) - teacher - } - } - - fun getTeacherByFirstLast(nameFirstLast: String, longId: String? = null): Teacher { - val nameParts = nameFirstLast.split(" ") - return getTeacher(nameParts[0], nameParts[1], longId) - } - - fun getTeacherByLastFirst(nameLastFirst: String, longId: String? = null): Teacher { - val nameParts = nameLastFirst.split(" ") - return getTeacher(nameParts[1], nameParts[0], longId) - } - fun getEventType(longId: String, name: String): EventType { val id = longId.crc16().toLong() return eventTypes.singleOrNull { it.id == id } ?: run { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebExams.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebExams.kt index f6abf637..c700100c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebExams.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebExams.kt @@ -40,7 +40,7 @@ class EdudziennikWebExams(override val data: DataEdudziennik, val subjectId = EDUDZIENNIK_SUBJECT_ID.find(subjectElement.attr("href"))?.get(1) ?: return@forEach val subjectName = subjectElement.text().trim() - val subject = data.getSubject(subjectId, subjectName) + val subject = data.getSubject(subjectId.crc32(), subjectName) val dateString = examElement.child(2).text().trim() if (dateString.isBlank()) return@forEach diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGrades.kt index 459f13aa..6a247290 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGrades.kt @@ -53,7 +53,7 @@ class EdudziennikWebGrades(override val data: DataEdudziennik, val subjectId = subjectElement.id().trim() val subjectName = subjectElement.child(0).text().trim() - val subject = data.getSubject(subjectId, subjectName) + val subject = data.getSubject(subjectId.crc32(), subjectName) val gradeType = when { subjectElement.select("#sum").text().isNotBlank() -> TYPE_POINT_SUM diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebHomework.kt index 169a5127..b843abe9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebHomework.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebHomework.kt @@ -41,7 +41,7 @@ class EdudziennikWebHomework(override val data: DataEdudziennik, val subjectId = EDUDZIENNIK_SUBJECT_ID.find(subjectElement.attr("href"))?.get(1) ?: return@forEach val subjectName = subjectElement.text() - val subject = data.getSubject(subjectId, subjectName) + val subject = data.getSubject(subjectId.crc32(), subjectName) val lessons = data.app.db.timetableDao().getAllForDateNow(profileId, date) val startTime = lessons.firstOrNull { it.subjectId == subject.id }?.displayStartTime diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebStart.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebStart.kt index 66cb7a59..3fdd6e0e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebStart.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebStart.kt @@ -73,7 +73,7 @@ class EdudziennikWebStart(override val data: DataEdudziennik, EDUDZIENNIK_SUBJECTS_START.findAll(text).forEach { val id = it[1].trim() val name = it[2].trim() - data.getSubject(id, name) + data.getSubject(id.crc32(), name) } } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebTimetable.kt index 94db15ef..33feb75e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebTimetable.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebTimetable.kt @@ -5,6 +5,7 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web import org.jsoup.Jsoup +import pl.szczodrzynski.edziennik.crc32 import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_SUBJECT_ID import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_TEACHER_ID import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik @@ -89,7 +90,7 @@ class EdudziennikWebTimetable(override val data: DataEdudziennik, val subjectId = EDUDZIENNIK_SUBJECT_ID.find(subjectElement.attr("href"))?.get(1) ?: return@forEachIndexed val subjectName = subjectElement.text().trim() - val subject = data.getSubject(subjectId, subjectName) + val subject = data.getSubject(subjectId.crc32(), subjectName) /* Getting teacher */ diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/MobidziennikFeatures.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/MobidziennikFeatures.kt index 7396ac52..7b39e455 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/MobidziennikFeatures.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/MobidziennikFeatures.kt @@ -18,6 +18,7 @@ const val ENDPOINT_MOBIDZIENNIK_WEB_ATTENDANCE = 2050 const val ENDPOINT_MOBIDZIENNIK_WEB_MANUALS = 2100 const val ENDPOINT_MOBIDZIENNIK_WEB_ACCOUNT_EMAIL = 2200 const val ENDPOINT_MOBIDZIENNIK_WEB_HOMEWORK = 2300 // not used as an endpoint +const val ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE = 2400 const val ENDPOINT_MOBIDZIENNIK_API2_MAIN = 3000 val MobidziennikFeatures = listOf( @@ -38,6 +39,12 @@ val MobidziennikFeatures = listOf( + /** + * Timetable - web scraping - does nothing if the API_MAIN timetable is enough. + */ + Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_TIMETABLE, listOf( + ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE to LOGIN_METHOD_MOBIDZIENNIK_WEB + ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB, LOGIN_METHOD_MOBIDZIENNIK_WEB)), /** * Agenda - "API" + web scraping. */ diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/MobidziennikData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/MobidziennikData.kt index 09e1e9fb..b82f6355 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/MobidziennikData.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/MobidziennikData.kt @@ -84,6 +84,10 @@ class MobidziennikData(val data: DataMobidziennik, val onSuccess: () -> Unit) { data.startProgress(R.string.edziennik_progress_endpoint_lucky_number) MobidziennikWebManuals(data, lastSync, onSuccess) }*/ + ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE-> { + data.startProgress(R.string.edziennik_progress_endpoint_timetable) + MobidziennikWebTimetable(data, lastSync, onSuccess) + } else -> onSuccess(endpointId) } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebTimetable.kt new file mode 100644 index 00000000..221c50aa --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebTimetable.kt @@ -0,0 +1,333 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2021-9-8. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.web + +import android.annotation.SuppressLint +import org.jsoup.Jsoup +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.Regexes +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.MobidziennikWeb +import pl.szczodrzynski.edziennik.data.db.entity.Lesson +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time +import pl.szczodrzynski.edziennik.utils.models.Week +import kotlin.collections.set +import kotlin.text.replace + +class MobidziennikWebTimetable( + override val data: DataMobidziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : MobidziennikWeb(data, lastSync) { + companion object { + private const val TAG = "MobidziennikWebTimetable" + } + + private val rangesH = mutableMapOf, Date>() + private val hoursV = mutableMapOf>() + private var startDate: Date + + private fun parseCss(css: String): Map { + return css.split(";").mapNotNull { + val spl = it.split(":") + if (spl.size != 2) + return@mapNotNull null + return@mapNotNull spl[0].trim() to spl[1].trim() + }.toMap() + } + + private fun getRangeH(h: Float): Date? { + return rangesH.entries.firstOrNull { + h in it.key + }?.value + } + + private fun stringToDate(date: String): Date? { + val items = date.split(" ") + val day = items.getOrNull(0)?.toIntOrNull() ?: return null + val year = items.getOrNull(2)?.toIntOrNull() ?: return null + val month = when (items.getOrNull(1)) { + "stycznia" -> 1 + "lutego" -> 2 + "marca" -> 3 + "kwietnia" -> 4 + "maja" -> 5 + "czerwca" -> 6 + "lipca" -> 7 + "sierpnia" -> 8 + "września" -> 9 + "października" -> 10 + "listopada" -> 11 + "grudnia" -> 12 + else -> return null + } + return Date(year, month, day) + } + + init { + val currentWeekStart = Week.getWeekStart() + val nextWeekEnd = Week.getWeekEnd().stepForward(0, 0, 7) + if (Date.getToday().weekDay > 4) { + currentWeekStart.stepForward(0, 0, 7) + } + startDate = data.arguments?.getString("weekStart")?.let { + Date.fromY_m_d(it) + } ?: currentWeekStart + + val syncFutureDate = startDate > nextWeekEnd + // TODO: 2021-09-09 make DataRemoveModel keep extra lessons + val syncExtraLessons = false && System.currentTimeMillis() - (lastSync ?: 0) > 2 * DAY * MS + if (!syncFutureDate && !syncExtraLessons) { + onSuccess(ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE) + } + else { + val types = when { + syncFutureDate -> mutableListOf("podstawowy")//, "pozalekcyjny") + syncExtraLessons -> mutableListOf("pozalekcyjny") + else -> mutableListOf() + } + + syncTypes(types, startDate) { + // set as synced now only when not syncing future date + // (to avoid waiting 2 days for normal sync after future sync) + if (syncExtraLessons) + data.setSyncNext(ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE, SYNC_ALWAYS) + onSuccess(ENDPOINT_MOBIDZIENNIK_WEB_TIMETABLE) + } + } + } + + private fun syncTypes(types: MutableList, startDate: Date, onSuccess: () -> Unit) { + if (types.isEmpty()) { + onSuccess() + return + } + val type = types.removeAt(0) + webGet(TAG, "/dziennik/planlekcji?typ=$type&tydzien=${startDate.stringY_m_d}") { html -> + MobidziennikLuckyNumberExtractor(data, html) + readRangesH(html) + readRangesV(html) + readLessons(html) + syncTypes(types, startDate, onSuccess) + } + } + + private fun readRangesH(html: String) { + val htmlH = Regexes.MOBIDZIENNIK_TIMETABLE_TOP.find(html) ?: return + val docH = Jsoup.parse(htmlH.value) + + var posH = 0f + for (el in docH.select("div > div")) { + val css = parseCss(el.attr("style")) + val width = css["width"] + ?.trimEnd('%') + ?.toFloatOrNull() + ?: continue + val value = stringToDate(el.attr("title")) + ?: continue + + val range = posH.rangeTo(posH + width) + posH += width + + rangesH[range] = value + } + } + + private fun readRangesV(html: String) { + val htmlV = Regexes.MOBIDZIENNIK_TIMETABLE_LEFT.find(html) ?: return + val docV = Jsoup.parse(htmlV.value) + + for (el in docV.select("div > div")) { + val css = parseCss(el.attr("style")) + val top = css["top"] + ?.trimEnd('%') + ?.toFloatOrNull() + ?: continue + val values = el.text().split(" ") + + val time = values.getOrNull(0)?.let { + Time.fromH_m(it) + } ?: continue + val num = values.getOrNull(1)?.toIntOrNull() + + hoursV[(top * 100).toInt()] = time to num + } + } + + private val whitespaceRegex = "\\s+".toRegex() + private val classroomRegex = "\\((.*)\\)".toRegex() + private fun cleanup(str: String): List { + return str + .replace(whitespaceRegex, " ") + .replace("\n", "") + .replace("<small>", "$") + .replace("</small>", "$") + .replace("<br />", "\n") + .replace("<br/>", "\n") + .replace("<br>", "\n") + .replace("
", "\n") + .replace("
", "\n") + .replace("
", "\n") + .replace("", "%") + .replace("", "%") + .replace("", "") + .replace("", "") + .split("\n") + .map { it.trim() } + } + + @SuppressLint("LongLogTag", "LogNotTimber") + private fun readLessons(html: String) { + val matches = Regexes.MOBIDZIENNIK_TIMETABLE_CELL.findAll(html) + + val noLessonDays = mutableListOf() + for (i in 0..6) { + noLessonDays.add(startDate.clone().stepForward(0, 0, i)) + } + + for (match in matches) { + val css = parseCss("${match[1]};${match[2]}") + val left = css["left"]?.trimEnd('%')?.toFloatOrNull() ?: continue + val top = css["top"]?.trimEnd('%')?.toFloatOrNull() ?: continue + val width = css["width"]?.trimEnd('%')?.toFloatOrNull() ?: continue + val height = css["height"]?.trimEnd('%')?.toFloatOrNull() ?: continue + + val posH = left + width / 2f + val topInt = (top * 100).toInt() + val bottomInt = ((top + height) * 100).toInt() + + val lessonDate = getRangeH(posH) ?: continue + val (startTime, lessonNumber) = hoursV[topInt] ?: continue + val endTime = hoursV[bottomInt]?.first ?: continue + + noLessonDays.remove(lessonDate) + + var typeName: String? = null + var subjectName: String? = null + var teacherName: String? = null + var classroomName: String? = null + var teamName: String? = null + val items = (cleanup(match[3]) + cleanup(match[4])).toMutableList() + + var length = 0 + while (items.isNotEmpty() && length != items.size) { + length = items.size + val toRemove = mutableListOf() + items.forEachIndexed { i, item -> + when { + item.isEmpty() -> + toRemove.add(item) + item.contains(":") && item.contains(" - ") -> + toRemove.add(item) + + item.startsWith("%") -> { + subjectName = item.trim('%') + toRemove.add(item) + toRemove.add(items[0]) + } + + item.startsWith("&") -> { + typeName = item.trim('&') + toRemove.add(item) + } + typeName != null && (item.contains(typeName!!) || item.contains("")) -> { + toRemove.add(item) + } + + item.contains("(") && item.contains(")") -> { + classroomName = classroomRegex.find(item)?.get(1) + items[i] = item.replace("($classroomName)", "").trim() + } + classroomName != null && item.contains(classroomName!!) -> { + items[i] = item.replace("($classroomName)", "").trim() + } + + item.contains("class=\"wyjatek tooltip\"") -> + toRemove.add(item) + } + } + items.removeAll(toRemove) + } + + if (items.size == 2 && items[0].contains(" - ")) { + val parts = items[0].split(" - ") + teamName = parts[0] + teacherName = parts[1] + } + else if (items.size == 2 && typeName?.contains("odwołana") == true) { + teamName = items[0] + } + else if (items.size == 4) { + teamName = items[0] + teacherName = items[1] + } + + val type = when (typeName) { + "zastępstwo" -> Lesson.TYPE_CHANGE + "lekcja odwołana", "odwołana" -> Lesson.TYPE_CANCELLED + else -> Lesson.TYPE_NORMAL + } + val subject = subjectName?.let { data.getSubject(null, it) } + val teacher = teacherName?.let { data.getTeacherByLastFirst(it) } + val team = teamName?.let { data.getTeam( + id = null, + name = it, + schoolCode = data.loginServerName ?: return@let null, + isTeamClass = false + ) } + + Lesson(data.profileId, -1).also { + it.type = type + if (type == Lesson.TYPE_CANCELLED) { + it.oldDate = lessonDate + it.oldLessonNumber = lessonNumber + it.oldStartTime = startTime + it.oldEndTime = endTime + it.oldSubjectId = subject?.id ?: -1 + it.oldTeamId = team?.id ?: -1 + } + else { + it.date = lessonDate + it.lessonNumber = lessonNumber + it.startTime = startTime + it.endTime = endTime + it.subjectId = subject?.id ?: -1 + it.teacherId = teacher?.id ?: -1 + it.teamId = team?.id ?: -1 + it.classroom = classroomName + } + + it.id = it.buildId() + + val seen = profile?.empty == false || lessonDate < Date.getToday() + + if (it.type != Lesson.TYPE_NORMAL) { + data.metadataList.add( + Metadata( + data.profileId, + Metadata.TYPE_LESSON_CHANGE, + it.id, + seen, + seen + ) + ) + } + data.lessonList += it + } + } + + for (date in noLessonDays) { + data.lessonList += Lesson(data.profileId, date.value.toLong()).also { + it.type = Lesson.TYPE_NO_LESSONS + it.date = date + } + } + } +} + diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/DataPodlasie.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/DataPodlasie.kt index d117c139..7962de7e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/DataPodlasie.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/DataPodlasie.kt @@ -81,39 +81,4 @@ class DataPodlasie(app: App, profile: Profile?, loginStore: LoginStore) : Data(a val loginShort: String? get() = studentLogin?.split('@')?.get(0) - - fun getSubject(name: String): Subject { - val id = name.crc32() - return subjectList.singleOrNull { it.id == id } ?: run { - val subject = Subject(profileId, id, name, name) - subjectList.put(id, subject) - subject - } - } - - fun getTeacher(firstName: String, lastName: String): Teacher { - val name = "$firstName $lastName".fixName() - return teacherList.singleOrNull { it.fullName == name } ?: run { - val id = name.crc32() - val teacher = Teacher(profileId, id, firstName, lastName) - teacherList.put(id, teacher) - teacher - } - } - - fun getTeam(name: String? = null): Team { - if (name == "cała klasa" || name == null) return teamClass ?: run { - val id = className!!.crc32() - val teamCode = "$schoolShortName:$className" - val team = Team(profileId, id, className, Team.TYPE_CLASS, teamCode, -1) - teamList.put(id, team) - return team - } else { - val id = name.crc32() - val teamCode = "$schoolShortName:$name" - val team = Team(profileId, id, name, Team.TYPE_VIRTUAL, teamCode, -1) - teamList.put(id, team) - return team - } - } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiFinalGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiFinalGrades.kt index 7c4575aa..bb4f696c 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiFinalGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiFinalGrades.kt @@ -36,7 +36,7 @@ class PodlasieApiFinalGrades(val data: DataPodlasie, val rows: List) } val subjectName = grade.getString("SchoolSubject") ?: return@forEach - val subject = data.getSubject(subjectName) + val subject = data.getSubject(null, subjectName) val addedDate = if (profile.empty) profile.getSemesterStart(semester).inMillis else System.currentTimeMillis() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiGrades.kt index d5696567..c3946c76 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiGrades.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiGrades.kt @@ -34,7 +34,7 @@ class PodlasieApiGrades(val data: DataPodlasie, val rows: List) { val teacher = data.getTeacher(teacherFirstName, teacherLastName) val subjectName = grade.getString("SchoolSubject") ?: return@forEach - val subject = data.getSubject(subjectName) + val subject = data.getSubject(null, subjectName) val addedDate = grade.getString("ReceivedDate")?.let { Date.fromY_m_d(it).inMillis } ?: System.currentTimeMillis() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiMain.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiMain.kt index 6fe77b5c..f108d35e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiMain.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiMain.kt @@ -22,7 +22,13 @@ class PodlasieApiMain(override val data: DataPodlasie, init { apiGet(TAG, PODLASIE_API_USER_ENDPOINT) { json -> - data.getTeam() // Save the class team when it doesn't exist. + // Save the class team when it doesn't exist. + data.getTeam( + id = null, + name = data.className ?: "", + schoolCode = data.schoolShortName ?: "", + isTeamClass = true + ) json.getInt("LuckyNumber")?.let { PodlasieApiLuckyNumber(data, it) } json.getJsonArray("Teacher")?.asJsonObjectList()?.let { PodlasieApiTeachers(data, it) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiTimetable.kt index a9a12d5d..13cff861 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiTimetable.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/podlasie/data/api/PodlasieApiTimetable.kt @@ -43,14 +43,21 @@ class PodlasieApiTimetable(val data: DataPodlasie, rows: List) { val startTime = lesson.getString("TimeFrom")?.let { Time.fromH_m_s(it) } ?: return@forEach val endTime = lesson.getString("TimeTo")?.let { Time.fromH_m_s(it) } ?: return@forEach - val subject = lesson.getString("SchoolSubject")?.let { data.getSubject(it) } + val subject = lesson.getString("SchoolSubject")?.let { data.getSubject(null, it) } ?: return@forEach val teacherFirstName = lesson.getString("TeacherFirstName") ?: return@forEach val teacherLastName = lesson.getString("TeacherLastName") ?: return@forEach val teacher = data.getTeacher(teacherFirstName, teacherLastName) - val team = lesson.getString("Group")?.let { data.getTeam(it) } ?: return@forEach + val team = lesson.getString("Group")?.let { + data.getTeam( + id = null, + name = it, + schoolCode = data.schoolShortName ?: "", + isTeamClass = it == "cała klasa" + ) + } ?: return@forEach val classroom = lesson.getString("Room") Lesson(data.profileId, -1).also { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt index 8dda9543..2b7bdf1a 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/models/Data.kt @@ -2,6 +2,7 @@ package pl.szczodrzynski.edziennik.data.api.models import android.util.LongSparseArray import android.util.SparseArray +import androidx.core.util.set import androidx.core.util.size import androidx.room.OnConflictStrategy import com.google.gson.JsonObject @@ -376,4 +377,108 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt fun startProgress(stringRes: Int) { callback.onStartProgress(stringRes) } + + /* _ _ _ _ _ + | | | | | (_) | + | | | | |_ _| |___ + | | | | __| | / __| + | |__| | |_| | \__ \ + \____/ \__|_|_|__*/ + fun getSubject(id: Long?, name: String, shortName: String = name): Subject { + var subject = subjectList.singleOrNull { it.id == id } + if (subject == null) + subject = subjectList.singleOrNull { it.longName == name } + if (subject == null) + subject = subjectList.singleOrNull { it.shortName == name } + + if (subject == null) { + subject = Subject( + profileId, + id ?: name.crc32(), + name, + shortName + ) + subjectList[subject.id] = subject + } + return subject + } + + fun getTeam(id: Long?, name: String, schoolCode: String, isTeamClass: Boolean = false): Team { + if (isTeamClass && teamClass != null) + return teamClass as Team + var team = teamList.singleOrNull { it.id == id } + + val namePlain = name.replace(" ", "") + if (team == null) + team = teamList.singleOrNull { it.name.replace(" ", "") == namePlain } + + if (team == null) { + team = Team( + profileId, + id ?: name.crc32(), + name, + if (isTeamClass) Team.TYPE_CLASS else Team.TYPE_VIRTUAL, + "$schoolCode:$name", + -1 + ) + teamList[team.id] = team + } + return team + } + + fun getTeacher(firstName: String, lastName: String, loginId: String? = null): Teacher { + val teacher = teacherList.singleOrNull { it.fullName == "$firstName $lastName" } + return validateTeacher(teacher, firstName, lastName, loginId) + } + + fun getTeacher(firstNameChar: Char, lastName: String, loginId: String? = null): Teacher { + val teacher = teacherList.singleOrNull { it.shortName == "$firstNameChar.$lastName" } + return validateTeacher(teacher, firstNameChar.toString(), lastName, loginId) + } + + fun getTeacherByLastFirst(nameLastFirst: String, loginId: String? = null): Teacher { + val nameParts = nameLastFirst.split(" ") + return if (nameParts.size == 1) + getTeacher(nameParts[0], "", loginId) + else + getTeacher(nameParts[1], nameParts[0], loginId) + } + + fun getTeacherByFirstLast(nameFirstLast: String, loginId: String? = null): Teacher { + val nameParts = nameFirstLast.split(" ") + return if (nameParts.size == 1) + getTeacher(nameParts[0], "", loginId) + else + getTeacher(nameParts[0], nameParts[1], loginId) + } + + fun getTeacherByFDotLast(nameFDotLast: String, loginId: String? = null): Teacher { + val nameParts = nameFDotLast.split(".") + return if (nameParts.size == 1) + getTeacher(nameParts[0], "", loginId) + else + getTeacher(nameParts[0][0], nameParts[1], loginId) + } + + fun getTeacherByFDotSpaceLast(nameFDotSpaceLast: String, loginId: String? = null): Teacher { + val nameParts = nameFDotSpaceLast.split(".") + return if (nameParts.size == 1) + getTeacher(nameParts[0], "", loginId) + else + getTeacher(nameParts[0][0], nameParts[1], loginId) + } + + private fun validateTeacher(teacher: Teacher?, firstName: String, lastName: String, loginId: String?): Teacher { + val obj = teacher ?: Teacher(profileId, -1, firstName, lastName, loginId).apply { + id = fullName.crc32() + teacherList[id] = this + } + return obj.also { + if (loginId != null && it.loginId != null) + it.loginId = loginId + if (firstName.length > 1) + it.name = firstName + it.surname = lastName + } + } } From 59137075197d8e4bbbc755b536e4b41f9d4caa95 Mon Sep 17 00:00:00 2001 From: Antoni Czaplicki <56671347+Antoni-Czaplicki@users.noreply.github.com> Date: Fri, 10 Sep 2021 06:49:17 +0200 Subject: [PATCH 22/30] [UI] Use number keyboard in the PIN field in Vulcan. (#68) --- .../edziennik/ui/modules/login/LoginFormFragment.kt | 3 +++ .../pl/szczodrzynski/edziennik/ui/modules/login/LoginInfo.kt | 2 ++ 2 files changed, 5 insertions(+) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFormFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFormFragment.kt index ea6bbe4d..3360e93b 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFormFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginFormFragment.kt @@ -85,6 +85,9 @@ class LoginFormFragment : Fragment(), CoroutineScope { if (credential is LoginInfo.FormField) { val b = LoginFormFieldItemBinding.inflate(layoutInflater) b.textLayout.hint = app.getString(credential.name) + if (credential.isNumber) { + b.textEdit.inputType = InputType.TYPE_CLASS_NUMBER + } if (credential.hideText) { b.textEdit.inputType = InputType.TYPE_TEXT_VARIATION_PASSWORD b.textLayout.endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginInfo.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginInfo.kt index 77036f19..8a5e60d1 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginInfo.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/login/LoginInfo.kt @@ -179,6 +179,7 @@ object LoginInfo { ERROR_LOGIN_VULCAN_INVALID_PIN_2_REMAINING to R.string.error_312_reason ), isRequired = true, + isNumber = true, validationRegex = "[0-9]+", caseMode = FormField.CaseMode.LOWER_CASE ) @@ -401,6 +402,7 @@ object LoginInfo { val validationRegex: String, val caseMode: CaseMode = CaseMode.UNCHANGED, val hideText: Boolean = false, + val isNumber: Boolean = false, val stripTextRegex: String? = null ) : BaseCredential(keyName, name, errorCodes) { enum class CaseMode { UNCHANGED, UPPER_CASE, LOWER_CASE } From b9aca981e51bffedae6fae7f483af36f4768eb3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Fri, 10 Sep 2021 16:56:52 +0200 Subject: [PATCH 23/30] [App] Change app-wide storage dir to Download subfolder. --- .../edziennik/ui/modules/views/AttachmentsView.kt | 4 +--- app/src/main/java/pl/szczodrzynski/edziennik/utils/Utils.java | 3 ++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/AttachmentsView.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/AttachmentsView.kt index 354a8921..564fbf12 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/AttachmentsView.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/AttachmentsView.kt @@ -37,9 +37,7 @@ class AttachmentsView @JvmOverloads constructor( } private val storageDir by lazy { - val storageDir = Environment.getExternalStoragePublicDirectory("Szkolny.eu") - storageDir.mkdirs() - storageDir + Utils.getStorageDir() } fun init(arguments: Bundle, owner: Any) { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/Utils.java b/app/src/main/java/pl/szczodrzynski/edziennik/utils/Utils.java index 910fc6f0..d4ffae05 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/Utils.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/Utils.java @@ -776,7 +776,8 @@ public class Utils { public static File getStorageDir() { if (storageDir != null) return storageDir; - storageDir = Environment.getExternalStoragePublicDirectory("Szkolny.eu"); + storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); + storageDir = new File(storageDir, "Szkolny.eu"); storageDir.mkdirs(); return storageDir; } From 83f84de01911e0702d0eeb3677c4adf1f6a737d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Fri, 10 Sep 2021 17:11:21 +0200 Subject: [PATCH 24/30] [UI] Fix attachments view cut off on API 30+. --- .../edziennik/ui/modules/views/AttachmentsView.kt | 1 + app/src/main/res/layout/message_fragment.xml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/AttachmentsView.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/AttachmentsView.kt index 564fbf12..c981a4ba 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/AttachmentsView.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/views/AttachmentsView.kt @@ -80,6 +80,7 @@ class AttachmentsView @JvmOverloads constructor( list.adapter = adapter list.apply { setHasFixedSize(false) + isNestedScrollingEnabled = false layoutManager = LinearLayoutManager(context) addItemDecoration(SimpleDividerItemDecoration(context)) } diff --git a/app/src/main/res/layout/message_fragment.xml b/app/src/main/res/layout/message_fragment.xml index 1bae5bb5..2ca32c3d 100644 --- a/app/src/main/res/layout/message_fragment.xml +++ b/app/src/main/res/layout/message_fragment.xml @@ -57,7 +57,7 @@ android:visibility="visible" tools:visibility="gone"/> - - + From efa63452e7c4332d0427ca2a54c8bf2bb8d1f26d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Fri, 10 Sep 2021 17:17:31 +0200 Subject: [PATCH 25/30] [App] Fix Apply Changes not working due to manifest changes. --- app/build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/build.gradle b/app/build.gradle index cc99abe8..62f4c95c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -36,6 +36,9 @@ android { buildTypes { debug { minifyEnabled = false + manifestPlaceholders = [ + buildTimestamp: 0 + ] } release { minifyEnabled = true From 21ddb9d706273b9a8ea2809fafec3518ceaf735f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Fri, 10 Sep 2021 17:20:12 +0200 Subject: [PATCH 26/30] [Git] Update .gitignore for .idea. --- .gitignore | 1 + .idea/discord.xml | 9 --------- .idea/kotlinc.xml | 6 ------ .idea/runConfigurations.xml | 13 ------------- 4 files changed, 1 insertion(+), 28 deletions(-) delete mode 100644 .idea/discord.xml delete mode 100644 .idea/kotlinc.xml delete mode 100644 .idea/runConfigurations.xml diff --git a/.gitignore b/.gitignore index 07e9f679..375b15e2 100644 --- a/.gitignore +++ b/.gitignore @@ -265,3 +265,4 @@ fabric.properties # End of https://www.toptal.com/developers/gitignore/api/android,androidstudio,gradle,java,kotlin signatures/ +.idea/*.xml diff --git a/.idea/discord.xml b/.idea/discord.xml deleted file mode 100644 index a04e4e5f..00000000 --- a/.idea/discord.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml deleted file mode 100644 index 0dd4b354..00000000 --- a/.idea/kotlinc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index e497da99..00000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - \ No newline at end of file From 2f7fcb6dc30351a223e3b80cf38a5e1e9d955f12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Fri, 10 Sep 2021 17:41:39 +0200 Subject: [PATCH 27/30] [API/Mobidziennik] Fix Web timetable scrapper. --- .../mobidziennik/data/web/MobidziennikWebTimetable.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebTimetable.kt index 221c50aa..6ceb3fd9 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebTimetable.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/web/MobidziennikWebTimetable.kt @@ -228,8 +228,15 @@ class MobidziennikWebTimetable( item.startsWith("%") -> { subjectName = item.trim('%') + // I have no idea what's going on here + // ok now seriously.. the subject (long or short) item + // may NOT be 0th, as the HH:MM - HH:MM item may be before + // or even the typeName item. As these are always **before**, + // they are removed in previous iterations, so the first not removed + // item should be the long/short subjectName needing to be removed now. + toRemove.add(items[toRemove.size]) + // ...and this has to be added later toRemove.add(item) - toRemove.add(items[0]) } item.startsWith("&") -> { From 118f5e1794318264d8a5e4c4b57162dbf4fce78f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Fri, 10 Sep 2021 23:45:29 +0200 Subject: [PATCH 28/30] [API/Vulcan] Fix missing attendance. (#72) --- .../data/api/edziennik/vulcan/data/hebe/VulcanHebeAttendance.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeAttendance.kt index 9e41bb6d..b3ebafa4 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeAttendance.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/vulcan/data/hebe/VulcanHebeAttendance.kt @@ -38,7 +38,7 @@ class VulcanHebeAttendance( lastSync = lastSync ) { list, _ -> list.forEach { attendance -> - val id = attendance.getLong("AuxPresenceId") ?: return@forEach + val id = attendance.getLong("Id") ?: return@forEach val type = attendance.getJsonObject("PresenceType") ?: return@forEach val baseType = getBaseType(type) val typeName = type.getString("Name") ?: return@forEach From 2e3e3dcf3cb147c6811d3ecbc6098685b9b00b87 Mon Sep 17 00:00:00 2001 From: Antoni Czaplicki <56671347+Antoni-Czaplicki@users.noreply.github.com> Date: Fri, 10 Sep 2021 23:47:46 +0200 Subject: [PATCH 29/30] [Lab] Add button to disable devmode and Chucker toggle. (#70) * Add option to disable/enable chucker and option to disable dev mode from lab page * Change "chucker" to "enableChucker" * Update App.kt --- .../java/pl/szczodrzynski/edziennik/App.kt | 10 ++++-- .../szczodrzynski/edziennik/config/Config.kt | 5 +++ .../ui/modules/debug/LabPageFragment.kt | 34 +++++++++++++++++++ .../ui/modules/debug/LabProfileFragment.kt | 2 +- app/src/main/res/layout/lab_fragment.xml | 17 +++++++++- 5 files changed, 63 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/App.kt b/app/src/main/java/pl/szczodrzynski/edziennik/App.kt index a9186d94..71e6c7e6 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/App.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/App.kt @@ -58,6 +58,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope { val profileId get() = profile.id + var enableChucker = false var debugMode = false var devMode = false } @@ -115,9 +116,11 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope { HyperLog.initialize(this) HyperLog.setLogLevel(Log.VERBOSE) HyperLog.setLogFormat(DebugLogFormat(this)) - val chuckerCollector = ChuckerCollector(this, true, RetentionManager.Period.ONE_HOUR) - val chuckerInterceptor = ChuckerInterceptor(this, chuckerCollector) - builder.addInterceptor(chuckerInterceptor) + if (enableChucker) { + val chuckerCollector = ChuckerCollector(this, true, RetentionManager.Period.ONE_HOUR) + val chuckerInterceptor = ChuckerInterceptor(this, chuckerCollector) + builder.addInterceptor(chuckerInterceptor) + } } http = builder.build() @@ -172,6 +175,7 @@ class App : MultiDexApplication(), Configuration.Provider, CoroutineScope { App.profile = Profile(0, 0, 0, "") debugMode = BuildConfig.DEBUG devMode = config.debugMode || debugMode + enableChucker = config.enableChucker || devMode if (!profileLoadById(config.lastProfileId)) { db.profileDao().firstId?.let { profileLoadById(it) } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt index 160deb23..0274912f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt @@ -80,6 +80,11 @@ class Config(val db: AppDb) : CoroutineScope, AbstractConfig { get() { mDebugMode = mDebugMode ?: values.get("debugMode", false); return mDebugMode ?: false } set(value) { set("debugMode", value); mDebugMode = value } + private var mEnableChucker: Boolean? = null + var enableChucker: Boolean + get() { mEnableChucker = mEnableChucker ?: values.get("enableChucker", false); return mEnableChucker ?: false } + set(value) { set("enableChucker", value); mEnableChucker = value } + private var mDevModePassword: String? = null var devModePassword: String? get() { mDevModePassword = mDevModePassword ?: values.get("devModePassword", null as String?); return mDevModePassword } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabPageFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabPageFragment.kt index b1b84f9d..4d8205bf 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabPageFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabPageFragment.kt @@ -5,10 +5,12 @@ package pl.szczodrzynski.edziennik.ui.modules.debug import android.os.Bundle +import android.os.Process import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.sqlite.db.SimpleSQLiteQuery +import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -21,6 +23,7 @@ import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment import pl.szczodrzynski.edziennik.utils.TextInputDropDown import pl.szczodrzynski.fslogin.decode import kotlin.coroutines.CoroutineContext +import kotlin.system.exitProcess class LabPageFragment : LazyFragment(), CoroutineScope { companion object { @@ -75,6 +78,37 @@ class LabPageFragment : LazyFragment(), CoroutineScope { app.db.eventDao().getRawNow("UPDATE events SET homeworkBody = NULL WHERE profileId = ${App.profileId}") } + b.chucker.isChecked = app.config.enableChucker + + b.chucker.onChange { _, isChecked -> + app.config.enableChucker = isChecked + MaterialAlertDialogBuilder(activity) + .setTitle("Restart") + .setMessage("Wymagany restart aplikacji") + .setPositiveButton(R.string.ok) { _, _ -> + Process.killProcess(Process.myPid()) + Runtime.getRuntime().exit(0) + exitProcess(0) + } + .setCancelable(false) + .show() + } + + + b.disableDebug.onClick { + app.config.debugMode = false + MaterialAlertDialogBuilder(activity) + .setTitle("Restart") + .setMessage("Wymagany restart aplikacji") + .setPositiveButton(R.string.ok) { _, _ -> + Process.killProcess(Process.myPid()) + Runtime.getRuntime().exit(0) + exitProcess(0) + } + .setCancelable(false) + .show() + } + b.unarchive.onClick { app.profile.archived = false app.profile.archiveId = null diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabProfileFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabProfileFragment.kt index ee3ea2c6..f4b03f55 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabProfileFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/debug/LabProfileFragment.kt @@ -166,7 +166,7 @@ class LabProfileFragment : LazyFragment(), CoroutineScope { json.add("App.profile", app.gson.toJsonTree(app.profile)) json.add("App.profile.studentData", app.profile.studentData) json.add("App.profile.loginStore", loginStore?.data ?: JsonObject()) - json.add("App.config", JsonParser().parse(app.gson.toJson(app.config.values))) + json.add("App.config", JsonParser().parse(app.gson.toJson(app.config.values.toSortedMap()))) } adapter.items = LabJsonAdapter.expand(json, 0) adapter.notifyDataSetChanged() diff --git a/app/src/main/res/layout/lab_fragment.xml b/app/src/main/res/layout/lab_fragment.xml index 8152f917..6c4bca77 100644 --- a/app/src/main/res/layout/lab_fragment.xml +++ b/app/src/main/res/layout/lab_fragment.xml @@ -3,7 +3,8 @@ ~ Copyright (c) Kuba Szczodrzyński 2020-4-3. --> - @@ -38,6 +39,12 @@ android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" />--> + +